wicked-crew 0.7.40 → 0.7.45
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/dist/api/audit.d.ts +3 -0
- package/dist/api/audit.d.ts.map +1 -1
- package/dist/api/audit.js +2 -0
- package/dist/api/audit.js.map +1 -1
- package/dist/api/capture.d.ts +149 -0
- package/dist/api/capture.d.ts.map +1 -0
- package/dist/api/capture.js +330 -0
- package/dist/api/capture.js.map +1 -0
- package/dist/api/chat-citations.d.ts +174 -0
- package/dist/api/chat-citations.d.ts.map +1 -0
- package/dist/api/chat-citations.js +528 -0
- package/dist/api/chat-citations.js.map +1 -0
- package/dist/api/chat-transcripts.d.ts +20 -1
- package/dist/api/chat-transcripts.d.ts.map +1 -1
- package/dist/api/chat-transcripts.js +29 -0
- package/dist/api/chat-transcripts.js.map +1 -1
- package/dist/api/delivery-cache.d.ts +2 -2
- package/dist/api/delivery-freeze.d.ts +66 -0
- package/dist/api/delivery-freeze.d.ts.map +1 -0
- package/dist/api/delivery-freeze.js +104 -0
- package/dist/api/delivery-freeze.js.map +1 -0
- package/dist/api/delivery-index.d.ts +53 -9
- package/dist/api/delivery-index.d.ts.map +1 -1
- package/dist/api/delivery-index.js +115 -22
- package/dist/api/delivery-index.js.map +1 -1
- package/dist/api/demo.d.ts +90 -0
- package/dist/api/demo.d.ts.map +1 -0
- package/dist/api/demo.js +492 -0
- package/dist/api/demo.js.map +1 -0
- package/dist/api/diagnostics.d.ts +64 -1
- package/dist/api/diagnostics.d.ts.map +1 -1
- package/dist/api/diagnostics.js +129 -1
- package/dist/api/diagnostics.js.map +1 -1
- package/dist/api/eval-sample.d.ts +6 -6
- package/dist/api/governance-steering.d.ts +2 -2
- package/dist/api/mcp.d.ts +282 -0
- package/dist/api/mcp.d.ts.map +1 -0
- package/dist/api/mcp.js +521 -0
- package/dist/api/mcp.js.map +1 -0
- package/dist/api/plan-schema.d.ts +56 -0
- package/dist/api/plan-schema.d.ts.map +1 -0
- package/dist/api/plan-schema.js +26 -0
- package/dist/api/plan-schema.js.map +1 -0
- package/dist/api/roster-standing.d.ts +15 -2
- package/dist/api/roster-standing.d.ts.map +1 -1
- package/dist/api/roster-standing.js +55 -3
- package/dist/api/roster-standing.js.map +1 -1
- package/dist/api/routes.d.ts +630 -51
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +738 -127
- package/dist/api/routes.js.map +1 -1
- package/dist/api/run-files.d.ts.map +1 -1
- package/dist/api/run-files.js +31 -2
- package/dist/api/run-files.js.map +1 -1
- package/dist/api/run-liveness.d.ts +41 -0
- package/dist/api/run-liveness.d.ts.map +1 -0
- package/dist/api/run-liveness.js +166 -0
- package/dist/api/run-liveness.js.map +1 -0
- package/dist/api/seat-health.d.ts.map +1 -1
- package/dist/api/seat-health.js +20 -4
- package/dist/api/seat-health.js.map +1 -1
- package/dist/api/seat-probe.d.ts +141 -0
- package/dist/api/seat-probe.d.ts.map +1 -0
- package/dist/api/seat-probe.js +257 -0
- package/dist/api/seat-probe.js.map +1 -0
- package/dist/api/seat-record.d.ts +40 -0
- package/dist/api/seat-record.d.ts.map +1 -0
- package/dist/api/seat-record.js +218 -0
- package/dist/api/seat-record.js.map +1 -0
- package/dist/api/seat-signin.d.ts.map +1 -1
- package/dist/api/seat-signin.js +3 -1
- package/dist/api/seat-signin.js.map +1 -1
- package/dist/api/seat-standing.d.ts +24 -4
- package/dist/api/seat-standing.d.ts.map +1 -1
- package/dist/api/seat-standing.js +50 -3
- package/dist/api/seat-standing.js.map +1 -1
- package/dist/api/server.d.ts +34 -48
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +283 -107
- package/dist/api/server.js.map +1 -1
- package/dist/api/skills.d.ts +3 -1
- package/dist/api/skills.d.ts.map +1 -1
- package/dist/api/skills.js +7 -2
- package/dist/api/skills.js.map +1 -1
- package/dist/api/stall-watchdog.d.ts +34 -3
- package/dist/api/stall-watchdog.d.ts.map +1 -1
- package/dist/api/stall-watchdog.js +107 -8
- package/dist/api/stall-watchdog.js.map +1 -1
- package/dist/api/steering-landing.d.ts +6 -6
- package/dist/api/steering-landing.d.ts.map +1 -1
- package/dist/api/steering-landing.js +7 -9
- package/dist/api/steering-landing.js.map +1 -1
- package/dist/api/testing.d.ts +6 -4
- package/dist/api/testing.d.ts.map +1 -1
- package/dist/api/testing.js +82 -47
- package/dist/api/testing.js.map +1 -1
- package/dist/campaign/boot-resume.d.ts +34 -0
- package/dist/campaign/boot-resume.d.ts.map +1 -0
- package/dist/campaign/boot-resume.js +45 -0
- package/dist/campaign/boot-resume.js.map +1 -0
- package/dist/campaigns/rollup.d.ts +4 -1
- package/dist/campaigns/rollup.d.ts.map +1 -1
- package/dist/campaigns/rollup.js +6 -1
- package/dist/campaigns/rollup.js.map +1 -1
- package/dist/campaigns/routes.d.ts +2 -2
- package/dist/cli/bearer.d.ts +27 -1
- package/dist/cli/bearer.d.ts.map +1 -1
- package/dist/cli/bearer.js +62 -2
- package/dist/cli/bearer.js.map +1 -1
- package/dist/cli/governance.d.ts +11 -0
- package/dist/cli/governance.d.ts.map +1 -1
- package/dist/cli/governance.js +8 -2
- package/dist/cli/governance.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +140 -75
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp.d.ts +11 -0
- package/dist/cli/mcp.d.ts.map +1 -1
- package/dist/cli/mcp.js +40 -9
- package/dist/cli/mcp.js.map +1 -1
- package/dist/cli/service.d.ts +78 -0
- package/dist/cli/service.d.ts.map +1 -0
- package/dist/cli/service.js +334 -0
- package/dist/cli/service.js.map +1 -0
- package/dist/core/adapter.d.ts +238 -12
- package/dist/core/adapter.d.ts.map +1 -1
- package/dist/core/adapter.js +619 -157
- package/dist/core/adapter.js.map +1 -1
- package/dist/core/bridge-reaper.d.ts +4 -1
- package/dist/core/bridge-reaper.d.ts.map +1 -1
- package/dist/core/bridge-reaper.js +15 -1
- package/dist/core/bridge-reaper.js.map +1 -1
- package/dist/core/bus-notice.d.ts +27 -0
- package/dist/core/bus-notice.d.ts.map +1 -0
- package/dist/core/bus-notice.js +24 -0
- package/dist/core/bus-notice.js.map +1 -0
- package/dist/core/bus.d.ts +99 -0
- package/dist/core/bus.d.ts.map +1 -0
- package/dist/core/bus.js +176 -0
- package/dist/core/bus.js.map +1 -0
- package/dist/core/deliver-exclusions.d.ts +71 -0
- package/dist/core/deliver-exclusions.d.ts.map +1 -0
- package/dist/core/deliver-exclusions.js +110 -0
- package/dist/core/deliver-exclusions.js.map +1 -0
- package/dist/core/deliver-text.d.ts +61 -14
- package/dist/core/deliver-text.d.ts.map +1 -1
- package/dist/core/deliver-text.js +178 -30
- package/dist/core/deliver-text.js.map +1 -1
- package/dist/core/deliver-triage.d.ts +11 -2
- package/dist/core/deliver-triage.d.ts.map +1 -1
- package/dist/core/deliver-triage.js +19 -1
- package/dist/core/deliver-triage.js.map +1 -1
- package/dist/core/deliver.d.ts +161 -3
- package/dist/core/deliver.d.ts.map +1 -1
- package/dist/core/deliver.js +463 -25
- package/dist/core/deliver.js.map +1 -1
- package/dist/core/deliverable-floor.d.ts.map +1 -1
- package/dist/core/deliverable-floor.js +5 -7
- package/dist/core/deliverable-floor.js.map +1 -1
- package/dist/core/engine-bus.d.ts +40 -0
- package/dist/core/engine-bus.d.ts.map +1 -0
- package/dist/core/engine-bus.js +27 -0
- package/dist/core/engine-bus.js.map +1 -0
- package/dist/core/estate-mcp-client.d.ts +87 -5
- package/dist/core/estate-mcp-client.d.ts.map +1 -1
- package/dist/core/estate-mcp-client.js +90 -11
- package/dist/core/estate-mcp-client.js.map +1 -1
- package/dist/core/linked-issues.d.ts +46 -0
- package/dist/core/linked-issues.d.ts.map +1 -0
- package/dist/core/linked-issues.js +123 -0
- package/dist/core/linked-issues.js.map +1 -0
- package/dist/core/run-identity.d.ts +100 -0
- package/dist/core/run-identity.d.ts.map +1 -0
- package/dist/core/run-identity.js +178 -0
- package/dist/core/run-identity.js.map +1 -0
- package/dist/core/shutdown.d.ts +16 -0
- package/dist/core/shutdown.d.ts.map +1 -0
- package/dist/core/shutdown.js +39 -0
- package/dist/core/shutdown.js.map +1 -0
- package/dist/core/types.d.ts +15 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +4 -0
- package/dist/core/types.js.map +1 -1
- package/dist/interactive/bridge-pool.d.ts +67 -3
- package/dist/interactive/bridge-pool.d.ts.map +1 -1
- package/dist/interactive/bridge-pool.js +129 -13
- package/dist/interactive/bridge-pool.js.map +1 -1
- package/dist/interactive/bridge-root.d.ts +9 -0
- package/dist/interactive/bridge-root.d.ts.map +1 -1
- package/dist/interactive/bridge-root.js +11 -4
- package/dist/interactive/bridge-root.js.map +1 -1
- package/dist/interactive/chat-events.d.ts +14 -9
- package/dist/interactive/chat-events.d.ts.map +1 -1
- package/dist/interactive/chat-events.js +55 -57
- package/dist/interactive/chat-events.js.map +1 -1
- package/dist/interactive/council-outcome.d.ts +1 -0
- package/dist/interactive/council-outcome.d.ts.map +1 -1
- package/dist/interactive/council-outcome.js +33 -0
- package/dist/interactive/council-outcome.js.map +1 -1
- package/dist/interactive/draft-events.d.ts +36 -17
- package/dist/interactive/draft-events.d.ts.map +1 -1
- package/dist/interactive/draft-events.js +121 -92
- package/dist/interactive/draft-events.js.map +1 -1
- package/dist/interactive/draft-skill.d.ts +59 -0
- package/dist/interactive/draft-skill.d.ts.map +1 -1
- package/dist/interactive/draft-skill.js +233 -0
- package/dist/interactive/draft-skill.js.map +1 -1
- package/dist/interactive/edit-events.d.ts +1 -14
- package/dist/interactive/edit-events.d.ts.map +1 -1
- package/dist/interactive/edit-events.js +37 -70
- package/dist/interactive/edit-events.js.map +1 -1
- package/dist/interactive/proxy-routes.d.ts +2 -2
- package/dist/interactive/proxy-routes.d.ts.map +1 -1
- package/dist/interactive/proxy-routes.js +4 -13
- package/dist/interactive/proxy-routes.js.map +1 -1
- package/dist/interactive/ws-relay.d.ts +9 -10
- package/dist/interactive/ws-relay.d.ts.map +1 -1
- package/dist/interactive/ws-relay.js +23 -50
- package/dist/interactive/ws-relay.js.map +1 -1
- package/dist/mcp/broker.d.ts +88 -0
- package/dist/mcp/broker.d.ts.map +1 -0
- package/dist/mcp/broker.js +307 -0
- package/dist/mcp/broker.js.map +1 -0
- package/dist/mcp/call-records.d.ts +61 -0
- package/dist/mcp/call-records.d.ts.map +1 -0
- package/dist/mcp/call-records.js +135 -0
- package/dist/mcp/call-records.js.map +1 -0
- package/dist/mcp/classify.d.ts +45 -0
- package/dist/mcp/classify.d.ts.map +1 -0
- package/dist/mcp/classify.js +83 -0
- package/dist/mcp/classify.js.map +1 -0
- package/dist/mcp/discovery.d.ts +24 -0
- package/dist/mcp/discovery.d.ts.map +1 -0
- package/dist/mcp/discovery.js +78 -0
- package/dist/mcp/discovery.js.map +1 -0
- package/dist/mcp/invoke.d.ts +36 -0
- package/dist/mcp/invoke.d.ts.map +1 -0
- package/dist/mcp/invoke.js +80 -0
- package/dist/mcp/invoke.js.map +1 -0
- package/dist/mcp/policies.d.ts +110 -0
- package/dist/mcp/policies.d.ts.map +1 -0
- package/dist/mcp/policies.js +400 -0
- package/dist/mcp/policies.js.map +1 -0
- package/dist/mcp/probe.d.ts +66 -0
- package/dist/mcp/probe.d.ts.map +1 -0
- package/dist/mcp/probe.js +116 -0
- package/dist/mcp/probe.js.map +1 -0
- package/dist/mcp/redact.d.ts +34 -0
- package/dist/mcp/redact.d.ts.map +1 -0
- package/dist/mcp/redact.js +84 -0
- package/dist/mcp/redact.js.map +1 -0
- package/dist/mcp/registry-store.d.ts +69 -0
- package/dist/mcp/registry-store.d.ts.map +1 -0
- package/dist/mcp/registry-store.js +82 -0
- package/dist/mcp/registry-store.js.map +1 -0
- package/dist/mcp/registry.d.ts +147 -0
- package/dist/mcp/registry.d.ts.map +1 -0
- package/dist/mcp/registry.js +509 -0
- package/dist/mcp/registry.js.map +1 -0
- package/dist/mcp/rest.d.ts +111 -0
- package/dist/mcp/rest.d.ts.map +1 -0
- package/dist/mcp/rest.js +606 -0
- package/dist/mcp/rest.js.map +1 -0
- package/dist/mcp/secrets.d.ts +66 -0
- package/dist/mcp/secrets.d.ts.map +1 -0
- package/dist/mcp/secrets.js +180 -0
- package/dist/mcp/secrets.js.map +1 -0
- package/dist/mcp/tool-list.d.ts +25 -0
- package/dist/mcp/tool-list.d.ts.map +1 -0
- package/dist/mcp/tool-list.js +62 -0
- package/dist/mcp/tool-list.js.map +1 -0
- package/dist/mcp/upstream-error.d.ts +12 -0
- package/dist/mcp/upstream-error.d.ts.map +1 -0
- package/dist/mcp/upstream-error.js +14 -0
- package/dist/mcp/upstream-error.js.map +1 -0
- package/dist/mcp/usage.d.ts +31 -0
- package/dist/mcp/usage.d.ts.map +1 -0
- package/dist/mcp/usage.js +159 -0
- package/dist/mcp/usage.js.map +1 -0
- package/dist/presets/routes.d.ts +53 -0
- package/dist/presets/routes.d.ts.map +1 -0
- package/dist/presets/routes.js +115 -0
- package/dist/presets/routes.js.map +1 -0
- package/dist/projects/activity.d.ts +6 -18
- package/dist/projects/activity.d.ts.map +1 -1
- package/dist/projects/activity.js +17 -60
- package/dist/projects/activity.js.map +1 -1
- package/dist/projects/events.d.ts +6 -5
- package/dist/projects/events.d.ts.map +1 -1
- package/dist/projects/events.js +33 -58
- package/dist/projects/events.js.map +1 -1
- package/dist/projects/routes.d.ts.map +1 -1
- package/dist/projects/routes.js +19 -0
- package/dist/projects/routes.js.map +1 -1
- package/dist/projects/state-home-preflight.d.ts.map +1 -1
- package/dist/projects/state-home-preflight.js +7 -0
- package/dist/projects/state-home-preflight.js.map +1 -1
- package/dist/projects/state-home-registry.d.ts.map +1 -1
- package/dist/projects/state-home-registry.js +9 -1
- package/dist/projects/state-home-registry.js.map +1 -1
- package/dist/qe/acceptance.d.ts +28 -28
- package/dist/qe/acceptance.d.ts.map +1 -1
- package/dist/qe/acceptance.js +60 -61
- package/dist/qe/acceptance.js.map +1 -1
- package/dist/qe/test-sets.d.ts +3 -5
- package/dist/qe/test-sets.d.ts.map +1 -1
- package/dist/qe/test-sets.js +5 -7
- package/dist/qe/test-sets.js.map +1 -1
- package/dist/skills/installed.d.ts +51 -0
- package/dist/skills/installed.d.ts.map +1 -0
- package/dist/skills/installed.js +46 -0
- package/dist/skills/installed.js.map +1 -0
- package/dist/standing-orders/evaluator.d.ts +113 -0
- package/dist/standing-orders/evaluator.d.ts.map +1 -0
- package/dist/standing-orders/evaluator.js +225 -0
- package/dist/standing-orders/evaluator.js.map +1 -0
- package/dist/standing-orders/history.d.ts +58 -0
- package/dist/standing-orders/history.d.ts.map +1 -0
- package/dist/standing-orders/history.js +117 -0
- package/dist/standing-orders/history.js.map +1 -0
- package/dist/standing-orders/parse.d.ts +19 -0
- package/dist/standing-orders/parse.d.ts.map +1 -0
- package/dist/standing-orders/parse.js +75 -0
- package/dist/standing-orders/parse.js.map +1 -0
- package/dist/standing-orders/routes.d.ts +180 -0
- package/dist/standing-orders/routes.d.ts.map +1 -0
- package/dist/standing-orders/routes.js +95 -0
- package/dist/standing-orders/routes.js.map +1 -0
- package/dist/standing-orders/store.d.ts +154 -0
- package/dist/standing-orders/store.d.ts.map +1 -0
- package/dist/standing-orders/store.js +189 -0
- package/dist/standing-orders/store.js.map +1 -0
- package/dist/studio/assets/index-CvV5qITf.css +32 -0
- package/dist/studio/assets/index-jCOy6Ns7.js +568 -0
- package/dist/studio/index.html +3 -3
- package/dist/studio/testid-inventory.json +3274 -894
- package/dist/team/key.d.ts +5 -0
- package/dist/team/key.d.ts.map +1 -0
- package/dist/team/key.js +21 -0
- package/dist/team/key.js.map +1 -0
- package/dist/team/routes.d.ts +196 -0
- package/dist/team/routes.d.ts.map +1 -0
- package/dist/team/routes.js +274 -0
- package/dist/team/routes.js.map +1 -0
- package/dist/team/ws-relay.d.ts +39 -0
- package/dist/team/ws-relay.d.ts.map +1 -0
- package/dist/team/ws-relay.js +63 -0
- package/dist/team/ws-relay.js.map +1 -0
- package/endpoint-manifest.json +502 -6
- package/package.json +6 -6
- package/dist/interactive/demo-events.d.ts +0 -367
- package/dist/interactive/demo-events.d.ts.map +0 -1
- package/dist/interactive/demo-events.js +0 -1293
- package/dist/interactive/demo-events.js.map +0 -1
- package/dist/qe/gate-events.d.ts +0 -113
- package/dist/qe/gate-events.d.ts.map +0 -1
- package/dist/qe/gate-events.js +0 -172
- package/dist/qe/gate-events.js.map +0 -1
- package/dist/studio/assets/index-Bg7Z3BR-.css +0 -32
- package/dist/studio/assets/index-BvOFMjf9.js +0 -554
package/dist/core/deliver.js
CHANGED
|
@@ -85,7 +85,8 @@
|
|
|
85
85
|
*/
|
|
86
86
|
import { execFile } from 'node:child_process';
|
|
87
87
|
import { childEnvWithBootEstateDb } from './governance-store.js';
|
|
88
|
-
import {
|
|
88
|
+
import { DELIVER_STRANDED_SENTINEL } from './deliver-exclusions.js';
|
|
89
|
+
import { composeEmbeddedDeliverText, factsFromWorkflow, framedDeliverText, runUrlFor, configuredPublicOrigin, commitSubject, urlPathSegment, } from './deliver-text.js';
|
|
89
90
|
/**
|
|
90
91
|
* The content-address of wicked-core's built-in evidence floor (`builtin_floors::EVIDENCE_FLOOR_PIN`,
|
|
91
92
|
* criterion "the run left a change in its worktree (done is re-derived from the diff, never
|
|
@@ -105,8 +106,10 @@ export const EVIDENCE_FLOOR_PIN = 'e2e7af1db9e48454';
|
|
|
105
106
|
export const DELIVER_PHASE_ID = 'deliver';
|
|
106
107
|
/**
|
|
107
108
|
* The sentinel the deliver script prints when the RUN'S WORK has been committed but its LIFT did
|
|
108
|
-
* not complete (crew#418
|
|
109
|
-
* `wicked/<id>` branch; an operator
|
|
109
|
+
* not complete (crew#418): a rebase onto the remote default branch hit a conflict outside the
|
|
110
|
+
* changelog. The work is safe on its `wicked/<id>` branch; an operator resolves the collision and
|
|
111
|
+
* retries delivery. A push the REMOTE refused is not a lift collision and carries
|
|
112
|
+
* {@link DELIVER_PUSH_REJECTED_MARKER} instead (N4).
|
|
110
113
|
*
|
|
111
114
|
* crew keys the "stranded, recoverable" reinterpretation on this EXACT substring appearing in
|
|
112
115
|
* the deliver unit's `denial_reason` (which carries the head+TAIL excerpt of the script's
|
|
@@ -116,6 +119,53 @@ export const DELIVER_PHASE_ID = 'deliver';
|
|
|
116
119
|
* terminal run failures exactly as before (the crew#400 refusal-vs-infra posture).
|
|
117
120
|
*/
|
|
118
121
|
export const DELIVER_LIFT_CONFLICT_MARKER = 'deliver: LIFT-CONFLICT';
|
|
122
|
+
/**
|
|
123
|
+
* The sentinel the deliver script prints LAST when the REMOTE REFUSED the push after the run's work
|
|
124
|
+
* was committed and its lift came out clean (N4, ship re-proof): a pre-receive hook, a protected
|
|
125
|
+
* ref, an auth 403, a transport failure, or a non-fast-forward on the run branch. It used to carry
|
|
126
|
+
* {@link DELIVER_LIFT_CONFLICT_MARKER}, which mislabelled a refusal as a lift collision — the lift
|
|
127
|
+
* read `unchanged` — and, because the engine EXEMPTS that marker from its deterministic
|
|
128
|
+
* deliver-refusal arm, sent the failure to the LLM triage judge, which re-ran the push once with no
|
|
129
|
+
* gate. Without the lift marker the failure takes the engine's deterministic arm: the run parks at
|
|
130
|
+
* an `escalation` gate on the deliver unit, and nothing is pushed again until a human approves.
|
|
131
|
+
* The untracked recovery sentinel ({@link DELIVER_STRANDED_SENTINEL}) still keeps the worktree.
|
|
132
|
+
*/
|
|
133
|
+
export const DELIVER_PUSH_REJECTED_MARKER = 'deliver: PUSH-REJECTED';
|
|
134
|
+
/**
|
|
135
|
+
* The machine line the deliver script prints LAST on a successful PUSH-ONLY delivery (N1, the half
|
|
136
|
+
* of F2 that did not hold): the branch is on the remote, and no pull request was opened because gh
|
|
137
|
+
* could not resolve the origin to a GitHub repository. `deliver: PUSHED-NO-PR <branch> <remote>` —
|
|
138
|
+
* the branch has no spaces (git forbids them in a ref), and the remote is the rest of the line.
|
|
139
|
+
* The daemon reads it from the APPROVED deliver unit's output ({@link pushedOnlyFrom}) into the
|
|
140
|
+
* durable `run.delivered` record, so the run reads `delivery: 'pushed'` instead of `'stranded'`.
|
|
141
|
+
*/
|
|
142
|
+
export const DELIVER_PUSHED_NO_PR_MARKER = 'deliver: PUSHED-NO-PR';
|
|
143
|
+
/** A remote URL with its userinfo removed — `https://tok@host/r` → `https://host/r`. A scp-like
|
|
144
|
+
* `git@host:path` keeps its user (it names an SSH login, not a secret). */
|
|
145
|
+
export function remoteWithoutUserinfo(url) {
|
|
146
|
+
return url.trim().replace(/^([A-Za-z][A-Za-z0-9+.-]*:\/\/)[^@/]*@/, '$1');
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* The push-only delivery a deliver transcript records, or `null`. The LAST
|
|
150
|
+
* {@link DELIVER_PUSHED_NO_PR_MARKER} line wins. Whether it outranks a PR URL in the same transcript
|
|
151
|
+
* is decided by position in `deliveryRecordFrom` (`api/delivery-index.ts`): the script's verdict is
|
|
152
|
+
* the last of the two, and anything earlier may be the remote's echo.
|
|
153
|
+
*/
|
|
154
|
+
export function pushedOnlyFrom(text) {
|
|
155
|
+
let found = null;
|
|
156
|
+
for (const line of text.split('\n')) {
|
|
157
|
+
const m = /^deliver: PUSHED-NO-PR (\S+) (.+)$/.exec(line.trimEnd());
|
|
158
|
+
if (m !== null)
|
|
159
|
+
found = { branch: m[1], remote: remoteWithoutUserinfo(m[2]) };
|
|
160
|
+
}
|
|
161
|
+
return found;
|
|
162
|
+
}
|
|
163
|
+
/** A GitHub login as GitHub itself allows it — alphanumerics and single hyphens, ≤ 39 chars. The
|
|
164
|
+
* ONE validator for the deliver identity: the setting's 400 and the script's splice share it, so
|
|
165
|
+
* a value that passed the route can always be baked into a single-quoted shell literal. */
|
|
166
|
+
export function isGitHubLogin(login) {
|
|
167
|
+
return /^[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}$/.test(login);
|
|
168
|
+
}
|
|
119
169
|
/** A branch name the script may splice into a single-quoted literal and hand to git: the ref
|
|
120
170
|
* charset git accepts for `wicked/<run>`-style heads, no `..`, no leading `-`, no quotes. */
|
|
121
171
|
const SAFE_REF = /^[A-Za-z0-9][A-Za-z0-9._/-]*$/;
|
|
@@ -131,7 +181,7 @@ const SAFE_PR_URL = /^https:\/\/[A-Za-z0-9.-]+\/[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+
|
|
|
131
181
|
* (≤ 90 ASCII bytes) on purpose — the PTY carrier's whole prompt is 1000 B and core's budget test
|
|
132
182
|
* keeps ≥ 300 B of intent headroom. */
|
|
133
183
|
export const BUG_FIX_SWEEP_INSTRUCTIONS = 'Update every consumer of behaviour this fix retires or changes: tests, docs, comments.';
|
|
134
|
-
const defaultGhExec = (args, opts) => new Promise((resolve) => {
|
|
184
|
+
export const defaultGhExec = (args, opts) => new Promise((resolve) => {
|
|
135
185
|
// The daemon's governance-store variables never ride into a child (crew#495): the boot value is
|
|
136
186
|
// restored and the exported store URL is stripped, the same helper every other spawn site uses.
|
|
137
187
|
execFile('gh', args, { cwd: opts.cwd, timeout: opts.timeoutMs, encoding: 'utf8', env: childEnvWithBootEstateDb(process.env) }, (err, stdout, stderr) => {
|
|
@@ -139,6 +189,39 @@ const defaultGhExec = (args, opts) => new Promise((resolve) => {
|
|
|
139
189
|
resolve({ stdout: String(stdout ?? ''), stderr: String(stderr ?? ''), code });
|
|
140
190
|
});
|
|
141
191
|
});
|
|
192
|
+
/**
|
|
193
|
+
* `git remote get-url --push origin` in `rootPath` — the deliver gate's origin preflight (F2).
|
|
194
|
+
*
|
|
195
|
+
* `--push` because the sentence is about the PUSH: `git push origin` uses `remote.origin.pushurl`
|
|
196
|
+
* when one is configured, so a repo whose FETCH url is GitHub while its PUSH url is not (or the
|
|
197
|
+
* reverse) would otherwise get a card describing a destination the branch never reaches (codex
|
|
198
|
+
* review of this PR, HIGH). With no `pushurl` configured git answers the fetch url, so the common
|
|
199
|
+
* case is unchanged.
|
|
200
|
+
*
|
|
201
|
+
* - the trimmed URL when git answered one;
|
|
202
|
+
* - `''` when git said there is NO such remote — the one answer that licenses the card to claim
|
|
203
|
+
* "this repository has no origin";
|
|
204
|
+
* - `null` for everything else (git missing, a timeout, a root that is not a checkout). "Could
|
|
205
|
+
* not read it" is not a licence to claim anything about it either way, so the card keeps its
|
|
206
|
+
* generic sentence.
|
|
207
|
+
*
|
|
208
|
+
* A run worktree shares its repository's remotes and does not exist yet at compose time, so this
|
|
209
|
+
* reads the REGISTERED repo root. Bounded to 5 s, once per delivering launch.
|
|
210
|
+
*/
|
|
211
|
+
export async function readDeliverOriginUrl(rootPath) {
|
|
212
|
+
const res = await new Promise((resolve) => {
|
|
213
|
+
execFile('git', ['remote', 'get-url', '--push', 'origin'], { cwd: rootPath, timeout: 5_000, encoding: 'utf8', env: childEnvWithBootEstateDb(process.env) }, (err, stdout, stderr) => {
|
|
214
|
+
const code = err === null ? 0 : typeof err.code === 'number' ? err.code : null;
|
|
215
|
+
resolve({ stdout: String(stdout ?? ''), stderr: String(stderr ?? ''), code });
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
if (res.code === 0) {
|
|
219
|
+
const url = res.stdout.trim();
|
|
220
|
+
// git can exit 0 with nothing for a remote configured with an empty URL — unknown, not absent.
|
|
221
|
+
return url === '' ? null : url;
|
|
222
|
+
}
|
|
223
|
+
return /no such remote/i.test(res.stderr) ? '' : null;
|
|
224
|
+
}
|
|
142
225
|
/**
|
|
143
226
|
* Resolve `revisesPr` (a PR number) to the head branch the run will base on and push to — the
|
|
144
227
|
* daemon's job, since the engine has no GitHub client (DES-L9 §2). Bounded to 5 s; every failure
|
|
@@ -300,6 +383,13 @@ export function deliverPrScript(intent, opts = {}) {
|
|
|
300
383
|
const api = apiOriginLiteral(opts.apiOrigin);
|
|
301
384
|
const fallbackLines = heredocLines(framedDeliverText(fallback));
|
|
302
385
|
const heredoc = heredocDelimiter(fallbackLines);
|
|
386
|
+
// crew#550: the PR title may run to GitHub's 256 characters, the commit subject stays at git's 72.
|
|
387
|
+
// The subject is composed here from the same title; the script puts it above the full text when
|
|
388
|
+
// the two differ, so the commit body opens with the whole title.
|
|
389
|
+
const subjectLines = heredocLines(commitSubject(fallback.title));
|
|
390
|
+
const fallbackTitleLines = heredocLines(fallback.title);
|
|
391
|
+
// Never the text heredoc's own delimiter, so each heredoc has exactly one closing line.
|
|
392
|
+
const subjectHeredoc = heredocDelimiter([...fallbackLines, ...subjectLines, ...fallbackTitleLines, heredoc]);
|
|
303
393
|
// The run id for the daemon URL: the LAUNCH id when the composer knows it, pre-encoded as one
|
|
304
394
|
// strict path segment (only `[A-Za-z0-9._~%-]` survive, so the single-quoted literal is safe);
|
|
305
395
|
// otherwise derived from the branch at run time and percent-encoded byte-wise by the script.
|
|
@@ -315,6 +405,20 @@ export function deliverPrScript(intent, opts = {}) {
|
|
|
315
405
|
if (!SAFE_PR_URL.test(revises.url))
|
|
316
406
|
throw new Error(`revisesPr #${revises.number}: not a pull request URL: ${JSON.stringify(revises.url)}`);
|
|
317
407
|
}
|
|
408
|
+
// (crew#549) The configured deliver identity, baked as a single-quoted literal. Fail CLOSED at
|
|
409
|
+
// compose time on anything that is not a GitHub login: a run that silently delivered under an
|
|
410
|
+
// unchecked identity is the defect, and a value that reached a shell literal unvalidated would
|
|
411
|
+
// be worse than either.
|
|
412
|
+
const identity = (opts.deliverIdentity ?? '').trim();
|
|
413
|
+
if (identity !== '' && !isGitHubLogin(identity)) {
|
|
414
|
+
throw new Error(`deliver identity: not a GitHub login: ${JSON.stringify(identity)}`);
|
|
415
|
+
}
|
|
416
|
+
// R1: the repository the consent card names IS the one `gh pr create` opens on. Without `--repo`
|
|
417
|
+
// gh resolves the base itself (a `gh repo set-default`, an `upstream` remote), so "opens a pull
|
|
418
|
+
// request there" could name one repository while the PR opened on another (codex review, HIGH).
|
|
419
|
+
// `githubRepoOf` admits only `[A-Za-z0-9._-]+/[A-Za-z0-9._-]+`, safe in a single-quoted literal;
|
|
420
|
+
// a non-github.com origin bakes '' and gh keeps resolving it (the push-only path keys on that).
|
|
421
|
+
const ghRepo = githubRepoOf(opts.originUrl) ?? '';
|
|
318
422
|
const prNum = revises === null ? '' : String(revises.number);
|
|
319
423
|
const target = revises === null ? '' : revises.headRef;
|
|
320
424
|
const prUrl = revises === null ? '' : revises.url;
|
|
@@ -332,17 +436,95 @@ export function deliverPrScript(intent, opts = {}) {
|
|
|
332
436
|
// token's login) holds, disclosed on the gate card, never switched at push time. Unset ⇒
|
|
333
437
|
// today's behaviour, now said aloud. No account name is baked into crew code (env-driven).
|
|
334
438
|
'L=$(gh api user -q .login 2>/dev/null || true)',
|
|
439
|
+
// (crew#549) The CONFIGURED identity is the system setting `deliverIdentityLogin` — baked
|
|
440
|
+
// here at compose time, validated against the GitHub login charset before it is spliced —
|
|
441
|
+
// and the `GH_ACCOUNT` env var when the setting is empty (still supported; the setting wins
|
|
442
|
+
// so a daemon started without the export is not silently unpinned). `GH_ACCOUNT` is then the
|
|
443
|
+
// ONE name the refusals use, whichever source set it.
|
|
444
|
+
`CFG='${identity}'`,
|
|
445
|
+
'if [ -n "$CFG" ]; then GH_ACCOUNT="$CFG"; fi',
|
|
335
446
|
'if [ -n "${GH_ACCOUNT:-}" ]; then',
|
|
336
447
|
' [ "$L" = "$GH_ACCOUNT" ] || { echo "deliver: identity mismatch — GH_ACCOUNT is $GH_ACCOUNT but gh\'s active login is ${L:-unreadable}; nothing was staged, committed or pushed. Fix the daemon\'s gh login (switch gh\'s active account, or export GH_TOKEN in the daemon environment) and approve to retry the deliver phase"; exit 1; }',
|
|
337
448
|
' if [ -n "${GH_TOKEN:-}" ]; then echo "deliver: pushing as $L (GH_ACCOUNT pinned by GH_TOKEN)"; else echo "deliver: pushing as $L (GH_ACCOUNT from the gh keyring — export GH_TOKEN to pin it)"; fi',
|
|
338
449
|
'elif [ -n "$L" ]; then echo "deliver: pushing as $L (GH_ACCOUNT not set — not pinned)"',
|
|
339
450
|
'else echo "deliver: pushing as an unknown login (gh not authenticated; GH_ACCOUNT not set — not pinned)"; fi',
|
|
451
|
+
// (crew#549 / F-RC1-010) THE CREDENTIAL CROSS-CHECK. `gh api user` says who gh is; it says
|
|
452
|
+
// NOTHING about the credential `git push` will use. On the RC1 rig those two disagreed —
|
|
453
|
+
// `git credential fill` handed git one account while `gh api user` reported another — and the
|
|
454
|
+
// run pushed under an identity the operator had not chosen (or took a 403 that hard-failed
|
|
455
|
+
// it). So ask git itself, for the remote's own host, BEFORE anything is fetched or staged,
|
|
456
|
+
// and REFUSE when both logins are concrete and differ — naming both, whether or not an
|
|
457
|
+
// identity is configured. `GIT_TERMINAL_PROMPT=0` keeps a helperless host from prompting (it
|
|
458
|
+
// errors instead, which reads as unresolved). Only an https remote has a git credential; an
|
|
459
|
+
// ssh or local remote authenticates another way, so the check does not apply and says so.
|
|
460
|
+
// A placeholder username (`x-access-token`, `oauth2`, `token`) identifies a TOKEN, not an
|
|
461
|
+
// account: unresolved, disclosed, never a refusal (the token's own login is what `gh api
|
|
462
|
+
// user` already reported).
|
|
463
|
+
//
|
|
464
|
+
// THE REMOTE'S OWN USERNAME COMES FIRST (codex review on #727). A remote spelled
|
|
465
|
+
// `https://other-bot@github.com/o/r.git` tells git which account to authenticate as, and git
|
|
466
|
+
// uses it whatever the helper would have answered for the bare host. Stripping it and asking
|
|
467
|
+
// only about the host left exactly the defect this guard exists for alive: gh reports
|
|
468
|
+
// `release-bot`, the URL says `other-bot`, the helper agrees with gh, and the push goes out as
|
|
469
|
+
// `other-bot`. So the URL username is read, refused against gh's login when both are concrete,
|
|
470
|
+
// and passed INTO the credential query so the helper answers for the account git will use.
|
|
471
|
+
'RU=$(git remote get-url origin 2>/dev/null || true)',
|
|
472
|
+
'case "$RU" in',
|
|
473
|
+
' https://*)',
|
|
474
|
+
' RH=${RU#https://}; RH=${RH%%/*}; RU_USER=""',
|
|
475
|
+
' case "$RH" in *@*) RU_USER=${RH%@*}; RH=${RH##*@};; esac',
|
|
476
|
+
// A `user:password@host` remote carries the secret in the URL; take the user, never the rest.
|
|
477
|
+
' case "$RU_USER" in *:*) RU_USER=${RU_USER%%:*};; esac',
|
|
478
|
+
' case "${RU_USER:-}" in',
|
|
479
|
+
' ""|x-access-token|oauth2|token|PRIVATE-TOKEN) ;;',
|
|
480
|
+
' *) if [ -n "$L" ] && [ "$RU_USER" != "$L" ]; then echo "deliver: identity mismatch — gh\'s active login is $L but origin\'s URL authenticates as $RU_USER (https://$RU_USER@$RH/…); nothing was staged, committed or pushed. The push would use $RU_USER, not $L. Point origin at https://$RH/… and pin ONE identity (export GH_TOKEN in the daemon environment, or fix the credential helper for $RH), then approve to retry the deliver phase"; exit 1; fi',
|
|
481
|
+
' echo "deliver: origin authenticates as $RU_USER (agrees with gh)";; esac',
|
|
482
|
+
' GC=$(printf "protocol=https\\nhost=%s\\n%s\\n" "$RH" "${RU_USER:+username=$RU_USER}" | GIT_TERMINAL_PROMPT=0 git credential fill 2>/dev/null | sed -n "s/^username=//p" | head -1 || true)',
|
|
483
|
+
' case "${GC:-}" in',
|
|
484
|
+
' "") echo "deliver: git\'s credential identity for $RH is unresolved (no credential helper answered) — the push uses whatever the helper hands git at push time";;',
|
|
485
|
+
' x-access-token|oauth2|token|PRIVATE-TOKEN) echo "deliver: git\'s credential for $RH is a token ($GC), so it names no account — gh reports ${L:-an unknown login}";;',
|
|
486
|
+
' *)',
|
|
487
|
+
' if [ -n "$L" ] && [ "$GC" != "$L" ]; then echo "deliver: identity mismatch — gh\'s active login is $L but git\'s credential for $RH is $GC; nothing was staged, committed or pushed. The push would use $GC, not $L. Pin ONE identity (export GH_TOKEN in the daemon environment, or fix the credential helper for $RH) and approve to retry the deliver phase"; exit 1; fi',
|
|
488
|
+
' echo "deliver: git\'s credential for $RH is $GC (agrees with gh)";;',
|
|
489
|
+
' esac;;',
|
|
490
|
+
' "") echo "deliver: this worktree has no origin remote — no git credential applies";;',
|
|
491
|
+
' *) echo "deliver: origin is not an https remote — no git credential applies (ssh keys or a local path authenticate the push)";;',
|
|
492
|
+
'esac',
|
|
340
493
|
// REVISION MODE inputs (DES-L9 / crew#550) — baked at compose time from the resolved PR, each
|
|
341
494
|
// validated against a strict charset before it is spliced into a single-quoted literal. Empty
|
|
342
495
|
// TARGET ⇒ today's new-PR delivery.
|
|
343
496
|
`PRNUM='${prNum}'`,
|
|
344
497
|
`TARGET='${target}'`,
|
|
345
498
|
`PRURL='${prUrl}'`,
|
|
499
|
+
`GHREPO='${ghRepo}'`,
|
|
500
|
+
// R1 (Copilot on crew#736): the card the operator approved named GHREPO; if origin was
|
|
501
|
+
// re-pointed since (the run can wait at its gate for hours), the push would leave for another
|
|
502
|
+
// repository than the one consented to. Refuse before anything is fetched, staged or pushed.
|
|
503
|
+
// The URL is PARSED, not globbed (codex review, HIGH: `*github.com/o/r` also matched
|
|
504
|
+
// `https://evil.example/github.com/o/r` and `git@evilgithub.com:o/r`): the host — userinfo and
|
|
505
|
+
// port stripped, lowercased — must be exactly github.com and the path exactly GHREPO (a
|
|
506
|
+
// trailing `/` or `.git` dropped). The URL itself (it may carry a token) is never printed.
|
|
507
|
+
// EVERY push URL is checked (`--all`): `git push origin` pushes to each `remote.origin.pushurl`,
|
|
508
|
+
// so a second one would receive the branch unnamed (Copilot on crew#736). The host must be
|
|
509
|
+
// github.com or ssh.github.com — the same two `githubRepoOf` names a repository for.
|
|
510
|
+
'if [ -n "$GHREPO" ]; then',
|
|
511
|
+
" GL=$(printf '%s' \"$GHREPO\" | tr 'A-Z' 'a-z'); ODRIFT=''; OANY=''",
|
|
512
|
+
' while IFS= read -r OU; do',
|
|
513
|
+
' [ -n "$OU" ] || continue; OANY=1',
|
|
514
|
+
' case "$OU" in',
|
|
515
|
+
' *://*) OH=${OU#*://}; OH=${OH%%/*}; OH=${OH##*@}; OH=${OH%%:*}; OP=${OU#*://*/};;',
|
|
516
|
+
' *) OH=${OU%%:*}; OH=${OH##*@}; OP=${OU#*:};;',
|
|
517
|
+
' esac',
|
|
518
|
+
// GitHub owner/repo names are case-insensitive: compare lowercased, keep GHREPO's spelling
|
|
519
|
+
// for `--repo` (codex review, MEDIUM). An absolute scp path (`git@github.com:/o/r`) is the same
|
|
520
|
+
// repository: the leading `/` goes.
|
|
521
|
+
" OH=$(printf '%s' \"$OH\" | tr 'A-Z' 'a-z'); OP=$(printf '%s' \"$OP\" | tr 'A-Z' 'a-z'); OP=${OP#/}; OP=${OP%/}; OP=${OP%.git}",
|
|
522
|
+
' case "$OH" in github.com|ssh.github.com) [ "$OP" = "$GL" ] || ODRIFT=1;; *) ODRIFT=1;; esac',
|
|
523
|
+
' done <<GHREPO_URLS',
|
|
524
|
+
'$(git remote get-url --push --all origin 2>/dev/null || true)',
|
|
525
|
+
'GHREPO_URLS',
|
|
526
|
+
' if [ -n "$ODRIFT" ] || [ -z "$OANY" ]; then echo "deliver: origin no longer points at $GHREPO (or also pushes elsewhere), the repository this delivery was approved for — nothing was staged, committed or pushed. Point origin back at $GHREPO alone, or reject and relaunch against the new remote"; exit 1; fi',
|
|
527
|
+
'fi',
|
|
346
528
|
// (a) The run branch: wicked/<worktree-basename> (the engine names run worktrees by run id),
|
|
347
529
|
// falling back to the currently checked-out branch when that ref does not exist.
|
|
348
530
|
'R=$(basename "$PWD")',
|
|
@@ -404,7 +586,7 @@ export function deliverPrScript(intent, opts = {}) {
|
|
|
404
586
|
// A failed PUSH happens after the product was committed. Keep its worktree from being reaped
|
|
405
587
|
// by leaving this reserved, untracked recovery sentinel; it is removed HERE (before staging)
|
|
406
588
|
// so a normal delivery never sees it, and a retry removes it before another attempt (crew#432).
|
|
407
|
-
|
|
589
|
+
`S=${DELIVER_STRANDED_SENTINEL}`,
|
|
408
590
|
'rm -f -- "$S"',
|
|
409
591
|
// (c1) COMMIT THE RUN'S WORK (crew#317). Agents write files; they do not commit — which is
|
|
410
592
|
// the premise of core#291 and the reason `d1bc72c2` pushed a branch identical to origin/main.
|
|
@@ -459,6 +641,18 @@ export function deliverPrScript(intent, opts = {}) {
|
|
|
459
641
|
heredoc,
|
|
460
642
|
'fi',
|
|
461
643
|
'TITLE=$(sed -n 1p "$TD/text")',
|
|
644
|
+
`cat > "$TD/subject" <<'${subjectHeredoc}'`,
|
|
645
|
+
...subjectLines,
|
|
646
|
+
subjectHeredoc,
|
|
647
|
+
`cat > "$TD/fbtitle" <<'${subjectHeredoc}'`,
|
|
648
|
+
...fallbackTitleLines,
|
|
649
|
+
subjectHeredoc,
|
|
650
|
+
// The subject for the title actually used: the composed one when the daemon's title is the
|
|
651
|
+
// embedded fallback's; else (the daemon titled it differently) a plain word-boundary cut at 72.
|
|
652
|
+
'if [ "$(cat "$TD/fbtitle")" != "$TITLE" ]; then printf \'%s\\n\' "$TITLE" | awk \'{ if (length($0) <= 72) print; else { s = substr($0, 1, 71); sub(/ [^ ]*$/, "", s); print s "…" } }\' > "$TD/subject"; fi',
|
|
653
|
+
// The commit message: the text verbatim when its title fits the subject width, else the 72-column
|
|
654
|
+
// subject, a blank line, then the text (whose first line is the full title) — crew#550.
|
|
655
|
+
'if [ "$(cat "$TD/subject")" = "$TITLE" ]; then cp "$TD/text" "$TD/commit"; else { cat "$TD/subject"; echo; cat "$TD/text"; } > "$TD/commit"; fi',
|
|
462
656
|
"sed '1,2d' \"$TD/text\" > \"$TD/body\"",
|
|
463
657
|
// (c0) DELIVER PREFLIGHT (crew#426) — a governed run that bumps an internal WORKSPACE package's
|
|
464
658
|
// version (e.g. packages/crew-api-types) leaves its version-derived codegen AND the lockfile
|
|
@@ -554,6 +748,9 @@ export function deliverPrScript(intent, opts = {}) {
|
|
|
554
748
|
'while IFS= read -r -d "" F; do',
|
|
555
749
|
' [ -n "$F" ] || continue',
|
|
556
750
|
' BN=${F##*/}; RN=""',
|
|
751
|
+
// The recovery sentinel is removed above, so this arm is the ONE predicate's first rule, kept
|
|
752
|
+
// here so `deliverExclusionByName` and this classifier stay the same ladder (N2).
|
|
753
|
+
' [ "$F" = "$S" ] && RN="delivery-sentinel"',
|
|
557
754
|
// Classify on a LOWERCASED basename so DEPLOY.KEY / .ENV / SOCKET.PATH cannot bypass the
|
|
558
755
|
// denylist by case (review, #439).
|
|
559
756
|
' LBN=$(printf "%s" "$BN" | tr "[:upper:]" "[:lower:]")',
|
|
@@ -571,7 +768,7 @@ export function deliverPrScript(intent, opts = {}) {
|
|
|
571
768
|
// `--cleanup=whitespace`, NOT git's default for `-F`: an operator/repo `commit.cleanup=strip`
|
|
572
769
|
// would otherwise treat every `## Intent` / `## Run` / `## Phases` heading as a `#` comment and
|
|
573
770
|
// strip it from the commit body (review W3-K1).
|
|
574
|
-
'git diff --cached --quiet || git commit -q --cleanup=whitespace -F "$TD/
|
|
771
|
+
'git diff --cached --quiet || git commit -q --cleanup=whitespace -F "$TD/commit"',
|
|
575
772
|
// (c2) NOTHING TO DELIVER — no staged work AND no commits of its own. Fail LOUDLY before the
|
|
576
773
|
// remote is touched: an empty ref pushed under a run id is worse than a failed phase.
|
|
577
774
|
'if [ -n "$TARGET" ]; then A=$(git rev-list --count "origin/$TARGET..$B"); [ "$A" -ge 1 ] || { echo "deliver: nothing to deliver — the run added no commit on top of PR #$PRNUM"; exit 1; }; else',
|
|
@@ -637,17 +834,20 @@ export function deliverPrScript(intent, opts = {}) {
|
|
|
637
834
|
'[ "$A" -ge 1 ] || { echo "deliver: nothing to deliver — the run produced no committed change (after rebasing onto $D, $B carries no commit of its own); nothing was pushed"; exit 1; }',
|
|
638
835
|
'fi',
|
|
639
836
|
// (d) Push. Any push failure happens AFTER the work was committed and its branch was proven
|
|
640
|
-
// ahead
|
|
641
|
-
//
|
|
642
|
-
//
|
|
837
|
+
// ahead, whether the remote branch moved, auth returned 403, the transport is down, or a hook
|
|
838
|
+
// rejected it. It is a REFUSAL BY THE REMOTE, not a lift collision (N4): the lift already came
|
|
839
|
+
// out clean. Preserve git's own output, leave the recovery sentinel, and print
|
|
840
|
+
// DELIVER_PUSH_REJECTED_MARKER last — never the LIFT-CONFLICT marker — so the engine takes its
|
|
841
|
+
// deterministic deliver-refusal arm and parks the run at a gate; nothing pushes again until a
|
|
842
|
+
// human approves the retry.
|
|
643
843
|
// A revision pushes the run branch ONTO the PR's head branch (`$B:refs/heads/$TARGET`) — the
|
|
644
844
|
// PR gains exactly the run's commits; a rejection there is the same recoverable strand.
|
|
645
845
|
'_push() { if [ -n "$TARGET" ]; then git push origin "$B:refs/heads/$TARGET"; else git push -u origin "$B"; fi; }',
|
|
646
846
|
'if PUSHOUT=$(_push 2>&1); then echo "$PUSHOUT"; else',
|
|
647
847
|
' echo "$PUSHOUT"',
|
|
648
848
|
' case "$PUSHOUT" in',
|
|
649
|
-
` *non-fast-forward*|*"fetch first"*|*"[rejected]"*|*"Updates were rejected"*) : > "$S"; echo "
|
|
650
|
-
` *) : > "$S"; PUSHERR="\${PUSHOUT:0:96} ... \${PUSHOUT: -128}"; PUSHERR=\${PUSHERR//$'\\n'/ }; echo "deliver:
|
|
849
|
+
` *non-fast-forward*|*"fetch first"*|*"[rejected]"*|*"Updates were rejected"*) : > "$S"; echo "deliver: the remote refused the push of $B because its branch moved (non-fast-forward); the work is committed on $B and nothing was pushed — approve to retry the deliver phase; ${DELIVER_PUSH_REJECTED_MARKER}"; exit 1;;`,
|
|
850
|
+
` *) : > "$S"; PUSHERR="\${PUSHOUT:0:96} ... \${PUSHOUT: -128}"; PUSHERR=\${PUSHERR//$'\\n'/ }; echo "deliver: the remote refused the push of $B after commit: $PUSHERR; the work is committed on $B and nothing was pushed — fix the remote condition, then approve to retry the deliver phase; ${DELIVER_PUSH_REJECTED_MARKER}"; exit 1;;`,
|
|
651
851
|
' esac',
|
|
652
852
|
'fi',
|
|
653
853
|
// (e) Open the PR with gh's OUTPUT and EXIT STATUS captured separately (crew#317). The old
|
|
@@ -669,7 +869,41 @@ export function deliverPrScript(intent, opts = {}) {
|
|
|
669
869
|
' if COUT=$(gh pr comment "$PRNUM" --body-file "$TD/body" 2>&1); then echo "deliver: run record commented on pull request #$PRNUM"; else echo "$COUT"; echo "deliver: could not comment on pull request #$PRNUM — the commits landed; the record is in the commit message"; fi',
|
|
670
870
|
' URL="$PRURL"',
|
|
671
871
|
'else',
|
|
672
|
-
|
|
872
|
+
// (e2) A NON-GITHUB ORIGIN DELIVERS THE BRANCH (F2). `gh pr create` failing used to be
|
|
873
|
+
// `exit 1` with no fallback and no push-only mode, so on a local, SSH, GitLab, ADO or Gitea
|
|
874
|
+
// origin the phase PUSHED THE BRANCH — step (d), above, already happened — and then died on
|
|
875
|
+
// "none of the git remotes configured for this repository point to a known GitHub host".
|
|
876
|
+
// Approving the retry re-ran the identical refusal; rejecting cancelled the run. The run could
|
|
877
|
+
// never reach a terminal state while the one irreversible side effect was already done.
|
|
878
|
+
//
|
|
879
|
+
// gh is the AUTHORITY on whether a remote is a GitHub remote — it resolves the remotes itself
|
|
880
|
+
// and that refusal IS its verdict — so the push-only success path opens on that message and on
|
|
881
|
+
// nothing else. The pattern requires BOTH halves of the diagnostic, in order, rather than one
|
|
882
|
+
// fragment (codex review of this PR, MEDIUM), while skipping the leading "none of the" and the
|
|
883
|
+
// point/points verb so a gh wording tweak cannot silently re-open the F2 hole. Every other gh failure (auth, validation, rate limit, a gh that is not
|
|
884
|
+
// installed) stays exactly as loud as it was, which is what keeps a GitHub Enterprise Server
|
|
885
|
+
// origin — where gh succeeds — on the pull-request path. No forge integration is invented
|
|
886
|
+
// here: the pushed branch IS the delivery, and the phase says so and says where to take it.
|
|
887
|
+
//
|
|
888
|
+
// Done is still RE-DERIVED before the claim: the remote ref must be ahead of the base. And
|
|
889
|
+
// nothing may read a pull request out of this output — there is no URL in it, so `prUrlFrom`
|
|
890
|
+
// answers null and the run's `delivery` never reads `delivered`.
|
|
891
|
+
' if ! OUT=$(gh pr create ${GHREPO:+--repo "$GHREPO"} --head "$B" --title "$TITLE" --body-file "$TD/body" 2>&1); then',
|
|
892
|
+
' echo "$OUT"',
|
|
893
|
+
' case "$OUT" in',
|
|
894
|
+
' *"git remotes configured for this repository"*"known GitHub host"*)',
|
|
895
|
+
' P=$(git rev-list --count "$D..origin/$B")',
|
|
896
|
+
' [ "$P" -ge 1 ] || { echo "deliver: $B is not ahead of $D on the remote after the push — refusing to report a delivery with no commits"; exit 1; }',
|
|
897
|
+
// The PUSH url, with any `user[:password]@` userinfo stripped before it reaches the transcript
|
|
898
|
+
// (codex review of N1: a push URL can carry a deploy token).
|
|
899
|
+
' R=$(git remote get-url --push origin | sed -E "s#^([A-Za-z][A-Za-z0-9+.-]*://)[^@/]*@#\\1#")',
|
|
900
|
+
' echo "deliver: pushed $B to origin ($R) with $P commit(s) on top of $D, and no pull request was opened because that remote is not a GitHub host gh can resolve. The branch IS the delivery — open the pull/merge request for $B on your forge; merge stays human.";',
|
|
901
|
+
// N1: the machine line the daemon records the push-only delivery from — LAST, one line.
|
|
902
|
+
` echo "${DELIVER_PUSHED_NO_PR_MARKER} $B $R";`,
|
|
903
|
+
' exit 0;;',
|
|
904
|
+
' esac',
|
|
905
|
+
' echo "deliver: gh pr create failed for $B — no PR was opened"; exit 1',
|
|
906
|
+
' fi',
|
|
673
907
|
' echo "$OUT"',
|
|
674
908
|
// (f) DONE IS RE-DERIVED, NOT ASSERTED — twice, from two independent facts, before the phase
|
|
675
909
|
// is allowed to report a delivery:
|
|
@@ -735,25 +969,225 @@ export function deliverPrPhase(dependsOn = [], intent, opts = {}) {
|
|
|
735
969
|
validator_pin: EVIDENCE_FLOOR_PIN,
|
|
736
970
|
};
|
|
737
971
|
}
|
|
972
|
+
/** The host of a git remote URL — `https://`/`ssh://` URLs and the scp-like `git@host:owner/repo`
|
|
973
|
+
* spelling — or `null` when the value names no host (a path). */
|
|
974
|
+
export function originRemoteHost(url) {
|
|
975
|
+
const trimmed = url.trim();
|
|
976
|
+
if (trimmed === '')
|
|
977
|
+
return null;
|
|
978
|
+
// Any `scheme://…` is decided HERE, authority or not: `file:///p` has an empty authority and
|
|
979
|
+
// names no host, and so does any other schemed URL written without one.
|
|
980
|
+
const scheme = /^([A-Za-z][A-Za-z0-9+.-]*):\/\//.exec(trimmed);
|
|
981
|
+
if (scheme !== null) {
|
|
982
|
+
if (scheme[1].toLowerCase() === 'file')
|
|
983
|
+
return null;
|
|
984
|
+
const authority = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/(?:[^@/]*@)?([^/:]+)/.exec(trimmed);
|
|
985
|
+
return authority !== null ? authority[1].toLowerCase() : null;
|
|
986
|
+
}
|
|
987
|
+
// A Windows drive is a PATH, excluded by shape rather than by "a host is at least two letters"
|
|
988
|
+
// — which also rejected the single-letter host `h:path` (codex review of this PR, LOW).
|
|
989
|
+
if (/^[A-Za-z]:[\\/]/.test(trimmed))
|
|
990
|
+
return null;
|
|
991
|
+
// scp-like: `[user@]host:path`. The path may be ABSOLUTE (`git@example.com:/srv/git/repo.git`
|
|
992
|
+
// is valid scp syntax) — an earlier `(?!\/)` guard rejected exactly that and called a real
|
|
993
|
+
// remote a local path (codex review, LOW).
|
|
994
|
+
const scp = /^(?:[^@/\\]+@)?([A-Za-z0-9._-]+):/.exec(trimmed);
|
|
995
|
+
return scp !== null ? scp[1].toLowerCase() : null;
|
|
996
|
+
}
|
|
997
|
+
/** {@link DeliverOriginKind} for an origin URL read at compose time. */
|
|
998
|
+
export function classifyDeliverOrigin(url) {
|
|
999
|
+
if (url === null || url === undefined || url.trim() === '')
|
|
1000
|
+
return 'none';
|
|
1001
|
+
const host = originRemoteHost(url);
|
|
1002
|
+
if (host === null)
|
|
1003
|
+
return 'local';
|
|
1004
|
+
return host === 'github.com' || host.endsWith('.github.com') ? 'github' : 'other';
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* The gate card's first sentence for a NEW-PR delivery — what will ACTUALLY happen (F2).
|
|
1008
|
+
*
|
|
1009
|
+
* It used to promise "pushes its branch → opens a pull request" unconditionally. On a local, SSH,
|
|
1010
|
+
* GitLab, ADO or Gitea origin that was a false promise twice over: the phase pushed the branch and
|
|
1011
|
+
* then died on `gh pr create`, so the operator consented to a pull request that could not exist and
|
|
1012
|
+
* got an irreversible push plus a run that could not reach a terminal state. The push-only success
|
|
1013
|
+
* path in the script fixes the outcome; this fixes the consent.
|
|
1014
|
+
*
|
|
1015
|
+
* `null`/absent origin ⇒ the generic sentence, unchanged: "could not read it" is not a licence to
|
|
1016
|
+
* claim anything about it either way.
|
|
1017
|
+
*/
|
|
1018
|
+
/** The hosts a GitHub repository is pushed to: the web host and the ssh-over-443 endpoint. */
|
|
1019
|
+
const GITHUB_PUSH_HOSTS = new Set(['github.com', 'ssh.github.com']);
|
|
738
1020
|
/**
|
|
739
|
-
*
|
|
740
|
-
*
|
|
741
|
-
*
|
|
742
|
-
*
|
|
743
|
-
* pin it), or unset (pushes as whatever login gh holds). Never the token, never a live probe.
|
|
1021
|
+
* `owner/repo` of a github.com origin URL — `https://[user[:token]@]github.com/owner/repo(.git)`,
|
|
1022
|
+
* `ssh://git@github.com/owner/repo`, `git@github.com:owner/repo.git` — or `null` when the value is
|
|
1023
|
+
* not a github.com URL with exactly an owner/name path. Credentials never survive: only the path is
|
|
1024
|
+
* read (R1, ship-prove-3).
|
|
744
1025
|
*/
|
|
1026
|
+
export function githubRepoOf(url) {
|
|
1027
|
+
if (classifyDeliverOrigin(url) !== 'github')
|
|
1028
|
+
return null;
|
|
1029
|
+
const trimmed = (url ?? '').trim();
|
|
1030
|
+
// Only the two hosts a repository is pushed to (`ssh.github.com` is GitHub's ssh-over-443
|
|
1031
|
+
// endpoint). Another `*.github.com` (gist, api) names no repository `--repo` could open on —
|
|
1032
|
+
// the card keeps its generic sentence and gh resolves the remote itself. The script's drift
|
|
1033
|
+
// check accepts exactly these two hosts too (Copilot on crew#736).
|
|
1034
|
+
if (!GITHUB_PUSH_HOSTS.has(originRemoteHost(trimmed) ?? ''))
|
|
1035
|
+
return null;
|
|
1036
|
+
const schemed = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/(?:[^@/]*@)?[^/]+\/(.*)$/.exec(trimmed);
|
|
1037
|
+
const scp = schemed === null ? /^(?:[^@/\\]+@)?[A-Za-z0-9._-]+:(.*)$/.exec(trimmed) : null;
|
|
1038
|
+
const path = (schemed ?? scp)?.[1] ?? null;
|
|
1039
|
+
if (path === null)
|
|
1040
|
+
return null;
|
|
1041
|
+
const segs = path.replace(/[?#].*$/, '').split('/').filter((x) => x !== '');
|
|
1042
|
+
if (segs.length !== 2)
|
|
1043
|
+
return null;
|
|
1044
|
+
const owner = segs[0];
|
|
1045
|
+
const name = segs[1].replace(/\.git$/i, '');
|
|
1046
|
+
const part = /^[A-Za-z0-9._-]+$/;
|
|
1047
|
+
return part.test(owner) && part.test(name) && name !== '' ? `${owner}/${name}` : null;
|
|
1048
|
+
}
|
|
1049
|
+
/** The run branch the deliver script pushes — `wicked/<run id>` (the engine names run worktrees by
|
|
1050
|
+
* run id and the script pushes `wicked/<worktree basename>`) — or `null` when the run is unknown. */
|
|
1051
|
+
export function deliverRunBranch(runId) {
|
|
1052
|
+
// The id as the engine receives it — NOT trimmed: `" run "` takes the engine's hash tier, and a
|
|
1053
|
+
// trim here would announce `wicked/run` (Copilot on crew#736).
|
|
1054
|
+
const id = runId ?? '';
|
|
1055
|
+
// The charset alone is not a git ref component: `.run`, `run..next`, `run.lock`, `run.` are
|
|
1056
|
+
// illegal refs the engine must sanitize, so they name no branch here (Copilot on crew#736).
|
|
1057
|
+
// NTFS reserved device stems (`CON`, `con.txt`, `COM1`…) are rewritten and hashed by the
|
|
1058
|
+
// engine's `sanitize_worktree_id` too, so they name no branch either (Copilot on crew#736).
|
|
1059
|
+
const reserved = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?$/i;
|
|
1060
|
+
const refSafe = (x) => /^[A-Za-z0-9._-]+$/.test(x) && !x.startsWith('.') && !x.startsWith('-') && !x.includes('..') && !x.endsWith('.') && !x.endsWith('.lock') && !reserved.test(x);
|
|
1061
|
+
if (refSafe(id))
|
|
1062
|
+
return `wicked/${id}`;
|
|
1063
|
+
// The engine's hashless COLON tier (core#345/#347): an id whose only illegal char is `:` (a
|
|
1064
|
+
// campaign-shaped `<label>:<repo>:a0`) names its worktree and branch with each `:` as `-`
|
|
1065
|
+
// (Copilot on crew#736). Any other shape takes the engine's hash-suffixed tier, which is not
|
|
1066
|
+
// re-derived here — the card then says "the run branch" rather than guess.
|
|
1067
|
+
if (/^[A-Za-z0-9._-]+(?::[A-Za-z0-9._-]+)+$/.test(id) && refSafe(id.replaceAll(':', '-'))) {
|
|
1068
|
+
return `wicked/${id.replaceAll(':', '-')}`;
|
|
1069
|
+
}
|
|
1070
|
+
return null;
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* The gate card's first sentence for a NEW-PR delivery — what will ACTUALLY happen (F2). `runId`
|
|
1074
|
+
* names the branch (R2: the card used to print the literal placeholder `wicked/<run>`); without
|
|
1075
|
+
* one — the LAUNCH composer, before the run exists — it says "the run branch". The same sentence
|
|
1076
|
+
* is what `GET /repos/:id/deliver-target` answers, so launch and gate cannot disagree (R3).
|
|
1077
|
+
*/
|
|
1078
|
+
export function newPrTargetSentence(originUrl, runId) {
|
|
1079
|
+
const kind = classifyDeliverOrigin(originUrl);
|
|
1080
|
+
const runBranch = deliverRunBranch(runId);
|
|
1081
|
+
const branch = runBranch !== null ? `branch ${runBranch}` : 'the run branch';
|
|
1082
|
+
if (kind === 'github') {
|
|
1083
|
+
// R1: name the GitHub repository the pull request opens on, read off the origin URL (never
|
|
1084
|
+
// crew's registry label, never a credential).
|
|
1085
|
+
const repo = githubRepoOf(originUrl);
|
|
1086
|
+
return repo !== null
|
|
1087
|
+
? `Pushes ${branch} to ${repo} on GitHub and opens a pull request there; merge stays human.`
|
|
1088
|
+
: `Pushes ${branch} to origin and opens a pull request; merge stays human.`;
|
|
1089
|
+
}
|
|
1090
|
+
if (kind === 'local') {
|
|
1091
|
+
// NOT a flat "no pull request is opened": `gh pr create` resolves every configured remote, not
|
|
1092
|
+
// only `origin`, so a checkout whose origin is a path while some other remote is a GitHub
|
|
1093
|
+
// repository can still get one (codex review of this PR, MEDIUM). The push DESTINATION is what
|
|
1094
|
+
// this read establishes; the pull request is stated as the condition it actually is.
|
|
1095
|
+
return (`Pushes ${branch} to origin (${(originUrl ?? '').trim()}) — a local path, so no pull request ` +
|
|
1096
|
+
'can be opened against it: unless another remote in this checkout is a GitHub repository gh ' +
|
|
1097
|
+
'resolves, the pushed branch IS the delivery.');
|
|
1098
|
+
}
|
|
1099
|
+
if (kind === 'other') {
|
|
1100
|
+
const host = originRemoteHost((originUrl ?? '').trim()) ?? 'that host';
|
|
1101
|
+
return (`Pushes ${branch} to origin (${host}) and opens a pull request only if gh resolves ${host} ` +
|
|
1102
|
+
'as a GitHub host it is logged in to; otherwise no pull request is opened and the pushed ' +
|
|
1103
|
+
'branch IS the delivery. Merge stays human.');
|
|
1104
|
+
}
|
|
1105
|
+
if (originUrl === '') {
|
|
1106
|
+
return (`Pushes ${branch} to origin — but this repository has no \`origin\` remote, so the push ` +
|
|
1107
|
+
'will fail and nothing will be delivered. Add the remote first.');
|
|
1108
|
+
}
|
|
1109
|
+
return `Pushes ${branch} to origin and opens a pull request; merge stays human.`;
|
|
1110
|
+
}
|
|
1111
|
+
/** The launch-time reading of the same origin preflight the deliver gate uses (R3, crew#730). */
|
|
1112
|
+
export function deliverTargetView(originUrl) {
|
|
1113
|
+
const unread = originUrl === null || originUrl === undefined;
|
|
1114
|
+
return {
|
|
1115
|
+
origin: unread ? 'unknown' : classifyDeliverOrigin(originUrl),
|
|
1116
|
+
githubRepo: githubRepoOf(originUrl),
|
|
1117
|
+
sentence: newPrTargetSentence(originUrl),
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
/** The registered repo a launch's `repoRef` names — by id, then name, then checkout basename: the
|
|
1121
|
+
* ONE matcher the launch's origin read and `GET /repos/:id/deliver-target` share. */
|
|
1122
|
+
export function deliverRepoFor(repos, repoRef) {
|
|
1123
|
+
// Three passes, so the precedence is real: an exact id never loses to an earlier repo whose
|
|
1124
|
+
// name or checkout basename collides with it (codex review, MEDIUM).
|
|
1125
|
+
return (repos.find((r) => r.id === repoRef) ??
|
|
1126
|
+
repos.find((r) => r.name === repoRef) ??
|
|
1127
|
+
repos.find((r) => r.root_path.split(/[\\/]/).filter((x) => x !== '').pop() === repoRef));
|
|
1128
|
+
}
|
|
745
1129
|
export function deliverGateInstructions(opts) {
|
|
746
1130
|
const pr = opts.revisesPr ?? null;
|
|
747
1131
|
const target = pr !== null
|
|
748
|
-
? `Pushes
|
|
749
|
-
:
|
|
750
|
-
|
|
1132
|
+
? `Pushes ${deliverRunBranch(opts.runId) ?? 'the run branch'} onto pull request #${pr.number} (branch ${pr.headRef}); no new PR.`
|
|
1133
|
+
: newPrTargetSentence(opts.originUrl, opts.runId);
|
|
1134
|
+
// (crew#549) The SETTING wins over the env var, and the card names which one it read, because
|
|
1135
|
+
// "pin it now if it must differ" is not an instruction an operator can follow without knowing
|
|
1136
|
+
// where the pin lives.
|
|
1137
|
+
const configured = (opts.deliverIdentity ?? '').trim();
|
|
1138
|
+
const account = configured !== '' ? configured : (opts.ghAccount ?? null);
|
|
1139
|
+
const source = configured !== '' ? 'the deliver identity setting' : 'GH_ACCOUNT';
|
|
751
1140
|
const who = account !== null && account !== ''
|
|
752
1141
|
? opts.ghTokenPinned === true
|
|
753
|
-
? `Push identity:
|
|
754
|
-
: `Push identity:
|
|
755
|
-
: 'Push identity:
|
|
756
|
-
|
|
1142
|
+
? `Push identity: ${account} (${source}), pinned by GH_TOKEN — the phase refuses if gh's login differs at push time.`
|
|
1143
|
+
: `Push identity: ${account} (${source}) against the gh keyring — the login can change between the check and the push; export GH_TOKEN to pin it. The phase refuses if gh's login differs.`
|
|
1144
|
+
: 'Push identity: none configured — pushes as whatever login gh holds (set the deliver identity in system settings to pin it).';
|
|
1145
|
+
// Whatever is configured, the phase also asks git which credential IT would use for the
|
|
1146
|
+
// remote's host and refuses when the two logins disagree — the flip that pushed under an
|
|
1147
|
+
// unintended account (F-RC1-010).
|
|
1148
|
+
return `${target} ${who} It refuses if gh's login and git's credential for the remote disagree.`;
|
|
1149
|
+
}
|
|
1150
|
+
/**
|
|
1151
|
+
* The `deliver` PLAN STEP a delivering PRESET launch hands the engine (DES-TEAMING-002 T3,
|
|
1152
|
+
* `LaunchOptions.deliverStepJson`): catalog `deliver`, id `deliver`, the same hardened push-and-PR
|
|
1153
|
+
* Tool command, gate-card instructions and evidence-floor pin as the composed phase below. The
|
|
1154
|
+
* engine appends it to the preset's plan and puts `deliver` in the floor, so the run stays ONE
|
|
1155
|
+
* team plan behind its `plan_approval` gate — crew never composes a def over a preset. The step
|
|
1156
|
+
* omits `depends_on` (compose gives `deliver` the step before it) and carries no
|
|
1157
|
+
* `verified_evidence` (a plan step cannot; DES rev 12 note 4: a recorded difference with no
|
|
1158
|
+
* runtime effect). `phases` only feeds the embedded fallback text.
|
|
1159
|
+
*/
|
|
1160
|
+
export function deliverPresetStep(
|
|
1161
|
+
/** The preset launched, or `null` for a user-composed plan. */
|
|
1162
|
+
presetName, phases, runId, intent, launch = {}) {
|
|
1163
|
+
const apiOrigin = launch.apiOrigin ?? null;
|
|
1164
|
+
const revisesPr = launch.revisesPr ?? null;
|
|
1165
|
+
const facts = factsFromWorkflow({
|
|
1166
|
+
runId,
|
|
1167
|
+
intent,
|
|
1168
|
+
workflowId: presetName,
|
|
1169
|
+
repoRef: launch.repoRef ?? null,
|
|
1170
|
+
phases,
|
|
1171
|
+
runUrl: runUrlFor(configuredPublicOrigin(), runId),
|
|
1172
|
+
revisesPr: revisesPr === null ? null : { number: revisesPr.number, url: revisesPr.url },
|
|
1173
|
+
});
|
|
1174
|
+
const phase = deliverPrPhase([], intent, {
|
|
1175
|
+
runId,
|
|
1176
|
+
facts,
|
|
1177
|
+
apiOrigin,
|
|
1178
|
+
revisesPr,
|
|
1179
|
+
ghAccount: launch.ghAccount ?? null,
|
|
1180
|
+
ghTokenPinned: launch.ghTokenPinned === true,
|
|
1181
|
+
originUrl: launch.originUrl ?? null,
|
|
1182
|
+
deliverIdentity: launch.deliverIdentity ?? null,
|
|
1183
|
+
});
|
|
1184
|
+
return {
|
|
1185
|
+
catalog: 'deliver',
|
|
1186
|
+
id: DELIVER_PHASE_ID,
|
|
1187
|
+
instructions: phase.instructions ?? '',
|
|
1188
|
+
executor: phase.executor,
|
|
1189
|
+
validator_pin: EVIDENCE_FLOOR_PIN,
|
|
1190
|
+
};
|
|
757
1191
|
}
|
|
758
1192
|
export function composeDeliverWorkflow(base, runId, intent, launch = {}) {
|
|
759
1193
|
if (base.phases.some((p) => p.id === DELIVER_PHASE_ID)) {
|
|
@@ -774,7 +1208,7 @@ export function composeDeliverWorkflow(base, runId, intent, launch = {}) {
|
|
|
774
1208
|
workflowId: base.id,
|
|
775
1209
|
repoRef: launch.repoRef ?? null,
|
|
776
1210
|
phases: base.phases,
|
|
777
|
-
runUrl: runUrlFor(
|
|
1211
|
+
runUrl: runUrlFor(configuredPublicOrigin(), runId),
|
|
778
1212
|
revisesPr: revisesPr === null ? null : { number: revisesPr.number, url: revisesPr.url },
|
|
779
1213
|
});
|
|
780
1214
|
return {
|
|
@@ -790,6 +1224,10 @@ export function composeDeliverWorkflow(base, runId, intent, launch = {}) {
|
|
|
790
1224
|
revisesPr,
|
|
791
1225
|
ghAccount: launch.ghAccount ?? null,
|
|
792
1226
|
ghTokenPinned: launch.ghTokenPinned === true,
|
|
1227
|
+
// F2 — the origin the push will actually go to, so the gate card cannot promise a pull
|
|
1228
|
+
// request on a remote that can never carry one.
|
|
1229
|
+
originUrl: launch.originUrl ?? null,
|
|
1230
|
+
deliverIdentity: launch.deliverIdentity ?? null,
|
|
793
1231
|
}),
|
|
794
1232
|
],
|
|
795
1233
|
};
|