@bongos/core 1.19.715 → 1.19.724
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bongos-core.json +472 -407
- package/.claude/skills/backlog-review/SKILL.md +3 -3
- package/.claude/skills/blocker-review/SKILL.md +1 -1
- package/.claude/skills/blocker-solve/SKILL.md +2 -2
- package/.claude/skills/builder-reauth/SKILL.md +1 -1
- package/.claude/skills/builder-redteam/SKILL.md +1 -1
- package/.claude/skills/builder-ship/SKILL.md +1 -1
- package/.claude/skills/builder-start/SKILL.md +1 -1
- package/.claude/skills/design/SKILL.md +2 -2
- package/.claude/skills/feedback/SKILL.md +1 -1
- package/.claude/skills/goal-create/SKILL.md +1 -1
- package/.claude/skills/goal-review/SKILL.md +2 -2
- package/.claude/skills/idea-triage/SKILL.md +5 -5
- package/.claude/skills/merge-mode/SKILL.md +4 -4
- package/.claude/skills/new-project/SKILL.md +17 -17
- package/.claude/skills/planning-session/SKILL.md +11 -11
- package/.claude/skills/recall/SKILL.md +2 -2
- package/.claude/skills/strand-fix/SKILL.md +1 -1
- package/.devcontainer/README.md +2 -2
- package/CONTRIBUTING.md +2 -2
- package/GOVERNANCE.md +4 -4
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +4 -0
- package/clients/bongos-client/index.cjs +4 -0
- package/clients/bongos-client/index.d.ts +5 -0
- package/clients/bongos-client/index.mjs +4 -0
- package/config/branding.neutral.json +4 -2
- package/docs/adr/0001-stack-choice-single-vps.md +2 -2
- package/docs/adr/0002-digitalocean-over-hetzner.md +1 -1
- package/docs/adr/0008-google-chat-oauth-user-auth.md +1 -1
- package/docs/adr/0009-autotile-architecture.md +1 -1
- package/docs/adr/0011-terrain-id-rendering-model.md +3 -3
- package/docs/adr/0013-paths-as-object-overlays-with-autotile-dispatch.md +1 -1
- package/docs/adr/0014-families-canonical-over-vocabulary.md +1 -1
- package/docs/adr/0015-task-dependencies-and-auto-promotion.md +1 -1
- package/docs/adr/0016-trust-boundary-server-enforced-permissions.md +1 -1
- package/docs/adr/0018-three-rank-model-goes-live.md +3 -3
- package/docs/adr/0020-security-model-public-stub.md +9 -9
- package/docs/adr/0021-per-builder-skill-model-preferences.md +2 -2
- package/docs/adr/0022-secrets-policy.md +5 -5
- package/docs/adr/0024-cloneable-repo-local-first-memory.md +3 -3
- package/docs/adr/0024-multi-agent-system-architecture.md +10 -10
- package/docs/adr/0025-offsite-backup-vendor-digitalocean-spaces.md +1 -1
- package/docs/adr/0025-structured-criterion-task-link.md +1 -1
- package/docs/adr/0026-bfg-memory-hygiene-and-cross-builder-learning.md +13 -13
- package/docs/adr/0027-bfg-session-inefficiency-evaluator.md +14 -14
- package/docs/adr/0029-status-page-outage-resilience.md +1 -1
- package/docs/adr/0030-builders-hall-live-updates.md +1 -1
- package/docs/adr/0031-cloud-dev-environments-for-builders.md +7 -7
- package/docs/adr/0032-discord-mirror-of-gds.md +3 -3
- package/docs/adr/0033-discord-bot-service-principal.md +4 -4
- package/docs/adr/0034-thetes-graduated-newcomer-rank.md +1 -1
- package/docs/adr/0035-builder-onboarding-three-paths.md +4 -4
- package/docs/adr/0036-builders-subdomain-and-ui-reorg.md +1 -1
- package/docs/adr/0037-discord-channel-management.md +1 -1
- package/docs/adr/0038-chromebook-ttyd-cloudflare-tunnel.md +5 -5
- package/docs/adr/0039-setup-first-onboarding-ux.md +6 -6
- package/docs/adr/0040-remote-control-default-browser-onramp.md +3 -3
- package/docs/adr/0041-temporary-grader-bypass-killswitch.md +2 -2
- package/docs/adr/0042-builder-self-deploy-ci-auto-merge.md +5 -5
- package/docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md +2 -2
- package/docs/adr/0044-mediterranean-palette-replacement.md +1 -1
- package/docs/adr/0044-per-box-live-game-preview.md +1 -1
- package/docs/adr/0046-sandbox-first-review-gate.md +1 -1
- package/docs/adr/0047-discord-bugs-intake-anti-abuse.md +1 -1
- package/docs/adr/0049-split-parallel-safety-contract.md +1 -1
- package/docs/adr/0050-device-flow-admission-invite-gated-by-default.md +2 -2
- package/docs/adr/0051-full-session-transcript-corpus.md +2 -2
- package/docs/adr/0052-sandbox-for-everyone-game-only-preview.md +1 -1
- package/docs/adr/0053-scoped-dev-box-session.md +1 -1
- package/docs/adr/0054-session-token-reward-cost-plus.md +7 -7
- package/docs/adr/0055-server-mediated-branch-publish.md +2 -2
- package/docs/adr/0056-prod-deploy-script-mirror-and-healthcheck-retry.md +3 -3
- package/docs/adr/0057-container-cost-ledger.md +1 -1
- package/docs/adr/0057-discord-archon-approval-channels.md +2 -2
- package/docs/adr/0058-automatic-gate-surface-gatekeeper.md +6 -6
- package/docs/adr/0059-single-approval-remove-devbox-approval-gate.md +11 -11
- package/docs/adr/0060-gds-retrieval-layer.md +3 -3
- package/docs/adr/0061-context-layer-decomposition.md +2 -2
- package/docs/adr/0062-medusa-instance-model-decoupling.md +5 -5
- package/docs/adr/0063-generated-repo-map-symbol-skeleton.md +2 -2
- package/docs/adr/0064-rename-medusa-to-cloud-bongos.md +3 -3
- package/docs/adr/0065-agpl-nonprofit-ai-first-governance.md +5 -5
- package/docs/adr/0066-generated-file-map-sections.md +2 -2
- package/docs/adr/0067-session-start-fast-forward-pull.md +1 -1
- package/docs/adr/0068-shared-art-key-for-newcomers.md +1 -1
- package/docs/adr/0069-hall-live-channel-sse-decouple.md +3 -3
- package/docs/adr/0070-deterministic-session-card-delivery.md +1 -1
- package/docs/adr/0071-box-confirm-before-destroyed-and-drift-reconcile.md +2 -2
- package/docs/adr/0072-bongos-app-mac-signed-first-windows-deferred.md +2 -2
- package/docs/adr/0072-dev-box-code-staleness-visibility.md +2 -2
- package/docs/adr/0073-builder-needs-signal-and-byok-gemini-key.md +3 -3
- package/docs/adr/0073-secrets-scan-exclude-uri-detector.md +2 -2
- package/docs/adr/0076-in-session-deterministic-compaction.md +2 -2
- package/docs/adr/0077-content-addressed-llm-cache.md +2 -2
- package/docs/adr/0078-deterministic-first-cron.md +3 -3
- package/docs/adr/0079-value-gated-autonomous-engagement.md +2 -2
- package/docs/adr/0080-llm-cache-api-transport-and-trust-model.md +3 -3
- package/docs/adr/0081-tool-agnostic-design-layer.md +3 -3
- package/docs/adr/0082-server-side-merge-conflict-auto-resolution.md +2 -2
- package/docs/adr/0083-modular-architecture-strangler-build-plan.md +3 -3
- package/docs/adr/0085-remove-default-per-builder-spend-cap.md +2 -2
- package/docs/adr/0086-goal-scoped-work-hierarchy.md +15 -15
- package/docs/adr/0087-bongos-app-architecture-and-handoff-contract.md +5 -5
- package/docs/adr/0087-compete-on-governance-not-tooling-cursor.md +1 -1
- package/docs/adr/0088-consistent-character-generative-animation.md +5 -5
- package/docs/adr/0089-modular-multi-model-adversarial-grader.md +15 -15
- package/docs/adr/0090-metic-task-authoring.md +8 -8
- package/docs/adr/0091-bounding-the-kernel-and-db-carve.md +3 -3
- package/docs/adr/0092-hosting-module-and-on-prem-target.md +11 -11
- package/docs/adr/0093-tranche-2-core-carve-sequence.md +5 -5
- package/docs/adr/0094-repo-atlas-self-drawing-codebase-map.md +1 -1
- package/docs/adr/0095-borrowed-memory-and-retrieval-concepts.md +6 -6
- package/docs/adr/0095-cross-agent-context-management.md +5 -5
- package/docs/adr/0096-require-reward-before-workable.md +5 -5
- package/docs/adr/0097-one-active-claim-per-session-and-worktree-binding.md +2 -2
- package/docs/adr/0097-retroactive-reward-backfill.md +4 -4
- package/docs/adr/0098-public-mirror-publish-manifest.md +3 -3
- package/docs/adr/0099-delayed-redacted-mirror-export.md +1 -1
- package/docs/adr/0100-two-repo-instance-model.md +6 -6
- package/docs/adr/0101-goal-hierarchy-phase-5-judge-membership-reward.md +9 -9
- package/docs/adr/0102-finish-bounding-the-kernel.md +1 -1
- package/docs/adr/0103-core-first-extraction-cloud-bongos-trunk.md +6 -6
- package/docs/adr/0103-gdsv4-bongos-consolidation.md +3 -3
- package/docs/adr/0104-trust-gds-api-channel-in-auto-mode.md +3 -3
- package/docs/adr/0105-instance-seed-migrations-out-of-core.md +2 -2
- package/docs/adr/0106-goal-owner-manager-authority.md +2 -2
- package/docs/adr/0107-module-upstreaming-contribution-to-core-catalog.md +6 -6
- package/docs/adr/0108-instance-composition-configurable-root.md +6 -6
- package/docs/adr/0109-self-describing-openapi-and-hosted-docs.md +3 -3
- package/docs/adr/0110-untrack-whole-file-generated-nav-docs.md +1 -1
- package/docs/adr/0111-instance-hosting-provisioning-module.md +18 -18
- package/docs/adr/0112-private-goals-invitations-join-requests.md +7 -7
- package/docs/adr/0113-sandbox-studio-descope-superseded-by-0044-0052.md +1 -1
- package/docs/adr/0114-instance-docs-discovery-contract.md +4 -4
- package/docs/adr/0115-scheduled-routines-portability.md +1 -1
- package/docs/adr/0117-api-versioning-and-deprecation-policy.md +1 -1
- package/docs/adr/0118-api-contract-source-of-truth-code-first.md +1 -1
- package/docs/adr/0120-pay-on-land-and-builder-owned-rebase-gate.md +2 -2
- package/docs/adr/0121-greenfield-vs-brownfield-onboarding-adopt-existing-repo.md +1 -1
- package/docs/adr/0122-onboarding-diagram-renders-deploy-rendered-off-git.md +3 -3
- package/docs/adr/0123-box-idle-sweep-autosave-before-destroy.md +1 -1
- package/docs/adr/0124-typed-api-response-bodies.md +2 -2
- package/docs/adr/0125-control-plane-scaffolds-standalone-instance-repo.md +2 -2
- package/docs/adr/0126-dedicated-cloudbongos-control-plane-droplet.md +3 -3
- package/docs/adr/0127-cross-box-fleet-observability.md +1 -1
- package/docs/adr/0128-provisioning-runner-privilege-and-db-auth-model.md +3 -3
- package/docs/adr/0129-cloudbongos-control-plane-pull-deploy.md +4 -4
- package/docs/adr/0130-provision-remote-exec-over-ssh.md +4 -4
- package/docs/adr/0131-rank-scoped-skill-visibility.md +2 -2
- package/docs/adr/0132-cohost-fleet-control-plane-migration.md +2 -2
- package/docs/adr/0133-github-app-manifest-onboarding-signin.md +1 -1
- package/docs/adr/0134-private-first-npm-distribution.md +3 -3
- package/docs/adr/0135-module-upstream-submission-interim-queue.md +1 -1
- package/docs/adr/0136-update-channel-subscription-policy.md +2 -2
- package/docs/adr/0137-upstream-publish-policy-manual-owner-gated.md +6 -6
- package/docs/adr/0138-bundled-module-deps-declared-per-module.md +4 -4
- package/docs/adr/0142-project-context-is-prose-only.md +2 -2
- package/docs/adr/0143-onboarding-repo-picker-oauth-scope.md +2 -2
- package/docs/adr/0144-devbox-rehome-onto-cloudbongos-plane.md +2 -2
- package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +2 -2
- package/docs/adr/0146-configurable-reward-mode-cost-plus-only.md +4 -4
- package/docs/adr/0147-standalone-live-docs-regen.md +1 -1
- package/docs/adr/0149-bongos-dev-local-launcher.md +1 -1
- package/docs/adr/0150-box-first-boot-bringup-vendored-instances.md +3 -3
- package/docs/adr/0151-governance-permissions-as-atom-ranks-as-roles.md +20 -20
- package/docs/adr/0152-landing-gate-served-by-the-core-app.md +2 -2
- package/docs/adr/0152-metic-task-abandonment.md +7 -7
- package/docs/adr/0153-sequential-chain-runner.md +1 -1
- package/docs/adr/0154-goal-owner-criterion-authoring.md +7 -7
- package/docs/adr/0155-adopt-private-repo-widen-oauth-scope.md +2 -2
- package/docs/adr/0156-user-set-display-name-wins.md +1 -1
- package/docs/adr/0157-archon-is-rank-and-identity-only.md +10 -10
- package/docs/adr/0158-choke-point-verdict-taxonomy.md +1 -1
- package/docs/adr/0159-merge-gate-reconciliation.md +1 -1
- package/docs/adr/0160-main-audit-repo-verifiable-caller.md +1 -1
- package/docs/adr/0161-publish-on-merge.md +2 -2
- package/docs/adr/0163-legacy-display-name-resolution.md +1 -1
- package/docs/adr/0165-instances-hookless-by-default.md +1 -1
- package/docs/adr/0166-module-maintenance-and-deprecation-policy.md +3 -3
- package/docs/adr/0167-gate-trust-link-agpl-default.md +2 -2
- package/docs/adr/0167-module-catalog-source-vs-provenance.md +3 -3
- package/docs/adr/0169-bongos-serve-server-bring-up.md +1 -1
- package/docs/adr/0171-profile-rollup-join-and-consent.md +1 -1
- package/docs/adr/0172-editable-rank-roles-substrate-axis.md +2 -2
- package/docs/adr/0172-per-craft-compensation-ideator-credit-lane.md +4 -4
- package/docs/adr/0173-legacy-multi-ship-shortfall-not-backfilled.md +1 -1
- package/docs/adr/0174-government-terminology-ranks-not-roles.md +1 -1
- package/docs/adr/0177-goal-membership-kind.md +3 -3
- package/docs/adr/0178-the-copy-desk-flag-and-queue.md +1 -1
- package/docs/adr/0179-board-membership-predicate-grammar.md +1 -1
- package/docs/adr/0181-teardown-state-gate-and-address-retention.md +1 -1
- package/docs/adr/0182-project-visibility-two-axes.md +1 -1
- package/docs/adr/0183-criteria-close-themselves.md +1 -1
- package/docs/adr/0184-ship-requires-an-assurance.md +1 -1
- package/docs/adr/0185-spark-handoff-credit-split.md +1 -1
- package/docs/adr/0186-per-project-idea-templates.md +1 -1
- package/docs/adr/0187-collab-four-decisions.md +1 -1
- package/docs/adr/0187-oauth-handshake-cookie-lifetime.md +1 -1
- package/docs/adr/0188-patrol-runs-on-ci-not-the-server.md +2 -2
- package/docs/adr/0189-baseline-floors-and-the-freeze-detector.md +2 -2
- package/docs/adr/0191-majority-and-the-author-who-may-not-decide-alone.md +1 -1
- package/docs/adr/0192-a-category-orients-and-authorises-nothing.md +2 -2
- package/docs/adr/0193-pause-task-scoped-box-slices.md +1 -1
- package/docs/adr/0195-adr-numbers-are-checked-like-migration-numbers.md +2 -2
- package/docs/adr/0196-chrome-world-token-contract-and-the-landing-sheets.md +2 -2
- package/docs/adr/0197-the-ui-design-module.md +1 -1
- package/docs/adr/0198-third-party-skill-vendoring-policy.md +1 -1
- package/docs/adr/0203-doorway-changelog-out-of-module-api.md +2 -2
- package/docs/adr/0204-the-apex-is-dark-only.md +2 -2
- package/docs/adr/0205-federated-checkin-needs-the-hubs-own-signin-witness.md +1 -1
- package/docs/adr/0206-own-scoped-session-revocation-no-absolute-ceiling.md +1 -1
- package/docs/adr/0207-connections-implementation-departures-and-honest-limits.md +1 -1
- package/docs/adr/0208-application-lifecycle-the-handle-waits-and-the-vouch-has-no-minter.md +2 -2
- package/docs/adr/0209-one-budget-for-the-account-existence-oracle.md +1 -1
- package/docs/adr/0210-scouting-graduates-on-an-instance-asserted-owner-claim.md +1 -1
- package/docs/adr/0212-connections-on-the-profile-surface-and-addressing.md +1 -1
- package/docs/adr/0213-platform-terms-acceptance-is-the-hubs-own-witness.md +2 -2
- package/docs/adr/0215-the-recruiter-sliver-is-a-ceiling-not-a-step.md +6 -6
- package/docs/adr/0216-a-fake-pool-interpreter-must-fail-loudly-not-silently-ignore.md +2 -2
- package/docs/adr/0217-rank-gates-inviting-not-viewing.md +1 -1
- package/docs/adr/0218-the-apex-root-web-surface.md +2 -2
- package/docs/adr/0219-a-look-is-a-branding-pack-the-style-library.md +1 -1
- package/docs/adr/0220-an-in-house-rebuilt-skill-is-a-first-party-skill.md +1 -1
- package/docs/adr/0221-the-impeccable-method-is-one-skill-with-sub-command-playbooks.md +2 -2
- package/docs/adr/0222-the-image-family-under-the-hero-only-rule.md +2 -2
- package/docs/adr/0223-the-detector-is-two-tiers-in-the-kit.md +3 -3
- package/docs/adr/0224-module-skills-reach-the-core-checkout-as-untracked-self-ignored-copies.md +1 -1
- package/docs/adr/0225-privacy-suppresses-cross-project-surfaces-only.md +1 -1
- package/docs/adr/0226-one-stale-timer-reporting-degrades-enforcement-refuses.md +1 -1
- package/docs/adr/0227-a-task-network-map-is-refused-the-goal-graph-is-the-map.md +1 -1
- package/docs/adr/0230-a-look-skill-is-a-composition-grammar-the-palette-is-the-pack.md +1 -1
- package/docs/adr/0231-the-style-session-an-owner-authors-a-look-inside-the-contract.md +2 -2
- package/docs/adr/0232-rot-is-derived-not-swept-and-water-is-the-only-new-verb.md +2 -2
- package/docs/adr/0233-a-copy-proposal-is-a-task-carrying-a-patch.md +2 -2
- package/docs/adr/0234-idea-routing-capture-time-promotion-landing-matrix-homeless-inbox.md +2 -2
- package/docs/adr/0235-a-fix-category-is-kindbug-under-a-goal-no-column-required.md +11 -11
- package/docs/adr/0236-many-ideas-one-task-the-lineage-key-is-the-pair.md +5 -5
- package/docs/adr/0237-a-starter-bundle-is-a-preset-over-the-always-on-core-keyed-by-declared-type.md +5 -5
- package/docs/adr/0238-the-join-door-is-a-provisioning-settings-policy-key.md +1 -1
- package/docs/adr/0239-a-recommendation-is-a-table-that-carries-its-own-reason.md +4 -4
- package/docs/adr/0240-a-skipped-module-picker-stores-nothing-and-resolves-the-bundle-on-read.md +4 -4
- package/docs/adr/0241-the-artist-gate-is-a-per-project-deploy-gate-that-reads-a-state.md +1 -1
- package/docs/adr/0242-a-cascade-is-a-declaration-table-on-the-event-that-already-exists.md +2 -2
- package/docs/adr/0243-a-bundle-adjustment-belongs-to-the-bundle-not-the-advice-about-it.md +4 -4
- package/docs/adr/0244-a-catalog-search-is-a-view-over-the-rows-never-an-edit-to-the-selection.md +3 -3
- package/docs/adr/0245-the-way-back-into-an-optional-step-is-that-step-s-own-answer-route.md +7 -7
- package/docs/adr/0246-catalog-membership-is-a-projection-not-a-federation-side-effect.md +1 -1
- package/docs/adr/0247-the-join-door-is-one-composed-answer-and-dark-matter-has-no-live-verdict-yet.md +2 -2
- package/docs/adr/0248-a-hub-project-s-owner-is-the-provisioning-row-carried-on-a-port.md +3 -3
- package/docs/adr/0249-the-invite-step-is-the-done-panel-s-first-act-not-a-rail-step.md +3 -3
- package/docs/adr/0250-strict-versioning-the-version-boundary-is-the-scope-gate.md +6 -6
- package/docs/adr/0251-a-suggestion-of-who-to-invite-is-a-recruiting-surface-and-owes-the-opt-out.md +5 -5
- package/docs/adr/0252-the-self-hosted-visibility-report-is-a-pull-and-auto-appear-buys-a-retraction-lever.md +19 -19
- package/docs/adr/0253-the-takedown-lever-has-two-writers-and-provenance-is-a-column.md +2 -2
- package/docs/adr/0254-a-relayed-link-is-contained-downward-or-it-is-not-published.md +6 -6
- package/docs/adr/0255-a-public-lists-ordering-is-part-of-its-payload.md +2 -2
- package/docs/adr/0256-the-visibility-ceiling-is-the-sign-in-gate-too-and-the-reader-moves-to-the-composer.md +3 -3
- package/docs/adr/0257-auth-resolves-before-the-hall-mounts-anything.md +1 -1
- package/docs/adr/0258-the-public-cli-is-a-generated-client-package-not-the-published-core.md +1 -1
- package/docs/adr/0259-a-projects-departure-from-the-public-list-is-public.md +7 -7
- package/docs/adr/0260-the-application-is-the-consent-and-the-echo-is-the-gate.md +4 -4
- package/docs/adr/0261-a-preselect-always-carries-a-reason-the-bundle-summary-is-the-floor.md +1 -1
- package/docs/adr/0262-a-bug-never-lands-in-the-inbox.md +1 -1
- package/docs/adr/0263-how-a-version-closes.md +3 -3
- package/docs/adr/0264-the-ten-working-areas.md +3 -3
- package/docs/adr/0265-work-with-no-goal-and-the-order-the-cut-needs.md +5 -5
- package/docs/adr/0266-the-board-room-is-its-own-surface-reachable-by-whoever-may-vote.md +2 -2
- package/docs/adr/0267-unanimity-and-the-revise-and-re-sit-loop.md +1 -1
- package/docs/adr/0268-the-constitution-comes-from-two-roots.md +3 -3
- package/docs/adr/0269-the-cli-session-store-is-host-keyed-at-a-fixed-anchor.md +1 -1
- package/docs/adr/0270-rank-floor-derives-without-a-prediction.md +2 -2
- package/docs/adr/0271-the-ideator-surface-is-two-halves.md +3 -3
- package/docs/adr/0272-a-craft-is-an-identity-not-a-toolchain.md +4 -4
- package/docs/adr/0273-hall-nav-can-gate-on-a-permission.md +1 -1
- package/docs/adr/0274-one-kernel-three-role-packs.md +1 -1
- package/docs/adr/0275-one-source-for-a-written-role-responsibility.md +1 -1
- package/docs/adr/0276-the-skill-listing-budget-cannot-hold-every-trigger.md +1 -1
- package/docs/adr/0277-a-box-is-in-use-only-while-a-human-is-attached.md +1 -1
- package/docs/adr/0278-a-gated-project-still-takes-applications.md +2 -2
- package/docs/adr/0279-an-upgrade-is-proven-by-the-served-version-not-the-health-check.md +1 -1
- package/docs/adr/0280-the-unattended-lane-may-drive-a-co-tenant-because-the-roster-is-the-decision.md +1 -1
- package/docs/adr/0281-an-instance-identity-is-its-own-unix-account-and-pg-role.md +2 -2
- package/docs/adr/README.md +294 -294
- package/docs/api/openapi.json +72 -5
- package/docs/api-reference.md +8 -2
- package/docs/architecture.md +22 -22
- package/docs/branding-contract.md +5 -5
- package/docs/canonical-permissions.md +22 -22
- package/docs/design/hall-direction-v2.md +3 -3
- package/docs/design/landing-page-direction-v2.md +1 -1
- package/docs/design/projects-hub-direction-v2.md +1 -1
- package/docs/file-map.md +3 -1
- package/docs/module-api-changelog.md +34 -1
- package/docs/modules-contract.md +25 -25
- package/docs/onboarding/browser-terminal-guide.md +1 -1
- package/docs/onboarding/diagrams/README.md +3 -3
- package/docs/onboarding/drachmae-and-karma.md +13 -13
- package/docs/onboarding/primer.md +7 -7
- package/docs/packs/artist.md +1 -1
- package/docs/packs/ideator.md +1 -1
- package/docs/project-context.template.md +3 -3
- package/docs/recipes/builder-box-lifecycle.md +5 -5
- package/docs/recipes/claude-design-loop.md +1 -1
- package/docs/recipes/core-release-pipeline.md +55 -14
- package/docs/recipes/figma-design-loop.md +1 -1
- package/docs/recipes/gds-db-backup.md +2 -2
- package/docs/recipes/instance-deploy.md +2 -2
- package/docs/recipes/instance-service-restart.md +3 -3
- package/docs/recipes/local-dev.md +2 -2
- package/docs/recipes/managed-settings-remote-control.md +3 -3
- package/docs/recipes/memory-backup.md +4 -4
- package/docs/recipes/memory-sync-conflicts.md +2 -2
- package/docs/recipes/multi-builder-merge.md +2 -2
- package/docs/recipes/ops-gotchas.md +4 -4
- package/docs/recipes/overnight-builder.md +1 -1
- package/docs/recipes/packaging-the-core.md +6 -6
- package/docs/recipes/private-npm-distribution.md +5 -5
- package/docs/recipes/search-index-ingestion.md +3 -3
- package/docs/recipes/self-host.md +5 -5
- package/docs/recipes/server-bring-up.md +1 -1
- package/docs/recipes/ship-and-regrade.md +2 -2
- package/docs/recipes/standalone-live-docs.md +1 -1
- package/docs/recipes/ui-look-before-you-ship.md +1 -1
- package/docs/recipes/upgrading-the-core.md +2 -2
- package/docs/recipes/work-on-a-task-from-your-dev-box.md +6 -6
- package/docs/research/cursor-competitive-analysis.md +3 -3
- package/docs/research/llm-model-agnostic-and-cheaper-grader.md +7 -7
- package/migrations/core_240_artist_gate_indexes.sql +48 -0
- package/migrations/core_240_goal_members_drop_legacy_role.sql +79 -0
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +4 -0
- package/modules/lifecycle/cascade.js +37 -9
- package/modules/lifecycle/db-claims.js +5 -2
- package/modules/lifecycle/db-goals.js +4 -3
- package/modules/lifecycle/db-overrides.js +117 -4
- package/modules/lifecycle/db-tasks.js +63 -0
- package/modules/lifecycle/migrations/lifecycle_001_goal_first_leads.sql +12 -4
- package/modules/lifecycle/module.json +2 -1
- package/modules/lifecycle/pg-constraint-fail.js +49 -0
- package/modules/lifecycle/routes/artist-gate.js +101 -0
- package/modules/lifecycle/routes/claims.js +13 -1
- package/modules/lifecycle/routes/lifecycle.js +6 -0
- package/modules/lifecycle/routes/task-write-routes.js +7 -0
- package/modules/lifecycle/routes/tasks.js +24 -0
- package/modules/lifecycle/task-classifier.js +10 -1
- package/modules/lifecycle/tests/cascade.mjs +43 -0
- package/modules/provisioning/provisioning.js +79 -3
- package/modules/provisioning/routes/provisioning.js +6 -1
- package/modules/provisioning/tests/provisioning.mjs +8 -8
- package/modules/security/secret-scrub.js +40 -4
- package/modules/status-ui/public/README.md +1 -1
- package/modules/ui-design/docs/design-contract.md +2 -2
- package/modules/ui-design/kit/README.md +1 -1
- package/modules/ui-design/kit/serve.js +1 -0
- package/modules/ui-design/skills/README.md +6 -6
- package/modules/ui-design/skills/brandkit/SKILL.md +1 -1
- package/modules/ui-design/skills/design-taste-frontend/SKILL.md +1 -1
- package/modules/ui-design/skills/design-taste-frontend-v1/SKILL.md +1 -1
- package/modules/ui-design/skills/gpt-taste/SKILL.md +1 -1
- package/modules/ui-design/skills/high-end-visual-design/SKILL.md +1 -1
- package/modules/ui-design/skills/image-to-code/SKILL.md +1 -1
- package/modules/ui-design/skills/imagegen-frontend-mobile/SKILL.md +1 -1
- package/modules/ui-design/skills/imagegen-frontend-web/SKILL.md +1 -1
- package/modules/ui-design/skills/impeccable/SKILL.md +1 -1
- package/modules/ui-design/skills/industrial-brutalist-ui/SKILL.md +1 -1
- package/modules/ui-design/skills/minimalist-ui/SKILL.md +1 -1
- package/modules/ui-design/skills/redesign-existing-projects/SKILL.md +1 -1
- package/modules/ui-design/skills/stitch-design-taste/SKILL.md +1 -1
- package/modules/ui-design/skills/style/SKILL.md +2 -2
- package/modules/ui-design/styles/README.md +3 -3
- package/modules/ui-design/styles/blueprint/DESIGN.md +1 -1
- package/modules/ui-design/styles/chrome-world/DESIGN.md +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/audit-rank-roles.js +172 -0
- package/scripts/gds/build-cli-package.js +1 -0
- package/scripts/gds/claim.js +94 -9
- package/scripts/gds/fitness.js +8 -8
- package/scripts/gds/go-live.js +139 -5
- package/scripts/gds/land-watch.js +13 -5
- package/scripts/gds/mirror-redact.js +13 -2
- package/scripts/gds/package-core.js +11 -3
- package/scripts/gds/release.js +55 -0
- package/scripts/gds/ship-land.js +10 -2
- package/scripts/gds/ship-regen.js +41 -8
- package/scripts/gds/start.js +7 -4
- package/scripts/gds/worktree-claim-guard.js +27 -1
- package/scripts/public-mirror-export.js +17 -4
- package/src/bongos/api-errors.js +9 -0
- package/src/bongos/artist-gate.js +204 -0
- package/src/branding.js +15 -0
- package/src/module-api.js +34 -1
- package/tests/api_path_404.mjs +28 -0
- package/tests/artist_gate.mjs +234 -0
- package/tests/artist_gate_release.mjs +283 -0
- package/tests/artist_gate_route.mjs +174 -0
- package/tests/box_scope_predicate.mjs +117 -0
- package/tests/claim_error_surface.mjs +163 -1
- package/tests/cli_exit_no_abort.mjs +256 -3
- package/tests/currency_label.mjs +11 -4
- package/tests/go_live.mjs +144 -0
- package/tests/main_worktree_busy_reason.mjs +167 -0
- package/tests/membership_kind_lockstep.mjs +88 -0
- package/tests/mirror_redact.mjs +14 -0
- package/tests/module_api.mjs +6 -0
- package/tests/provision_settings_apply.mjs +13 -5
- package/tests/provisioning_settings.mjs +11 -7
- package/tests/provisioning_settings_apply.mjs +4 -4
- package/tests/provisioning_settings_env.mjs +59 -7
- package/tests/public_mirror_export.mjs +12 -0
- package/tests/rank_roles_audit.mjs +114 -0
- package/tests/scrubber_corpus.mjs +70 -2
- package/tests/start_rebase_warning.mjs +50 -0
- package/tests/task_field_bounds_match_db.mjs +140 -0
package/scripts/gds/go-live.js
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
const fs = require('node:fs');
|
|
45
45
|
const path = require('node:path');
|
|
46
46
|
const { spawnSync } = require('node:child_process');
|
|
47
|
-
const { arg, hasFlag } = require('./cli-lib');
|
|
47
|
+
const { arg, hasFlag, apiCall } = require('./cli-lib');
|
|
48
48
|
const divergence = require('./deploy-divergence.js');
|
|
49
49
|
|
|
50
50
|
const ROOT = path.resolve(__dirname, '..', '..');
|
|
@@ -370,6 +370,57 @@ function refreshMain({ run = spawnSync } = {}) {
|
|
|
370
370
|
return { ok: true };
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
+
// Ask the LIVE instance about its own artist gate (ADR 0241, task 1003575).
|
|
374
|
+
//
|
|
375
|
+
// The instance answers because it is the only party that can: the policy arrives
|
|
376
|
+
// in its web.env and its board holds the reviews. This script only decides what to
|
|
377
|
+
// do with the answer, and the refusal sentence is composed there rather than here
|
|
378
|
+
// so one wording serves every caller.
|
|
379
|
+
//
|
|
380
|
+
// NEVER THROWS. Every failure — no session, a 401, an instance too old to carry
|
|
381
|
+
// the route, a network error — resolves to { unread: <why> }, which renders as a
|
|
382
|
+
// note. See artistGateEntries for why an unread gate is not a halt.
|
|
383
|
+
// The FULL path, not a bare `/artist-gate`. cli-lib's apiCall composes
|
|
384
|
+
// `${base}${urlPath}` verbatim and `base` is the instance ORIGIN — every other
|
|
385
|
+
// caller in this repo spells `/api/bongos/…` by hand for the same reason. Getting
|
|
386
|
+
// this wrong is silent in the worst way: the request lands on the origin's
|
|
387
|
+
// catch-all, the gate reports itself unread on every instance including the ones
|
|
388
|
+
// that fully support it, and the deploy check never fires. Exported so a test can
|
|
389
|
+
// pin it against the route's REAL mount point in the generated OpenAPI spec.
|
|
390
|
+
const ARTIST_GATE_PATH = '/api/bongos/artist-gate';
|
|
391
|
+
// The levels a real answer can carry — mirrored from src/bongos/artist-gate.js
|
|
392
|
+
// rather than imported, because this script also runs from an INSTALLED core on a
|
|
393
|
+
// box where that path is inside the package, and a require of it here would make
|
|
394
|
+
// the deploy tool depend on the server's own tree. Pinned by the same test that
|
|
395
|
+
// pins the path.
|
|
396
|
+
const ARTIST_GATE_LEVELS = ['off', 'advisory', 'strict'];
|
|
397
|
+
|
|
398
|
+
async function readArtistGate(call = apiCall) {
|
|
399
|
+
try {
|
|
400
|
+
const res = await call('GET', ARTIST_GATE_PATH, null, { allowUnauthenticated: true, allowBoxScope: true });
|
|
401
|
+
if (res.status === 404) return { unread: `this instance does not serve ${ARTIST_GATE_PATH} — it predates ADR 0241` };
|
|
402
|
+
// A 5xx is a gate that EXISTS and BROKE, which is not the same class of
|
|
403
|
+
// unknown as "no session" or "too old to have the route". The gate is
|
|
404
|
+
// fail-closed by design, so the one case where we know it is there and
|
|
405
|
+
// cannot get an answer from it HALTS rather than degrading to a note —
|
|
406
|
+
// otherwise a broken read is a quieter way past the gate than the owner's
|
|
407
|
+
// own override, which is the asymmetry worth refusing.
|
|
408
|
+
if (res.status >= 500) return { broken: `GET ${ARTIST_GATE_PATH} returned ${res.status}` };
|
|
409
|
+
if (!res.ok) return { unread: `GET ${ARTIST_GATE_PATH} returned ${res.status}` };
|
|
410
|
+
const d = res.data || {};
|
|
411
|
+
// A 200 that is not this route's answer — an SPA index.html served by a
|
|
412
|
+
// catch-all, a proxy's own page — must read as UNREAD, never as a gate that
|
|
413
|
+
// holds nothing. "I got a 200 from something" is the one failure that would
|
|
414
|
+
// wave a deploy through in silence, which is worse than any halt.
|
|
415
|
+
if (!ARTIST_GATE_LEVELS.includes(d.mode)) {
|
|
416
|
+
return { unread: `GET ${ARTIST_GATE_PATH} answered ${res.status} but not with a gate — got mode ${JSON.stringify(d.mode)}` };
|
|
417
|
+
}
|
|
418
|
+
return { mode: d.mode, since: d.since || null, holds: !!d.holds, reviews: d.reviews || [], halt: d.halt || null };
|
|
419
|
+
} catch (e) {
|
|
420
|
+
return { unread: (e && e.message) || 'request failed' };
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
373
424
|
// Parse a `key=value` marker into its parts. Kept pure + exported: the marker is
|
|
374
425
|
// the answer to the trap that the release current when your fix merges usually
|
|
375
426
|
// does NOT contain your fix (ADR 0161 carriers bump on their own cadence).
|
|
@@ -394,12 +445,68 @@ const HALTS = {
|
|
|
394
445
|
MARKER_UNCHECKED: 'could not inspect the target version — the content proof did not run',
|
|
395
446
|
BOX_TREE_DIRTY: 'the instance repo on the box has uncommitted changes',
|
|
396
447
|
LIVE_AHEAD: 'live is AHEAD of the target',
|
|
448
|
+
ARTIST_REVIEW_UNRESOLVED: 'an unresolved artist review holds this deploy (ADR 0241)',
|
|
449
|
+
ARTIST_GATE_UNREADABLE: 'the artist gate is there and answering with an error — this deploy cannot tell whether a review holds it',
|
|
397
450
|
};
|
|
398
451
|
|
|
399
|
-
|
|
452
|
+
// The artist gate (ADR 0241, task 1003575). This is the ONE place in the deploy
|
|
453
|
+
// path that the artist's authority touches, and the boundary is the whole point:
|
|
454
|
+
// `strict` may delay a PIN MOVE and nothing else. Nothing in the ship, grade,
|
|
455
|
+
// merge or land path consults this — a builder's work is credited and on `main`
|
|
456
|
+
// long before a go-live runs, which is what lets this gate coexist with ADR 0162
|
|
457
|
+
// instead of recreating the strand it retired.
|
|
458
|
+
//
|
|
459
|
+
// It reads a STATE, not a verdict: the instance reports which artist review tasks
|
|
460
|
+
// are still open, never whether an artist approved anything. See
|
|
461
|
+
// src/bongos/artist-gate.js, which composes the refusal sentence this renders.
|
|
462
|
+
// The halt and/or note one gate read contributes. Shared by planGoLive and by the
|
|
463
|
+
// gitless (on-box, no checkout) path, so a lane that skips the git-derived checks
|
|
464
|
+
// does not also silently skip the gate — the deploy is the same deploy either way.
|
|
465
|
+
// PURE.
|
|
466
|
+
function artistGateEntries(gate) {
|
|
467
|
+
const halts = [];
|
|
468
|
+
const notes = [];
|
|
469
|
+
if (gate && gate.halt) {
|
|
470
|
+
halts.push({ code: 'ARTIST_REVIEW_UNRESOLVED', message: gate.halt.message, detail: gate.halt.detail });
|
|
471
|
+
}
|
|
472
|
+
// A gate that is THERE and BROKEN halts. This is the fail-closed half: we know
|
|
473
|
+
// the instance has the route, so "it 500s" is a gate we cannot get an answer
|
|
474
|
+
// from, not a gate that does not apply. Its remedy is the instance's own logs,
|
|
475
|
+
// and it is named as such rather than as an artist problem.
|
|
476
|
+
if (gate && gate.broken) {
|
|
477
|
+
halts.push({
|
|
478
|
+
code: 'ARTIST_GATE_UNREADABLE',
|
|
479
|
+
message: HALTS.ARTIST_GATE_UNREADABLE,
|
|
480
|
+
detail: `${gate.broken}. The route exists, so this is a fault on the instance, not a missing feature — read its logs for the GET /artist-gate failure. Deploying past it would mean moving the pin without knowing whether a review holds it.`,
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
// NOT KNOWING is a different thing again, and it must not render as "nothing
|
|
484
|
+
// holds". It is a note rather than a halt on purpose: a go-live is already
|
|
485
|
+
// deliberate and reversible, and halting every deploy because there is no CLI
|
|
486
|
+
// session — or because the instance predates the route entirely — would brick
|
|
487
|
+
// the pin with no task id to release against, which is the blocked-state-that-
|
|
488
|
+
// does-not-explain-itself failure ADR 0241 §4 names.
|
|
489
|
+
if (gate && gate.unread) {
|
|
490
|
+
notes.push({
|
|
491
|
+
code: 'ARTIST_GATE_UNREAD',
|
|
492
|
+
message: `could not read this instance's artist gate (${gate.unread}) — if the project runs artist_gate 'strict', an unresolved artist review may be holding this deploy and this plan cannot see it`,
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
return { halts, notes };
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function planGoLive({ div, target, targetVersion, dirtyCheckout, allowUnreleased = false, artistGate = null }) {
|
|
400
499
|
const halts = [];
|
|
401
500
|
const notes = [];
|
|
402
501
|
|
|
502
|
+
// Evaluated FIRST, before the git-derived checks, because it is the halt whose
|
|
503
|
+
// remedy takes longest: a review someone has to do is not a thing you fix in the
|
|
504
|
+
// next ten seconds, and finding it out after the other five checks passed is
|
|
505
|
+
// strictly worse than finding it out now.
|
|
506
|
+
const gate = artistGateEntries(artistGate);
|
|
507
|
+
halts.push(...gate.halts);
|
|
508
|
+
notes.push(...gate.notes);
|
|
509
|
+
|
|
403
510
|
if (dirtyCheckout) halts.push({ code: 'DIRTY_CHECKOUT', message: HALTS.DIRTY_CHECKOUT, detail: dirtyCheckout });
|
|
404
511
|
|
|
405
512
|
if (!div || div.undetermined) {
|
|
@@ -446,6 +553,13 @@ function renderPlan(p, { targetName, configPath } = {}) {
|
|
|
446
553
|
L.push(` target ${p.targetVersion || '(unresolved)'}`);
|
|
447
554
|
if (p.marker) L.push(` verify ${p.marker.path} contains "${p.marker.needle}"`);
|
|
448
555
|
if (p.registry) L.push(` registry ${p.registry.ok ? `carries ${p.targetVersion}` : 'MISSING'}`);
|
|
556
|
+
// The gate's state is worth a line even when it holds nothing: "artist gate
|
|
557
|
+
// advisory" is the answer to "why did nothing stop?", and a reader who cannot
|
|
558
|
+
// see the level has to go and find it.
|
|
559
|
+
if (p.artistGate && p.artistGate.mode) {
|
|
560
|
+
const open = (p.artistGate.reviews || []).length;
|
|
561
|
+
L.push(` artist gate ${p.artistGate.mode}${open ? ` · ${open} unresolved review${open === 1 ? '' : 's'}` : ' · nothing open'}`);
|
|
562
|
+
}
|
|
449
563
|
L.push('');
|
|
450
564
|
for (const n of p.notes || []) L.push(` ⚠ ${n.code} — ${n.message}`);
|
|
451
565
|
if (!p.halts.length) {
|
|
@@ -790,9 +904,28 @@ async function runGoLive({ target, targetName, configPath, json, apply, allowUnr
|
|
|
790
904
|
const dirtyRes = gitless ? { status: 1 } : spawnSync('git', ['status', '--porcelain'], { cwd: ROOT, encoding: 'utf8' });
|
|
791
905
|
const dirtyCheckout = dirtyRes.status === 0 && (dirtyRes.stdout || '').trim() ? (dirtyRes.stdout || '').trim().split('\n').slice(0, 5).join(', ') : null;
|
|
792
906
|
|
|
907
|
+
// Always read, and deliberately NOT skippable by a flag. ADR 0241 §3 names one
|
|
908
|
+
// escape hatch — the project OWNER's override-request, rationale required and
|
|
909
|
+
// audit-logged — and a `--skip` here would be a second authority path that any
|
|
910
|
+
// builder with the shell could take silently, which is exactly the shape that
|
|
911
|
+
// ADR rejects. An unread gate already degrades to a note; that is the only
|
|
912
|
+
// by-pass, and it says so on screen.
|
|
913
|
+
const artistGate = await readArtistGate();
|
|
914
|
+
|
|
915
|
+
const gitlessGate = artistGateEntries(artistGate);
|
|
793
916
|
const plan = gitless
|
|
794
|
-
? {
|
|
795
|
-
|
|
917
|
+
? {
|
|
918
|
+
halts: gitlessGate.halts,
|
|
919
|
+
notes: [
|
|
920
|
+
{ code: 'NO_CORE_CHECKOUT', message: `no core git checkout here — target ${targetVersion} was given explicitly, so the git-derived checks (unreleased-on-main, dirty tree) are skipped` },
|
|
921
|
+
...gitlessGate.notes,
|
|
922
|
+
],
|
|
923
|
+
from: null,
|
|
924
|
+
targetVersion,
|
|
925
|
+
target,
|
|
926
|
+
}
|
|
927
|
+
: planGoLive({ div, target, targetVersion, dirtyCheckout, allowUnreleased, artistGate });
|
|
928
|
+
plan.artistGate = artistGate;
|
|
796
929
|
plan.marker = marker;
|
|
797
930
|
if (!fetched.ok && !fetched.skipped) {
|
|
798
931
|
// Say so rather than silently planning off a ref of unknown age.
|
|
@@ -860,7 +993,8 @@ if (require.main === module) {
|
|
|
860
993
|
}
|
|
861
994
|
|
|
862
995
|
module.exports = {
|
|
863
|
-
loadConfig, selectTarget, validateTarget, planGoLive,
|
|
996
|
+
loadConfig, selectTarget, validateTarget, planGoLive, artistGateEntries, readArtistGate, ARTIST_GATE_PATH, ARTIST_GATE_LEVELS,
|
|
997
|
+
parseMarker, parseMarkerHits,
|
|
864
998
|
renderPlan, renderReport, failureExcerpt, asOwner, isSafeToken, NPM_AUTH_PREAMBLE, refreshMain, isLocalTarget, gitRefExists, SUDO_PREAMBLE,
|
|
865
999
|
applyGoLive, upgradeCommand, pinStep, pinModeOf,
|
|
866
1000
|
CONFIG_TEMPLATE, HALTS, SEMVER_RE, PIN_MODES, DEFAULT_PIN_MODE,
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
const fs = require('node:fs');
|
|
23
23
|
const path = require('node:path');
|
|
24
|
-
const { SESSION_PATH, cliClient } = require('./cli-lib');
|
|
24
|
+
const { SESSION_PATH, cliClient, cliExit } = require('./cli-lib');
|
|
25
25
|
const shipCard = require('../../modules/lifecycle/ship-card');
|
|
26
26
|
|
|
27
27
|
// Breadcrumbs live beside the session token (per-machine, per-instance config dir —
|
|
@@ -174,14 +174,22 @@ module.exports = {
|
|
|
174
174
|
if (require.main === module) {
|
|
175
175
|
const hookMode = process.argv.includes('--hook');
|
|
176
176
|
(async () => {
|
|
177
|
+
// task 1002592: every exit below routes through cliExit (task 1002734's rule).
|
|
178
|
+
// This script exits 0 on ALL paths on purpose — it is hook-invoked and must
|
|
179
|
+
// never report failure — but on Windows the post-request teardown aborted with
|
|
180
|
+
// "Assertion failed: !(handle->flags & UV_HANDLE_CLOSING)" and handed the hook
|
|
181
|
+
// 127, which conventionally reads as "command not found". Measured 3/3 before
|
|
182
|
+
// this change: the one script written never to fail always reported failure.
|
|
183
|
+
// Draining also lets the --hook stdout write flush, which a hard exit can cut
|
|
184
|
+
// mid-JSON — the card hook parses that stdout.
|
|
177
185
|
// Cheap idle fast-path: no breadcrumbs → do nothing (no network).
|
|
178
|
-
if (readBreadcrumbs().length === 0) { if (!hookMode) console.error('land-watch: no pending lands.');
|
|
186
|
+
if (readBreadcrumbs().length === 0) { if (!hookMode) console.error('land-watch: no pending lands.'); return cliExit(0); }
|
|
179
187
|
const envelope = await watch();
|
|
180
188
|
if (hookMode) {
|
|
181
189
|
if (envelope) process.stdout.write(JSON.stringify(envelope));
|
|
182
|
-
|
|
190
|
+
return cliExit(0);
|
|
183
191
|
}
|
|
184
192
|
console.error(envelope ? `land-watch: a task landed — shipped card ready (${envelope.title}).` : 'land-watch: nothing landed this pass.');
|
|
185
|
-
|
|
186
|
-
})().catch(() =>
|
|
193
|
+
return cliExit(0);
|
|
194
|
+
})().catch(() => cliExit(0)); // never throw from a hook-invoked script
|
|
187
195
|
}
|
|
@@ -286,9 +286,20 @@ function findLeaks(text, sensitiveLiterals) {
|
|
|
286
286
|
// findSecretLeak — true if the generic secret scrubber WOULD change this text, i.e. a
|
|
287
287
|
// secret shape is present. The export gate runs this in addition to findLeaks so a
|
|
288
288
|
// stray credential in an old commit fails the publish closed rather than leaking.
|
|
289
|
-
|
|
289
|
+
//
|
|
290
|
+
// `opts` MUST be whatever the scrub being audited was given (task 1003873). Unlike
|
|
291
|
+
// findLeaks — which is deliberately STRICTER than redaction, and independent of it by
|
|
292
|
+
// design — this one is DIFFERENTIAL: it re-scrubs and asks "did anything change?". An
|
|
293
|
+
// option the audit does not share therefore makes it disagree with its own subject by
|
|
294
|
+
// construction, and reads that disagreement as a leak. That is not a safety margin, it
|
|
295
|
+
// is a false positive: task 1002460's knownDocNames allowlist spares a long ADR
|
|
296
|
+
// filename from the entropy sweep, an audit without the allowlist re-redacts it, the
|
|
297
|
+
// text differs, and EVERY published doc reports a phantom secret shape. Because the
|
|
298
|
+
// gate is fail-closed the packager then produces nothing — it took the publish lane
|
|
299
|
+
// down for seven consecutive core releases before anyone read past the symptom.
|
|
300
|
+
function findSecretLeak(text, extraSecrets = [], opts = undefined) {
|
|
290
301
|
if (typeof text !== 'string' || text.length === 0) return false;
|
|
291
|
-
return scrubSecrets(text, extraSecrets) !== text;
|
|
302
|
+
return scrubSecrets(text, extraSecrets, opts) !== text;
|
|
292
303
|
}
|
|
293
304
|
|
|
294
305
|
module.exports = {
|
|
@@ -193,7 +193,7 @@ function functionalSecretLeaks(view, envSecrets) {
|
|
|
193
193
|
// * FUNCTIONAL: VERBATIM buffer. `leaks` = exact live-credential matches only (FATAL).
|
|
194
194
|
// `advisories` = brand-identity substrings + public IPs that ship as-is (non-fatal, for
|
|
195
195
|
// human awareness) — REUSES findLeaks so the detector is defined once.
|
|
196
|
-
function processCoreFile({ pathRel, raw, rules, sensitiveLiterals, envSecrets }) {
|
|
196
|
+
function processCoreFile({ pathRel, raw, rules, sensitiveLiterals, envSecrets, knownDocNames }) {
|
|
197
197
|
// An auto-loaded agent doc (CLAUDE.md/AGENTS.md) → ship a brand-free stub, never the
|
|
198
198
|
// core's own project memory (task 2121). The stub is brand-free by construction, so it
|
|
199
199
|
// needs no redaction and can never leak — it short-circuits ahead of the doc path.
|
|
@@ -201,7 +201,7 @@ function processCoreFile({ pathRel, raw, rules, sensitiveLiterals, envSecrets })
|
|
|
201
201
|
return { buf: Buffer.from(CORE_AGENT_DOC_STUB, 'utf8'), leaks: [], advisories: [], kind: 'stub' };
|
|
202
202
|
}
|
|
203
203
|
if (isDocPath(pathRel)) {
|
|
204
|
-
const r = redactBlob({ buf: raw, rules, sensitiveLiterals, envSecrets });
|
|
204
|
+
const r = redactBlob({ buf: raw, rules, sensitiveLiterals, envSecrets, knownDocNames });
|
|
205
205
|
const leaks = r.leaks.map((l) => ({ label: `doc:${l.label}`, sample: l.sample }));
|
|
206
206
|
if (r.secret) leaks.push({ label: 'doc:secret-shape' });
|
|
207
207
|
return { buf: r.buf, leaks, advisories: [], kind: 'doc' };
|
|
@@ -817,6 +817,14 @@ function main() {
|
|
|
817
817
|
// 4. Process every file IN MEMORY (processCoreFile: docs redacted, functional verbatim)
|
|
818
818
|
// + the fail-closed no-leak gate. Nothing touches dist/ until the ENTIRE set is proven
|
|
819
819
|
// clean (atomic abort). Brand/topology in functional files are non-fatal ADVISORIES.
|
|
820
|
+
// task 1002460 — the doc-link allowlist. The entropy sweep cannot tell a long
|
|
821
|
+
// ADR filename from a secret by shape, so it collapsed every one of them to
|
|
822
|
+
// <redacted>.md on the way out. This is the set of names actually being
|
|
823
|
+
// published, so a name in it is a FILE and not a guess; anything else, secrets
|
|
824
|
+
// included, is redacted exactly as before.
|
|
825
|
+
const knownDocNames = new Set(
|
|
826
|
+
publishable.map((e) => e.path.split('/').pop()).filter((n) => /\.(md|sql)$/i.test(n)),
|
|
827
|
+
);
|
|
820
828
|
const staged = []; // { name, mode, buf } (name includes TAR_ROOT)
|
|
821
829
|
const filesMeta = []; // { path, mode, sha256 } (source files only — the pin domain)
|
|
822
830
|
const leaks = [];
|
|
@@ -829,7 +837,7 @@ function main() {
|
|
|
829
837
|
try { raw = gitBuf(['cat-file', 'blob', e.sha]); }
|
|
830
838
|
catch (err) { leaks.push({ path: e.path, label: `read-failed:${err.message}` }); continue; }
|
|
831
839
|
const { buf, leaks: fileLeaks, advisories: fileAdvisories, kind } =
|
|
832
|
-
processCoreFile({ pathRel: e.path, raw, rules, sensitiveLiterals, envSecrets });
|
|
840
|
+
processCoreFile({ pathRel: e.path, raw, rules, sensitiveLiterals, envSecrets, knownDocNames });
|
|
833
841
|
for (const l of fileLeaks) leaks.push({ path: e.path, label: l.label, sample: l.sample });
|
|
834
842
|
for (const a of fileAdvisories) advisories.push({ path: e.path, label: a.label, sample: a.sample });
|
|
835
843
|
if (kind === 'doc') docCount++; else if (kind === 'stub') stubCount++; else funcCount++;
|
package/scripts/gds/release.js
CHANGED
|
@@ -6,7 +6,46 @@
|
|
|
6
6
|
//
|
|
7
7
|
// The task returns to 'ready' status and is claimable by another session.
|
|
8
8
|
|
|
9
|
+
const { execFileSync } = require('node:child_process');
|
|
10
|
+
const path = require('node:path');
|
|
9
11
|
const { cliClient, requireSession, arg } = require('./cli-lib');
|
|
12
|
+
const { parkedMainCheckoutWarning } = require('./worktree-claim-guard');
|
|
13
|
+
|
|
14
|
+
// gitAt(args, cwd) — run git with an ARGV ARRAY and no shell.
|
|
15
|
+
//
|
|
16
|
+
// Deliberately not `execSync('git -C "<path>" …')`. A resolved filesystem path is
|
|
17
|
+
// not trusted input: a directory name containing a quote, backtick or `$()` would
|
|
18
|
+
// break out of the quoting and execute under cmd.exe/sh. execFileSync never
|
|
19
|
+
// spawns a shell, so the path cannot be interpreted as anything but a path — a
|
|
20
|
+
// stronger guarantee than the `{ cwd }` form, which still shells the command
|
|
21
|
+
// string. (Found by the grader on this task, and it was right: the first cut
|
|
22
|
+
// interpolated the path into a shell string.)
|
|
23
|
+
//
|
|
24
|
+
// Kept local rather than imported from ship-git.js on purpose: release.js ships
|
|
25
|
+
// in the PUBLIC client-only @cloudbongos/cli package, and nothing in the ship-*
|
|
26
|
+
// family is in that manifest. tests/cli_package.mjs installs the tarball and RUNS
|
|
27
|
+
// every verb, so an import from outside it is a crash at require time, not a
|
|
28
|
+
// lint-level nit — that is exactly how this file broke once already.
|
|
29
|
+
function gitAt(args, cwd) {
|
|
30
|
+
try {
|
|
31
|
+
return execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
|
|
32
|
+
} catch (_) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// The MAIN checkout, not this worktree — from a linked worktree they differ, and
|
|
38
|
+
// the parked-tree warning is only ever about the main one.
|
|
39
|
+
//
|
|
40
|
+
// Resolved with git rather than by importing ship-state's REPO_ROOT for the same
|
|
41
|
+
// packaging reason: ship-state pulls in modules/lifecycle/ship-preflight, which
|
|
42
|
+
// would drag server-side lifecycle code into a client-only package.
|
|
43
|
+
// `--git-common-dir` is the shared .git of the whole worktree family, so its
|
|
44
|
+
// parent is the main checkout from anywhere.
|
|
45
|
+
function mainCheckoutRoot() {
|
|
46
|
+
const common = gitAt(['rev-parse', '--path-format=absolute', '--git-common-dir'], process.cwd());
|
|
47
|
+
return common ? path.dirname(common) : null;
|
|
48
|
+
}
|
|
10
49
|
|
|
11
50
|
|
|
12
51
|
(async () => {
|
|
@@ -65,6 +104,22 @@ const { cliClient, requireSession, arg } = require('./cli-lib');
|
|
|
65
104
|
}
|
|
66
105
|
console.log(`Released task #${r.data.taskId}: ${r.data.taskTitle}`);
|
|
67
106
|
console.log(`Status returned to 'ready'. No credits awarded.`);
|
|
107
|
+
// task 1002871: a claim worked under the `--here` hatch leaves the MAIN
|
|
108
|
+
// checkout on the feature branch, and giving the claim up is exactly when that
|
|
109
|
+
// tree stops being watched. Warn, never restore — the branch may hold real
|
|
110
|
+
// uncommitted work, and release.js cannot tell that from debris. Strictly
|
|
111
|
+
// best-effort: a diagnostic must never fail a release that already succeeded.
|
|
112
|
+
try {
|
|
113
|
+
const root = mainCheckoutRoot();
|
|
114
|
+
const branch = root && gitAt(['rev-parse', '--abbrev-ref', 'HEAD'], root);
|
|
115
|
+
// gitAt already trims, so a non-empty string IS dirt.
|
|
116
|
+
const dirty = root && gitAt(['status', '--porcelain'], root);
|
|
117
|
+
const lines = parkedMainCheckoutWarning({ branch, dirty: !!dirty });
|
|
118
|
+
if (lines.length) {
|
|
119
|
+
console.log('');
|
|
120
|
+
for (const line of lines) console.log(line);
|
|
121
|
+
}
|
|
122
|
+
} catch (_) { /* never let a warning break a successful release */ }
|
|
68
123
|
})().catch((err) => {
|
|
69
124
|
console.error('fatal:', err);
|
|
70
125
|
process.exit(1);
|
package/scripts/gds/ship-land.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
const fs = require('node:fs');
|
|
12
12
|
const os = require('node:os');
|
|
13
13
|
const path = require('node:path');
|
|
14
|
-
const { cliClient } = require('./cli-lib');
|
|
14
|
+
const { cliClient, isBoxScope } = require('./cli-lib');
|
|
15
15
|
const { gitArgvOk, gitOk, sanitizeGitHubRefs, shellOk } = require('./ship-git.js');
|
|
16
16
|
const { buildPrCreateArgv, buildPrMergeArgv, deployRunSucceeded, deployedCommitStatus, fetchDeployedVersion, ghArgvOk, ghJson, pushVia, resolveInstanceBase } = require('./ship-deploy-target.js');
|
|
17
17
|
const { normalizeApiError } = require('./ship-io.js');
|
|
@@ -184,7 +184,15 @@ async function ciLandServer(taskId, branch, valueSummary) {
|
|
|
184
184
|
id: taskId,
|
|
185
185
|
body: { branch, head_sha: headSha, value_summary: valueSummary, bundle_b64: bundleB64 },
|
|
186
186
|
});
|
|
187
|
-
|
|
187
|
+
// task 1002593: ONE definition of "is this a box-scope refusal". This site used
|
|
188
|
+
// to hand-roll the comparison, which is how it silently died when routes moved
|
|
189
|
+
// to the object error envelope — `r.data.error` stopped being the bare string
|
|
190
|
+
// and the branch could never fire. Normalizing here fixed the symptom but left
|
|
191
|
+
// a SECOND predicate for the same question, and two copies of a predicate are
|
|
192
|
+
// how one of them gets a fix the other never sees (migration 177 dropping 176's
|
|
193
|
+
// blocker clause is the same shape). cli-lib's isBoxScope is the one every
|
|
194
|
+
// other caller already uses; this is the last site that did not.
|
|
195
|
+
if (isBoxScope(r)) {
|
|
188
196
|
console.log(' ci-land: this session is BOX-SCOPED — run /builder-reauth (browser, no terminal) to mint a real session, then re-ship. Task stays at confirmed.');
|
|
189
197
|
return landBailed('this CLI session is box-scoped', '/builder-reauth');
|
|
190
198
|
}
|
|
@@ -248,17 +248,49 @@ function regenerateFileMap() {
|
|
|
248
248
|
// ship-vs-merge-mode and merge-mode-vs-merge-mode races that the in-ship.js
|
|
249
249
|
// lock could not cover. See docs/recipes/multi-builder-merge.md.
|
|
250
250
|
|
|
251
|
+
// describeMainWorktreeBusy — the PURE verdict. Given the three facts about
|
|
252
|
+
// REPO_ROOT, is it safe to mutate, and if not, why? Returns a human reason string
|
|
253
|
+
// or null.
|
|
254
|
+
//
|
|
255
|
+
// task 1002871: it used to answer only 'a merge is already in progress' or 'the
|
|
256
|
+
// working tree is dirty…', and it runs BEFORE the named-branch diagnostic in
|
|
257
|
+
// ship-merge.js. So the shape of the original incident — a shared main checkout
|
|
258
|
+
// that is BOTH parked on a feature branch AND dirty (2026-07-28: parked on
|
|
259
|
+
// `task-1002503-backfill-recovery` with 2 unpushed commits and 4 modified
|
|
260
|
+
// generated files, blocking the auto-merge leg for 9 confirmed tasks) —
|
|
261
|
+
// short-circuited here and the builder never learned WHICH branch was squatting.
|
|
262
|
+
// The named-branch message was only reachable when the tree was clean, which is
|
|
263
|
+
// the one case where nobody needs to be told twice.
|
|
264
|
+
//
|
|
265
|
+
// Pure + exported so the wording is unit-tested rather than eyeballed: this is the
|
|
266
|
+
// ship hot path, and the whole value of the change is the exact string.
|
|
267
|
+
function describeMainWorktreeBusy({ mergeInProgress, dirty, branch, defaultBranch = 'main' } = {}) {
|
|
268
|
+
// A branch name is only worth naming when it is NOT the expected one. Saying
|
|
269
|
+
// "on main" in every bail would be noise that trains the reader to skip the line.
|
|
270
|
+
const occupant = branch && branch !== defaultBranch && branch !== 'HEAD'
|
|
271
|
+
? ` (on \`${branch}\`, not \`${defaultBranch}\`)`
|
|
272
|
+
: '';
|
|
273
|
+
if (mergeInProgress) return `a merge is already in progress${occupant}`;
|
|
274
|
+
if (dirty) return `the working tree is dirty or has unmerged files${occupant}`;
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
|
|
251
278
|
// REPO_ROOT must be on main, not mid-merge, and clean before we mutate it.
|
|
252
|
-
// Returns a human reason string if busy, or null if safe to proceed.
|
|
279
|
+
// Returns a human reason string if busy, or null if safe to proceed. The I/O
|
|
280
|
+
// shell over describeMainWorktreeBusy — gather the facts, let the pure half decide.
|
|
253
281
|
function mainWorktreeBusy() {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
282
|
+
const mergeInProgress = !!gitOk('git rev-parse -q --verify MERGE_HEAD', { cwd: REPO_ROOT });
|
|
283
|
+
// Short-circuit the moment the verdict is decided. `describeMainWorktreeBusy`
|
|
284
|
+
// prefers merge-in-progress over dirty, so once that is true the dirty probe's
|
|
285
|
+
// answer is discarded — spawning it anyway would be two subprocesses bought for
|
|
286
|
+
// nothing. The branch IS still needed on this arm: the reason names it either way.
|
|
287
|
+
const branch = gitOk('git rev-parse --abbrev-ref HEAD', { cwd: REPO_ROOT }) || null;
|
|
288
|
+
if (mergeInProgress) return describeMainWorktreeBusy({ mergeInProgress: true, branch });
|
|
257
289
|
const dirty = gitOk('git status --porcelain', { cwd: REPO_ROOT });
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
return
|
|
290
|
+
// Best-effort throughout: a probe failure yields null, which the pure half reads
|
|
291
|
+
// as "no occupant worth naming". A diagnostic must never be the thing that
|
|
292
|
+
// breaks the bail it is decorating.
|
|
293
|
+
return describeMainWorktreeBusy({ mergeInProgress: false, dirty: !!(dirty && dirty.length > 0), branch });
|
|
262
294
|
}
|
|
263
295
|
|
|
264
296
|
// task 391 (idea #37): the verdict for a ship whose branch has no commits ahead
|
|
@@ -276,6 +308,7 @@ module.exports = {
|
|
|
276
308
|
VENDORED_CLIENT_REL,
|
|
277
309
|
apiClientPathspec,
|
|
278
310
|
currentBranch,
|
|
311
|
+
describeMainWorktreeBusy,
|
|
279
312
|
mainWorktreeBusy,
|
|
280
313
|
regenerateApiClient,
|
|
281
314
|
regenerateApiDocs,
|
package/scripts/gds/start.js
CHANGED
|
@@ -272,8 +272,9 @@ function rebaseWarningModel(needsRebaseTasks) {
|
|
|
272
272
|
function rebaseWarningMarkdown(needsRebaseTasks) {
|
|
273
273
|
const w = rebaseWarningModel(needsRebaseTasks);
|
|
274
274
|
if (!w) return null;
|
|
275
|
-
return `⚠️ ${surface.strong('
|
|
276
|
-
+ ` until you
|
|
275
|
+
return `⚠️ ${surface.strong('Land owed — your queue is GATED.')} ${w.count} ${w.noun} can't land and block new claims`
|
|
276
|
+
+ ` until you ${surface.code('git merge origin/main')} + re-ship (or ${surface.code(surface.action('release'))}) ${w.pronoun}: ${w.refs}.`
|
|
277
|
+
+ ' Merge, never rebase — a published branch that is rewritten cannot be pushed again (task 1002788).';
|
|
277
278
|
}
|
|
278
279
|
|
|
279
280
|
// The widget warning block (Tabler alert icon, danger-tinted), or '' when none.
|
|
@@ -281,7 +282,7 @@ function rebaseWarningWidget(needsRebaseTasks) {
|
|
|
281
282
|
const w = rebaseWarningModel(needsRebaseTasks);
|
|
282
283
|
if (!w) return '';
|
|
283
284
|
return `<div class="bs-warn"><i class="ti ti-alert-triangle" aria-hidden="true"></i>`
|
|
284
|
-
+ `<span><strong>
|
|
285
|
+
+ `<span><strong>Land owed — your queue is GATED.</strong> ${w.count} ${w.noun} can't land and block new claims until you <code>git merge origin/main</code> + re-ship (or /builder-release) ${w.pronoun}: ${hesc(w.refs)}. Merge, never rebase — a published branch that is rewritten cannot be pushed again (task 1002788).</span></div>`;
|
|
285
286
|
}
|
|
286
287
|
|
|
287
288
|
// Run console.log-based helpers (printCostWarning/printOnboardingNudge) without
|
|
@@ -832,7 +833,9 @@ async function main() {
|
|
|
832
833
|
|
|
833
834
|
// ADR 0120 part 5: the owed-rebase gate warning — rendered directly under the
|
|
834
835
|
// identity line and before the claimable table, since it BLOCKS new claims
|
|
835
|
-
// until the builder lands (
|
|
836
|
+
// until the builder lands (`git merge origin/main` + re-ship — task 1002788:
|
|
837
|
+
// MERGE, because the branch is already published and the server's publish is
|
|
838
|
+
// append-only, so a rebase makes it permanently un-pushable) or releases them.
|
|
836
839
|
const rebaseLine = rebaseWarningMarkdown(needsRebaseTasks);
|
|
837
840
|
if (rebaseLine) {
|
|
838
841
|
console.log('');
|
|
@@ -101,4 +101,30 @@ function decideWorktreeClaim({
|
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
// task 1002871 — the OTHER end of the same failure. decideWorktreeClaim stops a
|
|
105
|
+
// builder parking the main checkout at CLAIM time; this speaks at RELEASE time,
|
|
106
|
+
// when a claim worked under the `--here` hatch is given up and the main checkout
|
|
107
|
+
// is left holding the feature branch. Nobody is watching that tree, and the next
|
|
108
|
+
// ship to reach for it — possibly another builder's — is who pays: the 2026-07-28
|
|
109
|
+
// incident was one parked checkout blocking the auto-merge leg for 9 tasks.
|
|
110
|
+
//
|
|
111
|
+
// IT WARNS AND NEVER RESTORES, deliberately. Switching the tree back to main
|
|
112
|
+
// would be a destructive guess: the parked branch may hold real uncommitted work,
|
|
113
|
+
// and release.js has no way to tell that from debris. Naming it is enough — the
|
|
114
|
+
// person reading has the context to decide.
|
|
115
|
+
//
|
|
116
|
+
// Pure; returns an array of lines (empty when there is nothing to say).
|
|
117
|
+
function parkedMainCheckoutWarning({ branch, defaultBranch = 'main', dirty = false } = {}) {
|
|
118
|
+
if (!branch || branch === defaultBranch || branch === 'HEAD') return [];
|
|
119
|
+
return [
|
|
120
|
+
`⚠ The main checkout is still on \`${branch}\`, not \`${defaultBranch}\`.`,
|
|
121
|
+
dirty
|
|
122
|
+
? ' It also has uncommitted changes, so nothing here will touch it.'
|
|
123
|
+
: ' Nothing here will touch it — the branch may hold work you still want.',
|
|
124
|
+
` While it stays parked, the local auto-merge at ship time has nowhere to merge INTO,`,
|
|
125
|
+
' so the next ship on this machine strands at `confirmed` — including someone else\'s.',
|
|
126
|
+
` When you are done with it: git -C <main checkout> checkout ${defaultBranch}`,
|
|
127
|
+
];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = { decideWorktreeClaim, parkedMainCheckoutWarning, ESCAPE_FLAG };
|
|
@@ -95,17 +95,23 @@ function isBinary(buf) {
|
|
|
95
95
|
// over their latin1 view (so an embedded literal still trips the gate). The gate uses
|
|
96
96
|
// `sensitiveLiterals` (raw substrings), NOT `rules` — stricter than redaction on
|
|
97
97
|
// purpose (see mirror-redact.js). Returns { buf:Buffer, leaks:[{label,sample}], secret:boolean }.
|
|
98
|
-
function redactBlob({ buf, rules, sensitiveLiterals, envSecrets }) {
|
|
98
|
+
function redactBlob({ buf, rules, sensitiveLiterals, envSecrets, knownDocNames }) {
|
|
99
99
|
if (isBinary(buf)) {
|
|
100
100
|
const view = buf.toString('latin1');
|
|
101
101
|
return { buf, leaks: findLeaks(view, sensitiveLiterals), secret: findSecretLeak(view, envSecrets) };
|
|
102
102
|
}
|
|
103
103
|
const redacted = redactText(buf.toString('utf8'), rules);
|
|
104
|
-
|
|
104
|
+
// task 1002460: the set of filenames THIS publish is shipping. The entropy
|
|
105
|
+
// sweep mistakes a long doc filename for a secret, and only the caller knows
|
|
106
|
+
// which names are real files. With no set there is no exemption at all.
|
|
107
|
+
const scrubbed = scrubSecrets(redacted, envSecrets, { knownDocNames });
|
|
105
108
|
return {
|
|
106
109
|
buf: Buffer.from(scrubbed, 'utf8'),
|
|
107
110
|
leaks: findLeaks(scrubbed, sensitiveLiterals),
|
|
108
|
-
|
|
111
|
+
// Same allowlist as the scrub directly above, and it has to be: findSecretLeak is a
|
|
112
|
+
// differential audit of THAT scrub's output, so an option only one of the two holds
|
|
113
|
+
// is read as a leak in every doc (task 1003873). Keep these two calls in step.
|
|
114
|
+
secret: findSecretLeak(scrubbed, envSecrets, { knownDocNames }),
|
|
109
115
|
};
|
|
110
116
|
}
|
|
111
117
|
|
|
@@ -206,6 +212,13 @@ function main() {
|
|
|
206
212
|
|
|
207
213
|
// 4. Redact every file IN MEMORY and run the fail-closed no-leak gate. Nothing touches
|
|
208
214
|
// the mirror filesystem until the ENTIRE set is proven clean (atomic abort).
|
|
215
|
+
// task 1002460 — the doc-link allowlist, computed for THIS export. Without it the
|
|
216
|
+
// entropy sweep cannot tell a long ADR filename from a secret and collapses every
|
|
217
|
+
// one to <redacted>.md. This is the mirror`s own file set, so a name in it is a
|
|
218
|
+
// FILE and not a guess; anything else, secrets included, redacts exactly as before.
|
|
219
|
+
const knownDocNames = new Set(
|
|
220
|
+
publishable.map((e) => e.path.split('/').pop()).filter((n) => /\.(md|sql)$/i.test(n)),
|
|
221
|
+
);
|
|
209
222
|
const staged = []; // { path, buf, mode }
|
|
210
223
|
const leaks = []; // { path, label, sample? }
|
|
211
224
|
for (const e of publishable) {
|
|
@@ -216,7 +229,7 @@ function main() {
|
|
|
216
229
|
leaks.push({ path: e.path, label: `read-failed:${err.message}` });
|
|
217
230
|
continue;
|
|
218
231
|
}
|
|
219
|
-
const { buf, leaks: fileLeaks, secret } = redactBlob({ buf: raw, rules, sensitiveLiterals, envSecrets });
|
|
232
|
+
const { buf, leaks: fileLeaks, secret } = redactBlob({ buf: raw, rules, sensitiveLiterals, envSecrets, knownDocNames });
|
|
220
233
|
for (const l of fileLeaks) leaks.push({ path: e.path, label: l.label, sample: l.sample });
|
|
221
234
|
if (secret) leaks.push({ path: e.path, label: 'secret-shape' }); // never print the secret sample
|
|
222
235
|
staged.push({ path: e.path, buf, mode: e.mode });
|
package/src/bongos/api-errors.js
CHANGED
|
@@ -167,6 +167,15 @@ const OVERRIDES = Object.freeze({
|
|
|
167
167
|
// --- validation ---
|
|
168
168
|
validation_failed: 'validation_failed',
|
|
169
169
|
schema_error: 'validation_failed',
|
|
170
|
+
|
|
171
|
+
// task 1002845: a Postgres CHECK violation surfaced as a 400 that names the
|
|
172
|
+
// constraint. It needs an explicit entry because the heuristics below would
|
|
173
|
+
// classify it `internal` (no matching prefix/suffix rule), which is the safe
|
|
174
|
+
// default for an unknown code but wrong here — the caller CAN fix this by
|
|
175
|
+
// changing the value they sent, so it must be bad_request and non-retriable.
|
|
176
|
+
// An unregistered code would still have answered 400 (the call site passes an
|
|
177
|
+
// explicit status), leaving the category quietly disagreeing with the status.
|
|
178
|
+
constraint_violation: 'bad_request',
|
|
170
179
|
});
|
|
171
180
|
|
|
172
181
|
// Suffix/prefix heuristic for the long tail. Order matters — first match wins.
|