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