wicked-crew 0.6.0 → 0.7.1

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.
Files changed (117) hide show
  1. package/README.md +103 -0
  2. package/dist/api/audit.d.ts +13 -0
  3. package/dist/api/audit.d.ts.map +1 -1
  4. package/dist/api/audit.js +18 -2
  5. package/dist/api/audit.js.map +1 -1
  6. package/dist/api/endpoint-manifest-live.d.ts +29 -0
  7. package/dist/api/endpoint-manifest-live.d.ts.map +1 -0
  8. package/dist/api/endpoint-manifest-live.js +72 -0
  9. package/dist/api/endpoint-manifest-live.js.map +1 -0
  10. package/dist/api/endpoint-manifest.d.ts +107 -0
  11. package/dist/api/endpoint-manifest.d.ts.map +1 -0
  12. package/dist/api/endpoint-manifest.js +108 -0
  13. package/dist/api/endpoint-manifest.js.map +1 -0
  14. package/dist/api/guidance-index.d.ts +39 -0
  15. package/dist/api/guidance-index.d.ts.map +1 -0
  16. package/dist/api/guidance-index.js +67 -0
  17. package/dist/api/guidance-index.js.map +1 -0
  18. package/dist/api/open-path.d.ts +16 -0
  19. package/dist/api/open-path.d.ts.map +1 -1
  20. package/dist/api/open-path.js +22 -0
  21. package/dist/api/open-path.js.map +1 -1
  22. package/dist/api/requirements.d.ts +7 -0
  23. package/dist/api/requirements.d.ts.map +1 -1
  24. package/dist/api/requirements.js +23 -2
  25. package/dist/api/requirements.js.map +1 -1
  26. package/dist/api/retry-index.d.ts +30 -0
  27. package/dist/api/retry-index.d.ts.map +1 -0
  28. package/dist/api/retry-index.js +45 -0
  29. package/dist/api/retry-index.js.map +1 -0
  30. package/dist/api/routes.d.ts +53 -1
  31. package/dist/api/routes.d.ts.map +1 -1
  32. package/dist/api/routes.js +399 -30
  33. package/dist/api/routes.js.map +1 -1
  34. package/dist/api/run-files.d.ts +63 -0
  35. package/dist/api/run-files.d.ts.map +1 -0
  36. package/dist/api/run-files.js +271 -0
  37. package/dist/api/run-files.js.map +1 -0
  38. package/dist/api/server.d.ts +79 -0
  39. package/dist/api/server.d.ts.map +1 -1
  40. package/dist/api/server.js +183 -8
  41. package/dist/api/server.js.map +1 -1
  42. package/dist/api/stall-watchdog.d.ts +62 -0
  43. package/dist/api/stall-watchdog.d.ts.map +1 -0
  44. package/dist/api/stall-watchdog.js +138 -0
  45. package/dist/api/stall-watchdog.js.map +1 -0
  46. package/dist/cli/index.js +89 -15
  47. package/dist/cli/index.js.map +1 -1
  48. package/dist/core/adapter.d.ts +36 -10
  49. package/dist/core/adapter.d.ts.map +1 -1
  50. package/dist/core/adapter.js +245 -31
  51. package/dist/core/adapter.js.map +1 -1
  52. package/dist/core/bridge-reaper.d.ts +134 -0
  53. package/dist/core/bridge-reaper.d.ts.map +1 -0
  54. package/dist/core/bridge-reaper.js +286 -0
  55. package/dist/core/bridge-reaper.js.map +1 -0
  56. package/dist/core/deliver.d.ts +118 -0
  57. package/dist/core/deliver.d.ts.map +1 -0
  58. package/dist/core/deliver.js +241 -0
  59. package/dist/core/deliver.js.map +1 -0
  60. package/dist/core/deliverable-floor.d.ts +155 -0
  61. package/dist/core/deliverable-floor.d.ts.map +1 -0
  62. package/dist/core/deliverable-floor.js +248 -0
  63. package/dist/core/deliverable-floor.js.map +1 -0
  64. package/dist/core/exec.d.ts +2 -0
  65. package/dist/core/exec.d.ts.map +1 -1
  66. package/dist/core/exec.js.map +1 -1
  67. package/dist/core/types.d.ts +79 -1
  68. package/dist/core/types.d.ts.map +1 -1
  69. package/dist/core/types.js +3 -0
  70. package/dist/core/types.js.map +1 -1
  71. package/dist/interactive/bridge-pool.d.ts +52 -0
  72. package/dist/interactive/bridge-pool.d.ts.map +1 -1
  73. package/dist/interactive/bridge-pool.js +93 -12
  74. package/dist/interactive/bridge-pool.js.map +1 -1
  75. package/dist/interactive/chat-events.d.ts +207 -0
  76. package/dist/interactive/chat-events.d.ts.map +1 -0
  77. package/dist/interactive/chat-events.js +769 -0
  78. package/dist/interactive/chat-events.js.map +1 -0
  79. package/dist/interactive/demo-events.d.ts +283 -0
  80. package/dist/interactive/demo-events.d.ts.map +1 -0
  81. package/dist/interactive/demo-events.js +889 -0
  82. package/dist/interactive/demo-events.js.map +1 -0
  83. package/dist/interactive/draft-events.d.ts +87 -7
  84. package/dist/interactive/draft-events.d.ts.map +1 -1
  85. package/dist/interactive/draft-events.js +352 -49
  86. package/dist/interactive/draft-events.js.map +1 -1
  87. package/dist/interactive/edit-events.d.ts +25 -2
  88. package/dist/interactive/edit-events.d.ts.map +1 -1
  89. package/dist/interactive/edit-events.js +88 -9
  90. package/dist/interactive/edit-events.js.map +1 -1
  91. package/dist/interactive/repo-snapshot.d.ts +100 -0
  92. package/dist/interactive/repo-snapshot.d.ts.map +1 -0
  93. package/dist/interactive/repo-snapshot.js +289 -0
  94. package/dist/interactive/repo-snapshot.js.map +1 -0
  95. package/dist/projects/graph-paths.d.ts +122 -0
  96. package/dist/projects/graph-paths.d.ts.map +1 -0
  97. package/dist/projects/graph-paths.js +175 -0
  98. package/dist/projects/graph-paths.js.map +1 -0
  99. package/dist/projects/graph.d.ts +191 -0
  100. package/dist/projects/graph.d.ts.map +1 -0
  101. package/dist/projects/graph.js +829 -0
  102. package/dist/projects/graph.js.map +1 -0
  103. package/dist/projects/routes.d.ts +17 -0
  104. package/dist/projects/routes.d.ts.map +1 -1
  105. package/dist/projects/routes.js +139 -0
  106. package/dist/projects/routes.js.map +1 -1
  107. package/dist/qe/ledger.d.ts +3 -2
  108. package/dist/qe/ledger.d.ts.map +1 -1
  109. package/dist/qe/ledger.js +5 -4
  110. package/dist/qe/ledger.js.map +1 -1
  111. package/dist/studio/assets/index-D-BFUYnY.js +530 -0
  112. package/dist/studio/assets/index-D6S9zUtO.css +32 -0
  113. package/dist/studio/index.html +5 -3
  114. package/endpoint-manifest.json +624 -0
  115. package/package.json +8 -5
  116. package/dist/studio/assets/index-CCwXa1cn.js +0 -428
  117. package/dist/studio/assets/index-HWxo0h41.css +0 -32
@@ -0,0 +1,241 @@
1
+ /**
2
+ * The first-class deliver phase (crew#293) — a run opens its own PR, opt-in.
3
+ *
4
+ * Productizes the operator-side `feature-pr` overlay proven during the DES-MERGE-001 campaign:
5
+ * a Tool phase appended after the workflow's last phase that COMMITS the run's work, pushes the
6
+ * run's branch and opens a PR via `gh`. What was data on one machine becomes a launch option
7
+ * (`deliver: "pr"` on `POST /runs`), composed PER RUN — the shared workflow def is never mutated.
8
+ *
9
+ * Field-proven hardening, replicated here:
10
+ * (a) the branch is derived from the run worktree's basename (`wicked/<run-id>`), falling back
11
+ * to the current branch when that ref does not exist;
12
+ * (b) the script REFUSES to push `main`/`master` (or an empty/detached branch name) — the
13
+ * deliver phase only ever pushes run branches;
14
+ * (c) it STAGES AND COMMITS the run's work, then rebases onto origin's default branch before
15
+ * pushing, and a conflicting rebase FAILS the phase visibly (aborting the rebase, pushing
16
+ * nothing) rather than pushing a conflicted tree;
17
+ * (d) `git push -u origin <branch>`;
18
+ * (e) `gh pr create --head <branch> --fill`, with gh's output and exit status captured
19
+ * SEPARATELY so a gh failure fails the phase carrying gh's own message;
20
+ * (f) the PR URL is the last line of the phase output.
21
+ *
22
+ * One deliberate change from the field version: NO gh account is baked into crew code (the
23
+ * overlay guarded a personal account). Instead, when the `GH_ACCOUNT` env var is set the script
24
+ * compares it against `gh api user -q .login` and runs
25
+ * `gh auth switch --hostname github.com --user "$GH_ACCOUNT"` only when they differ.
26
+ *
27
+ * Merge stays human: the phase opens the PR, never merges it.
28
+ *
29
+ * ## crew#317 — "pushed an empty branch and reported success"
30
+ *
31
+ * Run `d1bc72c2` (wicked-studio) delivered nothing while reporting `completed`. The persisted
32
+ * unit is the evidence, and it names the cause precisely. Its `tool_cmd` was NOT this script —
33
+ * it was the operator's hand-written `feature-pr` OVERLAY def, which begins `set -e` with **no
34
+ * `pipefail`**, so `gh pr create … | tail -1` reported `tail`'s status (0) and the phase passed
35
+ * with gh's error text where the PR URL belongs:
36
+ *
37
+ * ```text
38
+ * could not compute title or body defaults: could not find any commits between origin/main and
39
+ * wicked/d1bc72c2-…
40
+ * ```
41
+ *
42
+ * So the masking mechanism the issue hypothesised is real, but it belonged to the overlay, not
43
+ * here: `pipefail` IS in force for this executor (it is line 1 of this script, `bash -lc` runs it
44
+ * verbatim, and core's `run_tool_cmd` maps any non-zero exit to `StepStatus::Failed`). The three
45
+ * defects this script genuinely shared with the overlay are fixed below:
46
+ *
47
+ * 1. **No commit.** Agents write files and do not commit, so the pushed branch equalled the
48
+ * default branch. The script now stages and commits the run's work itself, and REFUSES to
49
+ * push when there is nothing to deliver.
50
+ * 2. **A masked result.** `| tail -1` discarded everything gh said except one line and made the
51
+ * phase's verdict depend on a pipe option. gh's output and status are now captured
52
+ * separately, and success is re-derived from a real PR URL rather than from an exit code.
53
+ * 3. **Ungoverned.** The phase shipped `verified_evidence: false` / `validator_pin: null`, so
54
+ * nothing re-derived what it claimed. It now declares `verified_evidence: true` — see
55
+ * {@link deliverPrPhase}.
56
+ */
57
+ /** The id of the appended phase — also the collision probe when a def already delivers. */
58
+ export const DELIVER_PHASE_ID = 'deliver';
59
+ /** How much of the run's intent rides in the commit subject before it is truncated. */
60
+ const INTENT_SUBJECT_CAP = 72;
61
+ /**
62
+ * The run's intent, reduced to something safe to embed in a single-quoted shell assignment and
63
+ * to use as a one-line commit subject.
64
+ *
65
+ * The intent is caller-supplied free text off `POST /runs`, and it is being spliced into a bash
66
+ * script — so this is a containment boundary, not cosmetics. Control characters (newlines
67
+ * included) and the single quote are REMOVED: with no `'` left, the value cannot escape the
68
+ * single-quoted assignment the script wraps it in, and with no control characters it cannot add
69
+ * a line. Everything else (unicode, `$`, backticks, backslashes) is inert inside single quotes
70
+ * and is kept, so the subject still reads like the intent it came from.
71
+ */
72
+ function commitSubjectIntent(intent) {
73
+ return (intent ?? '')
74
+ .replace(/[\u0000-\u001f\u007f]+/g, ' ')
75
+ .replace(/'/g, '')
76
+ .replace(/\s+/g, ' ')
77
+ .trim()
78
+ .slice(0, INTENT_SUBJECT_CAP)
79
+ .trim();
80
+ }
81
+ /**
82
+ * The hardened deliver script, run as `bash -lc <script>` (login shell so the operator's PATH —
83
+ * where `gh` lives — is loaded, same as the field overlay).
84
+ *
85
+ * `set -euo pipefail` is load-bearing and verified in force for this executor (crew#317): the
86
+ * engine spawns `bash -lc` with this text verbatim, and `run_tool_cmd` turns a non-zero exit into
87
+ * `StepStatus::Failed`. It is no longer the ONLY thing standing between a failed `gh` and a green
88
+ * phase, though — the gh result is captured explicitly and success is re-derived from evidence.
89
+ *
90
+ * `intent` (the run's problem statement) rides in the commit subject; it is sanitised by
91
+ * {@link commitSubjectIntent} before it is spliced in.
92
+ */
93
+ export function deliverPrScript(intent) {
94
+ return [
95
+ 'set -euo pipefail',
96
+ // The engine concatenates the child's stdout and THEN its stderr, so anything git writes to
97
+ // stderr would land after the PR URL and break "(f) the URL is the last line". Folding stderr
98
+ // into stdout for the whole phase keeps the output in true chronological order and makes the
99
+ // final `echo "$URL"` genuinely last.
100
+ 'exec 2>&1',
101
+ // Account guard — env-driven, never a name baked into crew code. Unset ⇒ whatever account
102
+ // gh already holds is used as-is.
103
+ 'if [ -n "${GH_ACCOUNT:-}" ]; then L=$(gh api user -q .login); [ "$L" = "$GH_ACCOUNT" ] || gh auth switch --hostname github.com --user "$GH_ACCOUNT"; fi',
104
+ // (a) The run branch: wicked/<worktree-basename> (the engine names run worktrees by run id),
105
+ // falling back to the currently checked-out branch when that ref does not exist.
106
+ 'R=$(basename "$PWD")',
107
+ 'B="wicked/$R"',
108
+ 'git rev-parse --verify "$B" >/dev/null 2>&1 || B=$(git branch --show-current)',
109
+ // Derive origin's default branch FIRST — the refusal below must cover a repo whose
110
+ // default is trunk/develop/anything, not just main/master (Copilot on #303).
111
+ 'git fetch origin',
112
+ 'D=$(git symbolic-ref -q --short refs/remotes/origin/HEAD || echo origin/main)',
113
+ 'DEF="${D#origin/}"',
114
+ // (b) Refuse the repo's own default branch (by derived name), the classic names, and an
115
+ // empty name (detached HEAD), which would otherwise turn the push into a garbage ref.
116
+ 'case "$B" in ""|main|master|"$DEF") echo "deliver: refusing to push branch \'$B\' — the deliver phase only pushes run branches, never the default branch"; exit 1;; esac',
117
+ // The commit below writes to whatever HEAD is, while the push sends $B. When those differ
118
+ // (a `wicked/<run-id>` ref exists but is NOT what this worktree has checked out) committing
119
+ // would put one branch's work on another and push a branch that never saw it. Refuse instead.
120
+ 'C=$(git branch --show-current)',
121
+ '[ "$C" = "$B" ] || { echo "deliver: the worktree is on \'$C\' but the run branch is \'$B\' — refusing to commit one branch\'s work onto another; nothing was pushed"; exit 1; }',
122
+ // (c1) COMMIT THE RUN'S WORK (crew#317). Agents write files; they do not commit — which is
123
+ // the premise of core#291 and the reason `d1bc72c2` pushed a branch identical to origin/main.
124
+ // Author identity is deliberately NOT set here: the run worktree belongs to the operator's
125
+ // own clone, so `git commit` uses the repo/user config that already exists (and fails loudly,
126
+ // pushing nothing, if that config is missing). `git add -A` respects .gitignore.
127
+ `I='${commitSubjectIntent(intent)}'`,
128
+ 'if [ -n "$I" ]; then M="wicked-crew run $R: $I"; else M="wicked-crew run $R"; fi',
129
+ 'git add -A',
130
+ // Only commit when something is staged — a run that committed incrementally (core#280's
131
+ // liveness contract) leaves a clean tree and must not gain an empty commit here.
132
+ 'git diff --cached --quiet || git commit -q -m "$M"',
133
+ // (c2) NOTHING TO DELIVER — no staged work AND no commits of its own. Fail LOUDLY before the
134
+ // remote is touched: an empty ref pushed under a run id is worse than a failed phase.
135
+ 'A=$(git rev-list --count "$D..$B")',
136
+ '[ "$A" -ge 1 ] || { echo "deliver: nothing to deliver — the run produced no committed change ($B is not ahead of $D); nothing was pushed"; exit 1; }',
137
+ // (c3) Rebase onto origin's default branch so the PR opens mergeable. A conflict must fail
138
+ // the phase VISIBLY with nothing pushed — the abort leaves the worktree on the pre-rebase
139
+ // branch tip instead of mid-rebase.
140
+ 'git rebase "$D" "$B" || { git rebase --abort >/dev/null 2>&1 || true; echo "deliver: rebase of $B onto $D failed (conflicts) — resolve on the branch and re-run; nothing was pushed"; exit 1; }',
141
+ // Re-derive after the rebase: it drops commits already upstream (patch-id equal), so a branch
142
+ // that WAS ahead can come out of a rebase carrying nothing of its own.
143
+ 'A=$(git rev-list --count "$D..$B")',
144
+ '[ "$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; }',
145
+ // (d) Push.
146
+ 'git push -u origin "$B"',
147
+ // (e) Open the PR with gh's OUTPUT and EXIT STATUS captured separately (crew#317). The old
148
+ // `| tail -1` threw away everything gh said but one line and made the phase's verdict a
149
+ // property of a shell option; a gh failure now fails the phase carrying gh's own message.
150
+ 'if ! OUT=$(gh pr create --head "$B" --fill 2>&1); then echo "$OUT"; echo "deliver: gh pr create failed for $B — no PR was opened"; exit 1; fi',
151
+ 'echo "$OUT"',
152
+ // (f) DONE IS RE-DERIVED, NOT ASSERTED — twice, from two independent facts, before the phase
153
+ // is allowed to report a delivery:
154
+ // 1. gh actually produced a PR URL (an exit code alone is a claim, not evidence);
155
+ // 2. the ref ON THE REMOTE is ahead of origin's default branch by at least one commit.
156
+ "URL=$(printf '%s\\n' \"$OUT\" | grep -Eo 'https://[^[:space:]]+/pull/[0-9]+' | tail -1 || true)",
157
+ '[ -n "$URL" ] || { echo "deliver: gh pr create exited 0 but produced no PR URL for $B — refusing to report a delivery nothing can be pointed at"; exit 1; }',
158
+ 'P=$(git rev-list --count "$D..origin/$B")',
159
+ '[ "$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; }',
160
+ 'echo "$URL"',
161
+ ].join('\n');
162
+ }
163
+ /**
164
+ * The deliver phase definition — the PhaseDef JSON shape core accepts, fully spelled out so the
165
+ * composed def round-trips through `registerWorkflow` (core's serde) and crew's own `WorkflowDef`
166
+ * type without casts. `gate: 'auto'` + `executes_code: false`: the phase is deterministic tooling,
167
+ * not governed agent work — its failure surface is the exit code + output, which core reports as
168
+ * a failed unit.
169
+ *
170
+ * ## Why `verified_evidence: true` and `validator_pin: null` (crew#317)
171
+ *
172
+ * The phase that touches the remote was the one phase nothing re-derived. The engine's phase model
173
+ * DOES let a Tool-executor phase carry a deterministic floor, and the mechanism is the flag rather
174
+ * than a pin crew mints itself:
175
+ *
176
+ * - a pin is a CONTENT ADDRESS into core's validator vault, and `attach_pinned_validators` is
177
+ * fail-closed on one that does not resolve — it BAILS the run at plan time. Authoring and
178
+ * approving a validator is `wicked-core provision-validator` + `approve-validator`, neither of
179
+ * which is exposed through the napi surface crew drives, so a pin invented here would fail
180
+ * every run of every deliver-composed workflow on a machine that had not been seeded by hand;
181
+ * - `verified_evidence: true` with no pin of its own is armed AT REGISTRATION by
182
+ * `enforce_verified_evidence` with the built-in evidence floor (`EVIDENCE_FLOOR_PIN`,
183
+ * criterion: "the run left a change in its worktree (done is re-derived from the diff, never
184
+ * asserted)"), which `pre_distribute` seeds on the plan path so it always resolves. Same
185
+ * mechanism `feature/test`, `bug/verify` and `migration/verify` reach it by.
186
+ *
187
+ * The floor then re-runs against the run's worktree at the gate, INDEPENDENTLY of anything this
188
+ * script printed, and denies the phase when the run left no change. Layer 2 (the agent judge)
189
+ * stays out of it: core hands the Tool path `agent_verdict: None`, so a tool phase's floor is
190
+ * deterministic and costs no LLM call. The PR-URL and branch-ahead assertions stay in the script
191
+ * because no vaulted floor can see the remote.
192
+ */
193
+ export function deliverPrPhase(dependsOn = [], intent) {
194
+ return {
195
+ id: DELIVER_PHASE_ID,
196
+ kind: 'build',
197
+ executor: { type: 'tool', cmd: ['bash', '-lc', deliverPrScript(intent)] },
198
+ gate_type: null,
199
+ gate: 'auto',
200
+ executes_code: false,
201
+ verified_evidence: true,
202
+ required_deliverables: [],
203
+ depends_on: dependsOn,
204
+ role: 'neutral',
205
+ skill_ref: null,
206
+ allowed_skills: [],
207
+ validator_pin: null,
208
+ };
209
+ }
210
+ /**
211
+ * Compose a PER-RUN workflow def: `base`'s phases (untouched — the shared def is never mutated)
212
+ * plus the deliver phase appended last, under a run-scoped id. The caller registers the result
213
+ * with the engine for THIS run only; nothing is written to the overlay dir and the composed id
214
+ * never enters the user-workflow registry, so the catalog (`GET /workflows`) stays clean.
215
+ *
216
+ * `intent` is the run's problem statement — it names WHAT was delivered in the commit subject
217
+ * (`wicked-crew run <run-id>: <intent>`); omit it and the subject carries the run id alone.
218
+ *
219
+ * Throws when `base` already carries a `deliver` phase — appending a second phase with the same
220
+ * id would be ambiguous at best; the caller launches such a def as-is instead (see
221
+ * `CoreAdapter.launchRun`).
222
+ */
223
+ export function composeDeliverWorkflow(base, runId, intent) {
224
+ if (base.phases.some((p) => p.id === DELIVER_PHASE_ID)) {
225
+ throw new Error(`workflow '${base.id}' already has a '${DELIVER_PHASE_ID}' phase — launch it without deliver: "pr"`);
226
+ }
227
+ const last = base.phases[base.phases.length - 1];
228
+ // Run ids are UUIDs from the route, but the CLI path accepts caller-supplied ids — keep the
229
+ // composed id inside the same safe charset `registerWorkflow` enforces for user defs.
230
+ const safeRunId = runId.replace(/[^a-zA-Z0-9._-]/g, '_');
231
+ // registerWorkflow enforces id.length <= 128; a caller-supplied CLI session id can be long.
232
+ // Truncate the run-id TAIL, keeping the base+marker prefix intact (Copilot on #303).
233
+ const composedId = `${base.id}-deliver-${safeRunId}`.slice(0, 128);
234
+ return {
235
+ // No `is_system` on purpose: core's overlay/register schema rejects unknown fields, and the
236
+ // composed def is engine-input, not catalog data.
237
+ id: composedId,
238
+ phases: [...base.phases, deliverPrPhase(last !== undefined ? [last.id] : [], intent)],
239
+ };
240
+ }
241
+ //# sourceMappingURL=deliver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deliver.js","sourceRoot":"","sources":["../../src/core/deliver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAIH,2FAA2F;AAC3F,MAAM,CAAC,MAAM,gBAAgB,GAAG,SAAS,CAAC;AAE1C,uFAAuF;AACvF,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAE9B;;;;;;;;;;GAUG;AACH,SAAS,mBAAmB,CAAC,MAA0B;IACrD,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;SAClB,OAAO,CAAC,yBAAyB,EAAE,GAAG,CAAC;SACvC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;SACjB,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,IAAI,EAAE;SACN,KAAK,CAAC,CAAC,EAAE,kBAAkB,CAAC;SAC5B,IAAI,EAAE,CAAC;AACZ,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAAC,MAAe;IAC7C,OAAO;QACL,mBAAmB;QACnB,4FAA4F;QAC5F,8FAA8F;QAC9F,6FAA6F;QAC7F,sCAAsC;QACtC,WAAW;QACX,0FAA0F;QAC1F,kCAAkC;QAClC,yJAAyJ;QACzJ,6FAA6F;QAC7F,iFAAiF;QACjF,sBAAsB;QACtB,eAAe;QACf,+EAA+E;QAC/E,mFAAmF;QACnF,6EAA6E;QAC7E,kBAAkB;QAClB,+EAA+E;QAC/E,oBAAoB;QACpB,wFAAwF;QACxF,sFAAsF;QACtF,0KAA0K;QAC1K,0FAA0F;QAC1F,4FAA4F;QAC5F,8FAA8F;QAC9F,gCAAgC;QAChC,iLAAiL;QACjL,2FAA2F;QAC3F,8FAA8F;QAC9F,2FAA2F;QAC3F,8FAA8F;QAC9F,iFAAiF;QACjF,MAAM,mBAAmB,CAAC,MAAM,CAAC,GAAG;QACpC,kFAAkF;QAClF,YAAY;QACZ,wFAAwF;QACxF,iFAAiF;QACjF,oDAAoD;QACpD,6FAA6F;QAC7F,sFAAsF;QACtF,oCAAoC;QACpC,sJAAsJ;QACtJ,2FAA2F;QAC3F,0FAA0F;QAC1F,oCAAoC;QACpC,iMAAiM;QACjM,8FAA8F;QAC9F,uEAAuE;QACvE,oCAAoC;QACpC,wLAAwL;QACxL,YAAY;QACZ,yBAAyB;QACzB,2FAA2F;QAC3F,wFAAwF;QACxF,0FAA0F;QAC1F,+IAA+I;QAC/I,aAAa;QACb,6FAA6F;QAC7F,mCAAmC;QACnC,oFAAoF;QACpF,yFAAyF;QACzF,iGAAiG;QACjG,6JAA6J;QAC7J,2CAA2C;QAC3C,mJAAmJ;QACnJ,aAAa;KACd,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,UAAU,cAAc,CAAC,YAAsB,EAAE,EAAE,MAAe;IACtE,OAAO;QACL,EAAE,EAAE,gBAAgB;QACpB,IAAI,EAAE,OAAO;QACb,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC,EAAE;QACzE,SAAS,EAAE,IAAI;QACf,IAAI,EAAE,MAAM;QACZ,aAAa,EAAE,KAAK;QACpB,iBAAiB,EAAE,IAAI;QACvB,qBAAqB,EAAE,EAAE;QACzB,UAAU,EAAE,SAAS;QACrB,IAAI,EAAE,SAAS;QACf,SAAS,EAAE,IAAI;QACf,cAAc,EAAE,EAAE;QAClB,aAAa,EAAE,IAAI;KACpB,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,sBAAsB,CACpC,IAAiB,EACjB,KAAa,EACb,MAAe;IAEf,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,gBAAgB,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,KAAK,CACb,aAAa,IAAI,CAAC,EAAE,oBAAoB,gBAAgB,2CAA2C,CACpG,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACjD,4FAA4F;IAC5F,sFAAsF;IACtF,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC;IACzD,4FAA4F;IAC5F,qFAAqF;IACrF,MAAM,UAAU,GAAG,GAAG,IAAI,CAAC,EAAE,YAAY,SAAS,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACnE,OAAO;QACL,4FAA4F;QAC5F,kDAAkD;QAClD,EAAE,EAAE,UAAU;QACd,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,cAAc,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;KACtF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,155 @@
1
+ /**
2
+ * THE DELIVERABLE FLOOR (crew#311) — "done" is re-derived from the artifact, never asserted.
3
+ *
4
+ * ## The defect
5
+ *
6
+ * A governed unit that produced NOTHING still passed its execution gate on the strength of a
7
+ * prose reply. The engine's only universal floor is the SUBSTANCE floor
8
+ * (wicked-core `actor.rs`, "phase produced no reviewable substance"): a governed
9
+ * Creator/Neutral unit is rejected only when its output carries BOTH under 200 trimmed chars
10
+ * of prose AND no worktree change. So ~200 characters of narration — "I'll read the design
11
+ * spec, let me check the API types…" — clears it, with no file anywhere. Prose length stood in
12
+ * for evidence.
13
+ *
14
+ * The engine has two real evidence instruments, and neither reaches a run of this shape:
15
+ *
16
+ * 1. `validator_pin` → the built-in EVIDENCE floor (`builtin_floors::EVIDENCE_FLOOR_PIN`,
17
+ * criterion "the run left a change in its worktree (done is re-derived from the diff, never
18
+ * asserted)"). It re-verifies a git DIFF, and is fail-closed on a run with no worktree — so
19
+ * an UNBOUND run (`repoRef` omitted, `workdir: null`) can never satisfy it. Every crew
20
+ * interactive seam launches unbound on purpose (wicked-core#293/#294), and their deliverable
21
+ * is a FILE in a declared write root, invisible to a diff.
22
+ * 2. `required_deliverables` → enforced by wicked-core as of FINDING-101, but only in the
23
+ * WRAPPED-CLI runner (`execute_wrapped.rs`), only against the unit's own cwd, and any
24
+ * absolute or `..`-escaping path is counted MISSING by construction. The ACP runner — the
25
+ * path a real seat takes — never consults it. So for a run whose deliverable is an absolute
26
+ * path under `extraWriteRoots`, declaring it there would fail every run on one runner and
27
+ * check nothing on the other. That gap is wicked-core's to close
28
+ * (wicked-core#297, items 1 and 3); once it is, this phase can retire in favour of the
29
+ * engine field. Crew must not paper over it meanwhile by declaring something unverifiable.
30
+ *
31
+ * ## What this module does
32
+ *
33
+ * It gives crew the instrument it can honestly own: a deterministic TOOL phase, appended to a
34
+ * PER-RUN copy of the workflow def (the exact mechanism `core/deliver.ts` proved for
35
+ * `deliver: "pr"` — the shared def is never mutated, nothing lands in the overlay dir, the
36
+ * catalog never grows a per-run entry), which asserts that the artifacts the launcher declared
37
+ * EXIST, are non-empty, and were written by THIS run (crew#320 — existence alone re-derives
38
+ * nothing at a path a prior run already populated). A Tool phase's failure surface is its exit
39
+ * code: wicked-core's
40
+ * `run_tool_cmd` maps a non-zero exit to `StepStatus::Failed`, which fails the unit and the run
41
+ * through the unchanged completion path. No LLM, no worktree, no judgement about the content —
42
+ * only "the phase promised this file; is it there".
43
+ *
44
+ * That is deliberately a FLOOR and not a review, in exactly the sense `builtin_floors` documents:
45
+ * it proves an artifact exists. It says nothing about whether the artifact is correct, or even
46
+ * related to the task. What it removes is the failure mode crew#311 recorded — a unit whose
47
+ * `Write` was policy-denied, which therefore produced no file at all, reporting done.
48
+ *
49
+ * ## The report
50
+ *
51
+ * The script prints what was EXPECTED and what was FOUND before it exits, on both branches, so
52
+ * the failure is legible from the unit output alone: which paths were declared, which exist
53
+ * (with their size), and which are missing and why (absent / empty file / empty directory).
54
+ * An operator reading a failed run must never have to guess what the phase was supposed to
55
+ * produce.
56
+ */
57
+ import type { PhaseDef, WorkflowDef } from './types.js';
58
+ /** The id of the appended floor phase — also the collision probe when a def already carries one. */
59
+ export declare const DELIVERABLE_FLOOR_PHASE_ID = "verify-deliverables";
60
+ /** The marker line the failing branch prints. Asserted by tests and greppable in a unit output. */
61
+ export declare const DELIVERABLE_FLOOR_FAILURE_MARKER = "[wicked-crew] DELIVERABLE FLOOR FAILED";
62
+ /**
63
+ * Slack allowed on the freshness comparison, in milliseconds (crew#320).
64
+ *
65
+ * The launch timestamp comes from the daemon's `Date.now()` (millisecond resolution); the
66
+ * artifact timestamp comes from the FILESYSTEM, and not every filesystem keeps milliseconds —
67
+ * HFS+ and ext3 truncate mtime to whole seconds, and some SMB/NFS mounts are coarser still. A
68
+ * file genuinely written 400 ms after a launch at `…:07.600Z` can therefore report an mtime of
69
+ * `…:07.000Z` and read as older than the launch that produced it.
70
+ *
71
+ * One second absorbs that truncation without weakening the check this exists to make: the stale
72
+ * artifact crew#320 describes is the PREVIOUS run's output, which predates this run's launch by
73
+ * that run's whole remaining lifetime plus the turnaround before the next one — orders of
74
+ * magnitude more than a timestamp-granularity rounding error.
75
+ */
76
+ export declare const DELIVERABLE_FLOOR_MTIME_SLACK_MS = 1000;
77
+ /**
78
+ * The floor program, run as `node -e <script> <launchedAtMs> <path...>`.
79
+ *
80
+ * Node rather than a shell: this must work identically on macOS, Linux, and Windows, and the
81
+ * daemon already IS node, so `process.execPath` is a guaranteed-present absolute interpreter
82
+ * (see {@link deliverableFloorPhase}). `-e` runs CommonJS, so `require` is available; the launch
83
+ * timestamp and the declared paths arrive as `process.argv.slice(1)`, passed as argv rather than
84
+ * interpolated into a shell string so a path containing a space, a quote, or a `$` cannot be
85
+ * re-parsed.
86
+ *
87
+ * A path counts as PRODUCED when it exists, carries bytes, and was written by THIS RUN:
88
+ *
89
+ * - a non-empty file whose `mtimeMs` is at or after the launch timestamp;
90
+ * - a directory holding at least one direct entry whose `mtimeMs` is at or after it (a phase may
91
+ * declare a directory of outputs; the directory's own mtime is not enough, because a directory
92
+ * left over from a prior run keeps its entries and its mtime).
93
+ *
94
+ * A zero-byte file is NOT evidence — it is the same "nothing was produced" this exists to catch,
95
+ * and the draft/chat seams already treated an empty deliverable as a failure at their own
96
+ * post-hoc check.
97
+ *
98
+ * ## Why the timestamp (crew#320)
99
+ *
100
+ * Existence alone re-derives nothing when the path can be pre-populated. The interactive seams
101
+ * copy their deliverable to `outPath` and never remove it, and demo/draft/edit run directories
102
+ * are keyed by DOCUMENT ID (edit by `doc:version`) — so a second run over the same key starts
103
+ * with the previous run's file already sitting at the declared path. The floor would find it,
104
+ * find bytes, and pass: the artifact exists, but this run did not produce it. "Done" would be
105
+ * asserted by a leftover rather than derived from this run's work — the exact failure shape one
106
+ * level in from the one crew#311 closed.
107
+ *
108
+ * The comparison is the stronger of the two available fixes. Clearing `outPath` at launch would
109
+ * also work, but only for as long as every seam remembers to clear it; the mtime check survives
110
+ * a seam that forgets, and it survives a seam that has not been written yet.
111
+ *
112
+ * A launch timestamp that does not parse FAILS CLOSED. An armed floor that cannot judge freshness
113
+ * is not a floor, and passing on the grounds that the check itself is broken is precisely the
114
+ * "assert done" move this module exists to refuse.
115
+ */
116
+ export declare function deliverableFloorScript(): string;
117
+ /**
118
+ * The floor PhaseDef for `paths`, judged against `launchedAtMs`.
119
+ *
120
+ * `launchedAtMs` is positional and required rather than defaulted (crew#320): a default would let
121
+ * a new caller arm a floor that checks existence only, and the resulting run would report done on
122
+ * a leftover with nothing in its output saying the freshness half was never applied. The compiler
123
+ * asking the question is the point.
124
+ *
125
+ * `cmd[0]` is `process.execPath` — the ABSOLUTE path of the node binary already running the
126
+ * daemon — not the bare name `node`. wicked-core preflights every Tool phase at launch
127
+ * (`workflow::preflight_tool_phases`) and REFUSES to start a run whose tool binary does not
128
+ * resolve; a bare `node` resolves only if the engine's PATH happens to carry one, which is
129
+ * exactly the kind of environment-dependent gate that fails in production and passes in tests.
130
+ * The composed def is per-run and hot-registered only, so baking a machine-local path into it
131
+ * costs nothing: it never reaches the catalog or the overlay dir.
132
+ *
133
+ * `role: 'neutral'` + `executes_code: false` + `governed`-free Tool dispatch: this phase is
134
+ * deterministic tooling, so the substance floor, the agent judge, and the input-governance fold
135
+ * do not apply to it — its verdict IS its exit code.
136
+ */
137
+ export declare function deliverableFloorPhase(paths: readonly string[], launchedAtMs: number, dependsOn?: string[]): PhaseDef;
138
+ /**
139
+ * Compose a PER-RUN def: `base`'s phases (untouched — the shared def is never mutated) plus the
140
+ * deliverable floor appended last, under a run-scoped id. Mirrors `composeDeliverWorkflow`'s
141
+ * contract exactly, including the id charset/length rules `registerWorkflow` enforces and the
142
+ * deliberate absence of `is_system` (core's register schema rejects unknown fields on this path,
143
+ * and a composed def is engine input, not catalog data).
144
+ *
145
+ * Throws on an empty `paths` list — a floor over nothing would pass vacuously, which is the
146
+ * defect, not the fix. Throws on a def that already carries the floor phase, for the same
147
+ * ambiguity reason `composeDeliverWorkflow` does.
148
+ *
149
+ * `launchedAtMs` defaults to composition time, which is the correct instant on every path that
150
+ * exists: composition is the last thing that happens before `core.launchRun`, so nothing this run
151
+ * produces can predate it, and a nonsense value is rejected here rather than being discovered by
152
+ * the tool phase minutes later (crew#320).
153
+ */
154
+ export declare function composeDeliverableFloor(base: WorkflowDef, runId: string, paths: readonly string[], launchedAtMs?: number): WorkflowDef;
155
+ //# sourceMappingURL=deliverable-floor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deliverable-floor.d.ts","sourceRoot":"","sources":["../../src/core/deliverable-floor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAExD,oGAAoG;AACpG,eAAO,MAAM,0BAA0B,wBAAwB,CAAC;AAEhE,mGAAmG;AACnG,eAAO,MAAM,gCAAgC,2CACH,CAAC;AAE3C;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,gCAAgC,OAAQ,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAuC/C;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,YAAY,EAAE,MAAM,EACpB,SAAS,GAAE,MAAM,EAAO,GACvB,QAAQ,CA4BV;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,YAAY,GAAE,MAAmB,GAChC,WAAW,CAiCb"}