@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
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// tests/rank_roles_audit.mjs
|
|
2
|
+
//
|
|
3
|
+
// task 1002873 — the re-runnable half of "does the live roster hold only the four
|
|
4
|
+
// live ranks?".
|
|
5
|
+
//
|
|
6
|
+
// THE GAP THIS CLOSES. migrations/023's CHECK on builders.rank still admits twelve
|
|
7
|
+
// legacy values. governance_002's backfill INNER JOINs rank → the four seeded
|
|
8
|
+
// governance_roles, so a builder on a legacy rank gets no role row and resolves to
|
|
9
|
+
// ZERO permissions — admitted by threshold under requireRank, refused by the
|
|
10
|
+
// resolver, and told nothing. ADR 0151 chose that fail-closed direction
|
|
11
|
+
// deliberately; what was missing was anyone CHECKING.
|
|
12
|
+
//
|
|
13
|
+
// Three things had looked like the check and were not:
|
|
14
|
+
// - governance_002's own comment ("none exist today — verified") is a
|
|
15
|
+
// point-in-time manual note that nothing re-runs.
|
|
16
|
+
// - tests/governance_parity.mjs is DB-free by necessity (the unit gate has no
|
|
17
|
+
// Postgres) and says in the repo that it defers the roster half.
|
|
18
|
+
// - R114's live proof ran on a scratch DB holding only the four seeded
|
|
19
|
+
// rank-roles; the production roster was never queried.
|
|
20
|
+
//
|
|
21
|
+
// So the decision is a PURE function and this is a DB-free test of it. That is not
|
|
22
|
+
// a compromise — no-Postgres-in-CI is precisely why the gap survived, and a
|
|
23
|
+
// decision that can be tested without a database is the only kind that gets tested
|
|
24
|
+
// at all here. The live read is the script's thin shell around it.
|
|
25
|
+
//
|
|
26
|
+
// Run: node tests/rank_roles_audit.mjs
|
|
27
|
+
|
|
28
|
+
import { strict as assert } from 'node:assert';
|
|
29
|
+
import path from 'node:path';
|
|
30
|
+
import { createRequire } from 'node:module';
|
|
31
|
+
import { fileURLToPath } from 'node:url';
|
|
32
|
+
|
|
33
|
+
const require = createRequire(import.meta.url);
|
|
34
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
35
|
+
const { findRanksWithoutRoles, summarize } = require(path.join(ROOT, 'scripts', 'gds', 'audit-rank-roles.js'));
|
|
36
|
+
const { LIVE_RANK_LADDER } = require(path.join(ROOT, 'src', 'bongos', 'db-kernel.js'));
|
|
37
|
+
|
|
38
|
+
let passed = 0;
|
|
39
|
+
let failed = 0;
|
|
40
|
+
function t(name, fn) {
|
|
41
|
+
try { fn(); passed++; console.log(` PASS ${name}`); }
|
|
42
|
+
catch (e) { failed++; console.log(` FAIL ${name}\n ${e.message}`); }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const live = (rank, id = 1) => ({ id, github_login: `u${id}`, rank, status: 'active' });
|
|
46
|
+
|
|
47
|
+
t('the live ladder is the four ranks this audit is about', () => {
|
|
48
|
+
// Derived, not retyped: if the ladder ever gains a rank, this audit must be
|
|
49
|
+
// re-reasoned rather than silently widened.
|
|
50
|
+
assert.deepEqual([...LIVE_RANK_LADDER].sort(), ['archon', 'metic', 'thetes', 'xenos']);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
t('a roster of only live ranks has no offenders', () => {
|
|
54
|
+
const rows = LIVE_RANK_LADDER.map((r, i) => live(r, i + 1));
|
|
55
|
+
assert.deepEqual(findRanksWithoutRoles(rows), []);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
t('a LEGACY rank is caught — the whole point', () => {
|
|
59
|
+
const out = findRanksWithoutRoles([live('archon', 1), { id: 2, github_login: 'old', rank: 'ephor', status: 'active' }]);
|
|
60
|
+
assert.equal(out.length, 1);
|
|
61
|
+
assert.equal(out[0].rank, 'ephor');
|
|
62
|
+
assert.equal(out[0].id, '2', 'the id must be reported as a string an operator can paste');
|
|
63
|
+
assert.equal(out[0].github_login, 'old', 'and the login, which is what identifies the account');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
t('a NULL or missing rank is caught too — it fails the same INNER JOIN', () => {
|
|
67
|
+
for (const row of [
|
|
68
|
+
{ id: 3, rank: null, status: 'active' },
|
|
69
|
+
{ id: 4, status: 'active' },
|
|
70
|
+
{ id: 5, rank: '', status: 'active' },
|
|
71
|
+
]) {
|
|
72
|
+
const out = findRanksWithoutRoles([row]);
|
|
73
|
+
assert.equal(out.length, 1, `${JSON.stringify(row)} must be reported`);
|
|
74
|
+
assert.equal(out[0].rank, row.rank === undefined ? null : row.rank);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
t('an INACTIVE builder on a legacy rank is still reported', () => {
|
|
79
|
+
// The roster query has no WHERE on `builders`, and this is the row a future
|
|
80
|
+
// CHECK-narrowing migration would fail to apply over. Skipping it would make a
|
|
81
|
+
// clean audit a false assurance for exactly the case that matters.
|
|
82
|
+
const out = findRanksWithoutRoles([{ id: 9, github_login: 'gone', rank: 'hoplite', status: 'inactive' }]);
|
|
83
|
+
assert.equal(out.length, 1);
|
|
84
|
+
assert.equal(out[0].status, 'inactive', 'and the status is carried so the operator can judge urgency');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
t('case matters — the backfill JOIN is exact', () => {
|
|
88
|
+
const out = findRanksWithoutRoles([{ id: 6, rank: 'Archon', status: 'active' }]);
|
|
89
|
+
assert.equal(out.length, 1, '`Archon` does not equal `archon` in SQL; a near-miss is still zero permissions');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
t('an empty or junk roster reports nothing and never throws', () => {
|
|
93
|
+
for (const bad of [[], null, undefined, 'nope', {}]) {
|
|
94
|
+
assert.deepEqual(findRanksWithoutRoles(bad), [], `${JSON.stringify(bad)} must be a quiet empty result`);
|
|
95
|
+
}
|
|
96
|
+
assert.deepEqual(findRanksWithoutRoles([null, undefined]), [], 'null rows are skipped, not dereferenced');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
t('the live-rank set is injectable, so the test is not asserting against itself', () => {
|
|
100
|
+
// Pass a deliberately different ladder: the function must obey its argument
|
|
101
|
+
// rather than a hardcoded list, or this whole file would be tautological.
|
|
102
|
+
assert.equal(findRanksWithoutRoles([live('archon')], ['xenos']).length, 1);
|
|
103
|
+
assert.equal(findRanksWithoutRoles([live('xenos')], ['xenos']).length, 0);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
t('summarize counts every rank, including the missing one', () => {
|
|
107
|
+
const h = summarize([live('xenos', 1), live('xenos', 2), live('archon', 3), { id: 4, rank: null }]);
|
|
108
|
+
assert.deepEqual(h, [['xenos', 2], ['archon', 1], ['(none)', 1]],
|
|
109
|
+
'a clean run must still say what it looked at — a check that prints nothing is '
|
|
110
|
+
+ 'indistinguishable from one that read nothing, which is the failure this task is about');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
console.log(`\nrank_roles_audit: ${passed} passed, ${failed} failed`);
|
|
114
|
+
process.exit(failed === 0 ? 0 : 1);
|
|
@@ -23,8 +23,9 @@ import { strict as assert } from 'node:assert';
|
|
|
23
23
|
import { createRequire } from 'node:module';
|
|
24
24
|
|
|
25
25
|
const require = createRequire(import.meta.url);
|
|
26
|
-
const {
|
|
27
|
-
|
|
26
|
+
const {
|
|
27
|
+
scrubSecrets, shannonEntropy, looksHighEntropySecret, collectEnvSecrets,
|
|
28
|
+
} = require('../scripts/gds/ship.js');
|
|
28
29
|
|
|
29
30
|
let passed = 0;
|
|
30
31
|
let failed = 0;
|
|
@@ -190,6 +191,73 @@ test('benign prose and short identifiers survive untouched', () => {
|
|
|
190
191
|
}
|
|
191
192
|
});
|
|
192
193
|
|
|
194
|
+
// ============================================================
|
|
195
|
+
// task 1002460 — the doc-path exemption is an ALLOWLIST, so recall is provable.
|
|
196
|
+
//
|
|
197
|
+
// Three shape-based rules were tried first and each leaked: anything built on what
|
|
198
|
+
// a filename LOOKS like also admits a secret dressed that way. The exemption now
|
|
199
|
+
// spares a candidate only when the caller says that exact name is a file it is
|
|
200
|
+
// publishing. A secret is not a file in the tree.
|
|
201
|
+
// ============================================================
|
|
202
|
+
|
|
203
|
+
const DOC_NAMES = new Set(["0016-trust-boundary-server-enforced-permissions.md","0083-module-system-plugin-api.md","core_231_project_settings_and_attention_water.sql"]);
|
|
204
|
+
|
|
205
|
+
test('1002460 with NO allowlist there is no exemption at all — recall byte-identical', () => {
|
|
206
|
+
// The guarantee that matters. Every caller that does not opt in — including every
|
|
207
|
+
// transcript scrub — behaves exactly as it did before this task existed.
|
|
208
|
+
for (const name of DOC_NAMES) {
|
|
209
|
+
const link = `[x](${name})`;
|
|
210
|
+
assert.ok(scrubSecrets(link).includes(REDACTED),
|
|
211
|
+
`without an allowlist a long filename is still redacted, as before: ${name}`);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('1002460 with the allowlist, real doc links survive', () => {
|
|
216
|
+
const opts = { knownDocNames: DOC_NAMES };
|
|
217
|
+
for (const name of DOC_NAMES) {
|
|
218
|
+
const link = `[x](${name})`;
|
|
219
|
+
assert.equal(scrubSecrets(link, [], opts), link, `over-redacted a real doc link: ${name}`);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test('1002460 a secret is never in the allowlist, so it is never spared', () => {
|
|
224
|
+
// The smuggling cases from rounds 1 and 2, now structurally impossible: each is
|
|
225
|
+
// offered WITH the allowlist active, and none of them is a name in it.
|
|
226
|
+
const opts = { knownDocNames: DOC_NAMES };
|
|
227
|
+
const dressed = [
|
|
228
|
+
'[x](k3f9d2m7-q8w1e4r6-t5y0u3i9-p2a7s4d1.md)',
|
|
229
|
+
'[x](qxzjvbwk-mrtplghd-nfyscuae-bvkxwqzj.md)',
|
|
230
|
+
'[x](qxjvbwkzptrmnfyaeuicohgdslx12-a.md)', // the unbounded-run case round 2 missed
|
|
231
|
+
// NOTE on all-numeric strings: a long digit run joined by a hyphen is NOT
|
|
232
|
+
// caught by the scrubber with or without an allowlist — the hyphen breaks the
|
|
233
|
+
// all-hex rule C and H=3.30 is under rules A and B. That is a PRE-EXISTING gap
|
|
234
|
+
// in the entropy heuristic, not something this exemption opens, so it is noted
|
|
235
|
+
// here rather than asserted: a case that fails identically both ways would
|
|
236
|
+
// prove nothing about the allowlist.
|
|
237
|
+
'[x](a3f5c9e12b47d8016fa2c4e7b9d03518aa.md)',
|
|
238
|
+
];
|
|
239
|
+
const leaked = dressed.filter((d) => !scrubSecrets(d, [], opts).includes(REDACTED));
|
|
240
|
+
assert.deepEqual(leaked, [], `these slipped the scrubber: ${leaked.join(" | ")}`);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test('1002460 the allowlist cannot spare a planted secret (recall unchanged, proven)', () => {
|
|
244
|
+
// The differential proof, now trivial: offer the WHOLE planted corpus with an
|
|
245
|
+
// allowlist active. Nothing in it is a filename, so nothing can be exempted.
|
|
246
|
+
const opts = { knownDocNames: DOC_NAMES };
|
|
247
|
+
for (const { name, secret, context } of positives()) {
|
|
248
|
+
const out = scrubSecrets(context, collectEnvSecrets({}), opts);
|
|
249
|
+
assert.ok(!out.includes(secret), `LEAK with allowlist active: "${name}" survived: ${out}`);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test('1002460 an allowlisted name only counts at a real doc extension boundary', () => {
|
|
254
|
+
// The name must be followed by its own extension. A secret that merely CONTAINS
|
|
255
|
+
// an allowlisted stem, or sits before a different extension, is not spared.
|
|
256
|
+
const opts = { knownDocNames: DOC_NAMES };
|
|
257
|
+
const notAName = '[x](0016-trust-boundary-server-enforced-permissions.env)';
|
|
258
|
+
assert.ok(scrubSecrets(notAName, [], opts).includes(REDACTED),
|
|
259
|
+
'the same stem before a NON-doc extension is not an allowlisted file');
|
|
260
|
+
});
|
|
193
261
|
test('empty / non-string / short-literal inputs are safe', () => {
|
|
194
262
|
assert.equal(scrubSecrets(''), '');
|
|
195
263
|
assert.equal(scrubSecrets(null), null);
|
|
@@ -99,5 +99,55 @@ t('widget HTML-escapes the refs (no raw angle brackets from ids)', () => {
|
|
|
99
99
|
assert.ok(html.includes('<b>'), 'the id is HTML-escaped');
|
|
100
100
|
});
|
|
101
101
|
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// task 1002788 — this banner must not send a builder into a deadlock.
|
|
104
|
+
//
|
|
105
|
+
// The branch is ALREADY PUBLISHED when the gate fires, and server-mediated
|
|
106
|
+
// publish is append-only by deliberate design (github-push.js: "Refuse
|
|
107
|
+
// non-fast-forward implicitly (no --force, ever)"). So "rebase + re-ship" —
|
|
108
|
+
// what this banner used to say — rewrites history the server already holds, and
|
|
109
|
+
// every later publish is rejected non-fast-forward. The builder is then gated
|
|
110
|
+
// from new claims AND unable to land the one they hold, which is exactly the
|
|
111
|
+
// loop reproduced end-to-end on task 1002485. `git merge origin/main` reaches
|
|
112
|
+
// the same up-to-date branch and stays a fast-forward.
|
|
113
|
+
//
|
|
114
|
+
// The negative assertion is deliberately narrow: it bans INSTRUCTING a rebase,
|
|
115
|
+
// not the word. "Merge, never rebase" must keep working — that sentence is the
|
|
116
|
+
// point.
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
const INSTRUCTS_A_REBASE = [
|
|
119
|
+
/\brebase\s*\+\s*re-ship\b/i,
|
|
120
|
+
/\brebase (the|its|your) branch\b/i,
|
|
121
|
+
/\brebases it\b/i,
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
t('markdown prescribes MERGE, and never instructs a rebase', () => {
|
|
125
|
+
const md = rebaseWarningMarkdown([{ id: 123 }]);
|
|
126
|
+
assert.ok(md.includes('git merge origin/main'), 'must name the fast-forward-safe command');
|
|
127
|
+
for (const re of INSTRUCTS_A_REBASE) {
|
|
128
|
+
assert.ok(!re.test(md), `must not instruct a rebase of a published branch (matched ${re}): ${md}`);
|
|
129
|
+
}
|
|
130
|
+
assert.match(md, /never rebase/i, 'and must say why the other move is wrong, not merely omit it');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
t('widget prescribes MERGE, and never instructs a rebase', () => {
|
|
134
|
+
const html = rebaseWarningWidget([{ id: 123 }]);
|
|
135
|
+
assert.ok(html.includes('git merge origin/main'), 'must name the fast-forward-safe command');
|
|
136
|
+
for (const re of INSTRUCTS_A_REBASE) {
|
|
137
|
+
assert.ok(!re.test(html), `must not instruct a rebase of a published branch (matched ${re}): ${html}`);
|
|
138
|
+
}
|
|
139
|
+
assert.match(html, /never rebase/i);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
t('the two renderings give the SAME advice — a builder must not get different instructions per surface', () => {
|
|
143
|
+
const md = rebaseWarningMarkdown([{ id: 123 }]);
|
|
144
|
+
const html = rebaseWarningWidget([{ id: 123 }]);
|
|
145
|
+
for (const [name, text] of [['markdown', md], ['widget', html]]) {
|
|
146
|
+
assert.ok(text.includes('git merge origin/main'), `${name} lost the command`);
|
|
147
|
+
assert.match(text, /never rebase/i, `${name} lost the warning`);
|
|
148
|
+
assert.ok(text.includes('/builder-release'), `${name} lost the give-up exit`);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
102
152
|
console.log(`\nstart_rebase_warning: ${passed} passed, ${failed} failed`);
|
|
103
153
|
if (failed > 0) process.exitCode = 1;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// tests/task_field_bounds_match_db.mjs
|
|
2
|
+
//
|
|
3
|
+
// task 1002845 — a value the route validator accepts must be one the INSERT
|
|
4
|
+
// accepts. When those two disagree the caller gets an opaque 500 from deep inside
|
|
5
|
+
// the DB, with nothing naming the field.
|
|
6
|
+
//
|
|
7
|
+
// THE DEFECT, measured against the live API before the fix:
|
|
8
|
+
// POST /tasks {manual_degree: 0} → 500 {"code":"create_failed","message":"internal error"}
|
|
9
|
+
// TASK_FIELD_BOUNDS said min 0; migrations/003_pms.sql says
|
|
10
|
+
// `manual_degree integer CHECK (manual_degree BETWEEN 1 AND 5)`. The route let 0
|
|
11
|
+
// through, the INSERT raised SQLSTATE 23514, and the generic catch answered
|
|
12
|
+
// "internal error". Idea 1000456 records roughly four blind retries spent on this.
|
|
13
|
+
//
|
|
14
|
+
// This is the SECOND time: task 1233 fixed the identical defect on `priority`
|
|
15
|
+
// (a laxer 0-100 range) and left a comment saying so, and `manual_degree` sat
|
|
16
|
+
// beside it unfixed. So the guard here is not "is manual_degree 1..5" — it is
|
|
17
|
+
// "does EVERY bound in TASK_FIELD_BOUNDS match the DB CHECK it mirrors", derived
|
|
18
|
+
// from the migration rather than hand-copied, so the third instance fails loudly.
|
|
19
|
+
//
|
|
20
|
+
// Run: node tests/task_field_bounds_match_db.mjs
|
|
21
|
+
|
|
22
|
+
import { strict as assert } from 'node:assert';
|
|
23
|
+
import fs from 'node:fs';
|
|
24
|
+
import path from 'node:path';
|
|
25
|
+
import { createRequire } from 'node:module';
|
|
26
|
+
import { fileURLToPath } from 'node:url';
|
|
27
|
+
|
|
28
|
+
const require = createRequire(import.meta.url);
|
|
29
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
30
|
+
const { TASK_FIELD_BOUNDS } = require(path.join(ROOT, 'modules', 'lifecycle', 'task-classifier.js'));
|
|
31
|
+
const { describeCheckViolation, CHECK_VIOLATION } = require(path.join(ROOT, 'modules', 'lifecycle', 'pg-constraint-fail.js'));
|
|
32
|
+
|
|
33
|
+
let passed = 0;
|
|
34
|
+
let failed = 0;
|
|
35
|
+
function t(name, fn) {
|
|
36
|
+
try { fn(); passed++; console.log(` PASS ${name}`); }
|
|
37
|
+
catch (e) { failed++; console.log(` FAIL ${name}\n ${e.message}`); }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Read the DB's own CHECK bounds out of the migrations, newest definition wins.
|
|
41
|
+
// Derived, not hand-copied: a migration that later widens or narrows a CHECK must
|
|
42
|
+
// move this test's expectation with it, which is the whole point.
|
|
43
|
+
function dbCheckBounds(column) {
|
|
44
|
+
const dir = path.join(ROOT, 'migrations');
|
|
45
|
+
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.sql')).sort();
|
|
46
|
+
assert.ok(files.length > 10, `only ${files.length} migrations read — the sweep is not finding the migrations dir`);
|
|
47
|
+
const re = new RegExp(`${column}\\s+integer[^,]*?CHECK\\s*\\(\\s*${column}\\s+BETWEEN\\s+(\\d+)\\s+AND\\s+(\\d+)\\s*\\)`, 'i');
|
|
48
|
+
const alterRe = new RegExp(`ALTER\\s+TABLE[^;]*?${column}[^;]*?BETWEEN\\s+(\\d+)\\s+AND\\s+(\\d+)`, 'is');
|
|
49
|
+
let found = null;
|
|
50
|
+
for (const f of files) {
|
|
51
|
+
const sql = fs.readFileSync(path.join(dir, f), 'utf8');
|
|
52
|
+
const m = sql.match(re) || sql.match(alterRe);
|
|
53
|
+
if (m) found = { min: Number(m[1]), max: Number(m[2]), file: f };
|
|
54
|
+
}
|
|
55
|
+
return found;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
t('the migrations really do declare a CHECK for every bounded field (the scan works)', () => {
|
|
59
|
+
for (const column of Object.keys(TASK_FIELD_BOUNDS)) {
|
|
60
|
+
const db = dbCheckBounds(column);
|
|
61
|
+
assert.ok(db, `no CHECK found for ${column} — either the column lost its constraint or this scan has drifted off the SQL shape`);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
t('every TASK_FIELD_BOUNDS range matches the DB CHECK it mirrors', () => {
|
|
66
|
+
for (const [column, bounds] of Object.entries(TASK_FIELD_BOUNDS)) {
|
|
67
|
+
const db = dbCheckBounds(column);
|
|
68
|
+
assert.equal(bounds.min, db.min,
|
|
69
|
+
`${column}: validator accepts from ${bounds.min} but the DB CHECK in ${db.file} starts at ${db.min} — a value in between passes the route and dies in the INSERT as an opaque 500`);
|
|
70
|
+
assert.equal(bounds.max, db.max,
|
|
71
|
+
`${column}: validator accepts up to ${bounds.max} but the DB CHECK in ${db.file} stops at ${db.max}`);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
t('manual_degree specifically — the regression this task fixed', () => {
|
|
76
|
+
assert.equal(TASK_FIELD_BOUNDS.manual_degree.min, 1,
|
|
77
|
+
'min 0 is what let POST /tasks {manual_degree:0} reach the INSERT and 500');
|
|
78
|
+
assert.equal(TASK_FIELD_BOUNDS.manual_degree.max, 5);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// --- the backstop: when a mismatch DOES slip through, say which constraint ----
|
|
82
|
+
|
|
83
|
+
t('describeCheckViolation names the constraint on a 23514', () => {
|
|
84
|
+
const out = describeCheckViolation({ code: CHECK_VIOLATION, constraint: 'tasks_manual_degree_check' });
|
|
85
|
+
assert.equal(out.status, 400, 'a client-correctable value must not answer 500');
|
|
86
|
+
assert.equal(out.code, 'constraint_violation');
|
|
87
|
+
assert.match(out.message, /tasks_manual_degree_check/);
|
|
88
|
+
assert.equal(out.details.constraint, 'tasks_manual_degree_check');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
t('describeCheckViolation NEVER echoes err.detail — it carries the failing row', () => {
|
|
92
|
+
const out = describeCheckViolation({
|
|
93
|
+
code: CHECK_VIOLATION,
|
|
94
|
+
constraint: 'tasks_manual_degree_check',
|
|
95
|
+
detail: 'Failing row contains (1, a-private-title, sk-live-abc123, 0).',
|
|
96
|
+
where: 'PL/pgSQL function some_trigger()',
|
|
97
|
+
});
|
|
98
|
+
const rendered = JSON.stringify(out);
|
|
99
|
+
assert.ok(!rendered.includes('Failing row'), 'the row dump must never reach the response body');
|
|
100
|
+
assert.ok(!rendered.includes('a-private-title'), 'row VALUES must never reach the response body');
|
|
101
|
+
assert.ok(!rendered.includes('sk-live-abc123'), 'a secret sitting in an adjacent column must never be echoed');
|
|
102
|
+
assert.ok(!rendered.includes('PL/pgSQL'), 'internal execution context is not the caller\'s business');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
t('describeCheckViolation returns null for anything that is not a CHECK violation', () => {
|
|
106
|
+
assert.equal(describeCheckViolation(null), null);
|
|
107
|
+
assert.equal(describeCheckViolation(undefined), null);
|
|
108
|
+
assert.equal(describeCheckViolation({}), null);
|
|
109
|
+
assert.equal(describeCheckViolation(new Error('boom')), null, 'a plain error must fall through to the generic 500');
|
|
110
|
+
assert.equal(describeCheckViolation({ code: '23505', constraint: 'x_key' }), null,
|
|
111
|
+
'a UNIQUE violation is a different problem and must not be relabelled as a bad value');
|
|
112
|
+
assert.equal(describeCheckViolation({ code: '23503' }), null, 'nor a foreign-key violation');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
t('describeCheckViolation still answers 400 when the driver gives no constraint name', () => {
|
|
116
|
+
const out = describeCheckViolation({ code: CHECK_VIOLATION });
|
|
117
|
+
assert.equal(out.status, 400);
|
|
118
|
+
assert.equal(out.details.constraint, null);
|
|
119
|
+
assert.ok(out.message.length > 0, 'a nameless violation still beats "internal error"');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
t('constraint_violation is registered so its CATEGORY agrees with its status', () => {
|
|
123
|
+
const { categoryForCode, isRetriable } = require(path.join(ROOT, 'src', 'bongos', 'api-errors.js'));
|
|
124
|
+
assert.equal(categoryForCode('constraint_violation'), 'bad_request',
|
|
125
|
+
'unregistered it would classify as `internal` — a 400 body labelled a server fault, which tells a retrying client exactly the wrong thing');
|
|
126
|
+
assert.equal(isRetriable('constraint_violation'), false, 'retrying the same bad value cannot help');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
t('the POST /tasks catch actually calls the backstop', () => {
|
|
130
|
+
const src = fs.readFileSync(path.join(ROOT, 'modules', 'lifecycle', 'routes', 'task-write-routes.js'), 'utf8');
|
|
131
|
+
const i = src.indexOf("log.error('[gds] POST /tasks'");
|
|
132
|
+
assert.ok(i !== -1, 'could not find the POST /tasks catch — this guard is reading the wrong shape');
|
|
133
|
+
const tail = src.slice(i, i + 800);
|
|
134
|
+
assert.match(tail, /describeCheckViolation\s*\(/, 'the catch must consult the backstop before answering create_failed');
|
|
135
|
+
assert.ok(tail.indexOf('describeCheckViolation') < tail.indexOf("res.fail('create_failed'"),
|
|
136
|
+
'the backstop must run BEFORE the generic 500, or it can never fire');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
console.log(`\ntask_field_bounds_match_db: ${passed} passed, ${failed} failed`);
|
|
140
|
+
process.exit(failed === 0 ? 0 : 1);
|