@bongos/core 1.19.715 → 1.19.725
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.
- package/.bongos-core.json +491 -421
- package/.claude/skills/backlog-review/SKILL.md +3 -3
- package/.claude/skills/blocker-review/SKILL.md +1 -1
- package/.claude/skills/blocker-solve/SKILL.md +2 -2
- package/.claude/skills/builder-reauth/SKILL.md +1 -1
- package/.claude/skills/builder-redteam/SKILL.md +1 -1
- package/.claude/skills/builder-ship/SKILL.md +1 -1
- package/.claude/skills/builder-start/SKILL.md +1 -1
- package/.claude/skills/design/SKILL.md +2 -2
- package/.claude/skills/feedback/SKILL.md +1 -1
- package/.claude/skills/goal-create/SKILL.md +1 -1
- package/.claude/skills/goal-review/SKILL.md +2 -2
- package/.claude/skills/idea-triage/SKILL.md +5 -5
- package/.claude/skills/merge-mode/SKILL.md +4 -4
- package/.claude/skills/new-project/SKILL.md +17 -17
- package/.claude/skills/planning-session/SKILL.md +11 -11
- package/.claude/skills/recall/SKILL.md +2 -2
- package/.claude/skills/strand-fix/SKILL.md +1 -1
- package/.devcontainer/README.md +2 -2
- package/CONTRIBUTING.md +2 -2
- package/GOVERNANCE.md +4 -4
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +4 -0
- package/clients/bongos-client/index.cjs +4 -0
- package/clients/bongos-client/index.d.ts +5 -0
- package/clients/bongos-client/index.mjs +4 -0
- package/config/branding.neutral.json +4 -2
- package/docs/adr/0001-stack-choice-single-vps.md +2 -2
- package/docs/adr/0002-digitalocean-over-hetzner.md +1 -1
- package/docs/adr/0008-google-chat-oauth-user-auth.md +1 -1
- package/docs/adr/0009-autotile-architecture.md +1 -1
- package/docs/adr/0011-terrain-id-rendering-model.md +3 -3
- package/docs/adr/0013-paths-as-object-overlays-with-autotile-dispatch.md +1 -1
- package/docs/adr/0014-families-canonical-over-vocabulary.md +1 -1
- package/docs/adr/0015-task-dependencies-and-auto-promotion.md +1 -1
- package/docs/adr/0016-trust-boundary-server-enforced-permissions.md +1 -1
- package/docs/adr/0018-three-rank-model-goes-live.md +3 -3
- package/docs/adr/0020-security-model-public-stub.md +9 -9
- package/docs/adr/0021-per-builder-skill-model-preferences.md +2 -2
- package/docs/adr/0022-secrets-policy.md +5 -5
- package/docs/adr/0024-cloneable-repo-local-first-memory.md +3 -3
- package/docs/adr/0024-multi-agent-system-architecture.md +10 -10
- package/docs/adr/0025-offsite-backup-vendor-digitalocean-spaces.md +1 -1
- package/docs/adr/0025-structured-criterion-task-link.md +1 -1
- package/docs/adr/0026-bfg-memory-hygiene-and-cross-builder-learning.md +13 -13
- package/docs/adr/0027-bfg-session-inefficiency-evaluator.md +14 -14
- package/docs/adr/0029-status-page-outage-resilience.md +1 -1
- package/docs/adr/0030-builders-hall-live-updates.md +1 -1
- package/docs/adr/0031-cloud-dev-environments-for-builders.md +7 -7
- package/docs/adr/0032-discord-mirror-of-gds.md +3 -3
- package/docs/adr/0033-discord-bot-service-principal.md +4 -4
- package/docs/adr/0034-thetes-graduated-newcomer-rank.md +1 -1
- package/docs/adr/0035-builder-onboarding-three-paths.md +4 -4
- package/docs/adr/0036-builders-subdomain-and-ui-reorg.md +1 -1
- package/docs/adr/0037-discord-channel-management.md +1 -1
- package/docs/adr/0038-chromebook-ttyd-cloudflare-tunnel.md +5 -5
- package/docs/adr/0039-setup-first-onboarding-ux.md +6 -6
- package/docs/adr/0040-remote-control-default-browser-onramp.md +3 -3
- package/docs/adr/0041-temporary-grader-bypass-killswitch.md +2 -2
- package/docs/adr/0042-builder-self-deploy-ci-auto-merge.md +5 -5
- package/docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md +2 -2
- package/docs/adr/0044-mediterranean-palette-replacement.md +1 -1
- package/docs/adr/0044-per-box-live-game-preview.md +1 -1
- package/docs/adr/0046-sandbox-first-review-gate.md +1 -1
- package/docs/adr/0047-discord-bugs-intake-anti-abuse.md +1 -1
- package/docs/adr/0049-split-parallel-safety-contract.md +1 -1
- package/docs/adr/0050-device-flow-admission-invite-gated-by-default.md +2 -2
- package/docs/adr/0051-full-session-transcript-corpus.md +2 -2
- package/docs/adr/0052-sandbox-for-everyone-game-only-preview.md +1 -1
- package/docs/adr/0053-scoped-dev-box-session.md +1 -1
- package/docs/adr/0054-session-token-reward-cost-plus.md +7 -7
- package/docs/adr/0055-server-mediated-branch-publish.md +2 -2
- package/docs/adr/0056-prod-deploy-script-mirror-and-healthcheck-retry.md +3 -3
- package/docs/adr/0057-container-cost-ledger.md +1 -1
- package/docs/adr/0057-discord-archon-approval-channels.md +2 -2
- package/docs/adr/0058-automatic-gate-surface-gatekeeper.md +6 -6
- package/docs/adr/0059-single-approval-remove-devbox-approval-gate.md +11 -11
- package/docs/adr/0060-gds-retrieval-layer.md +3 -3
- package/docs/adr/0061-context-layer-decomposition.md +2 -2
- package/docs/adr/0062-medusa-instance-model-decoupling.md +5 -5
- package/docs/adr/0063-generated-repo-map-symbol-skeleton.md +2 -2
- package/docs/adr/0064-rename-medusa-to-cloud-bongos.md +3 -3
- package/docs/adr/0065-agpl-nonprofit-ai-first-governance.md +5 -5
- package/docs/adr/0066-generated-file-map-sections.md +2 -2
- package/docs/adr/0067-session-start-fast-forward-pull.md +1 -1
- package/docs/adr/0068-shared-art-key-for-newcomers.md +1 -1
- package/docs/adr/0069-hall-live-channel-sse-decouple.md +3 -3
- package/docs/adr/0070-deterministic-session-card-delivery.md +1 -1
- package/docs/adr/0071-box-confirm-before-destroyed-and-drift-reconcile.md +2 -2
- package/docs/adr/0072-bongos-app-mac-signed-first-windows-deferred.md +2 -2
- package/docs/adr/0072-dev-box-code-staleness-visibility.md +2 -2
- package/docs/adr/0073-builder-needs-signal-and-byok-gemini-key.md +3 -3
- package/docs/adr/0073-secrets-scan-exclude-uri-detector.md +2 -2
- package/docs/adr/0076-in-session-deterministic-compaction.md +2 -2
- package/docs/adr/0077-content-addressed-llm-cache.md +2 -2
- package/docs/adr/0078-deterministic-first-cron.md +3 -3
- package/docs/adr/0079-value-gated-autonomous-engagement.md +2 -2
- package/docs/adr/0080-llm-cache-api-transport-and-trust-model.md +3 -3
- package/docs/adr/0081-tool-agnostic-design-layer.md +3 -3
- package/docs/adr/0082-server-side-merge-conflict-auto-resolution.md +2 -2
- package/docs/adr/0083-modular-architecture-strangler-build-plan.md +3 -3
- package/docs/adr/0085-remove-default-per-builder-spend-cap.md +2 -2
- package/docs/adr/0086-goal-scoped-work-hierarchy.md +15 -15
- package/docs/adr/0087-bongos-app-architecture-and-handoff-contract.md +5 -5
- package/docs/adr/0087-compete-on-governance-not-tooling-cursor.md +1 -1
- package/docs/adr/0088-consistent-character-generative-animation.md +5 -5
- package/docs/adr/0089-modular-multi-model-adversarial-grader.md +15 -15
- package/docs/adr/0090-metic-task-authoring.md +8 -8
- package/docs/adr/0091-bounding-the-kernel-and-db-carve.md +3 -3
- package/docs/adr/0092-hosting-module-and-on-prem-target.md +11 -11
- package/docs/adr/0093-tranche-2-core-carve-sequence.md +5 -5
- package/docs/adr/0094-repo-atlas-self-drawing-codebase-map.md +1 -1
- package/docs/adr/0095-borrowed-memory-and-retrieval-concepts.md +6 -6
- package/docs/adr/0095-cross-agent-context-management.md +5 -5
- package/docs/adr/0096-require-reward-before-workable.md +5 -5
- package/docs/adr/0097-one-active-claim-per-session-and-worktree-binding.md +2 -2
- package/docs/adr/0097-retroactive-reward-backfill.md +4 -4
- package/docs/adr/0098-public-mirror-publish-manifest.md +3 -3
- package/docs/adr/0099-delayed-redacted-mirror-export.md +1 -1
- package/docs/adr/0100-two-repo-instance-model.md +6 -6
- package/docs/adr/0101-goal-hierarchy-phase-5-judge-membership-reward.md +9 -9
- package/docs/adr/0102-finish-bounding-the-kernel.md +1 -1
- package/docs/adr/0103-core-first-extraction-cloud-bongos-trunk.md +6 -6
- package/docs/adr/0103-gdsv4-bongos-consolidation.md +3 -3
- package/docs/adr/0104-trust-gds-api-channel-in-auto-mode.md +3 -3
- package/docs/adr/0105-instance-seed-migrations-out-of-core.md +2 -2
- package/docs/adr/0106-goal-owner-manager-authority.md +2 -2
- package/docs/adr/0107-module-upstreaming-contribution-to-core-catalog.md +6 -6
- package/docs/adr/0108-instance-composition-configurable-root.md +6 -6
- package/docs/adr/0109-self-describing-openapi-and-hosted-docs.md +3 -3
- package/docs/adr/0110-untrack-whole-file-generated-nav-docs.md +1 -1
- package/docs/adr/0111-instance-hosting-provisioning-module.md +18 -18
- package/docs/adr/0112-private-goals-invitations-join-requests.md +7 -7
- package/docs/adr/0113-sandbox-studio-descope-superseded-by-0044-0052.md +1 -1
- package/docs/adr/0114-instance-docs-discovery-contract.md +4 -4
- package/docs/adr/0115-scheduled-routines-portability.md +1 -1
- package/docs/adr/0117-api-versioning-and-deprecation-policy.md +1 -1
- package/docs/adr/0118-api-contract-source-of-truth-code-first.md +1 -1
- package/docs/adr/0120-pay-on-land-and-builder-owned-rebase-gate.md +2 -2
- package/docs/adr/0121-greenfield-vs-brownfield-onboarding-adopt-existing-repo.md +1 -1
- package/docs/adr/0122-onboarding-diagram-renders-deploy-rendered-off-git.md +3 -3
- package/docs/adr/0123-box-idle-sweep-autosave-before-destroy.md +1 -1
- package/docs/adr/0124-typed-api-response-bodies.md +2 -2
- package/docs/adr/0125-control-plane-scaffolds-standalone-instance-repo.md +2 -2
- package/docs/adr/0126-dedicated-cloudbongos-control-plane-droplet.md +3 -3
- package/docs/adr/0127-cross-box-fleet-observability.md +1 -1
- package/docs/adr/0128-provisioning-runner-privilege-and-db-auth-model.md +3 -3
- package/docs/adr/0129-cloudbongos-control-plane-pull-deploy.md +4 -4
- package/docs/adr/0130-provision-remote-exec-over-ssh.md +4 -4
- package/docs/adr/0131-rank-scoped-skill-visibility.md +2 -2
- package/docs/adr/0132-cohost-fleet-control-plane-migration.md +2 -2
- package/docs/adr/0133-github-app-manifest-onboarding-signin.md +1 -1
- package/docs/adr/0134-private-first-npm-distribution.md +3 -3
- package/docs/adr/0135-module-upstream-submission-interim-queue.md +1 -1
- package/docs/adr/0136-update-channel-subscription-policy.md +2 -2
- package/docs/adr/0137-upstream-publish-policy-manual-owner-gated.md +6 -6
- package/docs/adr/0138-bundled-module-deps-declared-per-module.md +4 -4
- package/docs/adr/0142-project-context-is-prose-only.md +2 -2
- package/docs/adr/0143-onboarding-repo-picker-oauth-scope.md +2 -2
- package/docs/adr/0144-devbox-rehome-onto-cloudbongos-plane.md +2 -2
- package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +2 -2
- package/docs/adr/0146-configurable-reward-mode-cost-plus-only.md +4 -4
- package/docs/adr/0147-standalone-live-docs-regen.md +1 -1
- package/docs/adr/0149-bongos-dev-local-launcher.md +1 -1
- package/docs/adr/0150-box-first-boot-bringup-vendored-instances.md +3 -3
- package/docs/adr/0151-governance-permissions-as-atom-ranks-as-roles.md +20 -20
- package/docs/adr/0152-landing-gate-served-by-the-core-app.md +2 -2
- package/docs/adr/0152-metic-task-abandonment.md +7 -7
- package/docs/adr/0153-sequential-chain-runner.md +1 -1
- package/docs/adr/0154-goal-owner-criterion-authoring.md +7 -7
- package/docs/adr/0155-adopt-private-repo-widen-oauth-scope.md +2 -2
- package/docs/adr/0156-user-set-display-name-wins.md +1 -1
- package/docs/adr/0157-archon-is-rank-and-identity-only.md +10 -10
- package/docs/adr/0158-choke-point-verdict-taxonomy.md +1 -1
- package/docs/adr/0159-merge-gate-reconciliation.md +1 -1
- package/docs/adr/0160-main-audit-repo-verifiable-caller.md +1 -1
- package/docs/adr/0161-publish-on-merge.md +2 -2
- package/docs/adr/0163-legacy-display-name-resolution.md +1 -1
- package/docs/adr/0165-instances-hookless-by-default.md +1 -1
- package/docs/adr/0166-module-maintenance-and-deprecation-policy.md +3 -3
- package/docs/adr/0167-gate-trust-link-agpl-default.md +2 -2
- package/docs/adr/0167-module-catalog-source-vs-provenance.md +3 -3
- package/docs/adr/0169-bongos-serve-server-bring-up.md +1 -1
- package/docs/adr/0171-profile-rollup-join-and-consent.md +1 -1
- package/docs/adr/0172-editable-rank-roles-substrate-axis.md +2 -2
- package/docs/adr/0172-per-craft-compensation-ideator-credit-lane.md +4 -4
- package/docs/adr/0173-legacy-multi-ship-shortfall-not-backfilled.md +1 -1
- package/docs/adr/0174-government-terminology-ranks-not-roles.md +1 -1
- package/docs/adr/0177-goal-membership-kind.md +3 -3
- package/docs/adr/0178-the-copy-desk-flag-and-queue.md +1 -1
- package/docs/adr/0179-board-membership-predicate-grammar.md +1 -1
- package/docs/adr/0181-teardown-state-gate-and-address-retention.md +1 -1
- package/docs/adr/0182-project-visibility-two-axes.md +1 -1
- package/docs/adr/0183-criteria-close-themselves.md +1 -1
- package/docs/adr/0184-ship-requires-an-assurance.md +1 -1
- package/docs/adr/0185-spark-handoff-credit-split.md +1 -1
- package/docs/adr/0186-per-project-idea-templates.md +1 -1
- package/docs/adr/0187-collab-four-decisions.md +1 -1
- package/docs/adr/0187-oauth-handshake-cookie-lifetime.md +1 -1
- package/docs/adr/0188-patrol-runs-on-ci-not-the-server.md +2 -2
- package/docs/adr/0189-baseline-floors-and-the-freeze-detector.md +2 -2
- package/docs/adr/0191-majority-and-the-author-who-may-not-decide-alone.md +1 -1
- package/docs/adr/0192-a-category-orients-and-authorises-nothing.md +2 -2
- package/docs/adr/0193-pause-task-scoped-box-slices.md +1 -1
- package/docs/adr/0195-adr-numbers-are-checked-like-migration-numbers.md +2 -2
- package/docs/adr/0196-chrome-world-token-contract-and-the-landing-sheets.md +2 -2
- package/docs/adr/0197-the-ui-design-module.md +1 -1
- package/docs/adr/0198-third-party-skill-vendoring-policy.md +1 -1
- package/docs/adr/0203-doorway-changelog-out-of-module-api.md +2 -2
- package/docs/adr/0204-the-apex-is-dark-only.md +2 -2
- package/docs/adr/0205-federated-checkin-needs-the-hubs-own-signin-witness.md +1 -1
- package/docs/adr/0206-own-scoped-session-revocation-no-absolute-ceiling.md +1 -1
- package/docs/adr/0207-connections-implementation-departures-and-honest-limits.md +1 -1
- package/docs/adr/0208-application-lifecycle-the-handle-waits-and-the-vouch-has-no-minter.md +2 -2
- package/docs/adr/0209-one-budget-for-the-account-existence-oracle.md +1 -1
- package/docs/adr/0210-scouting-graduates-on-an-instance-asserted-owner-claim.md +1 -1
- package/docs/adr/0212-connections-on-the-profile-surface-and-addressing.md +1 -1
- package/docs/adr/0213-platform-terms-acceptance-is-the-hubs-own-witness.md +2 -2
- package/docs/adr/0215-the-recruiter-sliver-is-a-ceiling-not-a-step.md +6 -6
- package/docs/adr/0216-a-fake-pool-interpreter-must-fail-loudly-not-silently-ignore.md +2 -2
- package/docs/adr/0217-rank-gates-inviting-not-viewing.md +1 -1
- package/docs/adr/0218-the-apex-root-web-surface.md +2 -2
- package/docs/adr/0219-a-look-is-a-branding-pack-the-style-library.md +1 -1
- package/docs/adr/0220-an-in-house-rebuilt-skill-is-a-first-party-skill.md +1 -1
- package/docs/adr/0221-the-impeccable-method-is-one-skill-with-sub-command-playbooks.md +2 -2
- package/docs/adr/0222-the-image-family-under-the-hero-only-rule.md +2 -2
- package/docs/adr/0223-the-detector-is-two-tiers-in-the-kit.md +3 -3
- package/docs/adr/0224-module-skills-reach-the-core-checkout-as-untracked-self-ignored-copies.md +1 -1
- package/docs/adr/0225-privacy-suppresses-cross-project-surfaces-only.md +1 -1
- package/docs/adr/0226-one-stale-timer-reporting-degrades-enforcement-refuses.md +1 -1
- package/docs/adr/0227-a-task-network-map-is-refused-the-goal-graph-is-the-map.md +1 -1
- package/docs/adr/0230-a-look-skill-is-a-composition-grammar-the-palette-is-the-pack.md +1 -1
- package/docs/adr/0231-the-style-session-an-owner-authors-a-look-inside-the-contract.md +2 -2
- package/docs/adr/0232-rot-is-derived-not-swept-and-water-is-the-only-new-verb.md +2 -2
- package/docs/adr/0233-a-copy-proposal-is-a-task-carrying-a-patch.md +2 -2
- package/docs/adr/0234-idea-routing-capture-time-promotion-landing-matrix-homeless-inbox.md +2 -2
- package/docs/adr/0235-a-fix-category-is-kindbug-under-a-goal-no-column-required.md +11 -11
- package/docs/adr/0236-many-ideas-one-task-the-lineage-key-is-the-pair.md +5 -5
- package/docs/adr/0237-a-starter-bundle-is-a-preset-over-the-always-on-core-keyed-by-declared-type.md +5 -5
- package/docs/adr/0238-the-join-door-is-a-provisioning-settings-policy-key.md +1 -1
- package/docs/adr/0239-a-recommendation-is-a-table-that-carries-its-own-reason.md +4 -4
- package/docs/adr/0240-a-skipped-module-picker-stores-nothing-and-resolves-the-bundle-on-read.md +4 -4
- package/docs/adr/0241-the-artist-gate-is-a-per-project-deploy-gate-that-reads-a-state.md +1 -1
- package/docs/adr/0242-a-cascade-is-a-declaration-table-on-the-event-that-already-exists.md +2 -2
- package/docs/adr/0243-a-bundle-adjustment-belongs-to-the-bundle-not-the-advice-about-it.md +4 -4
- package/docs/adr/0244-a-catalog-search-is-a-view-over-the-rows-never-an-edit-to-the-selection.md +3 -3
- package/docs/adr/0245-the-way-back-into-an-optional-step-is-that-step-s-own-answer-route.md +7 -7
- package/docs/adr/0246-catalog-membership-is-a-projection-not-a-federation-side-effect.md +1 -1
- package/docs/adr/0247-the-join-door-is-one-composed-answer-and-dark-matter-has-no-live-verdict-yet.md +2 -2
- package/docs/adr/0248-a-hub-project-s-owner-is-the-provisioning-row-carried-on-a-port.md +3 -3
- package/docs/adr/0249-the-invite-step-is-the-done-panel-s-first-act-not-a-rail-step.md +3 -3
- package/docs/adr/0250-strict-versioning-the-version-boundary-is-the-scope-gate.md +6 -6
- package/docs/adr/0251-a-suggestion-of-who-to-invite-is-a-recruiting-surface-and-owes-the-opt-out.md +5 -5
- package/docs/adr/0252-the-self-hosted-visibility-report-is-a-pull-and-auto-appear-buys-a-retraction-lever.md +19 -19
- package/docs/adr/0253-the-takedown-lever-has-two-writers-and-provenance-is-a-column.md +2 -2
- package/docs/adr/0254-a-relayed-link-is-contained-downward-or-it-is-not-published.md +6 -6
- package/docs/adr/0255-a-public-lists-ordering-is-part-of-its-payload.md +2 -2
- package/docs/adr/0256-the-visibility-ceiling-is-the-sign-in-gate-too-and-the-reader-moves-to-the-composer.md +3 -3
- package/docs/adr/0257-auth-resolves-before-the-hall-mounts-anything.md +1 -1
- package/docs/adr/0258-the-public-cli-is-a-generated-client-package-not-the-published-core.md +1 -1
- package/docs/adr/0259-a-projects-departure-from-the-public-list-is-public.md +7 -7
- package/docs/adr/0260-the-application-is-the-consent-and-the-echo-is-the-gate.md +4 -4
- package/docs/adr/0261-a-preselect-always-carries-a-reason-the-bundle-summary-is-the-floor.md +1 -1
- package/docs/adr/0262-a-bug-never-lands-in-the-inbox.md +1 -1
- package/docs/adr/0263-how-a-version-closes.md +3 -3
- package/docs/adr/0264-the-ten-working-areas.md +3 -3
- package/docs/adr/0265-work-with-no-goal-and-the-order-the-cut-needs.md +5 -5
- package/docs/adr/0266-the-board-room-is-its-own-surface-reachable-by-whoever-may-vote.md +2 -2
- package/docs/adr/0267-unanimity-and-the-revise-and-re-sit-loop.md +1 -1
- package/docs/adr/0268-the-constitution-comes-from-two-roots.md +3 -3
- package/docs/adr/0269-the-cli-session-store-is-host-keyed-at-a-fixed-anchor.md +1 -1
- package/docs/adr/0270-rank-floor-derives-without-a-prediction.md +2 -2
- package/docs/adr/0271-the-ideator-surface-is-two-halves.md +3 -3
- package/docs/adr/0272-a-craft-is-an-identity-not-a-toolchain.md +4 -4
- package/docs/adr/0273-hall-nav-can-gate-on-a-permission.md +1 -1
- package/docs/adr/0274-one-kernel-three-role-packs.md +1 -1
- package/docs/adr/0275-one-source-for-a-written-role-responsibility.md +1 -1
- package/docs/adr/0276-the-skill-listing-budget-cannot-hold-every-trigger.md +1 -1
- package/docs/adr/0277-a-box-is-in-use-only-while-a-human-is-attached.md +1 -1
- package/docs/adr/0278-a-gated-project-still-takes-applications.md +2 -2
- package/docs/adr/0279-an-upgrade-is-proven-by-the-served-version-not-the-health-check.md +1 -1
- package/docs/adr/0280-the-unattended-lane-may-drive-a-co-tenant-because-the-roster-is-the-decision.md +1 -1
- package/docs/adr/0281-an-instance-identity-is-its-own-unix-account-and-pg-role.md +2 -2
- package/docs/adr/README.md +294 -294
- package/docs/api/openapi.json +72 -5
- package/docs/api-reference.md +8 -2
- package/docs/architecture.md +22 -22
- package/docs/branding-contract.md +5 -5
- package/docs/canonical-permissions.md +22 -22
- package/docs/design/hall-direction-v2.md +3 -3
- package/docs/design/landing-page-direction-v2.md +1 -1
- package/docs/design/projects-hub-direction-v2.md +1 -1
- package/docs/file-map.md +3 -1
- package/docs/module-api-changelog.md +36 -1
- package/docs/modules-contract.md +25 -25
- package/docs/onboarding/browser-terminal-guide.md +1 -1
- package/docs/onboarding/diagrams/README.md +3 -3
- package/docs/onboarding/drachmae-and-karma.md +13 -13
- package/docs/onboarding/primer.md +7 -7
- package/docs/packs/artist.md +1 -1
- package/docs/packs/ideator.md +1 -1
- package/docs/project-context.template.md +3 -3
- package/docs/recipes/builder-box-lifecycle.md +5 -5
- package/docs/recipes/claude-design-loop.md +1 -1
- package/docs/recipes/core-release-pipeline.md +55 -14
- package/docs/recipes/figma-design-loop.md +1 -1
- package/docs/recipes/gds-db-backup.md +2 -2
- package/docs/recipes/instance-deploy.md +2 -2
- package/docs/recipes/instance-service-restart.md +3 -3
- package/docs/recipes/local-dev.md +2 -2
- package/docs/recipes/managed-settings-remote-control.md +3 -3
- package/docs/recipes/memory-backup.md +4 -4
- package/docs/recipes/memory-sync-conflicts.md +2 -2
- package/docs/recipes/multi-builder-merge.md +2 -2
- package/docs/recipes/ops-gotchas.md +4 -4
- package/docs/recipes/overnight-builder.md +1 -1
- package/docs/recipes/packaging-the-core.md +6 -6
- package/docs/recipes/private-npm-distribution.md +5 -5
- package/docs/recipes/search-index-ingestion.md +3 -3
- package/docs/recipes/self-host.md +5 -5
- package/docs/recipes/server-bring-up.md +1 -1
- package/docs/recipes/ship-and-regrade.md +2 -2
- package/docs/recipes/standalone-live-docs.md +1 -1
- package/docs/recipes/ui-look-before-you-ship.md +1 -1
- package/docs/recipes/upgrading-the-core.md +2 -2
- package/docs/recipes/work-on-a-task-from-your-dev-box.md +6 -6
- package/docs/research/cursor-competitive-analysis.md +3 -3
- package/docs/research/llm-model-agnostic-and-cheaper-grader.md +7 -7
- package/migrations/core_240_artist_gate_indexes.sql +48 -0
- package/migrations/core_240_goal_members_drop_legacy_role.sql +79 -0
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +4 -0
- package/modules/lifecycle/cascade.js +37 -9
- package/modules/lifecycle/db-claims.js +5 -2
- package/modules/lifecycle/db-goals.js +4 -3
- package/modules/lifecycle/db-overrides.js +117 -4
- package/modules/lifecycle/db-tasks.js +63 -0
- package/modules/lifecycle/migrations/lifecycle_001_goal_first_leads.sql +12 -4
- package/modules/lifecycle/module.json +2 -1
- package/modules/lifecycle/pg-constraint-fail.js +49 -0
- package/modules/lifecycle/routes/artist-gate.js +101 -0
- package/modules/lifecycle/routes/claims.js +13 -1
- package/modules/lifecycle/routes/lifecycle.js +6 -0
- package/modules/lifecycle/routes/task-write-routes.js +7 -0
- package/modules/lifecycle/routes/tasks.js +24 -0
- package/modules/lifecycle/task-classifier.js +10 -1
- package/modules/lifecycle/tests/cascade.mjs +43 -0
- package/modules/onboarding/approval-broadcast.js +27 -0
- package/modules/provisioning/provisioning.js +79 -3
- package/modules/provisioning/routes/provisioning.js +6 -1
- package/modules/provisioning/tests/provisioning.mjs +8 -8
- package/modules/security/secret-scrub.js +40 -4
- package/modules/status-ui/public/README.md +1 -1
- package/modules/ui-design/docs/design-contract.md +2 -2
- package/modules/ui-design/kit/README.md +1 -1
- package/modules/ui-design/kit/serve.js +1 -0
- package/modules/ui-design/skills/README.md +6 -6
- package/modules/ui-design/skills/brandkit/SKILL.md +1 -1
- package/modules/ui-design/skills/design-taste-frontend/SKILL.md +1 -1
- package/modules/ui-design/skills/design-taste-frontend-v1/SKILL.md +1 -1
- package/modules/ui-design/skills/gpt-taste/SKILL.md +1 -1
- package/modules/ui-design/skills/high-end-visual-design/SKILL.md +1 -1
- package/modules/ui-design/skills/image-to-code/SKILL.md +1 -1
- package/modules/ui-design/skills/imagegen-frontend-mobile/SKILL.md +1 -1
- package/modules/ui-design/skills/imagegen-frontend-web/SKILL.md +1 -1
- package/modules/ui-design/skills/impeccable/SKILL.md +1 -1
- package/modules/ui-design/skills/industrial-brutalist-ui/SKILL.md +1 -1
- package/modules/ui-design/skills/minimalist-ui/SKILL.md +1 -1
- package/modules/ui-design/skills/redesign-existing-projects/SKILL.md +1 -1
- package/modules/ui-design/skills/stitch-design-taste/SKILL.md +1 -1
- package/modules/ui-design/skills/style/SKILL.md +2 -2
- package/modules/ui-design/styles/README.md +3 -3
- package/modules/ui-design/styles/blueprint/DESIGN.md +1 -1
- package/modules/ui-design/styles/chrome-world/DESIGN.md +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/adopt-preflight.js +68 -2
- package/scripts/gds/audit-rank-roles.js +172 -0
- package/scripts/gds/build-cli-package.js +2 -0
- package/scripts/gds/claim.js +94 -9
- package/scripts/gds/claude-md-template.js +18 -0
- package/scripts/gds/device-flow-help.js +72 -0
- package/scripts/gds/fitness.js +8 -8
- package/scripts/gds/go-live.js +139 -5
- package/scripts/gds/init.js +64 -8
- package/scripts/gds/land-watch.js +13 -5
- package/scripts/gds/login.js +18 -0
- package/scripts/gds/mirror-redact.js +13 -2
- package/scripts/gds/package-core.js +11 -3
- package/scripts/gds/release.js +55 -0
- package/scripts/gds/setup.js +5 -39
- package/scripts/gds/ship-land.js +10 -2
- package/scripts/gds/ship-regen.js +41 -8
- package/scripts/gds/start.js +7 -4
- package/scripts/gds/worktree-claim-guard.js +27 -1
- package/scripts/public-mirror-export.js +17 -4
- package/src/bongos/api-errors.js +9 -0
- package/src/bongos/artist-gate.js +204 -0
- package/src/bongos/repo-info.js +37 -6
- package/src/branding.js +15 -0
- package/src/module-api.js +41 -1
- package/tests/adopt_preflight.mjs +72 -0
- package/tests/api_path_404.mjs +28 -0
- package/tests/artist_gate.mjs +234 -0
- package/tests/artist_gate_release.mjs +283 -0
- package/tests/artist_gate_route.mjs +174 -0
- package/tests/bongos_login.mjs +69 -2
- package/tests/box_scope_predicate.mjs +117 -0
- package/tests/claim_error_surface.mjs +163 -1
- package/tests/claude_materialize.mjs +9 -0
- package/tests/cli_exit_no_abort.mjs +256 -3
- package/tests/currency_label.mjs +11 -4
- package/tests/discord_approvals.mjs +45 -0
- package/tests/go_live.mjs +144 -0
- package/tests/init.mjs +52 -2
- package/tests/main_worktree_busy_reason.mjs +167 -0
- package/tests/membership_kind_lockstep.mjs +88 -0
- package/tests/mirror_redact.mjs +14 -0
- package/tests/module_api.mjs +7 -0
- package/tests/provision_settings_apply.mjs +13 -5
- package/tests/provisioning_settings.mjs +11 -7
- package/tests/provisioning_settings_apply.mjs +4 -4
- package/tests/provisioning_settings_env.mjs +59 -7
- package/tests/public_mirror_export.mjs +12 -0
- package/tests/rank_roles_audit.mjs +114 -0
- package/tests/repo_info_binding.mjs +82 -0
- package/tests/scrubber_corpus.mjs +70 -2
- package/tests/setup_device_flow_fallback.mjs +16 -0
- package/tests/start_rebase_warning.mjs +50 -0
- package/tests/task_field_bounds_match_db.mjs +140 -0
package/src/module-api.js
CHANGED
|
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
71
71
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
72
72
|
// the entry to that file. Look for a version's history there, not here.
|
|
73
73
|
// ---------------------------------------------------------------------------
|
|
74
|
-
const CORE_VERSION = '1.19.
|
|
74
|
+
const CORE_VERSION = '1.19.725'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
75
75
|
|
|
76
76
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
77
77
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -164,6 +164,13 @@ module.exports = {
|
|
|
164
164
|
// db.js into the bounded src/bongos/db-kernel.js, which this surface resolves
|
|
165
165
|
// through — so loading a kernel accessor no longer drags in the monolith.
|
|
166
166
|
getBuilderById: (...a) => require('./bongos/db-kernel').getBuilderById(...a),
|
|
167
|
+
// getFoundingBuilder — the instance's PROJECT OWNER: its first admitted human
|
|
168
|
+
// builder (task 1002739). Kernel identity like getBuilderById beside it, and
|
|
169
|
+
// published because ADR 0241 §3 gives the artist gate's one escape hatch to
|
|
170
|
+
// "the project owner only — not the builder, not any Metic+, not the artist",
|
|
171
|
+
// which a module cannot enforce without being able to ask who that is. It is
|
|
172
|
+
// a READ of an existing row; it grants nothing by itself.
|
|
173
|
+
getFoundingBuilder: (...a) => require('./bongos/db-kernel').getFoundingBuilder(...a),
|
|
167
174
|
insertAuditLog: (...a) => require('./bongos/db-kernel').insertAuditLog(...a),
|
|
168
175
|
get LIVE_RANK_LADDER() { return require('./bongos/db-kernel').LIVE_RANK_LADDER; },
|
|
169
176
|
// The ladder as the canonical 1-based rank → tier map (BV1.R108) — modules
|
|
@@ -303,6 +310,32 @@ module.exports = {
|
|
|
303
310
|
// the other two had closed. Optional `{ publishable }` for a caller that
|
|
304
311
|
// holds a publish verdict — see src/bongos/project-door.js.
|
|
305
312
|
projectJoinDoor: (opts) => require('./bongos/project-door').effectiveJoinDoor(opts),
|
|
313
|
+
// artistGate — the project's artist strictness and the pure verdict helpers
|
|
314
|
+
// that read it (ADR 0241, task 1003575): 'off' | 'advisory' | 'strict'.
|
|
315
|
+
// `off` is what tells the lifecycle cascade not to file artist reviews at
|
|
316
|
+
// all, and `strict` is what lets an unresolved one hold a DEPLOY (never a
|
|
317
|
+
// ship). Published as the whole small surface rather than one call because
|
|
318
|
+
// two different modules ask two different questions of it — "should I file
|
|
319
|
+
// this?" and "does anything hold?" — and both must resolve the knob through
|
|
320
|
+
// this one reader, never from branding().project directly: the fail-closed
|
|
321
|
+
// default and the non-retroactivity cutoff live in there.
|
|
322
|
+
// The members are named ONE BY ONE rather than re-exported wholesale: a
|
|
323
|
+
// `return require(…)` would publish whatever that file happens to export
|
|
324
|
+
// next, which is how a doorway stops being a contract.
|
|
325
|
+
get artistGate() {
|
|
326
|
+
const g = require('./bongos/artist-gate'); // eslint-disable-line global-require
|
|
327
|
+
return Object.freeze({
|
|
328
|
+
ARTIST_REVIEW_SOURCE: g.ARTIST_REVIEW_SOURCE,
|
|
329
|
+
ARTIST_REVIEW_REF_PREFIX: g.ARTIST_REVIEW_REF_PREFIX,
|
|
330
|
+
RESOLVED_REVIEW_STATUSES: g.RESOLVED_REVIEW_STATUSES,
|
|
331
|
+
isArtistReview: g.isArtistReview,
|
|
332
|
+
isReviewResolved: g.isReviewResolved,
|
|
333
|
+
unresolvedArtistReviews: g.unresolvedArtistReviews,
|
|
334
|
+
artistGateHalt: g.artistGateHalt,
|
|
335
|
+
artistGateMode: g.artistGateMode,
|
|
336
|
+
artistGateSince: g.artistGateSince,
|
|
337
|
+
});
|
|
338
|
+
},
|
|
306
339
|
// readApplicantProfileFromHub(githubId) — the reviewer queue's LIVE per-render
|
|
307
340
|
// read of one applicant's hub profile view (privacy spec D7, task
|
|
308
341
|
// 1002972). Published as a NARROW port on purpose: the underlying call is
|
|
@@ -394,6 +427,13 @@ module.exports = {
|
|
|
394
427
|
// directly. Resolved lazily so requiring the doorway never forces a config
|
|
395
428
|
// read at module-load (BV1.R81 / ADR 0093 §1).
|
|
396
429
|
enabledDisciplines: (...a) => require('./modules').enabledDisciplines(...a),
|
|
430
|
+
// isModuleEnabled(name) — is THIS module on for this instance? A module needs
|
|
431
|
+
// it when its own behaviour depends on ANOTHER module being present: onboarding
|
|
432
|
+
// posts a Discord approve/decline control whose 👍 is only ever applied by the
|
|
433
|
+
// `discord` module's reaction handler, so with discord off the control is a lie
|
|
434
|
+
// and must not be posted at all. Throws on an unknown name (a typo is a bug, not
|
|
435
|
+
// a silent false). Lazy, like its sibling above.
|
|
436
|
+
isModuleEnabled: (...a) => require('./modules').isModuleEnabled(...a),
|
|
397
437
|
|
|
398
438
|
// --- the seam registry — how modules cooperate without importing each other.
|
|
399
439
|
// Three primitives: PORTS (one required provider), EVENTS (many optional
|
|
@@ -101,5 +101,77 @@ t('preflightAdopt: a clean repo → no blocking; every category reports', () =>
|
|
|
101
101
|
} finally { rmSync(dir, { recursive: true, force: true }); }
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
+
// ---- ignored-incoming: the check that was missing (task 1003400) ----------
|
|
105
|
+
//
|
|
106
|
+
// The failure it exists to stop: adopt onto a repo whose .gitignore already carries
|
|
107
|
+
// `.claude/`. The skills/hooks/settings are written to disk, `git add` drops them, the
|
|
108
|
+
// commit looks clean, and every clone of that repo is missing its /builder-* commands
|
|
109
|
+
// with nothing anywhere saying why.
|
|
110
|
+
|
|
111
|
+
t('checkIgnoredIncoming: an ignored incoming file BLOCKS and names the rule to change', () => {
|
|
112
|
+
const f = pf.checkIgnoredIncoming([{ path: '.claude/skills', rule: '.gitignore:7:.claude/' }]);
|
|
113
|
+
assert.equal(f.length, 1);
|
|
114
|
+
assert.equal(f[0].severity, pf.SEV.BLOCK, 'silent data loss at git add is not a warning');
|
|
115
|
+
assert.match(f[0].message, /\.claude\/skills/, 'names the swallowed path');
|
|
116
|
+
assert.match(f[0].message, /\.gitignore:7/, 'names the exact rule line so the owner need not hunt');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
t('checkIgnoredIncoming: nothing ignored → INFO, not blocking', () => {
|
|
120
|
+
const f = pf.checkIgnoredIncoming([]);
|
|
121
|
+
assert.equal(f[0].severity, pf.SEV.INFO);
|
|
122
|
+
assert.equal(f.filter((x) => x.severity === pf.SEV.BLOCK).length, 0);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
t('checkIgnoredIncoming: probe could not run → INFO that says so, never a false all-clear', () => {
|
|
126
|
+
const f = pf.checkIgnoredIncoming([], { checked: false });
|
|
127
|
+
assert.equal(f[0].severity, pf.SEV.INFO);
|
|
128
|
+
assert.match(f[0].message, /could not run/, 'unknown must not be reported as clean');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
t('probeIgnoredIncoming: parses git check-ignore -v into path + rule', () => {
|
|
132
|
+
const execFileSync = () => '.gitignore:7:.claude/\t.claude/skills\n.gitignore:7:.claude/\t.claude/hooks\n';
|
|
133
|
+
const { ignored, checked } = pf.probeIgnoredIncoming({ dir: '/x', execFileSync });
|
|
134
|
+
assert.equal(checked, true);
|
|
135
|
+
assert.deepEqual(ignored, [
|
|
136
|
+
{ path: '.claude/skills', rule: '.gitignore:7:.claude/' },
|
|
137
|
+
{ path: '.claude/hooks', rule: '.gitignore:7:.claude/' },
|
|
138
|
+
]);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
t('probeIgnoredIncoming: git exit 1 means NOTHING ignored — a real answer, not a failure', () => {
|
|
142
|
+
const execFileSync = () => { const e = new Error('exit 1'); e.status = 1; throw e; };
|
|
143
|
+
assert.deepEqual(pf.probeIgnoredIncoming({ dir: '/x', execFileSync }), { ignored: [], checked: true });
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
t('probeIgnoredIncoming: no git / not a repo → checked:false, distinct from "clean"', () => {
|
|
147
|
+
const execFileSync = () => { const e = new Error('not a git repository'); e.status = 128; throw e; };
|
|
148
|
+
assert.deepEqual(pf.probeIgnoredIncoming({ dir: '/x', execFileSync }), { ignored: [], checked: false });
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
t('probeIgnoredIncoming: strips our trailing dir slashes before handing paths to git', () => {
|
|
152
|
+
let input = null;
|
|
153
|
+
const execFileSync = (_cmd, _args, opts) => { input = opts.input; return ''; };
|
|
154
|
+
pf.probeIgnoredIncoming({ dir: '/x', incoming: ['.claude/hooks/', 'package.json'], execFileSync });
|
|
155
|
+
assert.equal(input, '.claude/hooks\npackage.json', 'git check-ignore wants plain pathnames');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
t('preflightAdopt (end to end): mercury\'s .gitignore would have BLOCKED the adopt', () => {
|
|
159
|
+
const dir = mkdtempSync(join(tmpdir(), 'pf-ignored-'));
|
|
160
|
+
try {
|
|
161
|
+
// The exact rule that swallowed the methodology surface on the real repo.
|
|
162
|
+
writeFileSync(join(dir, '.gitignore'), '.env\nvenv/\n\n#claude code\n.claude/\n');
|
|
163
|
+
const report = pf.preflightAdopt({
|
|
164
|
+
dir,
|
|
165
|
+
stack: { migrationsDirs: [], topLevelDirs: [], ci: [] },
|
|
166
|
+
// Stand in for git so the case is hermetic (no repo needed in tmp).
|
|
167
|
+
execFileSync: () => '.gitignore:5:.claude/\t.claude/settings.json\n.gitignore:5:.claude/\t.claude/skills\n',
|
|
168
|
+
});
|
|
169
|
+
assert.ok(report.hasBlocking, 'adopt must stop rather than produce a silently broken instance');
|
|
170
|
+
const found = cats(report.findings, 'ignored-incoming');
|
|
171
|
+
assert.equal(found.length, 1);
|
|
172
|
+
assert.equal(found[0].severity, pf.SEV.BLOCK);
|
|
173
|
+
} finally { rmSync(dir, { recursive: true, force: true }); }
|
|
174
|
+
});
|
|
175
|
+
|
|
104
176
|
console.log(`\nadopt_preflight: ${passed} passed, ${failed} failed`);
|
|
105
177
|
process.exit(failed ? 1 : 0);
|
package/tests/api_path_404.mjs
CHANGED
|
@@ -144,5 +144,33 @@ t('rebaseRequiredHint: says waiting is correct when CI is merely backed up', ()
|
|
|
144
144
|
assert.match(rebaseRequiredHint([{ id: 7 }]), /waiting is correct/);
|
|
145
145
|
});
|
|
146
146
|
|
|
147
|
+
// task 1002788 — the menu must not name the move that deadlocks the reader.
|
|
148
|
+
//
|
|
149
|
+
// This hint fires on a branch that is ALREADY PUBLISHED, and server-mediated
|
|
150
|
+
// publish is append-only by deliberate design (github-push.js: "Refuse
|
|
151
|
+
// non-fast-forward implicitly (no --force, ever)"). The old wording — "rebase
|
|
152
|
+
// the branch and re-ship" — rewrites history the server already holds, so every
|
|
153
|
+
// later publish is rejected non-fast-forward and the builder is left gated from
|
|
154
|
+
// new claims AND unable to land the one they hold. Reproduced end-to-end on task
|
|
155
|
+
// 1002485. ship-land.js's recovery text already says "Do NOT reset or re-rebase";
|
|
156
|
+
// this is the same advice given BEFORE the deadlock rather than after it.
|
|
157
|
+
t('rebaseRequiredHint: prescribes MERGE, and never instructs a rebase', () => {
|
|
158
|
+
const hint = rebaseRequiredHint([{ id: 7 }]);
|
|
159
|
+
assert.ok(hint.includes('git merge origin/main'),
|
|
160
|
+
'must name the fast-forward-safe command, not just forbid the other one');
|
|
161
|
+
// Narrow on purpose: bans INSTRUCTING a rebase, not the word — "never rebase"
|
|
162
|
+
// is the sentence that carries the lesson and must keep working.
|
|
163
|
+
for (const re of [/\brebase\s*\+\s*re-ship\b/i, /\brebase (the|its|your) branch\b/i, /\brebases it\b/i]) {
|
|
164
|
+
assert.ok(!re.test(hint), `must not instruct a rebase (matched ${re}): ${hint}`);
|
|
165
|
+
}
|
|
166
|
+
assert.match(hint, /never rebase/i, 'and must say why, so the next reader does not re-add it');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
t('rebaseRequiredHint: still leads with /strand-fix — merge is the manual fallback, not the first offer', () => {
|
|
170
|
+
const hint = rebaseRequiredHint([{ id: 7 }]);
|
|
171
|
+
assert.ok(hint.indexOf('/strand-fix') < hint.indexOf('git merge origin/main'),
|
|
172
|
+
'the command that works on any machine must still come first (task 1003768)');
|
|
173
|
+
});
|
|
174
|
+
|
|
147
175
|
console.log(`\napi_path_404: ${passed} passed, ${failed} failed`);
|
|
148
176
|
process.exit(failed ? 1 : 0);
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// tests/artist_gate.mjs
|
|
2
|
+
//
|
|
3
|
+
// The artist gate (ADR 0241, task 1003575) — src/bongos/artist-gate.js.
|
|
4
|
+
//
|
|
5
|
+
// WHAT IS ACTUALLY WORTH PINNING HERE is not that the filter filters. It is the
|
|
6
|
+
// four properties ADR 0241 spends its whole argument on, each of which is a way
|
|
7
|
+
// this gate could quietly become the gate ADR 0162 retired:
|
|
8
|
+
//
|
|
9
|
+
// 1. IT READS A STATE, NEVER A VERDICT. Nothing here may consult who reviewed,
|
|
10
|
+
// what they thought, or whether they approved — only whether the review task
|
|
11
|
+
// is still open. A verdict-reading gate is pinned to a person and stalls when
|
|
12
|
+
// that person is away.
|
|
13
|
+
// 2. IT NEVER HOLDS ANYTHING BUT AT 'strict'. `off` and `advisory` must be
|
|
14
|
+
// incapable of producing a halt, however many reviews are open.
|
|
15
|
+
// 3. TIGHTENING IS NOT RETROACTIVE (§5). A review filed before the gate was
|
|
16
|
+
// raised must not hold, or raising the setting means inheriting a backlog for
|
|
17
|
+
// work nobody remembers — which is what makes a setting frightening to raise.
|
|
18
|
+
// 4. THE REFUSAL EXPLAINS ITSELF (§4). It names what holds the deploy and which
|
|
19
|
+
// control releases it. ADR 0162's incident was made worse by a blocked state
|
|
20
|
+
// that did not say either.
|
|
21
|
+
//
|
|
22
|
+
// Pure: the module's read half is injectable, so nothing here needs a branding
|
|
23
|
+
// pack, an env or a database.
|
|
24
|
+
//
|
|
25
|
+
// Run: node --test tests/artist_gate.mjs
|
|
26
|
+
|
|
27
|
+
import { test } from 'node:test';
|
|
28
|
+
import { strict as assert } from 'node:assert';
|
|
29
|
+
// Static named imports, not createRequire: the same shape tests/project_join_door.mjs
|
|
30
|
+
// uses on the sibling kernel classifier. It reads better, and it is also what lets the
|
|
31
|
+
// dead-code ratchet SEE these exports being exercised — a createRequire call is opaque
|
|
32
|
+
// to it, so a fully-tested module reads as ten unused exports.
|
|
33
|
+
import {
|
|
34
|
+
ARTIST_GATE_LEVELS,
|
|
35
|
+
RESOLVED_REVIEW_STATUSES,
|
|
36
|
+
ARTIST_REVIEW_SOURCE,
|
|
37
|
+
ARTIST_REVIEW_REF_PREFIX,
|
|
38
|
+
isArtistReview,
|
|
39
|
+
isReviewResolved,
|
|
40
|
+
unresolvedArtistReviews,
|
|
41
|
+
artistGateHalt,
|
|
42
|
+
artistGateMode,
|
|
43
|
+
artistGateSince,
|
|
44
|
+
} from '../src/bongos/artist-gate.js';
|
|
45
|
+
|
|
46
|
+
const gate = {
|
|
47
|
+
ARTIST_GATE_LEVELS,
|
|
48
|
+
RESOLVED_REVIEW_STATUSES,
|
|
49
|
+
ARTIST_REVIEW_SOURCE,
|
|
50
|
+
ARTIST_REVIEW_REF_PREFIX,
|
|
51
|
+
isArtistReview,
|
|
52
|
+
isReviewResolved,
|
|
53
|
+
unresolvedArtistReviews,
|
|
54
|
+
artistGateHalt,
|
|
55
|
+
artistGateMode,
|
|
56
|
+
artistGateSince,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// A cascade-filed artist review row, as the board holds it.
|
|
60
|
+
const review = (over = {}) => ({
|
|
61
|
+
id: '9001',
|
|
62
|
+
title: 'Artist review: the copy and visuals that shipped in task 7001',
|
|
63
|
+
status: 'backlog',
|
|
64
|
+
source: 'cascade',
|
|
65
|
+
source_ref: 'cascade-artist-review-on-ship-7001',
|
|
66
|
+
created_at: '2026-09-10T00:00:00.000Z',
|
|
67
|
+
merged_into_task_id: null,
|
|
68
|
+
...over,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// A pack reader, as branding().project would answer.
|
|
72
|
+
const pack = (project) => () => ({ project });
|
|
73
|
+
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// What counts as a review at all
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
test('only a CASCADE-filed artist review counts — a hand-filed task with a similar title does not', () => {
|
|
79
|
+
assert.equal(gate.isArtistReview(review()), true);
|
|
80
|
+
// Same source, different rule: another cascade must never hold a deploy this
|
|
81
|
+
// gate was not given authority over.
|
|
82
|
+
assert.equal(gate.isArtistReview(review({ source_ref: 'cascade-some-other-rule-7001' })), false);
|
|
83
|
+
// Same ref shape, not the cascade's source.
|
|
84
|
+
assert.equal(gate.isArtistReview(review({ source: 'manual' })), false);
|
|
85
|
+
assert.equal(gate.isArtistReview({ title: 'Artist review: please look at this' }), false);
|
|
86
|
+
assert.equal(gate.isArtistReview(null), false);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('RESOLVED is a status, never a judgement — the two ways a task leaves the board, plus a merge', () => {
|
|
90
|
+
assert.equal(gate.isReviewResolved(review({ status: 'shipped' })), true);
|
|
91
|
+
assert.equal(gate.isReviewResolved(review({ status: 'abandoned' })), true, "triage's dismiss");
|
|
92
|
+
// A row merged into another is resolved even at an open status: its successor
|
|
93
|
+
// carries the work, and holding a deploy on a row nobody will action again is
|
|
94
|
+
// exactly the indefinite stall this gate must not have.
|
|
95
|
+
assert.equal(gate.isReviewResolved(review({ status: 'backlog', merged_into_task_id: '9100' })), true);
|
|
96
|
+
for (const status of ['backlog', 'ready', 'active', 'completed', 'confirmed']) {
|
|
97
|
+
assert.equal(gate.isReviewResolved(review({ status })), false, status);
|
|
98
|
+
}
|
|
99
|
+
assert.equal(gate.isReviewResolved(null), true, 'nothing is not an open request');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// §5 — tightening is not retroactive
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
test('a review filed BEFORE the gate was raised does not hold; one filed after does', () => {
|
|
107
|
+
const older = review({ id: '9001', created_at: '2026-09-01T00:00:00.000Z' });
|
|
108
|
+
const newer = review({ id: '9002', created_at: '2026-09-12T00:00:00.000Z' });
|
|
109
|
+
const held = gate.unresolvedArtistReviews([older, newer], { since: '2026-09-10T00:00:00.000Z' });
|
|
110
|
+
assert.deepEqual(held.map((t) => t.id), ['9002']);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('no cutoff means every open review counts — absent must not read as a licence to ignore', () => {
|
|
114
|
+
const rows = [review({ id: '9001' }), review({ id: '9002', created_at: '2020-01-01T00:00:00.000Z' })];
|
|
115
|
+
assert.equal(gate.unresolvedArtistReviews(rows, {}).length, 2);
|
|
116
|
+
assert.equal(gate.unresolvedArtistReviews(rows, { since: null }).length, 2);
|
|
117
|
+
assert.equal(gate.unresolvedArtistReviews(rows, { since: 'not a date' }).length, 2, 'junk is no cutoff, not no gate');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('a review with an unreadable filing date is KEPT — the cutoff spares work, it does not excuse it', () => {
|
|
121
|
+
const held = gate.unresolvedArtistReviews([review({ created_at: null })], { since: '2026-09-10T00:00:00.000Z' });
|
|
122
|
+
assert.equal(held.length, 1);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('resolved reviews are filtered out before the cutoff is even consulted', () => {
|
|
126
|
+
const rows = [review({ id: '9001', status: 'shipped' }), review({ id: '9002', status: 'abandoned' })];
|
|
127
|
+
assert.deepEqual(gate.unresolvedArtistReviews(rows, {}), []);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// §2 / §4 — what holds, and what the refusal says
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
test("only 'strict' can hold — off and advisory are incapable of it, however many are open", () => {
|
|
135
|
+
const open = [review({ id: '9001' }), review({ id: '9002' })];
|
|
136
|
+
assert.equal(gate.artistGateHalt({ mode: 'off', reviews: open }), null);
|
|
137
|
+
assert.equal(gate.artistGateHalt({ mode: 'advisory', reviews: open }), null);
|
|
138
|
+
assert.ok(gate.artistGateHalt({ mode: 'strict', reviews: open }));
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("'strict' with nothing open holds nothing — the gate reads state, so no state is no hold", () => {
|
|
142
|
+
assert.equal(gate.artistGateHalt({ mode: 'strict', reviews: [] }), null);
|
|
143
|
+
assert.equal(gate.artistGateHalt({ mode: 'strict' }), null);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('the refusal names WHAT holds the deploy and WHICH control releases it', () => {
|
|
147
|
+
const halt = gate.artistGateHalt({ mode: 'strict', reviews: [review({ id: '9001' }), review({ id: '9002' })] });
|
|
148
|
+
assert.equal(halt.code, 'ARTIST_REVIEW_UNRESOLVED');
|
|
149
|
+
assert.match(halt.message, /2 unresolved artist reviews/);
|
|
150
|
+
assert.match(halt.message, /strict/, 'and why it is holding at all');
|
|
151
|
+
// The two real ways out, and neither is "wait for someone".
|
|
152
|
+
assert.match(halt.detail, /task 9001, task 9002/);
|
|
153
|
+
assert.match(halt.detail, /resolve or dismiss/i);
|
|
154
|
+
assert.match(halt.detail, /override-request/);
|
|
155
|
+
assert.match(halt.detail, /OWNER/, 'the escape hatch is the owner, not any Metic+ (ADR 0241 §3)');
|
|
156
|
+
assert.match(halt.detail, /advisory/, 'and the setting that stops this happening again');
|
|
157
|
+
assert.deepEqual(halt.reviewIds, ['9001', '9002']);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('the refusal never writes a bare #id — that autolinks to a GitHub issue that does not exist', () => {
|
|
161
|
+
const halt = gate.artistGateHalt({ mode: 'strict', reviews: [review()] });
|
|
162
|
+
assert.doesNotMatch(`${halt.message} ${halt.detail}`, /#\d/);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('a TRUNCATED read reports a floor, and truncation can never change the verdict', () => {
|
|
166
|
+
const many = Array.from({ length: 3 }, (_, i) => review({ id: String(9000 + i) }));
|
|
167
|
+
const halt = artistGateHalt({ mode: 'strict', reviews: many, truncated: true });
|
|
168
|
+
assert.match(halt.message, /^3\+ unresolved artist reviews/, 'a floor, not a count it cannot stand behind');
|
|
169
|
+
// The verdict is identical either way: every row handed in is a holder, so one
|
|
170
|
+
// is enough and a capped read still holds.
|
|
171
|
+
assert.equal(artistGateHalt({ mode: 'strict', reviews: many }).code, halt.code);
|
|
172
|
+
// A single truncated row is still plural — "1+ review" would read as exactly one.
|
|
173
|
+
assert.match(artistGateHalt({ mode: 'strict', reviews: [review()], truncated: true }).message, /^1\+ unresolved artist reviews/);
|
|
174
|
+
assert.match(artistGateHalt({ mode: 'strict', reviews: [review()] }).message, /^1 unresolved artist review /);
|
|
175
|
+
// And truncation cannot manufacture a hold out of nothing.
|
|
176
|
+
assert.equal(artistGateHalt({ mode: 'strict', reviews: [], truncated: true }), null);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test('a long queue is summarised rather than dumped — five named, the rest counted', () => {
|
|
180
|
+
const many = Array.from({ length: 9 }, (_, i) => review({ id: String(9000 + i) }));
|
|
181
|
+
const halt = gate.artistGateHalt({ mode: 'strict', reviews: many });
|
|
182
|
+
assert.match(halt.detail, /and 4 more/);
|
|
183
|
+
assert.equal(halt.reviewIds.length, 9, 'but the machine-readable list is complete');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
// The reader — fail-closed in the direction the default already points
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
|
|
190
|
+
test('the level is read from the pack, and only the three declared levels are accepted', () => {
|
|
191
|
+
assert.equal(gate.artistGateMode(pack({ artistGate: 'off' })), 'off');
|
|
192
|
+
assert.equal(gate.artistGateMode(pack({ artistGate: 'advisory' })), 'advisory');
|
|
193
|
+
assert.equal(gate.artistGateMode(pack({ artistGate: 'strict' })), 'strict');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("an absent, unreadable or unrecognised level resolves to 'strict' — the platform default", () => {
|
|
197
|
+
// Absent is the honest case for a self-hosted instance nobody ever pushed a
|
|
198
|
+
// setting to; it takes the default rather than dropping the policy.
|
|
199
|
+
assert.equal(gate.artistGateMode(pack({})), 'strict');
|
|
200
|
+
assert.equal(gate.artistGateMode(pack({ artistGate: '' })), 'strict');
|
|
201
|
+
// A value this core cannot read is a policy it does not know — and the safe
|
|
202
|
+
// direction here is the strict one, because strict only ever delays a pin move.
|
|
203
|
+
assert.equal(gate.artistGateMode(pack({ artistGate: 'Strict' })), 'strict');
|
|
204
|
+
assert.equal(gate.artistGateMode(pack({ artistGate: 'sort-of' })), 'strict');
|
|
205
|
+
assert.equal(gate.artistGateMode(() => { throw new Error('no pack'); }), 'strict');
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test('the cutoff is read from the pack too, and junk reads as no cutoff', () => {
|
|
209
|
+
assert.equal(gate.artistGateSince(pack({ artistGateSince: '2026-09-10T00:00:00.000Z' })), '2026-09-10T00:00:00.000Z');
|
|
210
|
+
assert.equal(gate.artistGateSince(pack({ artistGateSince: '' })), null, 'the empty env value the transport always writes');
|
|
211
|
+
assert.equal(gate.artistGateSince(pack({ artistGateSince: 'whenever' })), null);
|
|
212
|
+
assert.equal(gate.artistGateSince(pack({})), null);
|
|
213
|
+
assert.equal(gate.artistGateSince(() => { throw new Error('no pack'); }), null);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
// The property the whole ADR turns on
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
|
|
220
|
+
test('NOTHING in the gate reads a verdict — only source, ref, status, merge and date', () => {
|
|
221
|
+
// A row carrying every judgement-shaped field the board could hand it must
|
|
222
|
+
// behave identically to one carrying none: if this ever fails, the gate has
|
|
223
|
+
// started reading an opinion and ADR 0162's objection applies again.
|
|
224
|
+
const opinionated = review({
|
|
225
|
+
grade: 'fail', verdict: 'rejected', approved_by: null, review_outcome: 'needs-work', artist_says: 'this is slop',
|
|
226
|
+
});
|
|
227
|
+
assert.deepEqual(
|
|
228
|
+
gate.unresolvedArtistReviews([opinionated], {}).map((t) => t.id),
|
|
229
|
+
gate.unresolvedArtistReviews([review()], {}).map((t) => t.id)
|
|
230
|
+
);
|
|
231
|
+
const a = gate.artistGateHalt({ mode: 'strict', reviews: [opinionated] });
|
|
232
|
+
const b = gate.artistGateHalt({ mode: 'strict', reviews: [review()] });
|
|
233
|
+
assert.deepEqual(a, b, 'an approving artist releases nothing; a disapproving one holds nothing extra');
|
|
234
|
+
});
|