@bongos/core 1.19.714 → 1.19.724
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bongos-core.json +488 -413
- 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/copy-inventory.md +15 -13
- package/docs/copy-registry.json +35 -17
- 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/hall-ui/public/watch.css +6 -0
- package/modules/hall-ui/public/watch.js +52 -6
- 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/backfill-task-rewards.js +9 -4
- 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/credit_log_single_writer.mjs +94 -0
- package/tests/currency_label.mjs +11 -4
- package/tests/go_live.mjs +144 -0
- package/tests/helpers.mjs +8 -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
- package/tests/watch_roster_self_row.mjs +289 -0
|
@@ -21,6 +21,12 @@
|
|
|
21
21
|
/* Row actions inside a ledger cell: ghost pills in a row. */
|
|
22
22
|
.watch-actions { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
|
|
23
23
|
|
|
24
|
+
/* The reader's own roster row (task 1003720). Nobody may change their own rank
|
|
25
|
+
or standing — the server refuses both outright — so that row states a fact
|
|
26
|
+
where the others carry a control, and a fact is written in the faint ink, not
|
|
27
|
+
painted as a disabled pill. The foot line under the table says why. */
|
|
28
|
+
.watch-self { color: var(--ink-faint); }
|
|
29
|
+
|
|
24
30
|
/* A sealed or failed section says so in place. Three states, three sentences:
|
|
25
31
|
"nothing here" is the kit's empty state, this is "you may not see this" and
|
|
26
32
|
"could not load". */
|
|
@@ -745,6 +745,13 @@
|
|
|
745
745
|
// sits at a rank outside this set, we inject it so the select still shows it.
|
|
746
746
|
const RANK_OPTIONS = ['xenos', 'thetes', 'metic', 'archon'];
|
|
747
747
|
|
|
748
|
+
// Who is reading this roster (task 1003720). `load()` already fetches /me to
|
|
749
|
+
// resolve the page's floor and then threw the id away, so the roster — which
|
|
750
|
+
// returns EVERY builder, the viewer included — could not tell which row was
|
|
751
|
+
// the reader's own. See renderRoster for why that mattered.
|
|
752
|
+
let viewerId = null;
|
|
753
|
+
const isViewer = (b) => viewerId != null && b && b.id != null && String(b.id) === String(viewerId);
|
|
754
|
+
|
|
748
755
|
function renderRoster(data, target) {
|
|
749
756
|
const builders = Array.isArray(data.builders) ? data.builders : [];
|
|
750
757
|
setAttention('att-stale', builders.filter((b) => b.stale_claim).length);
|
|
@@ -762,16 +769,39 @@
|
|
|
762
769
|
],
|
|
763
770
|
pager: makePager('watch-roster', 'otb.watch.rosterPerPage'),
|
|
764
771
|
rowHtml: (b) => {
|
|
772
|
+
// THE VIEWER'S OWN ROW OFFERS NOTHING (task 1003720, re-filed from 1002951
|
|
773
|
+
// — "I cannot press offboard", reported by the owner from Discord).
|
|
774
|
+
//
|
|
775
|
+
// The roster returns every builder, so the reader's own row is in it. Both
|
|
776
|
+
// routes behind that row refuse a self-change OUTRIGHT and always will:
|
|
777
|
+
// PATCH /builders/:id/rank answers cannot_change_own_rank and
|
|
778
|
+
// /builders/:id/status answers cannot_change_own_status — the guard that
|
|
779
|
+
// stops the last Archon demoting or offboarding themselves and locking
|
|
780
|
+
// everyone out. The row painted a live rank <select> and a live Offboard
|
|
781
|
+
// pill anyway, so from the owner's seat the page carried a control that
|
|
782
|
+
// could only ever answer with a raw refusal slug in a toast. That is what
|
|
783
|
+
// "I cannot press offboard" describes.
|
|
784
|
+
//
|
|
785
|
+
// This is not a SOFT gate (the primer's acknowledge control, which opens
|
|
786
|
+
// once you have read enough and so stays in the tab order as
|
|
787
|
+
// aria-disabled) — it is an action that can never open for this reader.
|
|
788
|
+
// So the row renders no control at all and the section's foot line says
|
|
789
|
+
// why, rather than painting a dead one.
|
|
790
|
+
const self = isViewer(b);
|
|
765
791
|
const opts = RANK_OPTIONS.slice();
|
|
766
792
|
if (b.rank && !opts.includes(b.rank)) opts.unshift(b.rank);
|
|
767
|
-
const
|
|
768
|
-
`<
|
|
769
|
-
|
|
770
|
-
|
|
793
|
+
const rankCell = self
|
|
794
|
+
? `<span class="watch-self">${escapeHtml(rankLabel(b.rank))}</span>`
|
|
795
|
+
: `<select data-roster-rank data-id="${escapeHtml(b.id)}" data-current="${escapeHtml(b.rank || '')}" aria-label="Rank">` +
|
|
796
|
+
opts.map((rk) => `<option value="${escapeHtml(rk)}"${rk === b.rank ? ' selected' : ''}>${escapeHtml(rankLabel(rk))}</option>`).join('') +
|
|
797
|
+
'</select>';
|
|
771
798
|
const isInactive = String(b.status) === 'inactive';
|
|
772
799
|
const statusBtn = isInactive
|
|
773
800
|
? ghost(`data-roster-status="active" data-id="${escapeHtml(b.id)}"`, 'Reactivate')
|
|
774
801
|
: ghost(`data-roster-status="inactive" data-id="${escapeHtml(b.id)}"`, 'Offboard', true);
|
|
802
|
+
const actionCell = self
|
|
803
|
+
? '<span class="watch-self">That\'s you</span>'
|
|
804
|
+
: `<div class="watch-actions">${statusBtn}</div>`;
|
|
775
805
|
const claims = num(b.active_claim_count);
|
|
776
806
|
const staleFlag = b.stale_claim ? ' <span class="badge badge--sev-medium">stale</span>' : '';
|
|
777
807
|
// task 1467: the roster name links to the builder's public profile page.
|
|
@@ -784,17 +814,28 @@
|
|
|
784
814
|
`<td>${rosterNameCell}` +
|
|
785
815
|
(b.github_login ? ` <span class="ov-mono">@${escapeHtml(b.github_login)}</span>` : '') +
|
|
786
816
|
'</td>' +
|
|
787
|
-
`<td>${
|
|
817
|
+
`<td>${rankCell}</td>` +
|
|
788
818
|
`<td class="num">${fmtInt(b.total_credits)}</td>` +
|
|
789
819
|
`<td class="num">${fmtInt(b.karma)}</td>` +
|
|
790
820
|
`<td class="num${b.stale_claim ? ' warn' : ''}">${fmtInt(claims)}${staleFlag}</td>` +
|
|
791
821
|
`<td><span class="badge badge--status">${escapeHtml(b.status || '—')}</span></td>` +
|
|
792
|
-
`<td
|
|
822
|
+
`<td>${actionCell}</td>` +
|
|
793
823
|
'</tr>'
|
|
794
824
|
);
|
|
795
825
|
},
|
|
796
826
|
}).render(builders);
|
|
797
827
|
|
|
828
|
+
// The one asymmetry in the table, stated once under it rather than repeated
|
|
829
|
+
// on the row. Appended as an ELEMENT, not with `innerHTML +=`: re-parsing the
|
|
830
|
+
// body would drop the pager's own wiring. The rank is written from
|
|
831
|
+
// rankLabel(), never a hardcoded "Archon", so the floor cannot drift.
|
|
832
|
+
if (builders.some(isViewer)) {
|
|
833
|
+
const note = document.createElement('p');
|
|
834
|
+
note.className = 'ov-foot';
|
|
835
|
+
note.textContent = `Your own row carries no controls. Nobody can change their own rank or standing — that guard is what stops the last ${rankLabel('archon')} locking everyone out — so another ${rankLabel('archon')} does it.`;
|
|
836
|
+
target.appendChild(note);
|
|
837
|
+
}
|
|
838
|
+
|
|
798
839
|
// Rank change via the <select>. Bespoke: a 'change' listener that rolls the
|
|
799
840
|
// select back to its previous value on failure, delegated so a page flip
|
|
800
841
|
// cannot strand a row without one.
|
|
@@ -1305,6 +1346,11 @@
|
|
|
1305
1346
|
if (!res.ok) throw new Error(`me → ${res.status}`);
|
|
1306
1347
|
const payload = res.data;
|
|
1307
1348
|
builder = (payload && payload.builder) ? payload.builder : payload;
|
|
1349
|
+
// Kept for the roster, which must know which row is the reader's own
|
|
1350
|
+
// (task 1003720). Null when /me answered without one — isViewer then
|
|
1351
|
+
// matches nothing, so the roster degrades to the old every-row-acts shape
|
|
1352
|
+
// rather than marking the wrong row as yours.
|
|
1353
|
+
viewerId = (builder && builder.id != null) ? builder.id : null;
|
|
1308
1354
|
} catch (err) {
|
|
1309
1355
|
console.error('[watch] failed to read viewer', err);
|
|
1310
1356
|
$('#watch-sub').textContent = 'Could not verify your access. Go back and try again.';
|
|
@@ -73,9 +73,13 @@
|
|
|
73
73
|
// 'engineer'). This is the whole point of the mechanism: the
|
|
74
74
|
// discipline of the generated task is not the discipline of the
|
|
75
75
|
// ship that caused it.
|
|
76
|
-
// when(task) a predicate over the shipped task row
|
|
77
|
-
// a throw is caught and treated as "no
|
|
78
|
-
// predicate must not stop the other
|
|
76
|
+
// when(task, ctx) a predicate over the shipped task row and the run CONTEXT.
|
|
77
|
+
// Must be pure and total — a throw is caught and treated as "no
|
|
78
|
+
// match", because a broken predicate must not stop the other
|
|
79
|
+
// rules. `ctx` is the per-run policy the runner resolved (today:
|
|
80
|
+
// `{ artistGate }`), injected rather than read, so this file keeps
|
|
81
|
+
// its "importable with nothing behind it" property. A rule that
|
|
82
|
+
// does not care about policy simply ignores the second argument.
|
|
79
83
|
// title(task) / body(task) the generated task's title and description.
|
|
80
84
|
// kind optional task kind; must be one the classifier knows
|
|
81
85
|
// (task-classifier.VALID_KINDS). Defaults to 'unclassified' —
|
|
@@ -151,7 +155,14 @@ const CASCADES = Object.freeze([
|
|
|
151
155
|
kind: 'unclassified',
|
|
152
156
|
// The anti-recursion guard (source !== CASCADE_SOURCE) is FIRST so a
|
|
153
157
|
// cascade-generated task is rejected before any other test runs.
|
|
154
|
-
|
|
158
|
+
//
|
|
159
|
+
// The project's artist_gate at 'off' means "not generated" (ADR 0241 §2's
|
|
160
|
+
// table, task 1003575) — the one level at which this rule files nothing. It
|
|
161
|
+
// is read from ctx, never from branding, so the predicate stays pure; an
|
|
162
|
+
// absent ctx (a caller that resolved no policy) files, because the gate's
|
|
163
|
+
// default is 'strict' and silence must not be read as an opt-out.
|
|
164
|
+
when: (t, ctx) => (ctx || {}).artistGate !== 'off'
|
|
165
|
+
&& t.source !== CASCADE_SOURCE && hasCopyOrVisualWork(t),
|
|
155
166
|
title: (t) => `Artist review: the copy and visuals that shipped in task ${t.id}`,
|
|
156
167
|
body: artistReviewBody,
|
|
157
168
|
}),
|
|
@@ -201,13 +212,13 @@ function validateCascades(rules = CASCADES) {
|
|
|
201
212
|
// ---------------------------------------------------------------------------
|
|
202
213
|
|
|
203
214
|
// The task rows a shipped task cascades into. Pure: no database, no clock, no
|
|
204
|
-
// side effect — hand it a task-shaped object
|
|
205
|
-
// the specs that would be filed.
|
|
215
|
+
// side effect — hand it a task-shaped object, a rule table and the run's policy
|
|
216
|
+
// context and it returns the specs that would be filed.
|
|
206
217
|
//
|
|
207
218
|
// A rule whose `when` THROWS is treated as no-match and reported in `errors`,
|
|
208
219
|
// never propagated: one broken predicate must not cost the other rules their
|
|
209
220
|
// tasks, and it certainly must not reach the ship.
|
|
210
|
-
function cascadesFor(task, rules = CASCADES) {
|
|
221
|
+
function cascadesFor(task, rules = CASCADES, ctx = {}) {
|
|
211
222
|
const matched = [];
|
|
212
223
|
const errors = [];
|
|
213
224
|
if (!task || task.id == null) return { matched, errors: ['no task'] };
|
|
@@ -216,7 +227,7 @@ function cascadesFor(task, rules = CASCADES) {
|
|
|
216
227
|
if (shape.length) { errors.push(`cascade "${rule && rule.key}": ${shape.join('; ')}`); continue; }
|
|
217
228
|
let hit = false;
|
|
218
229
|
try {
|
|
219
|
-
hit = rule.when(task) === true;
|
|
230
|
+
hit = rule.when(task, ctx) === true;
|
|
220
231
|
} catch (e) {
|
|
221
232
|
errors.push(`cascade "${rule.key}": when() threw (${e && e.message}) — treated as no match`);
|
|
222
233
|
continue;
|
|
@@ -281,6 +292,14 @@ function cascadeTaskSpec(rule, task) {
|
|
|
281
292
|
// goal_id / version_id / discipline)
|
|
282
293
|
// deps.findTaskBySourceRef({...}) -> an existing row or null
|
|
283
294
|
// deps.createTask(spec) -> the created row
|
|
295
|
+
// deps.artistGateMode() -> 'off' | 'advisory' | 'strict' — the
|
|
296
|
+
// project's artist gate (ADR 0241). Injected
|
|
297
|
+
// so this file reads no branding pack; a
|
|
298
|
+
// reader that throws leaves the context key
|
|
299
|
+
// undefined, which FILES (the gate's own
|
|
300
|
+
// default is 'strict', and a policy read
|
|
301
|
+
// that failed must not silently behave like
|
|
302
|
+
// 'off').
|
|
284
303
|
// deps.log -> optional { info, error }
|
|
285
304
|
async function runCascadesForShip(shipped, deps = {}) {
|
|
286
305
|
const report = { created: [], skipped: [], errors: [] };
|
|
@@ -292,7 +311,16 @@ async function runCascadesForShip(shipped, deps = {}) {
|
|
|
292
311
|
const task = deps.getTask ? await deps.getTask(id) : null;
|
|
293
312
|
if (!task) { report.errors.push(`cascade: task ${id} not readable after ship`); return report; }
|
|
294
313
|
|
|
295
|
-
|
|
314
|
+
// The per-run policy context. Resolved once, outside the rule loop, and
|
|
315
|
+
// never allowed to cost the run: a reader that throws is reported and leaves
|
|
316
|
+
// the key undefined rather than suppressing every rule that consults it.
|
|
317
|
+
const ctx = {};
|
|
318
|
+
if (typeof deps.artistGateMode === 'function') {
|
|
319
|
+
try { ctx.artistGate = deps.artistGateMode(); }
|
|
320
|
+
catch (e) { report.errors.push(`cascade: artistGateMode() threw (${e && e.message}) — policy unread`); }
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const { matched, errors } = cascadesFor(task, deps.rules || CASCADES, ctx);
|
|
296
324
|
report.errors.push(...errors);
|
|
297
325
|
|
|
298
326
|
for (const { rule, spec } of matched) {
|
|
@@ -94,8 +94,11 @@ async function claimTask({ taskId, builderId, worktreeName, headSha, creatorSess
|
|
|
94
94
|
|
|
95
95
|
// ADR 0120 part 3: the needs_rebase claim gate. A builder who owes a rebase
|
|
96
96
|
// on any un-landed confirmed task (flagged by the reconciler, part 2) is
|
|
97
|
-
// blocked from taking on NEW work until they LAND it (
|
|
98
|
-
// re-ship
|
|
97
|
+
// blocked from taking on NEW work until they LAND it (`git merge origin/main`
|
|
98
|
+
// + re-ship — task 1002788: MERGE, never rebase, because the branch is already
|
|
99
|
+
// published and server-mediated publish is append-only, so rewriting it makes
|
|
100
|
+
// the task permanently un-landable while the gate keeps their queue shut)
|
|
101
|
+
// or RELEASE it — both clear the flag. Their
|
|
99
102
|
// already-active claims are untouched (we only refuse fresh claims, never
|
|
100
103
|
// freeze in-flight work). Since the reward now lands only on ship (part 1),
|
|
101
104
|
// the builder is motivated to clear it; since it gates their queue, they
|
|
@@ -527,9 +527,10 @@ async function achieveGoalIfComplete({ goalId }, deps = {}) {
|
|
|
527
527
|
// precedent). membershipKind defaults to 'member'.
|
|
528
528
|
|
|
529
529
|
// THE COLUMN IS `membership_kind` (task 1003013, ADR 0177 — ADR 0174 reserves the
|
|
530
|
-
// other word for the three crafts). The
|
|
531
|
-
//
|
|
532
|
-
//
|
|
530
|
+
// other word for the three crafts). The rename is COMPLETE: core_240 (task
|
|
531
|
+
// 1003089) dropped the legacy column and its lockstep trigger, and the new column
|
|
532
|
+
// now carries NOT NULL + DEFAULT 'member' itself rather than resting on that
|
|
533
|
+
// trigger. There is no second spelling left to keep in step.
|
|
533
534
|
//
|
|
534
535
|
// ARRIVING AS A MEMBER CLOSES ANY PENDING ASK (task 1002888 / idea 1000688).
|
|
535
536
|
// The membership write used to be a bare upsert that never looked at
|
|
@@ -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 };
|