@bongos/core 1.19.715 → 1.19.724

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (414) hide show
  1. package/.bongos-core.json +472 -407
  2. package/.claude/skills/backlog-review/SKILL.md +3 -3
  3. package/.claude/skills/blocker-review/SKILL.md +1 -1
  4. package/.claude/skills/blocker-solve/SKILL.md +2 -2
  5. package/.claude/skills/builder-reauth/SKILL.md +1 -1
  6. package/.claude/skills/builder-redteam/SKILL.md +1 -1
  7. package/.claude/skills/builder-ship/SKILL.md +1 -1
  8. package/.claude/skills/builder-start/SKILL.md +1 -1
  9. package/.claude/skills/design/SKILL.md +2 -2
  10. package/.claude/skills/feedback/SKILL.md +1 -1
  11. package/.claude/skills/goal-create/SKILL.md +1 -1
  12. package/.claude/skills/goal-review/SKILL.md +2 -2
  13. package/.claude/skills/idea-triage/SKILL.md +5 -5
  14. package/.claude/skills/merge-mode/SKILL.md +4 -4
  15. package/.claude/skills/new-project/SKILL.md +17 -17
  16. package/.claude/skills/planning-session/SKILL.md +11 -11
  17. package/.claude/skills/recall/SKILL.md +2 -2
  18. package/.claude/skills/strand-fix/SKILL.md +1 -1
  19. package/.devcontainer/README.md +2 -2
  20. package/CONTRIBUTING.md +2 -2
  21. package/GOVERNANCE.md +4 -4
  22. package/clients/bongos-client/README.md +1 -1
  23. package/clients/bongos-client/bongos-client.global.js +4 -0
  24. package/clients/bongos-client/index.cjs +4 -0
  25. package/clients/bongos-client/index.d.ts +5 -0
  26. package/clients/bongos-client/index.mjs +4 -0
  27. package/config/branding.neutral.json +4 -2
  28. package/docs/adr/0001-stack-choice-single-vps.md +2 -2
  29. package/docs/adr/0002-digitalocean-over-hetzner.md +1 -1
  30. package/docs/adr/0008-google-chat-oauth-user-auth.md +1 -1
  31. package/docs/adr/0009-autotile-architecture.md +1 -1
  32. package/docs/adr/0011-terrain-id-rendering-model.md +3 -3
  33. package/docs/adr/0013-paths-as-object-overlays-with-autotile-dispatch.md +1 -1
  34. package/docs/adr/0014-families-canonical-over-vocabulary.md +1 -1
  35. package/docs/adr/0015-task-dependencies-and-auto-promotion.md +1 -1
  36. package/docs/adr/0016-trust-boundary-server-enforced-permissions.md +1 -1
  37. package/docs/adr/0018-three-rank-model-goes-live.md +3 -3
  38. package/docs/adr/0020-security-model-public-stub.md +9 -9
  39. package/docs/adr/0021-per-builder-skill-model-preferences.md +2 -2
  40. package/docs/adr/0022-secrets-policy.md +5 -5
  41. package/docs/adr/0024-cloneable-repo-local-first-memory.md +3 -3
  42. package/docs/adr/0024-multi-agent-system-architecture.md +10 -10
  43. package/docs/adr/0025-offsite-backup-vendor-digitalocean-spaces.md +1 -1
  44. package/docs/adr/0025-structured-criterion-task-link.md +1 -1
  45. package/docs/adr/0026-bfg-memory-hygiene-and-cross-builder-learning.md +13 -13
  46. package/docs/adr/0027-bfg-session-inefficiency-evaluator.md +14 -14
  47. package/docs/adr/0029-status-page-outage-resilience.md +1 -1
  48. package/docs/adr/0030-builders-hall-live-updates.md +1 -1
  49. package/docs/adr/0031-cloud-dev-environments-for-builders.md +7 -7
  50. package/docs/adr/0032-discord-mirror-of-gds.md +3 -3
  51. package/docs/adr/0033-discord-bot-service-principal.md +4 -4
  52. package/docs/adr/0034-thetes-graduated-newcomer-rank.md +1 -1
  53. package/docs/adr/0035-builder-onboarding-three-paths.md +4 -4
  54. package/docs/adr/0036-builders-subdomain-and-ui-reorg.md +1 -1
  55. package/docs/adr/0037-discord-channel-management.md +1 -1
  56. package/docs/adr/0038-chromebook-ttyd-cloudflare-tunnel.md +5 -5
  57. package/docs/adr/0039-setup-first-onboarding-ux.md +6 -6
  58. package/docs/adr/0040-remote-control-default-browser-onramp.md +3 -3
  59. package/docs/adr/0041-temporary-grader-bypass-killswitch.md +2 -2
  60. package/docs/adr/0042-builder-self-deploy-ci-auto-merge.md +5 -5
  61. package/docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md +2 -2
  62. package/docs/adr/0044-mediterranean-palette-replacement.md +1 -1
  63. package/docs/adr/0044-per-box-live-game-preview.md +1 -1
  64. package/docs/adr/0046-sandbox-first-review-gate.md +1 -1
  65. package/docs/adr/0047-discord-bugs-intake-anti-abuse.md +1 -1
  66. package/docs/adr/0049-split-parallel-safety-contract.md +1 -1
  67. package/docs/adr/0050-device-flow-admission-invite-gated-by-default.md +2 -2
  68. package/docs/adr/0051-full-session-transcript-corpus.md +2 -2
  69. package/docs/adr/0052-sandbox-for-everyone-game-only-preview.md +1 -1
  70. package/docs/adr/0053-scoped-dev-box-session.md +1 -1
  71. package/docs/adr/0054-session-token-reward-cost-plus.md +7 -7
  72. package/docs/adr/0055-server-mediated-branch-publish.md +2 -2
  73. package/docs/adr/0056-prod-deploy-script-mirror-and-healthcheck-retry.md +3 -3
  74. package/docs/adr/0057-container-cost-ledger.md +1 -1
  75. package/docs/adr/0057-discord-archon-approval-channels.md +2 -2
  76. package/docs/adr/0058-automatic-gate-surface-gatekeeper.md +6 -6
  77. package/docs/adr/0059-single-approval-remove-devbox-approval-gate.md +11 -11
  78. package/docs/adr/0060-gds-retrieval-layer.md +3 -3
  79. package/docs/adr/0061-context-layer-decomposition.md +2 -2
  80. package/docs/adr/0062-medusa-instance-model-decoupling.md +5 -5
  81. package/docs/adr/0063-generated-repo-map-symbol-skeleton.md +2 -2
  82. package/docs/adr/0064-rename-medusa-to-cloud-bongos.md +3 -3
  83. package/docs/adr/0065-agpl-nonprofit-ai-first-governance.md +5 -5
  84. package/docs/adr/0066-generated-file-map-sections.md +2 -2
  85. package/docs/adr/0067-session-start-fast-forward-pull.md +1 -1
  86. package/docs/adr/0068-shared-art-key-for-newcomers.md +1 -1
  87. package/docs/adr/0069-hall-live-channel-sse-decouple.md +3 -3
  88. package/docs/adr/0070-deterministic-session-card-delivery.md +1 -1
  89. package/docs/adr/0071-box-confirm-before-destroyed-and-drift-reconcile.md +2 -2
  90. package/docs/adr/0072-bongos-app-mac-signed-first-windows-deferred.md +2 -2
  91. package/docs/adr/0072-dev-box-code-staleness-visibility.md +2 -2
  92. package/docs/adr/0073-builder-needs-signal-and-byok-gemini-key.md +3 -3
  93. package/docs/adr/0073-secrets-scan-exclude-uri-detector.md +2 -2
  94. package/docs/adr/0076-in-session-deterministic-compaction.md +2 -2
  95. package/docs/adr/0077-content-addressed-llm-cache.md +2 -2
  96. package/docs/adr/0078-deterministic-first-cron.md +3 -3
  97. package/docs/adr/0079-value-gated-autonomous-engagement.md +2 -2
  98. package/docs/adr/0080-llm-cache-api-transport-and-trust-model.md +3 -3
  99. package/docs/adr/0081-tool-agnostic-design-layer.md +3 -3
  100. package/docs/adr/0082-server-side-merge-conflict-auto-resolution.md +2 -2
  101. package/docs/adr/0083-modular-architecture-strangler-build-plan.md +3 -3
  102. package/docs/adr/0085-remove-default-per-builder-spend-cap.md +2 -2
  103. package/docs/adr/0086-goal-scoped-work-hierarchy.md +15 -15
  104. package/docs/adr/0087-bongos-app-architecture-and-handoff-contract.md +5 -5
  105. package/docs/adr/0087-compete-on-governance-not-tooling-cursor.md +1 -1
  106. package/docs/adr/0088-consistent-character-generative-animation.md +5 -5
  107. package/docs/adr/0089-modular-multi-model-adversarial-grader.md +15 -15
  108. package/docs/adr/0090-metic-task-authoring.md +8 -8
  109. package/docs/adr/0091-bounding-the-kernel-and-db-carve.md +3 -3
  110. package/docs/adr/0092-hosting-module-and-on-prem-target.md +11 -11
  111. package/docs/adr/0093-tranche-2-core-carve-sequence.md +5 -5
  112. package/docs/adr/0094-repo-atlas-self-drawing-codebase-map.md +1 -1
  113. package/docs/adr/0095-borrowed-memory-and-retrieval-concepts.md +6 -6
  114. package/docs/adr/0095-cross-agent-context-management.md +5 -5
  115. package/docs/adr/0096-require-reward-before-workable.md +5 -5
  116. package/docs/adr/0097-one-active-claim-per-session-and-worktree-binding.md +2 -2
  117. package/docs/adr/0097-retroactive-reward-backfill.md +4 -4
  118. package/docs/adr/0098-public-mirror-publish-manifest.md +3 -3
  119. package/docs/adr/0099-delayed-redacted-mirror-export.md +1 -1
  120. package/docs/adr/0100-two-repo-instance-model.md +6 -6
  121. package/docs/adr/0101-goal-hierarchy-phase-5-judge-membership-reward.md +9 -9
  122. package/docs/adr/0102-finish-bounding-the-kernel.md +1 -1
  123. package/docs/adr/0103-core-first-extraction-cloud-bongos-trunk.md +6 -6
  124. package/docs/adr/0103-gdsv4-bongos-consolidation.md +3 -3
  125. package/docs/adr/0104-trust-gds-api-channel-in-auto-mode.md +3 -3
  126. package/docs/adr/0105-instance-seed-migrations-out-of-core.md +2 -2
  127. package/docs/adr/0106-goal-owner-manager-authority.md +2 -2
  128. package/docs/adr/0107-module-upstreaming-contribution-to-core-catalog.md +6 -6
  129. package/docs/adr/0108-instance-composition-configurable-root.md +6 -6
  130. package/docs/adr/0109-self-describing-openapi-and-hosted-docs.md +3 -3
  131. package/docs/adr/0110-untrack-whole-file-generated-nav-docs.md +1 -1
  132. package/docs/adr/0111-instance-hosting-provisioning-module.md +18 -18
  133. package/docs/adr/0112-private-goals-invitations-join-requests.md +7 -7
  134. package/docs/adr/0113-sandbox-studio-descope-superseded-by-0044-0052.md +1 -1
  135. package/docs/adr/0114-instance-docs-discovery-contract.md +4 -4
  136. package/docs/adr/0115-scheduled-routines-portability.md +1 -1
  137. package/docs/adr/0117-api-versioning-and-deprecation-policy.md +1 -1
  138. package/docs/adr/0118-api-contract-source-of-truth-code-first.md +1 -1
  139. package/docs/adr/0120-pay-on-land-and-builder-owned-rebase-gate.md +2 -2
  140. package/docs/adr/0121-greenfield-vs-brownfield-onboarding-adopt-existing-repo.md +1 -1
  141. package/docs/adr/0122-onboarding-diagram-renders-deploy-rendered-off-git.md +3 -3
  142. package/docs/adr/0123-box-idle-sweep-autosave-before-destroy.md +1 -1
  143. package/docs/adr/0124-typed-api-response-bodies.md +2 -2
  144. package/docs/adr/0125-control-plane-scaffolds-standalone-instance-repo.md +2 -2
  145. package/docs/adr/0126-dedicated-cloudbongos-control-plane-droplet.md +3 -3
  146. package/docs/adr/0127-cross-box-fleet-observability.md +1 -1
  147. package/docs/adr/0128-provisioning-runner-privilege-and-db-auth-model.md +3 -3
  148. package/docs/adr/0129-cloudbongos-control-plane-pull-deploy.md +4 -4
  149. package/docs/adr/0130-provision-remote-exec-over-ssh.md +4 -4
  150. package/docs/adr/0131-rank-scoped-skill-visibility.md +2 -2
  151. package/docs/adr/0132-cohost-fleet-control-plane-migration.md +2 -2
  152. package/docs/adr/0133-github-app-manifest-onboarding-signin.md +1 -1
  153. package/docs/adr/0134-private-first-npm-distribution.md +3 -3
  154. package/docs/adr/0135-module-upstream-submission-interim-queue.md +1 -1
  155. package/docs/adr/0136-update-channel-subscription-policy.md +2 -2
  156. package/docs/adr/0137-upstream-publish-policy-manual-owner-gated.md +6 -6
  157. package/docs/adr/0138-bundled-module-deps-declared-per-module.md +4 -4
  158. package/docs/adr/0142-project-context-is-prose-only.md +2 -2
  159. package/docs/adr/0143-onboarding-repo-picker-oauth-scope.md +2 -2
  160. package/docs/adr/0144-devbox-rehome-onto-cloudbongos-plane.md +2 -2
  161. package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +2 -2
  162. package/docs/adr/0146-configurable-reward-mode-cost-plus-only.md +4 -4
  163. package/docs/adr/0147-standalone-live-docs-regen.md +1 -1
  164. package/docs/adr/0149-bongos-dev-local-launcher.md +1 -1
  165. package/docs/adr/0150-box-first-boot-bringup-vendored-instances.md +3 -3
  166. package/docs/adr/0151-governance-permissions-as-atom-ranks-as-roles.md +20 -20
  167. package/docs/adr/0152-landing-gate-served-by-the-core-app.md +2 -2
  168. package/docs/adr/0152-metic-task-abandonment.md +7 -7
  169. package/docs/adr/0153-sequential-chain-runner.md +1 -1
  170. package/docs/adr/0154-goal-owner-criterion-authoring.md +7 -7
  171. package/docs/adr/0155-adopt-private-repo-widen-oauth-scope.md +2 -2
  172. package/docs/adr/0156-user-set-display-name-wins.md +1 -1
  173. package/docs/adr/0157-archon-is-rank-and-identity-only.md +10 -10
  174. package/docs/adr/0158-choke-point-verdict-taxonomy.md +1 -1
  175. package/docs/adr/0159-merge-gate-reconciliation.md +1 -1
  176. package/docs/adr/0160-main-audit-repo-verifiable-caller.md +1 -1
  177. package/docs/adr/0161-publish-on-merge.md +2 -2
  178. package/docs/adr/0163-legacy-display-name-resolution.md +1 -1
  179. package/docs/adr/0165-instances-hookless-by-default.md +1 -1
  180. package/docs/adr/0166-module-maintenance-and-deprecation-policy.md +3 -3
  181. package/docs/adr/0167-gate-trust-link-agpl-default.md +2 -2
  182. package/docs/adr/0167-module-catalog-source-vs-provenance.md +3 -3
  183. package/docs/adr/0169-bongos-serve-server-bring-up.md +1 -1
  184. package/docs/adr/0171-profile-rollup-join-and-consent.md +1 -1
  185. package/docs/adr/0172-editable-rank-roles-substrate-axis.md +2 -2
  186. package/docs/adr/0172-per-craft-compensation-ideator-credit-lane.md +4 -4
  187. package/docs/adr/0173-legacy-multi-ship-shortfall-not-backfilled.md +1 -1
  188. package/docs/adr/0174-government-terminology-ranks-not-roles.md +1 -1
  189. package/docs/adr/0177-goal-membership-kind.md +3 -3
  190. package/docs/adr/0178-the-copy-desk-flag-and-queue.md +1 -1
  191. package/docs/adr/0179-board-membership-predicate-grammar.md +1 -1
  192. package/docs/adr/0181-teardown-state-gate-and-address-retention.md +1 -1
  193. package/docs/adr/0182-project-visibility-two-axes.md +1 -1
  194. package/docs/adr/0183-criteria-close-themselves.md +1 -1
  195. package/docs/adr/0184-ship-requires-an-assurance.md +1 -1
  196. package/docs/adr/0185-spark-handoff-credit-split.md +1 -1
  197. package/docs/adr/0186-per-project-idea-templates.md +1 -1
  198. package/docs/adr/0187-collab-four-decisions.md +1 -1
  199. package/docs/adr/0187-oauth-handshake-cookie-lifetime.md +1 -1
  200. package/docs/adr/0188-patrol-runs-on-ci-not-the-server.md +2 -2
  201. package/docs/adr/0189-baseline-floors-and-the-freeze-detector.md +2 -2
  202. package/docs/adr/0191-majority-and-the-author-who-may-not-decide-alone.md +1 -1
  203. package/docs/adr/0192-a-category-orients-and-authorises-nothing.md +2 -2
  204. package/docs/adr/0193-pause-task-scoped-box-slices.md +1 -1
  205. package/docs/adr/0195-adr-numbers-are-checked-like-migration-numbers.md +2 -2
  206. package/docs/adr/0196-chrome-world-token-contract-and-the-landing-sheets.md +2 -2
  207. package/docs/adr/0197-the-ui-design-module.md +1 -1
  208. package/docs/adr/0198-third-party-skill-vendoring-policy.md +1 -1
  209. package/docs/adr/0203-doorway-changelog-out-of-module-api.md +2 -2
  210. package/docs/adr/0204-the-apex-is-dark-only.md +2 -2
  211. package/docs/adr/0205-federated-checkin-needs-the-hubs-own-signin-witness.md +1 -1
  212. package/docs/adr/0206-own-scoped-session-revocation-no-absolute-ceiling.md +1 -1
  213. package/docs/adr/0207-connections-implementation-departures-and-honest-limits.md +1 -1
  214. package/docs/adr/0208-application-lifecycle-the-handle-waits-and-the-vouch-has-no-minter.md +2 -2
  215. package/docs/adr/0209-one-budget-for-the-account-existence-oracle.md +1 -1
  216. package/docs/adr/0210-scouting-graduates-on-an-instance-asserted-owner-claim.md +1 -1
  217. package/docs/adr/0212-connections-on-the-profile-surface-and-addressing.md +1 -1
  218. package/docs/adr/0213-platform-terms-acceptance-is-the-hubs-own-witness.md +2 -2
  219. package/docs/adr/0215-the-recruiter-sliver-is-a-ceiling-not-a-step.md +6 -6
  220. package/docs/adr/0216-a-fake-pool-interpreter-must-fail-loudly-not-silently-ignore.md +2 -2
  221. package/docs/adr/0217-rank-gates-inviting-not-viewing.md +1 -1
  222. package/docs/adr/0218-the-apex-root-web-surface.md +2 -2
  223. package/docs/adr/0219-a-look-is-a-branding-pack-the-style-library.md +1 -1
  224. package/docs/adr/0220-an-in-house-rebuilt-skill-is-a-first-party-skill.md +1 -1
  225. package/docs/adr/0221-the-impeccable-method-is-one-skill-with-sub-command-playbooks.md +2 -2
  226. package/docs/adr/0222-the-image-family-under-the-hero-only-rule.md +2 -2
  227. package/docs/adr/0223-the-detector-is-two-tiers-in-the-kit.md +3 -3
  228. package/docs/adr/0224-module-skills-reach-the-core-checkout-as-untracked-self-ignored-copies.md +1 -1
  229. package/docs/adr/0225-privacy-suppresses-cross-project-surfaces-only.md +1 -1
  230. package/docs/adr/0226-one-stale-timer-reporting-degrades-enforcement-refuses.md +1 -1
  231. package/docs/adr/0227-a-task-network-map-is-refused-the-goal-graph-is-the-map.md +1 -1
  232. package/docs/adr/0230-a-look-skill-is-a-composition-grammar-the-palette-is-the-pack.md +1 -1
  233. package/docs/adr/0231-the-style-session-an-owner-authors-a-look-inside-the-contract.md +2 -2
  234. package/docs/adr/0232-rot-is-derived-not-swept-and-water-is-the-only-new-verb.md +2 -2
  235. package/docs/adr/0233-a-copy-proposal-is-a-task-carrying-a-patch.md +2 -2
  236. package/docs/adr/0234-idea-routing-capture-time-promotion-landing-matrix-homeless-inbox.md +2 -2
  237. package/docs/adr/0235-a-fix-category-is-kindbug-under-a-goal-no-column-required.md +11 -11
  238. package/docs/adr/0236-many-ideas-one-task-the-lineage-key-is-the-pair.md +5 -5
  239. package/docs/adr/0237-a-starter-bundle-is-a-preset-over-the-always-on-core-keyed-by-declared-type.md +5 -5
  240. package/docs/adr/0238-the-join-door-is-a-provisioning-settings-policy-key.md +1 -1
  241. package/docs/adr/0239-a-recommendation-is-a-table-that-carries-its-own-reason.md +4 -4
  242. package/docs/adr/0240-a-skipped-module-picker-stores-nothing-and-resolves-the-bundle-on-read.md +4 -4
  243. package/docs/adr/0241-the-artist-gate-is-a-per-project-deploy-gate-that-reads-a-state.md +1 -1
  244. package/docs/adr/0242-a-cascade-is-a-declaration-table-on-the-event-that-already-exists.md +2 -2
  245. package/docs/adr/0243-a-bundle-adjustment-belongs-to-the-bundle-not-the-advice-about-it.md +4 -4
  246. package/docs/adr/0244-a-catalog-search-is-a-view-over-the-rows-never-an-edit-to-the-selection.md +3 -3
  247. package/docs/adr/0245-the-way-back-into-an-optional-step-is-that-step-s-own-answer-route.md +7 -7
  248. package/docs/adr/0246-catalog-membership-is-a-projection-not-a-federation-side-effect.md +1 -1
  249. package/docs/adr/0247-the-join-door-is-one-composed-answer-and-dark-matter-has-no-live-verdict-yet.md +2 -2
  250. package/docs/adr/0248-a-hub-project-s-owner-is-the-provisioning-row-carried-on-a-port.md +3 -3
  251. package/docs/adr/0249-the-invite-step-is-the-done-panel-s-first-act-not-a-rail-step.md +3 -3
  252. package/docs/adr/0250-strict-versioning-the-version-boundary-is-the-scope-gate.md +6 -6
  253. package/docs/adr/0251-a-suggestion-of-who-to-invite-is-a-recruiting-surface-and-owes-the-opt-out.md +5 -5
  254. package/docs/adr/0252-the-self-hosted-visibility-report-is-a-pull-and-auto-appear-buys-a-retraction-lever.md +19 -19
  255. package/docs/adr/0253-the-takedown-lever-has-two-writers-and-provenance-is-a-column.md +2 -2
  256. package/docs/adr/0254-a-relayed-link-is-contained-downward-or-it-is-not-published.md +6 -6
  257. package/docs/adr/0255-a-public-lists-ordering-is-part-of-its-payload.md +2 -2
  258. package/docs/adr/0256-the-visibility-ceiling-is-the-sign-in-gate-too-and-the-reader-moves-to-the-composer.md +3 -3
  259. package/docs/adr/0257-auth-resolves-before-the-hall-mounts-anything.md +1 -1
  260. package/docs/adr/0258-the-public-cli-is-a-generated-client-package-not-the-published-core.md +1 -1
  261. package/docs/adr/0259-a-projects-departure-from-the-public-list-is-public.md +7 -7
  262. package/docs/adr/0260-the-application-is-the-consent-and-the-echo-is-the-gate.md +4 -4
  263. package/docs/adr/0261-a-preselect-always-carries-a-reason-the-bundle-summary-is-the-floor.md +1 -1
  264. package/docs/adr/0262-a-bug-never-lands-in-the-inbox.md +1 -1
  265. package/docs/adr/0263-how-a-version-closes.md +3 -3
  266. package/docs/adr/0264-the-ten-working-areas.md +3 -3
  267. package/docs/adr/0265-work-with-no-goal-and-the-order-the-cut-needs.md +5 -5
  268. package/docs/adr/0266-the-board-room-is-its-own-surface-reachable-by-whoever-may-vote.md +2 -2
  269. package/docs/adr/0267-unanimity-and-the-revise-and-re-sit-loop.md +1 -1
  270. package/docs/adr/0268-the-constitution-comes-from-two-roots.md +3 -3
  271. package/docs/adr/0269-the-cli-session-store-is-host-keyed-at-a-fixed-anchor.md +1 -1
  272. package/docs/adr/0270-rank-floor-derives-without-a-prediction.md +2 -2
  273. package/docs/adr/0271-the-ideator-surface-is-two-halves.md +3 -3
  274. package/docs/adr/0272-a-craft-is-an-identity-not-a-toolchain.md +4 -4
  275. package/docs/adr/0273-hall-nav-can-gate-on-a-permission.md +1 -1
  276. package/docs/adr/0274-one-kernel-three-role-packs.md +1 -1
  277. package/docs/adr/0275-one-source-for-a-written-role-responsibility.md +1 -1
  278. package/docs/adr/0276-the-skill-listing-budget-cannot-hold-every-trigger.md +1 -1
  279. package/docs/adr/0277-a-box-is-in-use-only-while-a-human-is-attached.md +1 -1
  280. package/docs/adr/0278-a-gated-project-still-takes-applications.md +2 -2
  281. package/docs/adr/0279-an-upgrade-is-proven-by-the-served-version-not-the-health-check.md +1 -1
  282. package/docs/adr/0280-the-unattended-lane-may-drive-a-co-tenant-because-the-roster-is-the-decision.md +1 -1
  283. package/docs/adr/0281-an-instance-identity-is-its-own-unix-account-and-pg-role.md +2 -2
  284. package/docs/adr/README.md +294 -294
  285. package/docs/api/openapi.json +72 -5
  286. package/docs/api-reference.md +8 -2
  287. package/docs/architecture.md +22 -22
  288. package/docs/branding-contract.md +5 -5
  289. package/docs/canonical-permissions.md +22 -22
  290. package/docs/design/hall-direction-v2.md +3 -3
  291. package/docs/design/landing-page-direction-v2.md +1 -1
  292. package/docs/design/projects-hub-direction-v2.md +1 -1
  293. package/docs/file-map.md +3 -1
  294. package/docs/module-api-changelog.md +34 -1
  295. package/docs/modules-contract.md +25 -25
  296. package/docs/onboarding/browser-terminal-guide.md +1 -1
  297. package/docs/onboarding/diagrams/README.md +3 -3
  298. package/docs/onboarding/drachmae-and-karma.md +13 -13
  299. package/docs/onboarding/primer.md +7 -7
  300. package/docs/packs/artist.md +1 -1
  301. package/docs/packs/ideator.md +1 -1
  302. package/docs/project-context.template.md +3 -3
  303. package/docs/recipes/builder-box-lifecycle.md +5 -5
  304. package/docs/recipes/claude-design-loop.md +1 -1
  305. package/docs/recipes/core-release-pipeline.md +55 -14
  306. package/docs/recipes/figma-design-loop.md +1 -1
  307. package/docs/recipes/gds-db-backup.md +2 -2
  308. package/docs/recipes/instance-deploy.md +2 -2
  309. package/docs/recipes/instance-service-restart.md +3 -3
  310. package/docs/recipes/local-dev.md +2 -2
  311. package/docs/recipes/managed-settings-remote-control.md +3 -3
  312. package/docs/recipes/memory-backup.md +4 -4
  313. package/docs/recipes/memory-sync-conflicts.md +2 -2
  314. package/docs/recipes/multi-builder-merge.md +2 -2
  315. package/docs/recipes/ops-gotchas.md +4 -4
  316. package/docs/recipes/overnight-builder.md +1 -1
  317. package/docs/recipes/packaging-the-core.md +6 -6
  318. package/docs/recipes/private-npm-distribution.md +5 -5
  319. package/docs/recipes/search-index-ingestion.md +3 -3
  320. package/docs/recipes/self-host.md +5 -5
  321. package/docs/recipes/server-bring-up.md +1 -1
  322. package/docs/recipes/ship-and-regrade.md +2 -2
  323. package/docs/recipes/standalone-live-docs.md +1 -1
  324. package/docs/recipes/ui-look-before-you-ship.md +1 -1
  325. package/docs/recipes/upgrading-the-core.md +2 -2
  326. package/docs/recipes/work-on-a-task-from-your-dev-box.md +6 -6
  327. package/docs/research/cursor-competitive-analysis.md +3 -3
  328. package/docs/research/llm-model-agnostic-and-cheaper-grader.md +7 -7
  329. package/migrations/core_240_artist_gate_indexes.sql +48 -0
  330. package/migrations/core_240_goal_members_drop_legacy_role.sql +79 -0
  331. package/modules/dev-box/app/src/vendor/bongos-client.cjs +4 -0
  332. package/modules/lifecycle/cascade.js +37 -9
  333. package/modules/lifecycle/db-claims.js +5 -2
  334. package/modules/lifecycle/db-goals.js +4 -3
  335. package/modules/lifecycle/db-overrides.js +117 -4
  336. package/modules/lifecycle/db-tasks.js +63 -0
  337. package/modules/lifecycle/migrations/lifecycle_001_goal_first_leads.sql +12 -4
  338. package/modules/lifecycle/module.json +2 -1
  339. package/modules/lifecycle/pg-constraint-fail.js +49 -0
  340. package/modules/lifecycle/routes/artist-gate.js +101 -0
  341. package/modules/lifecycle/routes/claims.js +13 -1
  342. package/modules/lifecycle/routes/lifecycle.js +6 -0
  343. package/modules/lifecycle/routes/task-write-routes.js +7 -0
  344. package/modules/lifecycle/routes/tasks.js +24 -0
  345. package/modules/lifecycle/task-classifier.js +10 -1
  346. package/modules/lifecycle/tests/cascade.mjs +43 -0
  347. package/modules/provisioning/provisioning.js +79 -3
  348. package/modules/provisioning/routes/provisioning.js +6 -1
  349. package/modules/provisioning/tests/provisioning.mjs +8 -8
  350. package/modules/security/secret-scrub.js +40 -4
  351. package/modules/status-ui/public/README.md +1 -1
  352. package/modules/ui-design/docs/design-contract.md +2 -2
  353. package/modules/ui-design/kit/README.md +1 -1
  354. package/modules/ui-design/kit/serve.js +1 -0
  355. package/modules/ui-design/skills/README.md +6 -6
  356. package/modules/ui-design/skills/brandkit/SKILL.md +1 -1
  357. package/modules/ui-design/skills/design-taste-frontend/SKILL.md +1 -1
  358. package/modules/ui-design/skills/design-taste-frontend-v1/SKILL.md +1 -1
  359. package/modules/ui-design/skills/gpt-taste/SKILL.md +1 -1
  360. package/modules/ui-design/skills/high-end-visual-design/SKILL.md +1 -1
  361. package/modules/ui-design/skills/image-to-code/SKILL.md +1 -1
  362. package/modules/ui-design/skills/imagegen-frontend-mobile/SKILL.md +1 -1
  363. package/modules/ui-design/skills/imagegen-frontend-web/SKILL.md +1 -1
  364. package/modules/ui-design/skills/impeccable/SKILL.md +1 -1
  365. package/modules/ui-design/skills/industrial-brutalist-ui/SKILL.md +1 -1
  366. package/modules/ui-design/skills/minimalist-ui/SKILL.md +1 -1
  367. package/modules/ui-design/skills/redesign-existing-projects/SKILL.md +1 -1
  368. package/modules/ui-design/skills/stitch-design-taste/SKILL.md +1 -1
  369. package/modules/ui-design/skills/style/SKILL.md +2 -2
  370. package/modules/ui-design/styles/README.md +3 -3
  371. package/modules/ui-design/styles/blueprint/DESIGN.md +1 -1
  372. package/modules/ui-design/styles/chrome-world/DESIGN.md +1 -1
  373. package/package-lock.json +2 -2
  374. package/package.json +1 -1
  375. package/scripts/gds/audit-rank-roles.js +172 -0
  376. package/scripts/gds/build-cli-package.js +1 -0
  377. package/scripts/gds/claim.js +94 -9
  378. package/scripts/gds/fitness.js +8 -8
  379. package/scripts/gds/go-live.js +139 -5
  380. package/scripts/gds/land-watch.js +13 -5
  381. package/scripts/gds/mirror-redact.js +13 -2
  382. package/scripts/gds/package-core.js +11 -3
  383. package/scripts/gds/release.js +55 -0
  384. package/scripts/gds/ship-land.js +10 -2
  385. package/scripts/gds/ship-regen.js +41 -8
  386. package/scripts/gds/start.js +7 -4
  387. package/scripts/gds/worktree-claim-guard.js +27 -1
  388. package/scripts/public-mirror-export.js +17 -4
  389. package/src/bongos/api-errors.js +9 -0
  390. package/src/bongos/artist-gate.js +204 -0
  391. package/src/branding.js +15 -0
  392. package/src/module-api.js +34 -1
  393. package/tests/api_path_404.mjs +28 -0
  394. package/tests/artist_gate.mjs +234 -0
  395. package/tests/artist_gate_release.mjs +283 -0
  396. package/tests/artist_gate_route.mjs +174 -0
  397. package/tests/box_scope_predicate.mjs +117 -0
  398. package/tests/claim_error_surface.mjs +163 -1
  399. package/tests/cli_exit_no_abort.mjs +256 -3
  400. package/tests/currency_label.mjs +11 -4
  401. package/tests/go_live.mjs +144 -0
  402. package/tests/main_worktree_busy_reason.mjs +167 -0
  403. package/tests/membership_kind_lockstep.mjs +88 -0
  404. package/tests/mirror_redact.mjs +14 -0
  405. package/tests/module_api.mjs +6 -0
  406. package/tests/provision_settings_apply.mjs +13 -5
  407. package/tests/provisioning_settings.mjs +11 -7
  408. package/tests/provisioning_settings_apply.mjs +4 -4
  409. package/tests/provisioning_settings_env.mjs +59 -7
  410. package/tests/public_mirror_export.mjs +12 -0
  411. package/tests/rank_roles_audit.mjs +114 -0
  412. package/tests/scrubber_corpus.mjs +70 -2
  413. package/tests/start_rebase_warning.mjs +50 -0
  414. package/tests/task_field_bounds_match_db.mjs +140 -0
@@ -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
  }
@@ -248,17 +248,49 @@ function regenerateFileMap() {
248
248
  // ship-vs-merge-mode and merge-mode-vs-merge-mode races that the in-ship.js
249
249
  // lock could not cover. See docs/recipes/multi-builder-merge.md.
250
250
 
251
+ // describeMainWorktreeBusy — the PURE verdict. Given the three facts about
252
+ // REPO_ROOT, is it safe to mutate, and if not, why? Returns a human reason string
253
+ // or null.
254
+ //
255
+ // task 1002871: it used to answer only 'a merge is already in progress' or 'the
256
+ // working tree is dirty…', and it runs BEFORE the named-branch diagnostic in
257
+ // ship-merge.js. So the shape of the original incident — a shared main checkout
258
+ // that is BOTH parked on a feature branch AND dirty (2026-07-28: parked on
259
+ // `task-1002503-backfill-recovery` with 2 unpushed commits and 4 modified
260
+ // generated files, blocking the auto-merge leg for 9 confirmed tasks) —
261
+ // short-circuited here and the builder never learned WHICH branch was squatting.
262
+ // The named-branch message was only reachable when the tree was clean, which is
263
+ // the one case where nobody needs to be told twice.
264
+ //
265
+ // Pure + exported so the wording is unit-tested rather than eyeballed: this is the
266
+ // ship hot path, and the whole value of the change is the exact string.
267
+ function describeMainWorktreeBusy({ mergeInProgress, dirty, branch, defaultBranch = 'main' } = {}) {
268
+ // A branch name is only worth naming when it is NOT the expected one. Saying
269
+ // "on main" in every bail would be noise that trains the reader to skip the line.
270
+ const occupant = branch && branch !== defaultBranch && branch !== 'HEAD'
271
+ ? ` (on \`${branch}\`, not \`${defaultBranch}\`)`
272
+ : '';
273
+ if (mergeInProgress) return `a merge is already in progress${occupant}`;
274
+ if (dirty) return `the working tree is dirty or has unmerged files${occupant}`;
275
+ return null;
276
+ }
277
+
251
278
  // REPO_ROOT must be on main, not mid-merge, and clean before we mutate it.
252
- // Returns a human reason string if busy, or null if safe to proceed.
279
+ // Returns a human reason string if busy, or null if safe to proceed. The I/O
280
+ // shell over describeMainWorktreeBusy — gather the facts, let the pure half decide.
253
281
  function mainWorktreeBusy() {
254
- if (gitOk('git rev-parse -q --verify MERGE_HEAD', { cwd: REPO_ROOT })) {
255
- return 'a merge is already in progress';
256
- }
282
+ const mergeInProgress = !!gitOk('git rev-parse -q --verify MERGE_HEAD', { cwd: REPO_ROOT });
283
+ // Short-circuit the moment the verdict is decided. `describeMainWorktreeBusy`
284
+ // prefers merge-in-progress over dirty, so once that is true the dirty probe's
285
+ // answer is discarded — spawning it anyway would be two subprocesses bought for
286
+ // nothing. The branch IS still needed on this arm: the reason names it either way.
287
+ const branch = gitOk('git rev-parse --abbrev-ref HEAD', { cwd: REPO_ROOT }) || null;
288
+ if (mergeInProgress) return describeMainWorktreeBusy({ mergeInProgress: true, branch });
257
289
  const dirty = gitOk('git status --porcelain', { cwd: REPO_ROOT });
258
- if (dirty && dirty.trim().length > 0) {
259
- return 'the working tree is dirty or has unmerged files';
260
- }
261
- return null;
290
+ // Best-effort throughout: a probe failure yields null, which the pure half reads
291
+ // as "no occupant worth naming". A diagnostic must never be the thing that
292
+ // breaks the bail it is decorating.
293
+ return describeMainWorktreeBusy({ mergeInProgress: false, dirty: !!(dirty && dirty.length > 0), branch });
262
294
  }
263
295
 
264
296
  // task 391 (idea #37): the verdict for a ship whose branch has no commits ahead
@@ -276,6 +308,7 @@ module.exports = {
276
308
  VENDORED_CLIENT_REL,
277
309
  apiClientPathspec,
278
310
  currentBranch,
311
+ describeMainWorktreeBusy,
279
312
  mainWorktreeBusy,
280
313
  regenerateApiClient,
281
314
  regenerateApiDocs,
@@ -272,8 +272,9 @@ function rebaseWarningModel(needsRebaseTasks) {
272
272
  function rebaseWarningMarkdown(needsRebaseTasks) {
273
273
  const w = rebaseWarningModel(needsRebaseTasks);
274
274
  if (!w) return null;
275
- return `⚠️ ${surface.strong('Rebase owed — your queue is GATED.')} ${w.count} ${w.noun} can't land and block new claims`
276
- + ` until you rebase + re-ship (or ${surface.code(surface.action('release'))}) ${w.pronoun}: ${w.refs}.`;
275
+ return `⚠️ ${surface.strong('Land owed — your queue is GATED.')} ${w.count} ${w.noun} can't land and block new claims`
276
+ + ` until you ${surface.code('git merge origin/main')} + re-ship (or ${surface.code(surface.action('release'))}) ${w.pronoun}: ${w.refs}.`
277
+ + ' Merge, never rebase — a published branch that is rewritten cannot be pushed again (task 1002788).';
277
278
  }
278
279
 
279
280
  // The widget warning block (Tabler alert icon, danger-tinted), or '' when none.
@@ -281,7 +282,7 @@ function rebaseWarningWidget(needsRebaseTasks) {
281
282
  const w = rebaseWarningModel(needsRebaseTasks);
282
283
  if (!w) return '';
283
284
  return `<div class="bs-warn"><i class="ti ti-alert-triangle" aria-hidden="true"></i>`
284
- + `<span><strong>Rebase owed — your queue is GATED.</strong> ${w.count} ${w.noun} can't land and block new claims until you rebase + re-ship (or /builder-release) ${w.pronoun}: ${hesc(w.refs)}.</span></div>`;
285
+ + `<span><strong>Land owed — your queue is GATED.</strong> ${w.count} ${w.noun} can't land and block new claims until you <code>git merge origin/main</code> + re-ship (or /builder-release) ${w.pronoun}: ${hesc(w.refs)}. Merge, never rebase — a published branch that is rewritten cannot be pushed again (task 1002788).</span></div>`;
285
286
  }
286
287
 
287
288
  // Run console.log-based helpers (printCostWarning/printOnboardingNudge) without
@@ -832,7 +833,9 @@ async function main() {
832
833
 
833
834
  // ADR 0120 part 5: the owed-rebase gate warning — rendered directly under the
834
835
  // identity line and before the claimable table, since it BLOCKS new claims
835
- // until the builder lands (rebase + re-ship) or releases the flagged tasks.
836
+ // until the builder lands (`git merge origin/main` + re-ship task 1002788:
837
+ // MERGE, because the branch is already published and the server's publish is
838
+ // append-only, so a rebase makes it permanently un-pushable) or releases them.
836
839
  const rebaseLine = rebaseWarningMarkdown(needsRebaseTasks);
837
840
  if (rebaseLine) {
838
841
  console.log('');
@@ -101,4 +101,30 @@ function decideWorktreeClaim({
101
101
  };
102
102
  }
103
103
 
104
- module.exports = { decideWorktreeClaim, ESCAPE_FLAG };
104
+ // task 1002871 — the OTHER end of the same failure. decideWorktreeClaim stops a
105
+ // builder parking the main checkout at CLAIM time; this speaks at RELEASE time,
106
+ // when a claim worked under the `--here` hatch is given up and the main checkout
107
+ // is left holding the feature branch. Nobody is watching that tree, and the next
108
+ // ship to reach for it — possibly another builder's — is who pays: the 2026-07-28
109
+ // incident was one parked checkout blocking the auto-merge leg for 9 tasks.
110
+ //
111
+ // IT WARNS AND NEVER RESTORES, deliberately. Switching the tree back to main
112
+ // would be a destructive guess: the parked branch may hold real uncommitted work,
113
+ // and release.js has no way to tell that from debris. Naming it is enough — the
114
+ // person reading has the context to decide.
115
+ //
116
+ // Pure; returns an array of lines (empty when there is nothing to say).
117
+ function parkedMainCheckoutWarning({ branch, defaultBranch = 'main', dirty = false } = {}) {
118
+ if (!branch || branch === defaultBranch || branch === 'HEAD') return [];
119
+ return [
120
+ `⚠ The main checkout is still on \`${branch}\`, not \`${defaultBranch}\`.`,
121
+ dirty
122
+ ? ' It also has uncommitted changes, so nothing here will touch it.'
123
+ : ' Nothing here will touch it — the branch may hold work you still want.',
124
+ ` While it stays parked, the local auto-merge at ship time has nowhere to merge INTO,`,
125
+ ' so the next ship on this machine strands at `confirmed` — including someone else\'s.',
126
+ ` When you are done with it: git -C <main checkout> checkout ${defaultBranch}`,
127
+ ];
128
+ }
129
+
130
+ module.exports = { decideWorktreeClaim, parkedMainCheckoutWarning, ESCAPE_FLAG };
@@ -95,17 +95,23 @@ function isBinary(buf) {
95
95
  // over their latin1 view (so an embedded literal still trips the gate). The gate uses
96
96
  // `sensitiveLiterals` (raw substrings), NOT `rules` — stricter than redaction on
97
97
  // purpose (see mirror-redact.js). Returns { buf:Buffer, leaks:[{label,sample}], secret:boolean }.
98
- function redactBlob({ buf, rules, sensitiveLiterals, envSecrets }) {
98
+ function redactBlob({ buf, rules, sensitiveLiterals, envSecrets, knownDocNames }) {
99
99
  if (isBinary(buf)) {
100
100
  const view = buf.toString('latin1');
101
101
  return { buf, leaks: findLeaks(view, sensitiveLiterals), secret: findSecretLeak(view, envSecrets) };
102
102
  }
103
103
  const redacted = redactText(buf.toString('utf8'), rules);
104
- const scrubbed = scrubSecrets(redacted, envSecrets);
104
+ // task 1002460: the set of filenames THIS publish is shipping. The entropy
105
+ // sweep mistakes a long doc filename for a secret, and only the caller knows
106
+ // which names are real files. With no set there is no exemption at all.
107
+ const scrubbed = scrubSecrets(redacted, envSecrets, { knownDocNames });
105
108
  return {
106
109
  buf: Buffer.from(scrubbed, 'utf8'),
107
110
  leaks: findLeaks(scrubbed, sensitiveLiterals),
108
- secret: findSecretLeak(scrubbed, envSecrets),
111
+ // Same allowlist as the scrub directly above, and it has to be: findSecretLeak is a
112
+ // differential audit of THAT scrub's output, so an option only one of the two holds
113
+ // is read as a leak in every doc (task 1003873). Keep these two calls in step.
114
+ secret: findSecretLeak(scrubbed, envSecrets, { knownDocNames }),
109
115
  };
110
116
  }
111
117
 
@@ -206,6 +212,13 @@ function main() {
206
212
 
207
213
  // 4. Redact every file IN MEMORY and run the fail-closed no-leak gate. Nothing touches
208
214
  // the mirror filesystem until the ENTIRE set is proven clean (atomic abort).
215
+ // task 1002460 — the doc-link allowlist, computed for THIS export. Without it the
216
+ // entropy sweep cannot tell a long ADR filename from a secret and collapses every
217
+ // one to <redacted>.md. This is the mirror`s own file set, so a name in it is a
218
+ // FILE and not a guess; anything else, secrets included, redacts exactly as before.
219
+ const knownDocNames = new Set(
220
+ publishable.map((e) => e.path.split('/').pop()).filter((n) => /\.(md|sql)$/i.test(n)),
221
+ );
209
222
  const staged = []; // { path, buf, mode }
210
223
  const leaks = []; // { path, label, sample? }
211
224
  for (const e of publishable) {
@@ -216,7 +229,7 @@ function main() {
216
229
  leaks.push({ path: e.path, label: `read-failed:${err.message}` });
217
230
  continue;
218
231
  }
219
- const { buf, leaks: fileLeaks, secret } = redactBlob({ buf: raw, rules, sensitiveLiterals, envSecrets });
232
+ const { buf, leaks: fileLeaks, secret } = redactBlob({ buf: raw, rules, sensitiveLiterals, envSecrets, knownDocNames });
220
233
  for (const l of fileLeaks) leaks.push({ path: e.path, label: l.label, sample: l.sample });
221
234
  if (secret) leaks.push({ path: e.path, label: 'secret-shape' }); // never print the secret sample
222
235
  staged.push({ path: e.path, buf, mode: e.mode });
@@ -167,6 +167,15 @@ const OVERRIDES = Object.freeze({
167
167
  // --- validation ---
168
168
  validation_failed: 'validation_failed',
169
169
  schema_error: 'validation_failed',
170
+
171
+ // task 1002845: a Postgres CHECK violation surfaced as a 400 that names the
172
+ // constraint. It needs an explicit entry because the heuristics below would
173
+ // classify it `internal` (no matching prefix/suffix rule), which is the safe
174
+ // default for an unknown code but wrong here — the caller CAN fix this by
175
+ // changing the value they sent, so it must be bad_request and non-retriable.
176
+ // An unregistered code would still have answered 400 (the call site passes an
177
+ // explicit status), leaving the category quietly disagreeing with the status.
178
+ constraint_violation: 'bad_request',
170
179
  });
171
180
 
172
181
  // Suffix/prefix heuristic for the long tail. Order matters — first match wins.