create-walle 0.10.2 → 0.10.3
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/README.md +3 -1
- package/lib/release-schema.json +2 -2
- package/lib/runtime-closure-policy.json +8 -7
- package/package.json +1 -1
- package/template/coding-task-manager/api-prompts.js +45 -7
- package/template/coding-task-manager/bin/claude-hook.js +55 -1
- package/template/coding-task-manager/bin/ctm-relay-client.js +35 -12
- package/template/coding-task-manager/bin/land.js +2069 -0
- package/template/coding-task-manager/bin/memory-census.js +4 -2
- package/template/coding-task-manager/bin/memory-sync-verify.js +151 -0
- package/template/coding-task-manager/bin/probe-refusal-scope.js +530 -0
- package/template/coding-task-manager/db.js +156 -32
- package/template/coding-task-manager/lib/acp/acp-client.js +383 -0
- package/template/coding-task-manager/lib/acp/dsh-context-truth.js +176 -0
- package/template/coding-task-manager/lib/acp/dsh-launch-model.js +302 -0
- package/template/coding-task-manager/lib/acp/dsh-mcp-servers.js +194 -0
- package/template/coding-task-manager/lib/acp/dsh-prompt-lane.js +616 -0
- package/template/coding-task-manager/lib/acp/dsh-route-patch.js +308 -0
- package/template/coding-task-manager/lib/acp/dsh-session-runtime.js +522 -0
- package/template/coding-task-manager/lib/acp/dsh-transcript.js +395 -0
- package/template/coding-task-manager/lib/acp/dsh-usage-log.js +162 -0
- package/template/coding-task-manager/lib/acp/ndjson-stdio-transport.js +225 -0
- package/template/coding-task-manager/lib/adopted-host-redial.js +47 -11
- package/template/coding-task-manager/lib/adopted-launch-environment.js +123 -0
- package/template/coding-task-manager/lib/agent-auto-update-runtime.js +0 -3
- package/template/coding-task-manager/lib/agent-auto-update.js +26 -10
- package/template/coding-task-manager/lib/agent-bundle/build.js +657 -0
- package/template/coding-task-manager/lib/agent-bundle/codex-install.js +558 -0
- package/template/coding-task-manager/lib/agent-bundle/drift.js +120 -0
- package/template/coding-task-manager/lib/agent-bundle/gc.js +522 -0
- package/template/coding-task-manager/lib/agent-bundle/inventory.js +346 -0
- package/template/coding-task-manager/lib/agent-bundle/push.js +717 -0
- package/template/coding-task-manager/lib/agent-bundle/skill-transfer.js +724 -0
- package/template/coding-task-manager/lib/agent-bundle/spawn.js +119 -0
- package/template/coding-task-manager/lib/agent-bundle/store.js +568 -0
- package/template/coding-task-manager/lib/agent-capabilities.js +142 -12
- package/template/coding-task-manager/lib/agent-cli-cache.js +30 -5
- package/template/coding-task-manager/lib/agent-definitions.js +19 -0
- package/template/coding-task-manager/lib/agent-hooks-codex-toml.js +28 -16
- package/template/coding-task-manager/lib/agent-hooks-codex-workspace.js +9 -1
- package/template/coding-task-manager/lib/agent-hooks-installer.js +23 -8
- package/template/coding-task-manager/lib/agent-hooks-prune.js +37 -11
- package/template/coding-task-manager/lib/agent-hooks-shim.js +105 -0
- package/template/coding-task-manager/lib/agent-presets.js +84 -1
- package/template/coding-task-manager/lib/agent-upgrade-jobs.js +12 -1
- package/template/coding-task-manager/lib/agent-version-service.js +74 -11
- package/template/coding-task-manager/lib/all-sessions-query.js +6 -2
- package/template/coding-task-manager/lib/api/agent-bundles.js +436 -0
- package/template/coding-task-manager/lib/api/backup-retention.js +83 -0
- package/template/coding-task-manager/lib/api/conversation-sync-routes.js +124 -0
- package/template/coding-task-manager/lib/api/diagnostics.js +549 -69
- package/template/coding-task-manager/lib/api/fleet.js +118 -0
- package/template/coding-task-manager/lib/api/hub-bridge.js +243 -0
- package/template/coding-task-manager/lib/api/ingress.js +9 -1
- package/template/coding-task-manager/lib/api/landing.js +403 -0
- package/template/coding-task-manager/lib/api/machines.js +691 -2
- package/template/coding-task-manager/lib/api/memory-sync-routes.js +335 -0
- package/template/coding-task-manager/lib/api/misc-ops.js +8 -3
- package/template/coding-task-manager/lib/api/remote-sessions.js +1326 -0
- package/template/coding-task-manager/lib/api/session-attach.js +623 -0
- package/template/coding-task-manager/lib/api/session-messages.js +1384 -0
- package/template/coding-task-manager/lib/api/sessions.js +77 -51
- package/template/coding-task-manager/lib/api/setup-network.js +544 -16
- package/template/coding-task-manager/lib/api/setup-wizard-handlers.js +884 -0
- package/template/coding-task-manager/lib/api/setup.js +829 -10
- package/template/coding-task-manager/lib/api/walle-bridge.js +9 -1
- package/template/coding-task-manager/lib/attachment-reconciler-wiring.js +5 -2
- package/template/coding-task-manager/lib/auth-rate-limit.js +23 -0
- package/template/coding-task-manager/lib/auth-rules.js +292 -21
- package/template/coding-task-manager/lib/background-stop.js +369 -0
- package/template/coding-task-manager/lib/best-of-attempts.js +4 -1
- package/template/coding-task-manager/lib/best-of-launch-plan.js +52 -0
- package/template/coding-task-manager/lib/box-keep-awake.js +89 -0
- package/template/coding-task-manager/lib/broadcast-reason-stats.js +152 -0
- package/template/coding-task-manager/lib/broadcast-serialize.js +5 -0
- package/template/coding-task-manager/lib/build-info.js +4 -2
- package/template/coding-task-manager/lib/claude-model-truth.js +13 -2
- package/template/coding-task-manager/lib/claude-transcript-terminal-renderer.js +17 -13
- package/template/coding-task-manager/lib/client-diag-handler.js +64 -2
- package/template/coding-task-manager/lib/codex-app-server-client.js +14 -16
- package/template/coding-task-manager/lib/codex-configuration-history.js +55 -0
- package/template/coding-task-manager/lib/codex-harness-qualification.js +43 -20
- package/template/coding-task-manager/lib/codex-rollout-history.js +743 -0
- package/template/coding-task-manager/lib/coding-agent-models.js +27 -12
- package/template/coding-task-manager/lib/coding-cli-skill-installer.js +44 -14
- package/template/coding-task-manager/lib/coding-session-model-adapters.js +43 -2
- package/template/coding-task-manager/lib/coding-session-model-owner.js +1 -1
- package/template/coding-task-manager/lib/coding-session-model-public.js +7 -1
- package/template/coding-task-manager/lib/coding-session-model-runtime.js +23 -4
- package/template/coding-task-manager/lib/coding-session-model-scan.js +31 -0
- package/template/coding-task-manager/lib/coding-session-model-state.js +7 -0
- package/template/coding-task-manager/lib/conversation-freshness-diagnostics.js +280 -0
- package/template/coding-task-manager/lib/conversation-item-clock.js +261 -0
- package/template/coding-task-manager/lib/conversation-source-registry.js +5 -0
- package/template/coding-task-manager/lib/conversation-store-v2.js +44 -0
- package/template/coding-task-manager/lib/conversation-sync.js +355 -0
- package/template/coding-task-manager/lib/create/resolve-launch-params.js +96 -12
- package/template/coding-task-manager/lib/create/worker-role-profile.js +4 -4
- package/template/coding-task-manager/lib/create-intent-lease.js +51 -0
- package/template/coding-task-manager/lib/ctm-session-context-api.js +97 -6
- package/template/coding-task-manager/lib/db-domain-tables.js +1 -0
- package/template/coding-task-manager/lib/db-domains.js +1 -0
- package/template/coding-task-manager/lib/db-owner-dispatch-probe.js +32 -0
- package/template/coding-task-manager/lib/db-owner-request-counts.js +178 -0
- package/template/coding-task-manager/lib/db-owner-worker-client.js +68 -14
- package/template/coding-task-manager/lib/db-schema-source-files.js +5 -0
- package/template/coding-task-manager/lib/deferred-restore-attempt.js +6 -2
- package/template/coding-task-manager/lib/feature-manifest.js +4 -0
- package/template/coding-task-manager/lib/fleet-conversation-route.js +97 -0
- package/template/coding-task-manager/lib/fleet-fanout.js +78 -0
- package/template/coding-task-manager/lib/fleet-hub.js +762 -0
- package/template/coding-task-manager/lib/fleet-origin-follower.js +252 -0
- package/template/coding-task-manager/lib/fleet-resolver-effects.js +147 -0
- package/template/coding-task-manager/lib/fleet-resolver-tick.js +148 -0
- package/template/coding-task-manager/lib/fleet-resolver.js +454 -0
- package/template/coding-task-manager/lib/fleet-session-list-routes.js +114 -0
- package/template/coding-task-manager/lib/fleet-session-lists.js +672 -0
- package/template/coding-task-manager/lib/fleet-update.js +798 -0
- package/template/coding-task-manager/lib/fs-watcher-root-watch.js +141 -0
- package/template/coding-task-manager/lib/fs-watcher.js +58 -42
- package/template/coding-task-manager/lib/harness-history-transition.js +50 -9
- package/template/coding-task-manager/lib/headless-term-service.js +84 -8
- package/template/coding-task-manager/lib/hermetic-agent-launch-guard.js +10 -0
- package/template/coding-task-manager/lib/history-transition-read-op.js +175 -8
- package/template/coding-task-manager/lib/host-cpu-categories.js +310 -0
- package/template/coding-task-manager/lib/host-fseventsd-sampler.js +399 -0
- package/template/coding-task-manager/lib/host-handoff.js +16 -19
- package/template/coding-task-manager/lib/host-stats.js +150 -3
- package/template/coding-task-manager/lib/hosted-sync-worker.js +43 -320
- package/template/coding-task-manager/lib/hub-bridge-access.js +798 -0
- package/template/coding-task-manager/lib/hub-bridge-box.js +409 -0
- package/template/coding-task-manager/lib/hub-bridge-mcp.js +380 -0
- package/template/coding-task-manager/lib/hub-bridge-mesh.js +313 -0
- package/template/coding-task-manager/lib/hub-bridge-ops.js +721 -0
- package/template/coding-task-manager/lib/hub-bridge.js +398 -0
- package/template/coding-task-manager/lib/idle-notify.js +491 -0
- package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +8 -0
- package/template/coding-task-manager/lib/instance-manifest.js +5 -1
- package/template/coding-task-manager/lib/instance-sync-status.js +18 -8
- package/template/coding-task-manager/lib/landing/agent-rule.txt +1 -0
- package/template/coding-task-manager/lib/landing/batch-runner.js +366 -0
- package/template/coding-task-manager/lib/landing/config.js +536 -0
- package/template/coding-task-manager/lib/landing/ctm-view.js +256 -0
- package/template/coding-task-manager/lib/landing/gate.js +360 -0
- package/template/coding-task-manager/lib/landing/git.js +344 -0
- package/template/coding-task-manager/lib/landing/lease.js +247 -0
- package/template/coding-task-manager/lib/landing/merge-delta.js +380 -0
- package/template/coding-task-manager/lib/landing/proof.js +223 -0
- package/template/coding-task-manager/lib/landing/queue.js +998 -0
- package/template/coding-task-manager/lib/landing/radar.js +489 -0
- package/template/coding-task-manager/lib/landing/resolve-cli.js +424 -0
- package/template/coding-task-manager/lib/landing/state.js +263 -0
- package/template/coding-task-manager/lib/landing/stop.js +49 -0
- package/template/coding-task-manager/lib/landing/sync-cli.js +475 -0
- package/template/coding-task-manager/lib/landing/sync.js +456 -0
- package/template/coding-task-manager/lib/landing/train.js +885 -0
- package/template/coding-task-manager/lib/landing/window.js +57 -0
- package/template/coding-task-manager/lib/launch-environment.js +121 -0
- package/template/coding-task-manager/lib/launch-model-override.js +36 -10
- package/template/coding-task-manager/lib/launch-plan-pool.js +2 -1
- package/template/coding-task-manager/lib/launch-plan-warm-cache.js +6 -0
- package/template/coding-task-manager/lib/lead-model-observer.js +57 -1
- package/template/coding-task-manager/lib/lead-model-options.js +160 -3
- package/template/coding-task-manager/lib/lead-model-restore-reconciliation.js +106 -24
- package/template/coding-task-manager/lib/lead-model-switch-runtime.js +352 -68
- package/template/coding-task-manager/lib/lead-model-switch.js +114 -10
- package/template/coding-task-manager/lib/lead-model-transition-runtime.js +14 -2
- package/template/coding-task-manager/lib/lead-model-transition-store.js +24 -6
- package/template/coding-task-manager/lib/lead-route-preflight.js +179 -0
- package/template/coding-task-manager/lib/lead-switch-history-mode.js +110 -0
- package/template/coding-task-manager/lib/lead-wire-agent.js +7 -0
- package/template/coding-task-manager/lib/lifecycle-wake-experiment.js +9 -2
- package/template/coding-task-manager/lib/live-defer-announce.js +45 -0
- package/template/coding-task-manager/lib/machine-registry.js +750 -5
- package/template/coding-task-manager/lib/macos-capabilities.js +32 -15
- package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +433 -53
- package/template/coding-task-manager/lib/mainloop-observability.js +14 -9
- package/template/coding-task-manager/lib/media-draft-owner.js +16 -3
- package/template/coding-task-manager/lib/memory-join.js +337 -0
- package/template/coding-task-manager/lib/memory-sync-verify.js +393 -0
- package/template/coding-task-manager/lib/memory-sync.js +565 -0
- package/template/coding-task-manager/lib/migrations/v57-delegate-pref-mirror-opencode.js +68 -0
- package/template/coding-task-manager/lib/migrations/v58-subagent-launch-native-subagents.js +42 -0
- package/template/coding-task-manager/lib/migrations/v59-coding-session-model-state-dsh.js +73 -0
- package/template/coding-task-manager/lib/migrations/v60-delegate-pref-mirror-dsh.js +78 -0
- package/template/coding-task-manager/lib/migrations/v61-conversation-item-clock.js +71 -0
- package/template/coding-task-manager/lib/model-availability.js +727 -0
- package/template/coding-task-manager/lib/model-execution-recipe-metadata.js +4 -6
- package/template/coding-task-manager/lib/model-host-receipt.js +6 -3
- package/template/coding-task-manager/lib/model-id-rule.js +19 -0
- package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
- package/template/coding-task-manager/lib/model-proxy/auto-mode-review.js +110 -23
- package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
- package/template/coding-task-manager/lib/model-proxy/generation-probe.js +60 -17
- package/template/coding-task-manager/lib/model-proxy/historical-tool-declarations.js +37 -0
- package/template/coding-task-manager/lib/model-proxy/lead-request-validation.js +1 -0
- package/template/coding-task-manager/lib/model-proxy/lead-token-admin.js +130 -0
- package/template/coding-task-manager/lib/model-proxy/refusal-scope.js +151 -0
- package/template/coding-task-manager/lib/model-proxy/request-budget-store.js +11 -2
- package/template/coding-task-manager/lib/model-proxy/tokens.js +69 -5
- package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
- package/template/coding-task-manager/lib/native-agent-model-args.js +25 -22
- package/template/coding-task-manager/lib/native-history-control.js +6 -4
- package/template/coding-task-manager/lib/native-lead-settings-client.js +44 -4
- package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +173 -36
- package/template/coding-task-manager/lib/online-backup-worker-client.js +2 -0
- package/template/coding-task-manager/lib/opencode-cli.js +480 -0
- package/template/coding-task-manager/lib/opencode-launch-model.js +471 -0
- package/template/coding-task-manager/lib/opencode-v1-plugin/model-carrier.mjs +35 -0
- package/template/coding-task-manager/lib/opencode-v2-plugin/server.js +118 -0
- package/template/coding-task-manager/lib/owner-action-manifest-data.json +1 -1
- package/template/coding-task-manager/lib/park-composer-draft.js +54 -0
- package/template/coding-task-manager/lib/park-fresh-relaunch.js +41 -10
- package/template/coding-task-manager/lib/perf-phase-trace.js +72 -0
- package/template/coding-task-manager/lib/perf-summary.js +7 -0
- package/template/coding-task-manager/lib/perf-tracker.js +63 -2
- package/template/coding-task-manager/lib/project-config.js +7 -0
- package/template/coding-task-manager/lib/prompt-purge-v2.js +52 -1
- package/template/coding-task-manager/lib/provider-login-broker.js +72 -15
- package/template/coding-task-manager/lib/provider-login-tab.js +10 -2
- package/template/coding-task-manager/lib/read-pool-client.js +89 -2
- package/template/coding-task-manager/lib/read-pool-worker-handle.js +29 -4
- package/template/coding-task-manager/lib/recent-catalog-query.js +15 -0
- package/template/coding-task-manager/lib/recent-session-catalog-response.js +67 -10
- package/template/coding-task-manager/lib/recovery-evidence.js +26 -11
- package/template/coding-task-manager/lib/relay-client.js +57 -3
- package/template/coding-task-manager/lib/relink-audit.js +34 -5
- package/template/coding-task-manager/lib/remote-machine-catalog.js +321 -26
- package/template/coding-task-manager/lib/remote-machine-create.js +576 -36
- package/template/coding-task-manager/lib/remote-machine-link.js +546 -59
- package/template/coding-task-manager/lib/remote-machine-proxy.js +143 -9
- package/template/coding-task-manager/lib/remote-session-forward.js +65 -7
- package/template/coding-task-manager/lib/remote-terminal-target.js +9 -2
- package/template/coding-task-manager/lib/restart-guard.js +18 -6
- package/template/coding-task-manager/lib/restore-seed-modes.js +87 -0
- package/template/coding-task-manager/lib/restored-scrollback-hydration-runtime.js +22 -2
- package/template/coding-task-manager/lib/resume-admission.js +17 -3
- package/template/coding-task-manager/lib/review-repo-registry.js +58 -19
- package/template/coding-task-manager/lib/root-delivery-runtime.js +2 -2
- package/template/coding-task-manager/lib/root-prompt-flow.js +1 -1
- package/template/coding-task-manager/lib/runtime-contract.js +3 -1
- package/template/coding-task-manager/lib/schema-indexes.js +1 -0
- package/template/coding-task-manager/lib/session-context-locator.js +742 -0
- package/template/coding-task-manager/lib/session-create/attachment.js +2 -2
- package/template/coding-task-manager/lib/session-create/create-phases.js +118 -0
- package/template/coding-task-manager/lib/session-create/delegate-worker.js +86 -16
- package/template/coding-task-manager/lib/session-create/dsh-session.js +930 -0
- package/template/coding-task-manager/lib/session-create/lead-route-launch.js +186 -13
- package/template/coding-task-manager/lib/session-create/native-lead-lifetime.js +55 -3
- package/template/coding-task-manager/lib/session-create/opencode-lead-provider.js +50 -0
- package/template/coding-task-manager/lib/session-create/prepared.js +162 -38
- package/template/coding-task-manager/lib/session-create/provider-process.js +160 -28
- package/template/coding-task-manager/lib/session-create/response.js +7 -7
- package/template/coding-task-manager/lib/session-create/structured-session.js +27 -0
- package/template/coding-task-manager/lib/session-create/subagent-pick-table.js +3 -1
- package/template/coding-task-manager/lib/session-create/subagent-pick.js +458 -9
- package/template/coding-task-manager/lib/session-create/transcript.js +1 -1
- package/template/coding-task-manager/lib/session-create/worker-budget-setup.js +21 -2
- package/template/coding-task-manager/lib/session-create/worker-continuation.js +7 -1
- package/template/coding-task-manager/lib/session-create/worker-launch-sandbox.js +39 -10
- package/template/coding-task-manager/lib/session-create/worker-observability.js +2 -2
- package/template/coding-task-manager/lib/session-empty.js +13 -10
- package/template/coding-task-manager/lib/session-host-manager.js +60 -0
- package/template/coding-task-manager/lib/session-jobs.js +3 -1
- package/template/coding-task-manager/lib/session-lifecycle.js +10 -4
- package/template/coding-task-manager/lib/session-model-action-preconditions.js +6 -2
- package/template/coding-task-manager/lib/session-model-intent-contract.js +11 -8
- package/template/coding-task-manager/lib/session-park.js +738 -0
- package/template/coding-task-manager/lib/session-path-grant.js +13 -4
- package/template/coding-task-manager/lib/session-status-ledger.js +106 -4
- package/template/coding-task-manager/lib/session-transport.js +17 -0
- package/template/coding-task-manager/lib/session-workspace-projection.js +54 -5
- package/template/coding-task-manager/lib/session-workspace-tracking-runtime.js +213 -1
- package/template/coding-task-manager/lib/skill-autocomplete-query.js +120 -3
- package/template/coding-task-manager/lib/skill-autocomplete.js +15 -1
- package/template/coding-task-manager/lib/sqlite-file-set.js +39 -0
- package/template/coding-task-manager/lib/standup-search-adapter.js +16 -2
- package/template/coding-task-manager/lib/state-sync/client-ledger.js +12 -0
- package/template/coding-task-manager/lib/state-sync/frame-emitter.js +319 -100
- package/template/coding-task-manager/lib/storage/coding-session-model-store.js +4 -1
- package/template/coding-task-manager/lib/storage/delegate-pref-mirror-store.js +32 -9
- package/template/coding-task-manager/lib/structured-session-boot-sweep.js +235 -0
- package/template/coding-task-manager/lib/submission-ack.js +90 -2
- package/template/coding-task-manager/lib/systemd-scope.js +115 -0
- package/template/coding-task-manager/lib/telemetry-receiver.js +39 -0
- package/template/coding-task-manager/lib/terminal-attach-geometry.js +985 -0
- package/template/coding-task-manager/lib/terminal-bootstrap.js +30 -0
- package/template/coding-task-manager/lib/terminal-draft-operation.js +357 -0
- package/template/coding-task-manager/lib/terminal-input-admission.js +17 -1
- package/template/coding-task-manager/lib/terminal-input-prepared.js +563 -0
- package/template/coding-task-manager/lib/transcript-cache-temps.js +171 -0
- package/template/coding-task-manager/lib/transcript-frame-cache.js +8 -3
- package/template/coding-task-manager/lib/transcript-source.js +36 -4
- package/template/coding-task-manager/lib/transcript-store.js +9 -0
- package/template/coding-task-manager/lib/typing-interaction-trace.js +106 -3
- package/template/coding-task-manager/lib/usage-ledger/backfill.js +40 -5
- package/template/coding-task-manager/lib/usage-ledger/client.js +4 -1
- package/template/coding-task-manager/lib/usage-ledger/dsh-parse.js +152 -0
- package/template/coding-task-manager/lib/usage-ledger/dsh-read.js +73 -0
- package/template/coding-task-manager/lib/usage-ledger/ingest.js +34 -6
- package/template/coding-task-manager/lib/usage-ledger/live.js +35 -8
- package/template/coding-task-manager/lib/usage-ledger/read.js +29 -20
- package/template/coding-task-manager/lib/usage-ledger/schema.js +61 -1
- package/template/coding-task-manager/lib/usage-ledger/task-coordinator.js +4 -2
- package/template/coding-task-manager/lib/wake-skip-reasons.js +1 -0
- package/template/coding-task-manager/lib/walle/turn.js +1 -1
- package/template/coding-task-manager/lib/walle-history-serve.js +838 -0
- package/template/coding-task-manager/lib/walle-kill-authority.js +2 -2
- package/template/coding-task-manager/lib/walle-log-rotation.js +164 -1
- package/template/coding-task-manager/lib/walle-mcp-auto-config.js +56 -2
- package/template/coding-task-manager/lib/walle-row-history.js +495 -0
- package/template/coding-task-manager/lib/walle-supervisor.js +33 -14
- package/template/coding-task-manager/lib/watch-worker-client.js +203 -0
- package/template/coding-task-manager/lib/worker-terminal-output.js +5 -2
- package/template/coding-task-manager/lib/ws/hub-bridge.js +35 -0
- package/template/coding-task-manager/lib/ws/lifecycle.js +6 -11
- package/template/coding-task-manager/lib/ws/misc.js +1 -1
- package/template/coding-task-manager/lib/ws/state-sync.js +106 -4
- package/template/coding-task-manager/lib/ws/terminal-attach.js +91 -0
- package/template/coding-task-manager/oauth-proxy.js +112 -115
- package/template/coding-task-manager/package-lock.json +10 -94
- package/template/coding-task-manager/package.json +4 -3
- package/template/coding-task-manager/protocol/message-table.json +10 -0
- package/template/coding-task-manager/protocol/messages.js +10 -0
- package/template/coding-task-manager/protocol/ws-schema.json +20 -0
- package/template/coding-task-manager/protocol/ws-schema.md +2 -0
- package/template/coding-task-manager/public/css/app.css +170 -0
- package/template/coding-task-manager/public/css/walle-session.css +4 -0
- package/template/coding-task-manager/public/index.html +473 -243
- package/template/coding-task-manager/public/js/agent-command.js +86 -0
- package/template/coding-task-manager/public/js/app/backups-panel.js +111 -5
- package/template/coding-task-manager/public/js/app/coding-agent-upgrade.js +103 -4
- package/template/coding-task-manager/public/js/app/machines-page.js +1328 -8
- package/template/coding-task-manager/public/js/app/models-tab.js +113 -38
- package/template/coding-task-manager/public/js/app/restart-banner.js +53 -0
- package/template/coding-task-manager/public/js/app/worktrees-panel.js +184 -2
- package/template/coding-task-manager/public/js/background-work.js +196 -27
- package/template/coding-task-manager/public/js/coding-session-model-presentation.js +138 -10
- package/template/coding-task-manager/public/js/core/active-session-groups.js +179 -0
- package/template/coding-task-manager/public/js/core/pending-create.js +238 -0
- package/template/coding-task-manager/public/js/core/recent-machine-source.js +228 -0
- package/template/coding-task-manager/public/js/core/session-commands.js +180 -14
- package/template/coding-task-manager/public/js/dashboard-client-function-order.json +8 -0
- package/template/coding-task-manager/public/js/document-review-links.js +9 -1
- package/template/coding-task-manager/public/js/file-resolution-client.js +52 -1
- package/template/coding-task-manager/public/js/history-app-seam.js +22 -2
- package/template/coding-task-manager/public/js/machine-pool-shared.js +558 -3
- package/template/coding-task-manager/public/js/message-renderer.js +4 -3
- package/template/coding-task-manager/public/js/model-picker.js +3 -1
- package/template/coding-task-manager/public/js/native-subagent-review.js +15 -2
- package/template/coding-task-manager/public/js/new-session-agent-grid.js +346 -0
- package/template/coding-task-manager/public/js/pasted-content.js +236 -0
- package/template/coding-task-manager/public/js/pe-private-content-lifetime.js +39 -7
- package/template/coding-task-manager/public/js/private-model-lifetime.js +6 -2
- package/template/coding-task-manager/public/js/prompts.js +11 -10
- package/template/coding-task-manager/public/js/protocol.js +10 -0
- package/template/coding-task-manager/public/js/restart-banner-policy.js +29 -1
- package/template/coding-task-manager/public/js/session-model-picker.js +447 -57
- package/template/coding-task-manager/public/js/session-search-utils.js +18 -2
- package/template/coding-task-manager/public/js/session-usage-view.js +2 -1
- package/template/coding-task-manager/public/js/session-workspace-tracking.js +51 -1
- package/template/coding-task-manager/public/js/state-sync-client.js +10 -1
- package/template/coding-task-manager/public/js/stream-view.js +6 -5
- package/template/coding-task-manager/public/js/terminal/conv-only-session.js +287 -0
- package/template/coding-task-manager/public/js/terminal/runtime-09.js +2 -0
- package/template/coding-task-manager/public/js/terminal/runtime-10.js +5 -5
- package/template/coding-task-manager/public/js/terminal/runtime-12.js +8 -1
- package/template/coding-task-manager/public/js/terminal/runtime-16.js +4 -0
- package/template/coding-task-manager/public/js/terminal/runtime-20.js +1 -1
- package/template/coding-task-manager/public/js/terminal-attachment-draft.js +67 -13
- package/template/coding-task-manager/public/js/terminal-image-orchestration.js +52 -6
- package/template/coding-task-manager/public/js/terminal-wheel.js +23 -3
- package/template/coding-task-manager/public/js/walle/chat-conversation.js +4 -4
- package/template/coding-task-manager/public/js/walle.js +6 -0
- package/template/coding-task-manager/public/js/ws/live-dispatch.js +10 -0
- package/template/coding-task-manager/public/m/app.css +140 -0
- package/template/coding-task-manager/public/m/app.js +507 -68
- package/template/coding-task-manager/public/m/index.html +17 -0
- package/template/coding-task-manager/public/m/machines.js +838 -4
- package/template/coding-task-manager/public/m/private-api.js +31 -1
- package/template/coding-task-manager/public/usage.html +1 -0
- package/template/coding-task-manager/scripts/dev-route-fixture/synthetic-route-upstream.js +104 -0
- package/template/coding-task-manager/scripts/dev-route-fixture.js +339 -0
- package/template/coding-task-manager/scripts/opencode-lead-route-probe.js +195 -0
- package/template/coding-task-manager/scripts/opencode-v2-contract-probe/fake-anthropic.js +168 -0
- package/template/coding-task-manager/scripts/opencode-v2-contract-probe/probe-plugin/server.js +95 -0
- package/template/coding-task-manager/scripts/opencode-v2-contract-probe.js +673 -0
- package/template/coding-task-manager/server.js +1950 -11679
- package/template/coding-task-manager/session-integrity.js +64 -4
- package/template/coding-task-manager/workers/db-owner-worker.js +19 -2
- package/template/coding-task-manager/workers/online-backup-worker.js +1 -0
- package/template/coding-task-manager/workers/read-pool-model-collector.js +51 -3
- package/template/coding-task-manager/workers/read-pool-worker.js +76 -6
- package/template/coding-task-manager/workers/session-host-pool-process.js +6 -6
- package/template/coding-task-manager/workers/session-host-process.js +5 -5
- package/template/coding-task-manager/workers/watch-worker.js +105 -0
- package/template/coding-task-manager/workers/worktree-worker.js +168 -33
- package/template/ctm-relay/lib/protocol.js +14 -1
- package/template/package-lock.json +2 -2
- package/template/package.json +1 -1
- package/template/shared/backup-retention.js +82 -0
- package/template/shared/conversation-admission.js +2 -0
- package/template/shared/conversation-provenance.js +3 -7
- package/template/shared/conversation-storage-v2-contract.js +5 -0
- package/template/shared/linked-worktree.js +51 -0
- package/template/shared/macos-fda-target.js +57 -0
- package/template/shared/orphan-parents.js +55 -0
- package/template/shared/release-schema.json +2 -2
- package/template/shared/storage/contract.js +3 -0
- package/template/wall-e/adapters/imessage.js +12 -0
- package/template/wall-e/agent.js +71 -4
- package/template/wall-e/api-walle.js +21 -1
- package/template/wall-e/bin/train-gemma-e4b-tooluse.js +2 -2
- package/template/wall-e/bin/walle-mcp-stdio.js +5 -5
- package/template/wall-e/brain/brain-join.js +859 -0
- package/template/wall-e/brain/brain-sync-membership.js +20 -0
- package/template/wall-e/brain/brain-sync-store.js +131 -77
- package/template/wall-e/brain/brain-sync-v2-schema.js +203 -0
- package/template/wall-e/brain/brain-sync-v2.js +578 -0
- package/template/wall-e/brain/delegate-model-prefs-store.js +13 -4
- package/template/wall-e/brain/deterministic-id.js +51 -0
- package/template/wall-e/brain/memories.js +2 -2
- package/template/wall-e/brain/migrations.js +40 -6
- package/template/wall-e/brain/model-identity-migration.js +1 -1
- package/template/wall-e/brain/owner-call-whitelist.js +23 -7
- package/template/wall-e/brain/schema.js +45 -85
- package/template/wall-e/brain.js +171 -35
- package/template/wall-e/chat.js +3 -3
- package/template/wall-e/coding/agent-shell-env.js +7 -2
- package/template/wall-e/coding/prompt-bundle.js +66 -21
- package/template/wall-e/config/main-loop-exemptions.json +2 -2
- package/template/wall-e/http/admin-api.js +37 -0
- package/template/wall-e/http/gws-api.js +4 -0
- package/template/wall-e/http/helper-rules-api.js +7 -1
- package/template/wall-e/http/model-admin.js +8 -1
- package/template/wall-e/http/model-delegate-pref-admin.js +3 -3
- package/template/wall-e/http/model-proxy-providers.js +174 -1
- package/template/wall-e/http/model-route-health.js +157 -0
- package/template/wall-e/http/sync-api.js +278 -464
- package/template/wall-e/jobs/job-runner.js +28 -1
- package/template/wall-e/lib/brain-owner-op-priority.js +9 -1
- package/template/wall-e/lib/ctm-data-dir.js +26 -0
- package/template/wall-e/lib/derivation-home.js +147 -0
- package/template/wall-e/lib/helper-rules/evaluate.js +50 -9
- package/template/wall-e/lib/helper-rules/grade.js +51 -8
- package/template/wall-e/lib/helper-rules/lead-call-hook.js +38 -5
- package/template/wall-e/lib/helper-rules/store.js +4 -2
- package/template/wall-e/lib/landing-mcp-tools.js +170 -0
- package/template/wall-e/lib/mcp-client-config.js +133 -14
- package/template/wall-e/lib/mcp-gateway-migrate.js +28 -64
- package/template/wall-e/lib/mcp-integration.js +58 -13
- package/template/wall-e/lib/parent-death.js +7 -1
- package/template/wall-e/lib/scheduler-worker-jobs.js +6 -4
- package/template/wall-e/llm/delegate-lead-prefs.js +16 -1
- package/template/wall-e/llm/delegate-vendor-default.js +12 -5
- package/template/wall-e/llm/model-roles.js +1 -1
- package/template/wall-e/llm/native-delegate-selection.js +56 -8
- package/template/wall-e/llm/worker-delegate-args.js +72 -7
- package/template/wall-e/llm/worker-mcp-routing.js +66 -17
- package/template/wall-e/loops/brain-custom-writes.js +19 -0
- package/template/wall-e/loops/ingest.js +17 -1
- package/template/wall-e/loops/think.js +19 -4
- package/template/wall-e/mcp-contract.js +222 -6
- package/template/wall-e/mcp-protocol-contract.js +40 -5
- package/template/wall-e/mcp-server.js +128 -29
- package/template/wall-e/mcp-worker-schemas.js +5 -5
- package/template/wall-e/memory/ctm-context-client.js +85 -0
- package/template/wall-e/memory/ctm-session-context.js +9 -3
- package/template/wall-e/memory/hub-bridge-client.js +388 -0
- package/template/wall-e/memory/replica-origin.js +56 -0
- package/template/wall-e/memory/session-pool.js +135 -0
- package/template/wall-e/runtime/live-capabilities.js +8 -3
- package/template/wall-e/server.js +4 -2
- package/template/wall-e/shared/release-schema.json +2 -2
- package/template/wall-e/shared/sqlite-file-set.js +39 -0
- package/template/wall-e/shared/storage/contract.js +3 -0
- package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
- package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +7 -1
- package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +4 -4
- package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +6 -6
- package/template/wall-e/skills/_templates/fleet-sync/VERSION +1 -0
- package/template/wall-e/skills/_templates/fleet-sync/released-sha256.json +16 -0
- package/template/wall-e/skills/_templates/fleet-sync/templates/claude/SKILL.md +64 -0
- package/template/wall-e/skills/_templates/fleet-sync/templates/codex/SKILL.md +64 -0
- package/template/wall-e/tools/chrome-automation.js +6 -0
- package/template/wall-e/training/setup-cuda-train-env.sh +2 -2
- package/template/wall-e/workers/brain-owner-worker.js +18 -4
- package/template/wall-e/workers/mcp-gateway-process.js +5 -5
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// WP-3a (docs/landing-queue-gate-once-merge-delta-2026-10-29.html §5 D-3/D-3c, §6.5, §8 WP-3a,
|
|
3
|
+
// §9 S-9, §10 T-4/T-6): merge-delta selection, in SHADOW mode.
|
|
4
|
+
//
|
|
5
|
+
// For a merge train T = commit-tree(tree, [B, P]) the CAV'14 two-sided rule (§3 P-1, Theorem 1) says
|
|
6
|
+
// a test that neither side of the merge changes keeps the result it already had on that side, so the
|
|
7
|
+
// safe Node test set is S = sel(B→T) ∩ sel(P→T). This module COMPUTES S and records it on the job's
|
|
8
|
+
// `gate.intersection` telemetry — nothing else. The gate keeps running today's selection
|
|
9
|
+
// (`--plan-base B`); WP-3b is the work package that would ENFORCE S, and it is gated on OQ-2, which
|
|
10
|
+
// this shadow record is the instrument for (§5 Open questions OQ-2).
|
|
11
|
+
//
|
|
12
|
+
// Every precondition of §5 D-3 is a FALLBACK here, never a failure: a missing proof for tree(B), a
|
|
13
|
+
// broken proof chain, a broad plan on either side, a planner that cannot run, or LAND_MERGE_DELTA=0
|
|
14
|
+
// all return `{ mode: 'fallback', reason, detail }`, and the train gates exactly as it did before
|
|
15
|
+
// this module existed. Nothing here can change a gate argv, a verdict or a landing outcome.
|
|
16
|
+
//
|
|
17
|
+
// `planner` is injectable and defaults to the real spawn (`node <trainDir>/bin/test-plan.js`), so
|
|
18
|
+
// the unit tests never start a child process (tests/ is a no-spawn tier: bin/tier-placement-lint.js
|
|
19
|
+
// rejects a spawning test in tests/). The default resolves the planner FROM THE TRAIN WORKTREE,
|
|
20
|
+
// which is what bin/test-gate.sh already does ($SCRIPT_ROOT/bin/test-plan.js, test-gate.sh:1282):
|
|
21
|
+
// the planner belongs to the repository whose tests are being planned, and the train worktree is a
|
|
22
|
+
// checked-out copy of exactly that repository.
|
|
23
|
+
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
const path = require('path');
|
|
26
|
+
const crypto = require('crypto');
|
|
27
|
+
const { spawn } = require('child_process');
|
|
28
|
+
|
|
29
|
+
const proof = require('./proof');
|
|
30
|
+
const queue = require('./queue');
|
|
31
|
+
const state = require('./state');
|
|
32
|
+
const { childEnv, revParse } = require('./git');
|
|
33
|
+
|
|
34
|
+
// The planner is a `test-plan.js` on a repo that is already loaded (it parses the whole impact graph
|
|
35
|
+
// in the worst case); 5 minutes is a safety net against a wedged child, not a budget.
|
|
36
|
+
const PLANNER_TIMEOUT_MS = 5 * 60 * 1000;
|
|
37
|
+
|
|
38
|
+
// bin/test-plan.js's disposition for "this file must run in this gate" (its `dispositions[]` rows).
|
|
39
|
+
// A broad plan marks every file `scheduled-broad-lane` instead, which is exactly why the caller
|
|
40
|
+
// refuses to intersect two plans that are not both narrow (§5 D-3(c)).
|
|
41
|
+
const RUN_DISPOSITION = 'run';
|
|
42
|
+
|
|
43
|
+
// The runners' own failure summary, verbatim (bin/test-runner-adapter.js:921-941):
|
|
44
|
+
//
|
|
45
|
+
// [test-suite] FAILED (2 file(s)):
|
|
46
|
+
// - tests/foo.test.js: <reasons joined by '; '>
|
|
47
|
+
// - tests/bar.test.js: killed after 300000ms (possible synchronous spin)
|
|
48
|
+
//
|
|
49
|
+
// `test-suite` is the CTM/`coding-task-manager` label and `run-hermetic` the root/wall-e one
|
|
50
|
+
// (test-runner-adapter.js:850), so the label is matched generically. The ` - <file>: …` rows are
|
|
51
|
+
// the run's failing test files, relative to the repository the runner ran in.
|
|
52
|
+
const FAILED_BLOCK_RE = /^\s*\[[^\]]+\] FAILED \(\d+ file\(s\)\):\s*$/;
|
|
53
|
+
const FAILED_ROW_RE = /^\s{2}-\s+(\S+?):\s*(.*)$/;
|
|
54
|
+
|
|
55
|
+
// The three reads §5 D-3(a)/(b) need, in one object so the unit tests never spawn git (tests/ is a
|
|
56
|
+
// no-spawn tier). Production is `realProofs`:
|
|
57
|
+
// treeOf — `git rev-parse <base>^{tree}`; THROWS on an unresolvable revision, which the
|
|
58
|
+
// caller records as `plan_error` rather than guessing at a tree.
|
|
59
|
+
// treeHasProof — §5 D-3(a): has tree(B) ever passed a gate under this gates hash?
|
|
60
|
+
// proofChain — §5 D-3c: is every commit of mb..P proved?
|
|
61
|
+
const realProofs = {
|
|
62
|
+
treeOf: (cwd, rev) => revParse(cwd, `${rev}^{tree}`),
|
|
63
|
+
treeHasProof: (root, query) => proof.treeHasProof(root, query),
|
|
64
|
+
proofChain: (cwd, root, query) => proof.proofChain(cwd, root, query),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// --- the planner (§6.5) -------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
function plannerArgs(trainDir, base) {
|
|
70
|
+
return [
|
|
71
|
+
path.join(trainDir, 'bin', 'test-plan.js'),
|
|
72
|
+
'--root', trainDir,
|
|
73
|
+
'--mode', 'snapshot',
|
|
74
|
+
'--base', base,
|
|
75
|
+
'--json',
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function firstLine(text) {
|
|
80
|
+
return String(text === undefined || text === null ? '' : text)
|
|
81
|
+
.split('\n').map((line) => line.trim()).filter(Boolean)[0] || null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// The default planner: one `node <trainDir>/bin/test-plan.js --mode snapshot --base <base> --json`
|
|
85
|
+
// child, resolved from the train worktree. It NEVER throws and never rejects — a failure is
|
|
86
|
+
// `{ ok: false, detail }`, which the caller turns into the `plan_error` fallback — and it reports its
|
|
87
|
+
// own wall time in milliseconds so two runs can be summed (`plannerMs`, AC-3c) without a second clock
|
|
88
|
+
// outside this module.
|
|
89
|
+
//
|
|
90
|
+
// Returns `{ ok: true, plan: <parsed JSON>, ms }` | `{ ok: false, detail: <first stderr line>, ms }`.
|
|
91
|
+
function runPlanner({ trainDir, base }) {
|
|
92
|
+
return new Promise((resolve) => {
|
|
93
|
+
const started = Date.now();
|
|
94
|
+
const done = (result) => resolve({ ...result, ms: Date.now() - started });
|
|
95
|
+
let child;
|
|
96
|
+
try {
|
|
97
|
+
child = spawn(process.execPath, plannerArgs(trainDir, base), {
|
|
98
|
+
cwd: trainDir,
|
|
99
|
+
env: childEnv(),
|
|
100
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
101
|
+
});
|
|
102
|
+
} catch (error) {
|
|
103
|
+
done({ ok: false, detail: error && error.message ? error.message : String(error) });
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
let stdout = '';
|
|
107
|
+
let stderr = '';
|
|
108
|
+
let settled = false;
|
|
109
|
+
const finish = (result) => {
|
|
110
|
+
if (settled) return;
|
|
111
|
+
settled = true;
|
|
112
|
+
clearTimeout(timer);
|
|
113
|
+
done(result);
|
|
114
|
+
};
|
|
115
|
+
const timer = setTimeout(() => {
|
|
116
|
+
try {
|
|
117
|
+
child.kill('SIGKILL');
|
|
118
|
+
} catch (error) {
|
|
119
|
+
/* expected: the planner exited between the deadline firing and this kill */
|
|
120
|
+
}
|
|
121
|
+
finish({ ok: false, detail: `test-plan.js did not finish within ${PLANNER_TIMEOUT_MS}ms` });
|
|
122
|
+
}, PLANNER_TIMEOUT_MS);
|
|
123
|
+
child.stdout.setEncoding('utf8');
|
|
124
|
+
child.stderr.setEncoding('utf8');
|
|
125
|
+
child.stdout.on('data', (chunk) => { stdout += chunk; });
|
|
126
|
+
child.stderr.on('data', (chunk) => { stderr += chunk; });
|
|
127
|
+
child.on('error', (error) => finish({ ok: false, detail: error && error.message ? error.message : String(error) }));
|
|
128
|
+
child.on('close', (status) => {
|
|
129
|
+
if (status !== 0) {
|
|
130
|
+
finish({ ok: false, detail: firstLine(stderr) || `test-plan.js exited ${status}` });
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
finish({ ok: true, plan: JSON.parse(stdout) });
|
|
135
|
+
} catch (error) {
|
|
136
|
+
finish({ ok: false, detail: `unparseable plan JSON: ${error && error.message ? error.message : String(error)}` });
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// sel(X→T) for one plan: the repo-relative files a narrow plan says must run. A plan that is not an
|
|
143
|
+
// object, or that carries no `dispositions[]`, yields the empty set — the caller has already refused
|
|
144
|
+
// a broad plan, so "no run rows" is a real (empty) selection, never a reason to fall back.
|
|
145
|
+
function runFiles(plan) {
|
|
146
|
+
const rows = plan && Array.isArray(plan.dispositions) ? plan.dispositions : [];
|
|
147
|
+
const files = new Set();
|
|
148
|
+
for (const row of rows) {
|
|
149
|
+
if (row && row.disposition === RUN_DISPOSITION && typeof row.file === 'string' && row.file) files.add(row.file);
|
|
150
|
+
}
|
|
151
|
+
return files;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// --- the selection (§5 D-3, §6.5) ----------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
// §5 D-3: S = sel(B→T) ∩ sel(P→T), computed in the train worktree T and recorded — never enforced.
|
|
157
|
+
//
|
|
158
|
+
// Returns `{ mode: 'merge-delta', files, sizes: { fromBase, fromTip, intersection }, plannerMs }`
|
|
159
|
+
// on success, or `{ mode: 'fallback', reason, detail }` for every §5 D-3 precondition miss:
|
|
160
|
+
//
|
|
161
|
+
// disabled — LAND_MERGE_DELTA === '0' (the D-3 kill switch; §6 "No env flag except the D-3
|
|
162
|
+
// kill-switch"). Checked first: the switch is a cheap short-circuit, and it must
|
|
163
|
+
// hold even when the proofs below are missing.
|
|
164
|
+
// no_base_proof — (a) no PASS proof exists for tree(B) under this gates hash, so the base side of
|
|
165
|
+
// the merge was never tested and Theorem 1's induction has no first step.
|
|
166
|
+
// chain_broken — (b) some commit on `rev-list --first-parent mb..P` has no proof for
|
|
167
|
+
// (tree(c), planBaseTree(c)) (§5 D-3c).
|
|
168
|
+
// broad_plan — (c) either plan is a fallback to the broad lane, whose "run" set is the whole
|
|
169
|
+
// inventory and whose intersection would be a full run wearing a narrow plan's name.
|
|
170
|
+
// plan_error — (c) the planner could not run or did not print JSON (detail = its first stderr
|
|
171
|
+
// line), or a git read behind (a)/(b) failed.
|
|
172
|
+
//
|
|
173
|
+
// `base`/`tip` are revisions (commit oids in the runner's call), `trainDir` is the train worktree,
|
|
174
|
+
// `root` the landing state directory. `planner` and `proofs` are injected by the unit tests;
|
|
175
|
+
// production uses `runPlanner` and `realProofs`.
|
|
176
|
+
async function mergeDeltaSelection({
|
|
177
|
+
trainDir, root, base, tip, gatesHash, env = process.env, planner = runPlanner, proofs = realProofs,
|
|
178
|
+
} = {}) {
|
|
179
|
+
if (env && env.LAND_MERGE_DELTA === '0') return { mode: 'fallback', reason: 'disabled', detail: null };
|
|
180
|
+
|
|
181
|
+
const baseTree = proofs.treeOf(trainDir, base);
|
|
182
|
+
if (!proofs.treeHasProof(root, { tree: baseTree, gatesHash })) {
|
|
183
|
+
return { mode: 'fallback', reason: 'no_base_proof', detail: `no PASS proof for tree ${baseTree} (base ${base})` };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const chain = proofs.proofChain(trainDir, root, { base, tip, gatesHash });
|
|
187
|
+
if (!chain.ok) {
|
|
188
|
+
return {
|
|
189
|
+
mode: 'fallback',
|
|
190
|
+
reason: 'chain_broken',
|
|
191
|
+
detail: `${chain.missing.length} commit(s) of ${chain.checked} unproven: ${chain.missing.join(', ')}`,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const plans = [];
|
|
196
|
+
let plannerMs = 0;
|
|
197
|
+
for (const side of [base, tip]) {
|
|
198
|
+
// The two passes are sequential on the ONE host this queue owns (§11 "Host contention remains"):
|
|
199
|
+
// they are cheap next to a gate, but there is no reason to add load to a machine whose median
|
|
200
|
+
// load is already 19.9 on 16 cores.
|
|
201
|
+
// eslint-disable-next-line no-await-in-loop
|
|
202
|
+
const result = await planner({ trainDir, root, base: side, env });
|
|
203
|
+
plannerMs += result && Number.isFinite(result.ms) ? result.ms : 0;
|
|
204
|
+
if (!result || !result.ok) {
|
|
205
|
+
return { mode: 'fallback', reason: 'plan_error', detail: (result && result.detail) || 'planner returned no result' };
|
|
206
|
+
}
|
|
207
|
+
plans.push(result.plan);
|
|
208
|
+
}
|
|
209
|
+
const broadSide = plans.findIndex((plan) => plan && plan.fallback === true);
|
|
210
|
+
if (broadSide !== -1) {
|
|
211
|
+
return {
|
|
212
|
+
mode: 'fallback',
|
|
213
|
+
reason: 'broad_plan',
|
|
214
|
+
detail: `plan(${broadSide === 0 ? 'B' : 'P'}) fell back to the broad lane`,
|
|
215
|
+
plannerMs,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const fromBase = runFiles(plans[0]);
|
|
220
|
+
const fromTip = runFiles(plans[1]);
|
|
221
|
+
const files = [...fromBase].filter((file) => fromTip.has(file)).sort();
|
|
222
|
+
return {
|
|
223
|
+
mode: 'merge-delta',
|
|
224
|
+
files,
|
|
225
|
+
sizes: { fromBase: fromBase.size, fromTip: fromTip.size, intersection: files.length },
|
|
226
|
+
plannerMs,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// --- the shadow record (§6.6, §8 WP-3a) ----------------------------------------------------------
|
|
231
|
+
|
|
232
|
+
// The gate telemetry shape `gateSelectionTelemetry` builds (bin/land.js), with every field
|
|
233
|
+
// "unknown". A shadow record only exists AFTER a gate ran, so a gate that wrote no `--selection-out`
|
|
234
|
+
// (a repository whose `gates[].run` is a plain script, or this repository's own queue fixtures) still
|
|
235
|
+
// gets a telemetry object — one that says "the gate ran and said nothing", which is the truth.
|
|
236
|
+
const UNKNOWN_TELEMETRY = Object.freeze({
|
|
237
|
+
mode: null, planBase: null, selected: null, executed: null, cached: null,
|
|
238
|
+
missReasons: null, fallbackReason: null,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// `gate.intersection` is written on the 3.9 kB job record, so it carries the SHAPE of the measurement
|
|
242
|
+
// and not the measurement: |S| can be hundreds of files, and every job file and every metrics row
|
|
243
|
+
// would pay for the list forever. The full S goes to `<root>/logs/<jobId>.merge-delta.json` and the
|
|
244
|
+
// job keeps `sHash = sha256(S.join('\n'))` — enough to compare two jobs' selections, or to re-read the
|
|
245
|
+
// list from the log directory.
|
|
246
|
+
function sha256(text) {
|
|
247
|
+
return crypto.createHash('sha256').update(text, 'utf8').digest('hex');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function readLogText(file) {
|
|
251
|
+
if (!file) return null;
|
|
252
|
+
try {
|
|
253
|
+
return fs.readFileSync(file, 'utf8');
|
|
254
|
+
} catch (error) {
|
|
255
|
+
/* expected: the log is written by the runner; if it is gone there is simply no evidence to parse */
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// The Node test files a runner reported as FAIL, or null when the log carries no runner failure block
|
|
261
|
+
// at all. NULL IS NOT "NONE": an empty array means "a block was found and every failing file is in
|
|
262
|
+
// S", which is the OQ-2 answer this WP exists to collect; null means "the log cannot say" (the gate
|
|
263
|
+
// failed in a lint phase, or the file was pruned).
|
|
264
|
+
function failingFiles(logText) {
|
|
265
|
+
// One parser for the runner's failure block: train.failedFilesFromLog (landing-throughput WP-2)
|
|
266
|
+
// reads the same `[<label>] FAILED (N file(s)):` block and ` - <file>: …` rows; `raw` is the
|
|
267
|
+
// files exactly as printed (S is compared in the log's own terms), `sawBlock` the null/[] split.
|
|
268
|
+
const parsed = require('./train').failedFilesFromLog(logText);
|
|
269
|
+
return parsed.sawBlock ? [...parsed.raw].sort() : null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// §8 WP-3a: after a FAIL, the failing Node test files that are NOT in S. `[]` is a real, encouraging
|
|
273
|
+
// answer (every file the gate failed is one S said to run); null means the log could not be read.
|
|
274
|
+
function outsideFailing(failing, files) {
|
|
275
|
+
if (!failing) return null;
|
|
276
|
+
const selected = new Set(files || []);
|
|
277
|
+
return failing.filter((file) => !selected.has(file)).sort();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Attach the shadow record to this run's `gate` telemetry (§6.6). Returns the telemetry object the
|
|
281
|
+
// job record and the terminal metrics row carry. `shadow` is the value `runGate`'s `onTrainReady`
|
|
282
|
+
// returned; `telemetry` is `gateSelectionTelemetry(result.selection)` (possibly null).
|
|
283
|
+
function recordIntersection(telemetry, shadow, { root, jobId, trainId = null, verdict, logFile } = {}) {
|
|
284
|
+
const files = shadow && shadow.mode === 'merge-delta' && Array.isArray(shadow.files) ? shadow.files : null;
|
|
285
|
+
if (files) {
|
|
286
|
+
queue.ensureLogsDir(root);
|
|
287
|
+
// WP-1 §9 S-4: keyed by the TRAIN id (the batch id for a batch, the job id for a singleton), so a
|
|
288
|
+
// batch has one shadow record the whole train shares.
|
|
289
|
+
state.writeJsonAtomic(path.join(root, 'logs', `${trainId || jobId}.merge-delta.json`), {
|
|
290
|
+
contract: state.CONTRACT,
|
|
291
|
+
jobId,
|
|
292
|
+
trainId,
|
|
293
|
+
mode: shadow.mode,
|
|
294
|
+
sizes: shadow.sizes || null,
|
|
295
|
+
plannerMs: Number.isFinite(shadow.plannerMs) ? shadow.plannerMs : null,
|
|
296
|
+
files,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
const intersection = {
|
|
300
|
+
shadow: true,
|
|
301
|
+
mode: shadow && shadow.mode ? shadow.mode : null,
|
|
302
|
+
reason: shadow && shadow.reason ? shadow.reason : null,
|
|
303
|
+
sizes: shadow && shadow.sizes ? shadow.sizes : null,
|
|
304
|
+
plannerMs: shadow && Number.isFinite(shadow.plannerMs) ? shadow.plannerMs : null,
|
|
305
|
+
sHash: files ? sha256(files.join('\n')) : null,
|
|
306
|
+
// Only a FAIL can have a failing file: a PASS ran S inside a green gate, and a stopped gate ran no
|
|
307
|
+
// verdict at all.
|
|
308
|
+
outsideFailing: verdict === 'FAIL' ? outsideFailing(failingFiles(readLogText(logFile)), files) : null,
|
|
309
|
+
};
|
|
310
|
+
return { ...(telemetry || UNKNOWN_TELEMETRY), intersection };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// --- the report (§8 AC-3b, §5 OQ-2) --------------------------------------------------------------
|
|
314
|
+
|
|
315
|
+
// Every job whose `gate.intersection` this module wrote — i.e. every merge train gated since WP-3a.
|
|
316
|
+
function shadowRows(root) {
|
|
317
|
+
return queue.listJobs(root)
|
|
318
|
+
.map((job) => job && job.gate && job.gate.intersection)
|
|
319
|
+
.filter((row) => row && typeof row === 'object');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// AC-3b/the OQ-2 data: n trains, n per mode (and per fallback reason), the median |S| as a percentage
|
|
323
|
+
// of |sel(B→T)|, the median planner time, and the count of FAILED trains with a failing file OUTSIDE
|
|
324
|
+
// S. The last number is the one that decides WP-3b: §5 OQ-2 enforces only when no failing test outside
|
|
325
|
+
// S was found across the sample AND the median |S|/|sel(B→T)| is ≤ 0.5.
|
|
326
|
+
function mergeDeltaReport(root) {
|
|
327
|
+
const rows = shadowRows(root);
|
|
328
|
+
const modes = {};
|
|
329
|
+
const ratios = [];
|
|
330
|
+
const plannerMs = [];
|
|
331
|
+
let outsideFailingTrains = 0;
|
|
332
|
+
for (const row of rows) {
|
|
333
|
+
const key = row.mode === 'merge-delta' ? 'merge-delta' : `fallback:${row.reason || 'unknown'}`;
|
|
334
|
+
modes[key] = (modes[key] || 0) + 1;
|
|
335
|
+
if (row.sizes && Number.isFinite(row.sizes.fromBase) && row.sizes.fromBase > 0 && Number.isFinite(row.sizes.intersection)) {
|
|
336
|
+
ratios.push((row.sizes.intersection / row.sizes.fromBase) * 100);
|
|
337
|
+
}
|
|
338
|
+
if (Number.isFinite(row.plannerMs)) plannerMs.push(row.plannerMs);
|
|
339
|
+
if (Array.isArray(row.outsideFailing) && row.outsideFailing.length > 0) outsideFailingTrains += 1;
|
|
340
|
+
}
|
|
341
|
+
const percent = queue.median(ratios);
|
|
342
|
+
return {
|
|
343
|
+
shadow: true,
|
|
344
|
+
trains: rows.length,
|
|
345
|
+
modes,
|
|
346
|
+
// §5 OQ-2's decision inputs, spelled the same way the decision rule reads them.
|
|
347
|
+
medianPercentOfBase: percent === null ? null : Number(percent.toFixed(1)),
|
|
348
|
+
medianPlannerMs: queue.median(plannerMs),
|
|
349
|
+
outsideFailingTrains,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function reportLines(report) {
|
|
354
|
+
const modes = Object.entries(report.modes).sort(([a], [b]) => a.localeCompare(b));
|
|
355
|
+
const percent = report.medianPercentOfBase === null ? '?' : `${report.medianPercentOfBase}%`;
|
|
356
|
+
const planner = report.medianPlannerMs === null ? '?' : `${report.medianPlannerMs}ms`;
|
|
357
|
+
return [
|
|
358
|
+
`merge-delta shadow: ${report.trains} train(s)`,
|
|
359
|
+
...(modes.length ? modes.map(([mode, count]) => ` ${mode}: ${count}`) : [' (no merge train has been gated with shadow telemetry yet)']),
|
|
360
|
+
`median |S| / |sel(B→T)|: ${percent}`,
|
|
361
|
+
`median plannerMs: ${planner}`,
|
|
362
|
+
`failed trains with a failing file outside S: ${report.outsideFailingTrains}`,
|
|
363
|
+
];
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
module.exports = {
|
|
367
|
+
FAILED_BLOCK_RE,
|
|
368
|
+
FAILED_ROW_RE,
|
|
369
|
+
realProofs,
|
|
370
|
+
plannerArgs,
|
|
371
|
+
runPlanner,
|
|
372
|
+
runFiles,
|
|
373
|
+
mergeDeltaSelection,
|
|
374
|
+
recordIntersection,
|
|
375
|
+
failingFiles,
|
|
376
|
+
outsideFailing,
|
|
377
|
+
shadowRows,
|
|
378
|
+
mergeDeltaReport,
|
|
379
|
+
reportLines,
|
|
380
|
+
};
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// WP-2 (docs/landing-queue-gate-once-merge-delta-2026-10-29.html §5 D-1/D-3c/D-4, §6.3, §6.4,
|
|
3
|
+
// §8 WP-2, §9 S-5..S-8): the proof store.
|
|
4
|
+
//
|
|
5
|
+
// D-1: a gate PASS is a PROOF, and it is keyed by the PAIR (tree, planBaseTree) plus the gates
|
|
6
|
+
// hash — not by the tree alone. The commit's own gate proves the delta against its parent (`bin/
|
|
7
|
+
// pre-commit-hook.sh` passes that parent as `--plan-base`), so a proof the author's commit gate
|
|
8
|
+
// produced is exactly as good as one the landing runner produced, and the queue can land the tip
|
|
9
|
+
// without re-running the identical gate (S-5: today the hook's PASS is checked and thrown away).
|
|
10
|
+
//
|
|
11
|
+
// The legacy tree-keyed store `verdicts/<tree>.json` (lib/landing/gate.js) stays readable and is
|
|
12
|
+
// still written by the runner; this file is the second, narrower key and never writes that one.
|
|
13
|
+
//
|
|
14
|
+
// Only PASS is ever written (§5 D-1: "INCONCLUSIVE, FAIL and a PASS that contains a FLAKY line are
|
|
15
|
+
// never recorded as proofs"), and a proof is refused outright when the diff against its plan base
|
|
16
|
+
// touches a dependency manifest: the train gate symlinks the MAIN checkout's node_modules in
|
|
17
|
+
// (gate.js `linkCachePaths`), so a tree whose dependencies differ from the author's was tested
|
|
18
|
+
// against something the author never installed.
|
|
19
|
+
//
|
|
20
|
+
// Keyed entries live at `verdicts/<tree>.<planBaseTree>.json` under the landing state root — the
|
|
21
|
+
// same `<common-dir>/landing` directory `lib/landing/state.js` owns. Nothing here throws for a
|
|
22
|
+
// state file that is merely absent or torn: a missing proof is a cache miss, never a failure.
|
|
23
|
+
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
const path = require('path');
|
|
26
|
+
|
|
27
|
+
const state = require('./state');
|
|
28
|
+
const { landingError, runGitRaw, isAncestor } = require('./git');
|
|
29
|
+
|
|
30
|
+
// Git object names, both 40-hex (sha1) and 64-hex (sha256) repositories. The name is used as a
|
|
31
|
+
// path component, so it is validated rather than interpolated blindly.
|
|
32
|
+
const OID_RE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
|
|
33
|
+
|
|
34
|
+
// The one terminal line `bin/test-gate.sh` prints on a green run (bin/lib-test-gate-summary.sh
|
|
35
|
+
// `print_gate_verdict`); the pre-commit hook greps it with `grep -Fqx`, so it is matched as a whole
|
|
36
|
+
// line here too.
|
|
37
|
+
const PASS_LINE = '[test-gate] VERDICT: PASS';
|
|
38
|
+
|
|
39
|
+
// The FLAKY evidence lines the runners actually print, verbatim, case-sensitively:
|
|
40
|
+
// bin/test-runner-adapter.js:869 `[test-suite] FLAKY (passed on isolated rerun): …`
|
|
41
|
+
// `[run-hermetic] FLAKY (passed on isolated rerun): …`
|
|
42
|
+
// bin/test-browser-selection-rerun.js:260 `[browser-selection] FLAKY (rerun passed alone): …`
|
|
43
|
+
// bin/test-browser-selection.js:793 `[browser-selection] VERDICT: PASS (with N FLAKY file(s), …)`
|
|
44
|
+
// A bare `FLAKY` substring would also fire on a TAP line that merely NAMES a flaky-related file
|
|
45
|
+
// (`flaky-report`, `flaky-ledger`), and a rerun-pass is a real flake — the proof must not claim it.
|
|
46
|
+
const FLAKY_RE = /FLAKY \(passed on isolated rerun\)|FLAKY \(rerun passed alone\)|\d+ FLAKY file\(s\)/;
|
|
47
|
+
|
|
48
|
+
// §5 D-1: "Dependency installs differ between the author's worktree and the train's symlinked
|
|
49
|
+
// node_modules". Manifest and lock files, at the repository root or in a package subdirectory.
|
|
50
|
+
const DEPENDENCY_RE = /(^|\/)(?:package(?:-lock)?|npm-shrinkwrap)\.json$/;
|
|
51
|
+
|
|
52
|
+
function isOid(value) {
|
|
53
|
+
return typeof value === 'string' && OID_RE.test(value);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// §6.3: the proof file for the exact pair. Both names must be object ids — they are path
|
|
57
|
+
// components, so a caller that hands over a revision like `main` or `../../etc/passwd` is a bug,
|
|
58
|
+
// never a path traversal.
|
|
59
|
+
function proofFile(root, tree, planBaseTree) {
|
|
60
|
+
if (!isOid(tree) || !isOid(planBaseTree)) {
|
|
61
|
+
throw landingError('proof_invalid', `a proof is keyed by two object ids, got tree=${JSON.stringify(tree)} planBaseTree=${JSON.stringify(planBaseTree)}`, { tree, planBaseTree, root });
|
|
62
|
+
}
|
|
63
|
+
return path.join(root, 'verdicts', `${tree}.${planBaseTree}.json`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// The recorded proof for exactly this (tree, planBaseTree, gatesHash), or null. Null covers every
|
|
67
|
+
// "not a proof" case on purpose: absent file, unreadable/torn JSON, a different tree or base, a
|
|
68
|
+
// different gates hash (§6.2: changing `gates` invalidates every proof), and a non-PASS record.
|
|
69
|
+
function readProof(root, { tree, planBaseTree, gatesHash } = {}) {
|
|
70
|
+
if (!isOid(tree) || !isOid(planBaseTree) || typeof gatesHash !== 'string' || !gatesHash) return null;
|
|
71
|
+
const raw = state.readJsonSafe(proofFile(root, tree, planBaseTree));
|
|
72
|
+
if (!raw || raw.tree !== tree || raw.planBaseTree !== planBaseTree) return null;
|
|
73
|
+
if (raw.gatesHash !== gatesHash) return null;
|
|
74
|
+
if (raw.verdict !== 'PASS') return null;
|
|
75
|
+
return raw;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// §6.3 entry: { contract, tree, planBaseTree, gatesHash, verdict: 'PASS', source: 'commit'|'train',
|
|
79
|
+
// commit, seconds, at, logPath, selection }. Written tmp+rename (state.writeJsonAtomic), so a
|
|
80
|
+
// reader never sees a half-written proof. Only PASS is a proof; anything else throws.
|
|
81
|
+
function recordProof(root, entry) {
|
|
82
|
+
const record = entry || {};
|
|
83
|
+
if (record.verdict !== 'PASS') {
|
|
84
|
+
throw landingError('proof_not_pass', `refusing to record a ${JSON.stringify(record.verdict === undefined ? null : record.verdict)} proof (only PASS is a proof)`, {
|
|
85
|
+
tree: record.tree === undefined ? null : record.tree,
|
|
86
|
+
planBaseTree: record.planBaseTree === undefined ? null : record.planBaseTree,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
const file = proofFile(root, record.tree, record.planBaseTree);
|
|
90
|
+
state.writeJsonAtomic(file, { contract: state.CONTRACT, ...record });
|
|
91
|
+
return file;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Every proof file name in the store, sorted (readdir order is filesystem-dependent, so a reader
|
|
95
|
+
// that picks the first match must sort first to be deterministic). A store that does not exist yet is
|
|
96
|
+
// an empty store: `verdicts/` is created lazily by the first write.
|
|
97
|
+
function proofNames(root) {
|
|
98
|
+
try {
|
|
99
|
+
return fs.readdirSync(path.join(root, 'verdicts')).sort();
|
|
100
|
+
} catch (error) {
|
|
101
|
+
/* expected: nothing has ever been proved in this repository — an empty store, not an error */
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// §5 D-3(a), §6.3: is there ANY proof for this tree, against any plan base? The base side of a merge
|
|
107
|
+
// (tree(B)) is not proved against `P` — it is proved against whatever its own commit gate used — so
|
|
108
|
+
// the D-3(a) question is "has this tree ever passed a gate under these gates", not "is this exact
|
|
109
|
+
// pair recorded". Both layouts count: the legacy tree-keyed `verdicts/<tree>.json` (§6.3 "stays
|
|
110
|
+
// readable, never written") and the pair-keyed `verdicts/<tree>.<planBaseTree>.json`. The gates hash
|
|
111
|
+
// must still match (§6.2: changing `gates` invalidates every proof) and only PASS is a proof (D-1).
|
|
112
|
+
function treeHasProof(root, { tree, gatesHash } = {}) {
|
|
113
|
+
if (!isOid(tree) || typeof gatesHash !== 'string' || !gatesHash) return null;
|
|
114
|
+
for (const name of proofNames(root)) {
|
|
115
|
+
if (name !== `${tree}.json` && !name.startsWith(`${tree}.`)) continue;
|
|
116
|
+
const raw = state.readJsonSafe(path.join(root, 'verdicts', name));
|
|
117
|
+
if (!raw || raw.tree !== tree) continue;
|
|
118
|
+
if (raw.gatesHash !== gatesHash) continue;
|
|
119
|
+
if (raw.verdict !== 'PASS') continue;
|
|
120
|
+
return raw;
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// --- the chain (§5 D-3c, §5 D-4) -----------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
// `git rev-list --first-parent <base>..<tip>` — the commits a fast-forward of `base` to `tip`
|
|
128
|
+
// would add, oldest-history-last (order is irrelevant here: each commit is decided on its own).
|
|
129
|
+
function firstParentRange(cwd, base, tip) {
|
|
130
|
+
const args = ['rev-list', '--first-parent', `${base}..${tip}`];
|
|
131
|
+
const res = runGitRaw(cwd, args);
|
|
132
|
+
if (res.status !== 0) {
|
|
133
|
+
throw landingError('git_failed', `git ${args.join(' ')} exited ${res.status}: ${String(res.stderr || '').trim()}`, { cwd, args, status: res.status });
|
|
134
|
+
}
|
|
135
|
+
return res.stdout.split('\n').map((line) => line.trim()).filter(Boolean);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function treeOf(cwd, commit) {
|
|
139
|
+
const res = runGitRaw(cwd, ['rev-parse', '--verify', '--quiet', `${commit}^{tree}`]);
|
|
140
|
+
if (res.status !== 0) return null;
|
|
141
|
+
const value = res.stdout.trim();
|
|
142
|
+
return isOid(value) ? value : null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// The parent oids of ONE commit, first parent first (`git rev-list --parents -n 1 <c>` prints
|
|
146
|
+
// `<c> <p1> <p2> …`).
|
|
147
|
+
function parentsOf(cwd, commit) {
|
|
148
|
+
const args = ['rev-list', '--parents', '-n', '1', commit];
|
|
149
|
+
const res = runGitRaw(cwd, args);
|
|
150
|
+
if (res.status !== 0) {
|
|
151
|
+
throw landingError('git_failed', `git ${args.join(' ')} exited ${res.status}: ${String(res.stderr || '').trim()}`, { cwd, args, status: res.status });
|
|
152
|
+
}
|
|
153
|
+
const tokens = res.stdout.split('\n')[0].trim().split(/\s+/).filter(Boolean);
|
|
154
|
+
return tokens.slice(1);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// One commit's link in the chain: it holds iff some parent `p` has a proof for (tree(c), tree(p)).
|
|
158
|
+
// `p` qualifies when it is c's FIRST parent — which, walking first-parents, is either the next
|
|
159
|
+
// commit on the chain or (for the oldest one) an ancestor of `base` — or when it is an ancestor of
|
|
160
|
+
// `base` itself. That second case is what makes an author's merge-of-main commit provable: its
|
|
161
|
+
// second parent IS the target, so the proof the hook recorded for it is accepted (D-2).
|
|
162
|
+
function commitProven(cwd, root, commit, base, gatesHash) {
|
|
163
|
+
const tree = treeOf(cwd, commit);
|
|
164
|
+
if (!tree) return false;
|
|
165
|
+
const parents = parentsOf(cwd, commit);
|
|
166
|
+
for (let index = 0; index < parents.length; index += 1) {
|
|
167
|
+
const parent = parents[index];
|
|
168
|
+
if (index > 0 && !isAncestor(cwd, parent, base)) continue;
|
|
169
|
+
if (readProof(root, { tree, planBaseTree: treeOf(cwd, parent), gatesHash })) return true;
|
|
170
|
+
}
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// §5 D-4, generalized to the per-commit induction §5 D-3c describes. `base` is the target commit
|
|
175
|
+
// the fast-forward starts from, `tip` the commit being landed. The chain holds iff EVERY commit
|
|
176
|
+
// `base..tip` (first-parent order) has a proof against a qualifying parent; `missing` lists the
|
|
177
|
+
// commits that do not. An empty range is trivially proven — nothing is being added.
|
|
178
|
+
//
|
|
179
|
+
// `checked` is the number of commits walked (0 for an empty range): the D-4 one-commit case and the
|
|
180
|
+
// runner's own progress line both need it, and recomputing the range outside would be a second git
|
|
181
|
+
// call for a number this walk already knows.
|
|
182
|
+
function proofChain(cwd, root, { base, tip, gatesHash } = {}) {
|
|
183
|
+
if (!base || !tip) return { ok: false, missing: [], checked: 0 };
|
|
184
|
+
const commits = firstParentRange(cwd, base, tip);
|
|
185
|
+
const missing = commits.filter((commit) => !commitProven(cwd, root, commit, base, gatesHash));
|
|
186
|
+
return { ok: missing.length === 0, missing, checked: commits.length };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// --- the refusal (§5 D-1, §6.4) ------------------------------------------------------------------
|
|
190
|
+
|
|
191
|
+
// Why a gate log is NOT a proof, in the order that decides it. `null` means "recordable".
|
|
192
|
+
// 'no_pass' — no line exactly `[test-gate] VERDICT: PASS`
|
|
193
|
+
// 'flaky' — the log carries a runner's FLAKY evidence line (FLAKY_RE)
|
|
194
|
+
// 'dependency_change' — `git diff --name-only <planBase> <tree>` touches a dependency manifest
|
|
195
|
+
// The dependency check is skipped when either side is unreadable: a caller that cannot name both
|
|
196
|
+
// sides has nothing to diff, and the ledger check must never be the thing that fabricates a refusal.
|
|
197
|
+
function proofRefusal(cwd, { planBase, tree, logText } = {}) {
|
|
198
|
+
const text = String(logText === undefined || logText === null ? '' : logText);
|
|
199
|
+
const green = text.split('\n').map((line) => line.replace(/\r$/, '')).some((line) => line === PASS_LINE);
|
|
200
|
+
if (!green) return 'no_pass';
|
|
201
|
+
if (FLAKY_RE.test(text)) return 'flaky';
|
|
202
|
+
if (!planBase || !tree) return null;
|
|
203
|
+
const args = ['diff', '--name-only', planBase, tree];
|
|
204
|
+
const res = runGitRaw(cwd, args);
|
|
205
|
+
if (res.status !== 0) {
|
|
206
|
+
throw landingError('git_failed', `git ${args.join(' ')} exited ${res.status}: ${String(res.stderr || '').trim()}`, { cwd, args, status: res.status });
|
|
207
|
+
}
|
|
208
|
+
const touched = res.stdout.split('\n').map((line) => line.trim()).filter(Boolean);
|
|
209
|
+
if (touched.some((file) => DEPENDENCY_RE.test(file))) return 'dependency_change';
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
module.exports = {
|
|
214
|
+
PASS_LINE,
|
|
215
|
+
FLAKY_RE,
|
|
216
|
+
DEPENDENCY_RE,
|
|
217
|
+
proofFile,
|
|
218
|
+
readProof,
|
|
219
|
+
recordProof,
|
|
220
|
+
treeHasProof,
|
|
221
|
+
proofChain,
|
|
222
|
+
proofRefusal,
|
|
223
|
+
};
|