@deftai/directive-core 0.100.0 → 0.101.0

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 (85) hide show
  1. package/dist/authz/classify.js +100 -23
  2. package/dist/authz/decompose-apply.d.ts +14 -0
  3. package/dist/authz/decompose-apply.js +93 -18
  4. package/dist/check/cached-orchestrator.d.ts +26 -5
  5. package/dist/check/cached-orchestrator.js +228 -6
  6. package/dist/check/gate-lists.d.ts +10 -4
  7. package/dist/check/gate-lists.js +19 -5
  8. package/dist/check/index.d.ts +3 -2
  9. package/dist/check/index.js +2 -1
  10. package/dist/check/named-cause.d.ts +45 -0
  11. package/dist/check/named-cause.js +121 -0
  12. package/dist/check/orchestrator.js +6 -1
  13. package/dist/doctor/help.d.ts +5 -0
  14. package/dist/doctor/help.js +31 -0
  15. package/dist/doctor/index.d.ts +2 -0
  16. package/dist/doctor/index.js +2 -0
  17. package/dist/doctor/main.js +58 -0
  18. package/dist/doctor/session-coda.d.ts +82 -0
  19. package/dist/doctor/session-coda.js +151 -0
  20. package/dist/doctor/types.d.ts +16 -0
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.js +2 -0
  23. package/dist/init-deposit/gitignore.js +2 -0
  24. package/dist/intake/issue-ingest.js +7 -0
  25. package/dist/policy/ac-pass-banking.d.ts +58 -0
  26. package/dist/policy/ac-pass-banking.js +150 -0
  27. package/dist/policy/ceremony-dial.d.ts +29 -0
  28. package/dist/policy/ceremony-dial.js +76 -0
  29. package/dist/policy/index.d.ts +1 -0
  30. package/dist/policy/index.js +15 -1
  31. package/dist/preflight-cache/evaluate.d.ts +3 -0
  32. package/dist/preflight-cache/evaluate.js +3 -0
  33. package/dist/product-first-done-gate/acceptance.d.ts +25 -0
  34. package/dist/product-first-done-gate/acceptance.js +247 -0
  35. package/dist/product-first-done-gate/check-mode.d.ts +45 -0
  36. package/dist/product-first-done-gate/check-mode.js +148 -0
  37. package/dist/product-first-done-gate/evaluate.d.ts +50 -0
  38. package/dist/product-first-done-gate/evaluate.js +255 -0
  39. package/dist/product-first-done-gate/index.d.ts +11 -0
  40. package/dist/product-first-done-gate/index.js +11 -0
  41. package/dist/product-first-done-gate/types.d.ts +64 -0
  42. package/dist/product-first-done-gate/types.js +61 -0
  43. package/dist/run-summary/emit.d.ts +56 -0
  44. package/dist/run-summary/emit.js +171 -0
  45. package/dist/run-summary/index.d.ts +4 -0
  46. package/dist/run-summary/index.js +4 -0
  47. package/dist/run-summary/path.d.ts +25 -0
  48. package/dist/run-summary/path.js +78 -0
  49. package/dist/run-summary/types.d.ts +76 -0
  50. package/dist/run-summary/types.js +21 -0
  51. package/dist/scope/acceptance-evidence.d.ts +34 -0
  52. package/dist/scope/acceptance-evidence.js +84 -10
  53. package/dist/scope/decompose.d.ts +5 -0
  54. package/dist/scope/decompose.js +11 -2
  55. package/dist/session/ac-pass-banking.d.ts +221 -0
  56. package/dist/session/ac-pass-banking.js +761 -0
  57. package/dist/session/deposit-sha.d.ts +49 -0
  58. package/dist/session/deposit-sha.js +121 -0
  59. package/dist/session/effort-budget.d.ts +6 -0
  60. package/dist/session/effort-budget.js +21 -3
  61. package/dist/session/index.d.ts +5 -0
  62. package/dist/session/index.js +5 -0
  63. package/dist/session/orientation-compression.d.ts +127 -0
  64. package/dist/session/orientation-compression.js +425 -0
  65. package/dist/session/orientation-state.d.ts +29 -0
  66. package/dist/session/orientation-state.js +85 -0
  67. package/dist/session/session-start.d.ts +25 -0
  68. package/dist/session/session-start.js +175 -0
  69. package/dist/session/toolchain-preflight.d.ts +64 -0
  70. package/dist/session/toolchain-preflight.js +151 -0
  71. package/dist/triage/bootstrap/gitignore.d.ts +1 -1
  72. package/dist/triage/bootstrap/gitignore.js +8 -2
  73. package/dist/triage/bootstrap/index.js +15 -3
  74. package/dist/triage/classify/index.d.ts +1 -0
  75. package/dist/triage/classify/index.js +2 -0
  76. package/dist/triage/classify/label-mirror.js +16 -0
  77. package/dist/triage/classify/mirror-discovery-tip.d.ts +95 -0
  78. package/dist/triage/classify/mirror-discovery-tip.js +234 -0
  79. package/dist/triage/reconcile/reconcile.js +28 -6
  80. package/dist/triage/summary/index.js +12 -4
  81. package/dist/triage/welcome/default-mode.js +16 -0
  82. package/dist/ts-check-lane/run-lane.js +21 -4
  83. package/dist/vbrief-validate/conformance.js +7 -0
  84. package/dist/vitest-runner/coverage-debt-teardown.js +16 -4
  85. package/package.json +11 -3
@@ -204,11 +204,25 @@ const DOWNLOADER_DECODER_BINS = new Set([
204
204
  "fetch",
205
205
  "socat",
206
206
  "lftp",
207
+ // #3288 residual after #3245: crypto / alt-download / pipe / archive bins.
208
+ "gpg",
209
+ "age",
210
+ "zstd",
211
+ "unzstd",
212
+ "sftp",
213
+ "wget2",
214
+ "http",
215
+ "https",
216
+ "yt-dlp",
217
+ "ytdlp",
218
+ "aria2",
219
+ "mbuffer",
220
+ "cpio",
207
221
  ]);
208
222
  /**
209
223
  * Archive extractors / alt writers that can plant via pathish operands without
210
- * shell redirects (#3245). Used for pathish authz/kill scans (prefer deny) and
211
- * write-shape residual under UAT — not bare curl-class URL fetches.
224
+ * shell redirects (#3245 / #3288). Used for pathish authz/kill scans (prefer deny)
225
+ * and write-shape residual under UAT — not bare curl-class URL fetches.
212
226
  */
213
227
  const ARCHIVE_ALT_WRITE_BINS = new Set([
214
228
  "tar",
@@ -222,7 +236,54 @@ const ARCHIVE_ALT_WRITE_BINS = new Set([
222
236
  "fetch",
223
237
  "socat",
224
238
  "lftp",
239
+ // #3288 residual after #3245.
240
+ "gpg",
241
+ "age",
242
+ "zstd",
243
+ "unzstd",
244
+ "sftp",
245
+ "wget2",
246
+ "http",
247
+ "https",
248
+ "yt-dlp",
249
+ "ytdlp",
250
+ "aria2",
251
+ "mbuffer",
252
+ "cpio",
225
253
  ]);
254
+ /**
255
+ * Bins whose pathish operands are scanned for authz/kill destinations (#3213 / #3245 / #3288).
256
+ * Prefer a Set over a long `||` chain so coverage counts one membership check, not N branches.
257
+ */
258
+ const PROTECTED_POSITIONAL_BINS = new Set([
259
+ "scp",
260
+ "certutil",
261
+ "rclone",
262
+ "tar",
263
+ "bsdtar",
264
+ "unzip",
265
+ "7z",
266
+ "7za",
267
+ "7zr",
268
+ "socat",
269
+ "lftp",
270
+ // #3288 residual.
271
+ "sftp",
272
+ "cpio",
273
+ "gpg",
274
+ "age",
275
+ "zstd",
276
+ "unzstd",
277
+ "mbuffer",
278
+ ]);
279
+ /** wget family (directory-prefix dest flags). */
280
+ const WGET_FAMILY_BINS = new Set(["wget", "wget2"]);
281
+ /** aria2 family (dir dest flags). */
282
+ const ARIA2_FAMILY_BINS = new Set(["aria2c", "aria2"]);
283
+ /** 7z family (attached -oDIR only). */
284
+ const SEVEN_Z_FAMILY_BINS = new Set(["7z", "7za", "7zr"]);
285
+ /** tar family (chdir -C / --directory). */
286
+ const TAR_FAMILY_BINS = new Set(["tar", "bsdtar"]);
226
287
  /**
227
288
  * File destination flags for downloaders/decoders (#3206).
228
289
  * normalizeToken lowercases, so wget `-O` and curl `-o` share `-o`.
@@ -248,6 +309,13 @@ const TAR_DIR_DEST_FLAGS_EXACT = new Set(["-C"]);
248
309
  const TAR_DIR_DEST_FLAGS_LOWER = new Set(["--directory"]);
249
310
  /** unzip extract directory (#3245). */
250
311
  const UNZIP_DIR_DEST_FLAGS = new Set(["-d"]);
312
+ /**
313
+ * cpio chdir before extract/create (#3288).
314
+ * Case-sensitive short form: POSIX cpio uses capital `-D`; lower `-d` is a create option bit.
315
+ * Long form `--directory` is accepted lowercased.
316
+ */
317
+ const CPIO_DIR_DEST_FLAGS_EXACT = new Set(["-D"]);
318
+ const CPIO_DIR_DEST_FLAGS_LOWER = new Set(["--directory"]);
251
319
  /**
252
320
  * Symlink / hard-link plant bins (#3213). Absent from prior killWriteBins →
253
321
  * `ln -sf … .deft-directive-disable` classified empty → UAT fail-open.
@@ -285,18 +353,27 @@ function isDownloaderDestFlag(flag, bin, rawFlag) {
285
353
  if (bin === "scp")
286
354
  return false;
287
355
  // 7z family: only attached `-oDIR` (parsed in attached-short branch), not separate `-o PATH`.
288
- if (bin === "7z" || bin === "7za" || bin === "7zr") {
356
+ if (SEVEN_Z_FAMILY_BINS.has(bin)) {
357
+ return false;
358
+ }
359
+ // #3288: cpio `-o` is copy-out (create archive), not a file dest; dest is `-D` / `--directory`.
360
+ if (bin === "cpio") {
361
+ if (rawFlag !== undefined && CPIO_DIR_DEST_FLAGS_EXACT.has(rawFlag))
362
+ return true;
363
+ // Long form is case-insensitive via normalizeToken (`flag` already lowercased).
364
+ if (CPIO_DIR_DEST_FLAGS_LOWER.has(flag))
365
+ return true;
289
366
  return false;
290
367
  }
291
368
  if (DOWNLOADER_FILE_DEST_FLAGS.has(flag))
292
369
  return true;
293
370
  if (bin === "curl" && CURL_DIR_DEST_FLAGS.has(flag))
294
371
  return true;
295
- if (bin === "wget" && WGET_DIR_DEST_FLAGS.has(flag))
372
+ if (WGET_FAMILY_BINS.has(bin) && WGET_DIR_DEST_FLAGS.has(flag))
296
373
  return true;
297
- if (bin === "aria2c" && ARIA2C_DIR_DEST_FLAGS.has(flag))
374
+ if (ARIA2_FAMILY_BINS.has(bin) && ARIA2C_DIR_DEST_FLAGS.has(flag))
298
375
  return true;
299
- if (bin === "tar" || bin === "bsdtar") {
376
+ if (TAR_FAMILY_BINS.has(bin)) {
300
377
  if (rawFlag !== undefined && TAR_DIR_DEST_FLAGS_EXACT.has(rawFlag))
301
378
  return true;
302
379
  if (TAR_DIR_DEST_FLAGS_LOWER.has(flag))
@@ -386,17 +463,7 @@ function downloaderDecoderDestinations(tokens) {
386
463
  // Segment breaks (`;`/`\n`/glued ops) prevent following-command overwrite.
387
464
  let lastPositionalPath = null;
388
465
  const protectedPathish = [];
389
- const collectsProtectedPositionals = bin === "scp" ||
390
- bin === "certutil" ||
391
- bin === "rclone" ||
392
- bin === "tar" ||
393
- bin === "bsdtar" ||
394
- bin === "unzip" ||
395
- bin === "7z" ||
396
- bin === "7za" ||
397
- bin === "7zr" ||
398
- bin === "socat" ||
399
- bin === "lftp";
466
+ const collectsProtectedPositionals = PROTECTED_POSITIONAL_BINS.has(bin);
400
467
  while (i < tokens.length) {
401
468
  const raw = tokens[i];
402
469
  const n = normalizeToken(raw);
@@ -463,19 +530,23 @@ function downloaderDecoderDestinations(tokens) {
463
530
  i++;
464
531
  continue;
465
532
  }
466
- if (n.startsWith("-o") && !n.startsWith("-out") && n.length > 2) {
533
+ // cpio `-o` is copy-out create mode, not a file dest (#3288).
534
+ if (bin !== "cpio" && n.startsWith("-o") && !n.startsWith("-out") && n.length > 2) {
467
535
  dests.push(pathishToken(raw.slice(2)));
468
536
  i++;
469
537
  continue;
470
538
  }
471
- // wget attached -Pdir
472
- if (bin === "wget" && n.startsWith("-p") && n.length > 2 && !n.startsWith("-proxy")) {
539
+ // wget / wget2 attached -Pdir
540
+ if (WGET_FAMILY_BINS.has(bin) &&
541
+ n.startsWith("-p") &&
542
+ n.length > 2 &&
543
+ !n.startsWith("-proxy")) {
473
544
  dests.push(pathishToken(raw.slice(2)));
474
545
  i++;
475
546
  continue;
476
547
  }
477
- // aria2c attached -dDIR (#3213)
478
- if (bin === "aria2c" && n.startsWith("-d") && n.length > 2) {
548
+ // aria2c / aria2 attached -dDIR (#3213 / #3288)
549
+ if (ARIA2_FAMILY_BINS.has(bin) && n.startsWith("-d") && n.length > 2) {
479
550
  dests.push(pathishToken(raw.slice(2)));
480
551
  i++;
481
552
  continue;
@@ -487,7 +558,13 @@ function downloaderDecoderDestinations(tokens) {
487
558
  continue;
488
559
  }
489
560
  // tar attached -CDIR (rare; capital C required)
490
- if ((bin === "tar" || bin === "bsdtar") && raw.startsWith("-C") && raw.length > 2) {
561
+ if (TAR_FAMILY_BINS.has(bin) && raw.startsWith("-C") && raw.length > 2) {
562
+ dests.push(pathishToken(raw.slice(2)));
563
+ i++;
564
+ continue;
565
+ }
566
+ // cpio attached -DDIR (#3288; capital D required)
567
+ if (bin === "cpio" && raw.startsWith("-D") && raw.length > 2) {
491
568
  dests.push(pathishToken(raw.slice(2)));
492
569
  i++;
493
570
  continue;
@@ -34,6 +34,20 @@ export interface EvaluateDecomposeStructuralApplyInput {
34
34
  export declare function sha256Hex(data: Buffer | string): string;
35
35
  /** SHA-256 hex of file bytes at path (exact on-disk digest). */
36
36
  export declare function sha256FileHex(path: string): string;
37
+ /**
38
+ * Exact operator CLI mint command for a structural decompose apply grant (#3291).
39
+ * Paths are project-relative POSIX when inside projectRoot; otherwise as provided.
40
+ * Parent / draft / repo tokens are shell-quoted when they contain whitespace or
41
+ * shell metacharacters. When POSIX and PowerShell apostrophe escapes differ,
42
+ * both forms are emitted on separate labeled lines (`bash:` / `pwsh:`) so each
43
+ * is independently copy-pasteable. Paths containing newline or NUL are refused
44
+ * (no rewritten fake path) so the operator renames before minting. Does not
45
+ * include agent/CI/TTY gates — those remain on the CLI multi-factor path.
46
+ */
47
+ export declare function formatDecomposeStructuralMintCommand(parentPath: string, draftPath: string, options?: {
48
+ readonly projectRoot?: string;
49
+ readonly repo?: string | null;
50
+ }): string;
37
51
  /**
38
52
  * Normalize a path to project-relative POSIX form for grant binding compare.
39
53
  * Returns null when the path escapes the project root.
@@ -23,6 +23,69 @@ export function sha256Hex(data) {
23
23
  export function sha256FileHex(path) {
24
24
  return sha256Hex(readFileSync(path));
25
25
  }
26
+ /**
27
+ * Quote a CLI token for operator copy-paste when it has whitespace or shell
28
+ * metacharacters (Greptile #3291 / PR #3300). Safe bare tokens stay unquoted
29
+ * so deny-reason mint hints match the common path shape.
30
+ *
31
+ * Single quotes keep `$()`, backticks, and `!` inert. Apostrophe escape differs
32
+ * by shell: POSIX uses `'\''`; PowerShell uses `''`. Callers that need both
33
+ * pass dialect `"posix"` or `"pwsh"`. Does not rewrite path bytes (no newline
34
+ * flattening) — callers must refuse control chars before quoting.
35
+ */
36
+ function shellQuoteCliArg(value, dialect) {
37
+ // Allowlist: alnum + common path/repo chars without whitespace or shell meta.
38
+ if (value.length > 0 && /^[A-Za-z0-9_./:@+=,-]+$/.test(value)) {
39
+ return value;
40
+ }
41
+ if (dialect === "pwsh") {
42
+ // PowerShell single-quoted string: only ' is special, doubled as ''.
43
+ return `'${value.replace(/'/g, "''")}'`;
44
+ }
45
+ // POSIX single-quote; only ' needs escaping as '\''
46
+ return `'${value.replace(/'/g, `'\\''`)}'`;
47
+ }
48
+ function mintCommandWithDialect(parent, draft, repo, dialect) {
49
+ return (`deft authz:grant -- --parent ${shellQuoteCliArg(parent, dialect)} --draft ${shellQuoteCliArg(draft, dialect)}` +
50
+ (repo ? ` --repo ${shellQuoteCliArg(repo, dialect)}` : "") +
51
+ " --confirm");
52
+ }
53
+ /** True when a path/repo token cannot be represented as an exact shell mint arg. */
54
+ function hasUnsafeMintControlChars(value) {
55
+ return /[\r\n\0]/.test(value);
56
+ }
57
+ /**
58
+ * Exact operator CLI mint command for a structural decompose apply grant (#3291).
59
+ * Paths are project-relative POSIX when inside projectRoot; otherwise as provided.
60
+ * Parent / draft / repo tokens are shell-quoted when they contain whitespace or
61
+ * shell metacharacters. When POSIX and PowerShell apostrophe escapes differ,
62
+ * both forms are emitted on separate labeled lines (`bash:` / `pwsh:`) so each
63
+ * is independently copy-pasteable. Paths containing newline or NUL are refused
64
+ * (no rewritten fake path) so the operator renames before minting. Does not
65
+ * include agent/CI/TTY gates — those remain on the CLI multi-factor path.
66
+ */
67
+ export function formatDecomposeStructuralMintCommand(parentPath, draftPath, options) {
68
+ let parent = parentPath.replace(/\\/g, "/");
69
+ let draft = draftPath.replace(/\\/g, "/");
70
+ const root = options?.projectRoot;
71
+ if (root) {
72
+ parent = toProjectRelativePosix(root, parentPath) ?? parent;
73
+ draft = toProjectRelativePosix(root, draftPath) ?? draft;
74
+ }
75
+ const repo = (options?.repo ?? "").trim();
76
+ if (hasUnsafeMintControlChars(parent) ||
77
+ hasUnsafeMintControlChars(draft) ||
78
+ hasUnsafeMintControlChars(repo)) {
79
+ return ("rename parent/draft/repo to remove newline or NUL characters, then run: " +
80
+ "deft authz:grant -- --parent <parent.xbrief.json> --draft <draft.json> [--repo owner/name] --confirm");
81
+ }
82
+ const posix = mintCommandWithDialect(parent, draft, repo, "posix");
83
+ const pwsh = mintCommandWithDialect(parent, draft, repo, "pwsh");
84
+ // Labels on their own lines so the executable command is independently pasteable.
85
+ if (posix === pwsh)
86
+ return posix;
87
+ return `bash:\n${posix}\npwsh:\n${pwsh}`;
88
+ }
26
89
  /**
27
90
  * Normalize a path to project-relative POSIX form for grant binding compare.
28
91
  * Returns null when the path escapes the project root.
@@ -56,7 +119,7 @@ function normalizeDigest(raw) {
56
119
  function pathsEqual(a, b) {
57
120
  return a.replace(/\\/g, "/") === b.replace(/\\/g, "/");
58
121
  }
59
- function grantValidity(grant, now) {
122
+ function grantValidity(grant, now, mintHint) {
60
123
  if (!isHumanOriginGrant(grant)) {
61
124
  const kind = grant.origin.kind;
62
125
  if (isRejectedOriginKind(kind)) {
@@ -65,14 +128,14 @@ function grantValidity(grant, now) {
65
128
  code: "authz-grant-origin-reject",
66
129
  reason: `Directive denied scope:decompose apply: grant ${grant.id} origin.kind=${kind} is ` +
67
130
  "agent/self-authored and cannot approve a decomposition draft. " +
68
- "Human action required: mint via `deft authz:grant` (operator-cli) bound to the draft digest.",
131
+ `Human action required: \`${mintHint}\`.`,
69
132
  };
70
133
  }
71
134
  return {
72
135
  ok: false,
73
136
  code: "authz-grant-origin-reject",
74
137
  reason: `Directive denied scope:decompose apply: grant ${grant.id} lacks human-origin provenance. ` +
75
- "Human action required: `deft authz:grant` with operator-cli origin.",
138
+ `Human action required: \`${mintHint}\`.`,
76
139
  };
77
140
  }
78
141
  if (grant.semantics.revokedAt !== null) {
@@ -86,7 +149,8 @@ function grantValidity(grant, now) {
86
149
  return {
87
150
  ok: false,
88
151
  code: "authz-grant-single-use-spent",
89
- reason: `Directive denied scope:decompose apply: single-use grant ${grant.id} already spent at ${grant.semantics.usedAt}.`,
152
+ reason: `Directive denied scope:decompose apply: single-use grant ${grant.id} already spent at ${grant.semantics.usedAt}. ` +
153
+ `Human action required: \`${mintHint}\`.`,
90
154
  };
91
155
  }
92
156
  if (grant.semantics.expiresAt !== null) {
@@ -96,7 +160,7 @@ function grantValidity(grant, now) {
96
160
  ok: false,
97
161
  code: "authz-grant-expired",
98
162
  reason: `Directive denied scope:decompose apply: grant ${grant.id} expired at ${grant.semantics.expiresAt}. ` +
99
- "Human action required: re-mint a grant for the current draft digest.",
163
+ `Human action required: \`${mintHint}\`.`,
100
164
  };
101
165
  }
102
166
  }
@@ -116,11 +180,16 @@ export function evaluateDecomposeStructuralApply(input) {
116
180
  const targetRel = toProjectRelativePosix(input.projectRoot, input.draftPath);
117
181
  const projectAbs = resolve(input.projectRoot);
118
182
  const grants = input.grants ?? listGrants(input.projectRoot);
183
+ const mintHint = formatDecomposeStructuralMintCommand(input.parentPath, input.draftPath, {
184
+ projectRoot: input.projectRoot,
185
+ repo: input.repo,
186
+ });
119
187
  if (parentRel === null || targetRel === null) {
120
188
  return {
121
189
  allowed: false,
122
190
  code: "authz-grant-scope-deny",
123
- reason: "Directive denied scope:decompose apply: parent or draft path is outside the project root.",
191
+ reason: "Directive denied scope:decompose apply: parent or draft path is outside the project root. " +
192
+ `Human action required: \`${mintHint}\` with paths inside the project root.`,
124
193
  humanApprovalRef: null,
125
194
  draftDigest: digest,
126
195
  };
@@ -128,7 +197,7 @@ export function evaluateDecomposeStructuralApply(input) {
128
197
  let lastReject = null;
129
198
  for (const grant of grants) {
130
199
  if (!evidenceSatisfiesImplementationApproval({ grant })) {
131
- const validity = grantValidity(grant, now);
200
+ const validity = grantValidity(grant, now, mintHint);
132
201
  if (!validity.ok) {
133
202
  lastReject = { code: validity.code, reason: validity.reason, grantId: grant.id };
134
203
  continue;
@@ -136,12 +205,12 @@ export function evaluateDecomposeStructuralApply(input) {
136
205
  lastReject = {
137
206
  code: "authz-grant-origin-reject",
138
207
  reason: `Directive denied scope:decompose apply: grant ${grant.id} does not satisfy ` +
139
- "human-origin implementation approval. Human action required: `deft authz:grant`.",
208
+ `human-origin implementation approval. Human action required: \`${mintHint}\`.`,
140
209
  grantId: grant.id,
141
210
  };
142
211
  continue;
143
212
  }
144
- const validity = grantValidity(grant, now);
213
+ const validity = grantValidity(grant, now, mintHint);
145
214
  if (!validity.ok) {
146
215
  lastReject = { code: validity.code, reason: validity.reason, grantId: grant.id };
147
216
  continue;
@@ -150,7 +219,7 @@ export function evaluateDecomposeStructuralApply(input) {
150
219
  lastReject = {
151
220
  code: "authz-grant-scope-deny",
152
221
  reason: `Directive denied scope:decompose apply: grant ${grant.id} does not include operation ` +
153
- `'${SCOPE_DECOMPOSE_APPLY_STRUCTURAL}'. Human action required: mint with that operation.`,
222
+ `'${SCOPE_DECOMPOSE_APPLY_STRUCTURAL}'. Human action required: \`${mintHint}\`.`,
154
223
  grantId: grant.id,
155
224
  };
156
225
  continue;
@@ -165,7 +234,7 @@ export function evaluateDecomposeStructuralApply(input) {
165
234
  code: "authz-grant-binding-incomplete",
166
235
  reason: `Directive denied scope:decompose apply: grant ${grant.id} is missing required ` +
167
236
  "structural bindings (contentDigest, parentPath, targetPath). " +
168
- "Human action required: re-mint bound to the exact draft.",
237
+ `Human action required: \`${mintHint}\`.`,
169
238
  grantId: grant.id,
170
239
  };
171
240
  continue;
@@ -174,7 +243,8 @@ export function evaluateDecomposeStructuralApply(input) {
174
243
  lastReject = {
175
244
  code: "authz-grant-binding-incomplete",
176
245
  reason: `Directive denied scope:decompose apply: grant ${grant.id} does not bind project ` +
177
- "identity (repo and/or worktree). Human action required: re-mint with --repo or worktree.",
246
+ "identity (repo and/or worktree). " +
247
+ `Human action required: \`${mintHint}\`.`,
178
248
  grantId: grant.id,
179
249
  };
180
250
  continue;
@@ -184,7 +254,8 @@ export function evaluateDecomposeStructuralApply(input) {
184
254
  code: "authz-grant-digest-mismatch",
185
255
  reason: `Directive denied scope:decompose apply: grant ${grant.id} is bound to draft digest ` +
186
256
  `${boundDigest.slice(0, 12)}… but the draft bytes hash to ${digest.slice(0, 12)}…. ` +
187
- "Any content change after approval invalidates the grant. Re-mint for the current draft.",
257
+ "Any content change after approval invalidates the grant. " +
258
+ `Human action required: \`${mintHint}\`.`,
188
259
  grantId: grant.id,
189
260
  };
190
261
  continue;
@@ -193,7 +264,8 @@ export function evaluateDecomposeStructuralApply(input) {
193
264
  lastReject = {
194
265
  code: "authz-grant-parent-mismatch",
195
266
  reason: `Directive denied scope:decompose apply: grant ${grant.id} is bound to parent ` +
196
- `'${boundParent}' but apply targets '${parentRel}'.`,
267
+ `'${boundParent}' but apply targets '${parentRel}'. ` +
268
+ `Human action required: \`${mintHint}\`.`,
197
269
  grantId: grant.id,
198
270
  };
199
271
  continue;
@@ -202,7 +274,8 @@ export function evaluateDecomposeStructuralApply(input) {
202
274
  lastReject = {
203
275
  code: "authz-grant-target-mismatch",
204
276
  reason: `Directive denied scope:decompose apply: grant ${grant.id} is bound to draft path ` +
205
- `'${boundTarget}' but apply uses '${targetRel}'.`,
277
+ `'${boundTarget}' but apply uses '${targetRel}'. ` +
278
+ `Human action required: \`${mintHint}\`.`,
206
279
  grantId: grant.id,
207
280
  };
208
281
  continue;
@@ -214,7 +287,8 @@ export function evaluateDecomposeStructuralApply(input) {
214
287
  lastReject = {
215
288
  code: "authz-grant-project-mismatch",
216
289
  reason: `Directive denied scope:decompose apply: grant ${grant.id} is bound to repo ` +
217
- `'${boundRepo}' which does not match the apply context.`,
290
+ `'${boundRepo}' which does not match the apply context. ` +
291
+ `Human action required: \`${mintHint}\`.`,
218
292
  grantId: grant.id,
219
293
  };
220
294
  continue;
@@ -228,7 +302,8 @@ export function evaluateDecomposeStructuralApply(input) {
228
302
  lastReject = {
229
303
  code: "authz-grant-project-mismatch",
230
304
  reason: `Directive denied scope:decompose apply: grant ${grant.id} is bound to worktree ` +
231
- `'${boundWorktree}' which does not match project root '${projectAbs}'.`,
305
+ `'${boundWorktree}' which does not match project root '${projectAbs}'. ` +
306
+ `Human action required: \`${mintHint}\`.`,
232
307
  grantId: grant.id,
233
308
  };
234
309
  continue;
@@ -257,7 +332,7 @@ export function evaluateDecomposeStructuralApply(input) {
257
332
  code: "authz-grant-missing",
258
333
  reason: "Directive denied scope:decompose apply: no human-origin grant covers " +
259
334
  `'${SCOPE_DECOMPOSE_APPLY_STRUCTURAL}' for this draft digest. ` +
260
- "Human action required: mint a grant bound to the exact draft SHA-256 " +
335
+ `Human action required: \`${mintHint}\` ` +
261
336
  "(self-authored lifecycle/dispatch tokens do not count). --check remains ungated.",
262
337
  humanApprovalRef: null,
263
338
  draftDigest: digest,
@@ -1,4 +1,5 @@
1
1
  import type { TaskRunResult } from "../cache/task-cache/types.js";
2
+ import { type ToolchainPreflightResult } from "../session/toolchain-preflight.js";
2
3
  import { type CheckOrchestratorSeams } from "./context.js";
3
4
  export interface CachedCheckOptions extends CheckOrchestratorSeams {
4
5
  readonly onGateStart?: (gateId: string) => void;
@@ -6,16 +7,36 @@ export interface CachedCheckOptions extends CheckOrchestratorSeams {
6
7
  readonly gateSpawnFn?: (gateId: string, taskBin: string, taskArgs: string[], opts: {
7
8
  cwd: string;
8
9
  env?: NodeJS.ProcessEnv;
9
- }) => Pick<TaskRunResult, "exitCode" | "stdout" | "stderr">;
10
+ }) => Pick<TaskRunResult, "exitCode" | "stdout" | "stderr"> & {
11
+ spawnError?: string;
12
+ };
13
+ /**
14
+ * #3282: inject preflight (tests). When omitted, runs live toolchain preflight.
15
+ * Pass `null` to skip preflight entirely (legacy test paths).
16
+ */
17
+ readonly preflight?: ToolchainPreflightResult | null;
18
+ /** #3282: session id for run-summary lines (default: new UUID). */
19
+ readonly sessionId?: string;
20
+ /** #3282: disable run-summary emission (tests). */
21
+ readonly emitRunSummary?: boolean;
10
22
  }
11
23
  /**
12
24
  * Run check gates sequentially with content-hash caching (#1713).
13
25
  * Falls back to fail-open execution for undeclared / non-cacheable gates.
14
26
  *
15
- * Gate order is fast-before-slow (#3188): non-suite gates complete (or fail)
16
- * before any suite gate (`ts:check-lane` / vitest+coverage) is started. A
17
- * non-zero exit aborts the loop immediately the suite never starts after a
18
- * fast-gate failure (observable via `onGateStart` / suite start log).
27
+ * Gate order (#3284 / #3188):
28
+ * 1. Product AC (`verify:ac`) first fail-fast; never skippable when commands exist
29
+ * 2. Hygiene preflight (may be advisory under pressure / degraded)
30
+ * 3. Suite last (`ts:check-lane`)
31
+ *
32
+ * Modes (env / ceremony dial / hard budget — see resolveProductFirstCheckMode):
33
+ * - full: AC hard → hygiene hard → suite
34
+ * - pressure: AC hard → hygiene advisory → suite
35
+ * - rapid: AC only (ceremony dial rapid/minimal positive content)
36
+ *
37
+ * #3282: toolchain preflight enables degraded skip report when go-task/pnpm
38
+ * are missing; gate failures print named cause + remedy; run-summary JSONL
39
+ * is appended when DEFT_RUN_SUMMARY_PATH is set (fail-open).
19
40
  */
20
41
  export declare function dispatchCachedTaskCheck(frameworkRoot: string, projectRoot: string, options?: CachedCheckOptions): number;
21
42
  //# sourceMappingURL=cached-orchestrator.d.ts.map