@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
package/scripts/gds/claim.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
const { execSync } = require('node:child_process');
|
|
11
11
|
const fs = require('node:fs');
|
|
12
12
|
const path = require('node:path');
|
|
13
|
-
const { cliClient, requireSession, fmtTouches, printOnboardingNudge, rewardPolicy, costPlusOnlyNote, detectWorktreeName } = require('./cli-lib');
|
|
13
|
+
const { cliClient, requireSession, fmtTouches, printOnboardingNudge, rewardPolicy, costPlusOnlyNote, detectWorktreeName, cliExit } = require('./cli-lib');
|
|
14
14
|
const { nextNumbers, padAdr, padMig } = require('./next-number');
|
|
15
15
|
const { CORE_NS } = require('./migration-namespace');
|
|
16
16
|
const { contextPack, formatContextPack, resolveWorktreeRoot } = require('./context-pack');
|
|
@@ -147,10 +147,44 @@ function enforceWorktreeClaim(taskId, { allowMainCheckout }) {
|
|
|
147
147
|
allowMainCheckout,
|
|
148
148
|
taskId,
|
|
149
149
|
});
|
|
150
|
-
if (d.verdict === 'allow') return;
|
|
150
|
+
if (d.verdict === 'allow') return tree;
|
|
151
151
|
for (const line of d.lines) console.error(line);
|
|
152
152
|
if (d.verdict === 'refuse') process.exit(2);
|
|
153
153
|
console.error('');
|
|
154
|
+
return tree;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// task 1002592 — a failed worktree DETECTION reaches the builder disguised as a
|
|
158
|
+
// claim CONFLICT, and that disguise is the whole of the original bug report.
|
|
159
|
+
// When detectWorktreeName() yields nothing the claim goes up with
|
|
160
|
+
// worktree_name: null; the server's one-active-claim guard keys on the WORKING
|
|
161
|
+
// TREE, so an unnamed claim falls back to the per-session compare and a
|
|
162
|
+
// genuinely-parallel worktree is refused as a duplicate. The builder then hunts
|
|
163
|
+
// for a claim conflict that does not exist.
|
|
164
|
+
//
|
|
165
|
+
// Only speak when null is WRONG. In the main checkout null is the documented
|
|
166
|
+
// correct answer and the refusal is genuine, so a note there would send the next
|
|
167
|
+
// reader down precisely the wrong path — that distinction is the fix, not a
|
|
168
|
+
// detail of it.
|
|
169
|
+
function worktreeDetectionNote(ctx, taskId) {
|
|
170
|
+
// The caller must have actually PROBED the tree. Without that this cannot tell
|
|
171
|
+
// "detection failed" from "nobody looked", and guessing would hang a worktree
|
|
172
|
+
// warning on every refusal rendered by a caller that never had tree info —
|
|
173
|
+
// including every pre-existing one. Absence of knowledge is not evidence.
|
|
174
|
+
if (!ctx || !('worktree' in ctx)) return [];
|
|
175
|
+
const { worktree, isMainCheckout } = ctx;
|
|
176
|
+
if (worktree) return []; // named fine — the refusal is genuine
|
|
177
|
+
if (isMainCheckout === true) return []; // null is correct here
|
|
178
|
+
const why = isMainCheckout === false
|
|
179
|
+
? 'this IS a linked worktree, so a name was expected'
|
|
180
|
+
: 'this tree is not recognisable as the main checkout either';
|
|
181
|
+
return [
|
|
182
|
+
` ⚠ This session could not name its worktree — auto-detect returned nothing, and ${why}.`,
|
|
183
|
+
' An unnamed claim is compared per-SESSION instead of per-worktree, so a real parallel',
|
|
184
|
+
' worktree is refused as a duplicate. That is very likely what you are looking at, and',
|
|
185
|
+
' it is a path-detection problem, not a claim conflict. Name it explicitly to rule it out:',
|
|
186
|
+
` node scripts/gds/claim.js ${taskId} --worktree <name>`,
|
|
187
|
+
];
|
|
154
188
|
}
|
|
155
189
|
|
|
156
190
|
function captureHeadSha() {
|
|
@@ -247,7 +281,25 @@ function claimErrorInfo(r) {
|
|
|
247
281
|
// string), so every branch below silently missed and the refusal printed as
|
|
248
282
|
// `[object Object]`. Pure (no console / no process.exit) + exported so it can be
|
|
249
283
|
// unit-tested against the real envelope shapes. Returns { lines, exit }.
|
|
250
|
-
|
|
284
|
+
//
|
|
285
|
+
// task 1002592: server-supplied prose (a task title, a flag reason, a hint) is
|
|
286
|
+
// echoed to the operator's terminal by the branches below. It is our own API, so
|
|
287
|
+
// this is hygiene rather than a live threat — but an unbounded string can carry
|
|
288
|
+
// control characters that repaint a terminal, and a runaway one can bury the
|
|
289
|
+
// guidance it was meant to add. Clip to one line, strip C0/C1 controls, cap length.
|
|
290
|
+
const MAX_LISTED_TASKS = 20;
|
|
291
|
+
function clip(s, max = 160) {
|
|
292
|
+
if (s == null) return '';
|
|
293
|
+
// eslint-disable-next-line no-control-regex
|
|
294
|
+
const flat = String(s).replace(/[--]+/g, ' ').trim();
|
|
295
|
+
return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// task 1002592: `ctx` carries what the CALLER knows about the tree
|
|
299
|
+
// ({ worktree, isMainCheckout }) so the session-claim branch can tell a real
|
|
300
|
+
// conflict apart from a failed worktree detection. Optional and defaulted — the
|
|
301
|
+
// function stays pure and every existing caller/test keeps working.
|
|
302
|
+
function formatClaimFailure(r, taskId, ctx) {
|
|
251
303
|
const lines = [];
|
|
252
304
|
const { code, message, d } = claimErrorInfo(r);
|
|
253
305
|
|
|
@@ -271,6 +323,7 @@ function formatClaimFailure(r, taskId) {
|
|
|
271
323
|
? `Run /builder-ship ${n} (or /builder-release ${n}) first, or open a fresh worktree/session for this task.`
|
|
272
324
|
: 'Ship or release the in-flight claim, or start a fresh worktree/session.')}`);
|
|
273
325
|
lines.push(' One claim per session keeps each ship isolated (task 1642). To work tasks in PARALLEL, open a separate worktree/session per task — each holds its own single claim — or use the hall work board’s multi-select bulk-claim.');
|
|
326
|
+
lines.push(...worktreeDetectionNote(ctx, taskId));
|
|
274
327
|
return { lines, exit: 1 };
|
|
275
328
|
}
|
|
276
329
|
if (code === 'newcomer_must_ship_first') {
|
|
@@ -294,6 +347,26 @@ function formatClaimFailure(r, taskId) {
|
|
|
294
347
|
lines.push(` ${d.what_to_do || `Run /builder-ship ${n || '<task>'} (or /builder-release ${n || '<task>'}) first, claim from a separate worktree, or claim with {"bind_session":false} (an unbound claim always coexists).`}`);
|
|
295
348
|
return { lines, exit: 1 };
|
|
296
349
|
}
|
|
350
|
+
// task 1002592: ADR 0120 part 3's owed-rebase gate refuses EVERY new claim, so
|
|
351
|
+
// it is the refusal a builder is most stuck behind — and it was the only one
|
|
352
|
+
// with no branch here, printing as a bare `[REBASE_REQUIRED]: REBASE_REQUIRED`
|
|
353
|
+
// code echo. The server builds a full way-forward menu for it
|
|
354
|
+
// (rebaseRequiredHint, task 1003768 — /strand-fix leads because it is the one
|
|
355
|
+
// exit that works with no GitHub push credential, the DOCUMENTED default
|
|
356
|
+
// posture) and this surface dropped it on the floor. Name the owed tasks, then
|
|
357
|
+
// print the server's menu.
|
|
358
|
+
if (code === 'REBASE_REQUIRED') {
|
|
359
|
+
const owed = (d.rebaseTasks || d.rebase_tasks || []).slice(0, MAX_LISTED_TASKS);
|
|
360
|
+
lines.push(owed.length
|
|
361
|
+
? `Your queue is gated — ${owed.length} confirmed task(s) are waiting on your rebase:`
|
|
362
|
+
: 'Your queue is gated — a confirmed task is waiting on your rebase.');
|
|
363
|
+
for (const t of owed) {
|
|
364
|
+
lines.push(` - task ${t.id}${t.title ? ` ${clip(t.title)}` : ''}`);
|
|
365
|
+
if (t.reason) lines.push(` flagged: ${clip(t.reason)}`);
|
|
366
|
+
}
|
|
367
|
+
lines.push(` ${clip(d.hint, 600) || 'Clear it with /strand-fix N, or rebase the branch and re-ship — or /builder-release it to give up on one.'}`);
|
|
368
|
+
return { lines, exit: 1 };
|
|
369
|
+
}
|
|
297
370
|
if (code === 'ALREADY_CLAIMED') {
|
|
298
371
|
lines.push(`Task #${taskId} was just claimed by another session. Try a different task.`);
|
|
299
372
|
return { lines, exit: 1 };
|
|
@@ -356,6 +429,11 @@ function formatClaimFailure(r, taskId) {
|
|
|
356
429
|
}
|
|
357
430
|
// Fallback — still readable: the message + code, never a raw [object Object].
|
|
358
431
|
lines.push(`Claim failed (${r ? r.status : '?'})${code ? ` [${code}]` : ''}${message ? `: ${message}` : ''}.`);
|
|
432
|
+
// task 1002592: whatever the code, if the server sent a way forward, PRINT it.
|
|
433
|
+
// REBASE_REQUIRED sat in this fallback with a fully-formed hint in its body and
|
|
434
|
+
// nothing rendered it. The branches above stay the curated wording; this is the
|
|
435
|
+
// floor, so the next unmapped code can never be mute the same way.
|
|
436
|
+
if (d && d.hint) lines.push(` ${clip(d.hint, 600)}`);
|
|
359
437
|
return { lines, exit: 1 };
|
|
360
438
|
}
|
|
361
439
|
|
|
@@ -488,7 +566,7 @@ async function main() {
|
|
|
488
566
|
// task 1002579: worktree-per-claim, enforced BEFORE anything happens — before
|
|
489
567
|
// the session check, before the POST. The whole point is that the builder
|
|
490
568
|
// learns at claim time, not from a stranded ship hours later.
|
|
491
|
-
enforceWorktreeClaim(taskId, { allowMainCheckout: args.includes(ESCAPE_FLAG) });
|
|
569
|
+
const tree = enforceWorktreeClaim(taskId, { allowMainCheckout: args.includes(ESCAPE_FLAG) });
|
|
492
570
|
await requireSession();
|
|
493
571
|
const wIdx = args.indexOf('--worktree');
|
|
494
572
|
// Explicit --worktree always wins; otherwise auto-detect a linked worktree so
|
|
@@ -527,7 +605,7 @@ async function main() {
|
|
|
527
605
|
if (outcome === 'retry') {
|
|
528
606
|
r = await postClaim();
|
|
529
607
|
} else if (outcome === 'stop') {
|
|
530
|
-
|
|
608
|
+
return cliExit(1); // everything already printed; no claim landed
|
|
531
609
|
}
|
|
532
610
|
}
|
|
533
611
|
if (r.status === 201) {
|
|
@@ -651,9 +729,16 @@ async function main() {
|
|
|
651
729
|
}
|
|
652
730
|
// Any non-201 comes here. formatClaimFailure normalizes the R01 error envelope
|
|
653
731
|
// and renders the refusal as guidance (task 1476) — one place, unit-tested.
|
|
654
|
-
const failure = formatClaimFailure(r, taskId
|
|
732
|
+
const failure = formatClaimFailure(r, taskId, {
|
|
733
|
+
worktree,
|
|
734
|
+
isMainCheckout: tree ? tree.isMainCheckout : null,
|
|
735
|
+
});
|
|
655
736
|
for (const line of failure.lines) console.error(line);
|
|
656
|
-
|
|
737
|
+
// task 1002592: this exit runs immediately after the claim request, which is
|
|
738
|
+
// exactly where Windows aborts teardown — "Assertion failed: !(handle->flags &
|
|
739
|
+
// UV_HANDLE_CLOSING)" — turning a refusal's intended exit 1 into 127. Measured
|
|
740
|
+
// 3/3 aborting before this change. Set the code, let the loop drain (1002734).
|
|
741
|
+
cliExit(failure.exit);
|
|
657
742
|
}
|
|
658
743
|
|
|
659
744
|
// Only auto-run when invoked directly (`node claim.js …`); a `require()` (the
|
|
@@ -661,8 +746,8 @@ async function main() {
|
|
|
661
746
|
if (require.main === module) {
|
|
662
747
|
main().catch((err) => {
|
|
663
748
|
console.error('fatal:', err);
|
|
664
|
-
|
|
749
|
+
cliExit(1);
|
|
665
750
|
});
|
|
666
751
|
}
|
|
667
752
|
|
|
668
|
-
module.exports = { formatClaimFailure, claimErrorInfo, goalMembershipOfferPlan, materializeModeSkills };
|
|
753
|
+
module.exports = { formatClaimFailure, claimErrorInfo, goalMembershipOfferPlan, materializeModeSkills, worktreeDetectionNote };
|
package/scripts/gds/fitness.js
CHANGED
|
@@ -520,17 +520,17 @@ const KERNEL_FILES = [
|
|
|
520
520
|
// KERNEL (already in the module-scope-map kernel key) and imports only the
|
|
521
521
|
// kernel (instance-config) + node built-ins, so it joins the roster cleanly.
|
|
522
522
|
'src/bongos/repo-info.js',
|
|
523
|
-
// task 1002331 (ADR 0182 D6 / ADR 0247): the project's composed join/apply door —
|
|
524
|
-
//
|
|
523
|
+
// task 1002331 (ADR 0182 D6 / ADR 0247): the project's composed join/apply door — a
|
|
524
|
+
// deterministic classifier over three policy values, the same category as
|
|
525
525
|
// route-rank-check.js and goal-scope-check.js above (a trust decision the doorway
|
|
526
526
|
// hands to modules so no module re-derives it). The doorway re-exports it, so
|
|
527
527
|
// leaving it off this roster reads as a kernel->non-kernel leak; it imports only
|
|
528
|
-
// branding.js, instance-config.js and logger.js — all on this roster — so
|
|
529
|
-
//
|
|
530
|
-
//
|
|
531
|
-
//
|
|
532
|
-
//
|
|
533
|
-
'src/bongos/project-door.js',
|
|
528
|
+
// branding.js, instance-config.js and logger.js — all on this roster — so joining
|
|
529
|
+
// costs nothing and buys the import-direction guarantee on the file that decides
|
|
530
|
+
// who may knock. (Task 1003579 moved joinabilityMode the other way: the gate reads
|
|
531
|
+
// the composed door, so reader and composer had to sit together or the two would
|
|
532
|
+
// cycle.) The ARTIST GATE joins for the same reason — task 1003575, ADR 0241.
|
|
533
|
+
'src/bongos/project-door.js', 'src/bongos/artist-gate.js',
|
|
534
534
|
];
|
|
535
535
|
|
|
536
536
|
// Kernel files exempt from the no-module-keys scan (Check 10) because naming a
|
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
|
}
|