@deftai/directive-core 0.106.0 → 0.108.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 (249) hide show
  1. package/dist/architecture/sor-preflight.js +2 -0
  2. package/dist/check/cached-orchestrator.js +28 -3
  3. package/dist/check/gate-lists.js +2 -0
  4. package/dist/check/named-cause.js +18 -3
  5. package/dist/codebase/provider.js +7 -1
  6. package/dist/delivery-attempt/handoff.js +1 -1
  7. package/dist/design-critique/citation-grammar.d.ts +53 -0
  8. package/dist/design-critique/citation-grammar.js +303 -0
  9. package/dist/design-critique/completed-arc-record.d.ts +62 -0
  10. package/dist/design-critique/completed-arc-record.js +232 -0
  11. package/dist/design-critique/exclusive-chip.d.ts +33 -0
  12. package/dist/design-critique/exclusive-chip.js +68 -0
  13. package/dist/design-critique/parent-audit.d.ts +59 -0
  14. package/dist/design-critique/parent-audit.js +121 -0
  15. package/dist/doctor/checks.d.ts +6 -0
  16. package/dist/doctor/checks.js +35 -0
  17. package/dist/freshness/bind.d.ts +32 -3
  18. package/dist/freshness/bind.js +58 -12
  19. package/dist/hooks/classify/host-session-identity.d.ts +76 -0
  20. package/dist/hooks/classify/host-session-identity.js +470 -0
  21. package/dist/hooks/classify/index.d.ts +3 -2
  22. package/dist/hooks/classify/index.js +3 -2
  23. package/dist/hooks/classify/paths.d.ts +7 -0
  24. package/dist/hooks/classify/paths.js +35 -0
  25. package/dist/hooks/classify/stdin.d.ts +5 -0
  26. package/dist/hooks/classify/stdin.js +27 -0
  27. package/dist/hooks/dest-form.d.ts +46 -0
  28. package/dist/hooks/dest-form.js +575 -0
  29. package/dist/hooks/dispatcher.d.ts +69 -2
  30. package/dist/hooks/dispatcher.js +609 -45
  31. package/dist/hooks/fixtures/cases.d.ts +16 -2
  32. package/dist/hooks/fixtures/cases.js +175 -0
  33. package/dist/hooks/index.d.ts +1 -0
  34. package/dist/hooks/index.js +1 -0
  35. package/dist/hooks/scope.js +3 -1
  36. package/dist/init-deposit/agent-hooks.d.ts +2 -1
  37. package/dist/init-deposit/agent-hooks.js +8 -1
  38. package/dist/init-deposit/hook-runtime-travel.d.ts +77 -0
  39. package/dist/init-deposit/hook-runtime-travel.js +269 -0
  40. package/dist/intake/clause-derivation.js +9 -3
  41. package/dist/intake/github-auth-modes-cli.js +14 -2
  42. package/dist/intake/github-auth-modes.d.ts +54 -9
  43. package/dist/intake/github-auth-modes.js +326 -123
  44. package/dist/intake/issue-ingest.js +62 -2
  45. package/dist/intake/platform-capabilities.d.ts +9 -2
  46. package/dist/intake/platform-capabilities.js +38 -9
  47. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +26 -0
  48. package/dist/lifecycle/completed-tracked-on-delivery.js +37 -14
  49. package/dist/lifecycle/completed-write-guard.d.ts +48 -0
  50. package/dist/lifecycle/completed-write-guard.js +373 -0
  51. package/dist/lifecycle/index.d.ts +1 -0
  52. package/dist/lifecycle/index.js +1 -0
  53. package/dist/literal-acceptance/capture.d.ts +16 -0
  54. package/dist/literal-acceptance/capture.js +75 -7
  55. package/dist/literal-acceptance/evaluate.js +25 -3
  56. package/dist/literal-acceptance/index.d.ts +2 -2
  57. package/dist/literal-acceptance/index.js +2 -2
  58. package/dist/literal-acceptance/run.js +9 -3
  59. package/dist/literal-acceptance/types.d.ts +6 -0
  60. package/dist/literal-acceptance/types.js +6 -0
  61. package/dist/orphan-active/evaluate.d.ts +25 -0
  62. package/dist/orphan-active/evaluate.js +193 -128
  63. package/dist/orphan-active/index.d.ts +1 -0
  64. package/dist/orphan-active/index.js +1 -0
  65. package/dist/orphan-active/issue-state.d.ts +121 -0
  66. package/dist/orphan-active/issue-state.js +278 -0
  67. package/dist/platform/cursor-managed-runtime.d.ts +95 -0
  68. package/dist/platform/cursor-managed-runtime.js +241 -0
  69. package/dist/platform/index.d.ts +1 -0
  70. package/dist/platform/index.js +1 -0
  71. package/dist/platform/platform-capabilities.d.ts +23 -1
  72. package/dist/platform/platform-capabilities.js +55 -15
  73. package/dist/policy/ceremony-dial.js +5 -9
  74. package/dist/policy/host-hooks.js +4 -9
  75. package/dist/policy/org-force-on-migration.js +5 -10
  76. package/dist/policy/plan-extensions.d.ts +14 -2
  77. package/dist/policy/plan-extensions.js +24 -3
  78. package/dist/policy/product-signal.js +5 -10
  79. package/dist/policy/require-human-merge.js +5 -9
  80. package/dist/policy/resolve.js +77 -16
  81. package/dist/policy/runtime-authority.d.ts +18 -0
  82. package/dist/policy/runtime-authority.js +11 -0
  83. package/dist/policy/value-feedback.js +9 -18
  84. package/dist/policy/write-fence.js +3 -0
  85. package/dist/pr-closeout-attestable/evaluate.d.ts +80 -0
  86. package/dist/pr-closeout-attestable/evaluate.js +306 -0
  87. package/dist/pr-closeout-attestable/index.d.ts +2 -0
  88. package/dist/pr-closeout-attestable/index.js +2 -0
  89. package/dist/pr-protected-issues/gh.d.ts +6 -2
  90. package/dist/pr-protected-issues/gh.js +7 -3
  91. package/dist/pr-wait-mergeable/cascade.d.ts +9 -1
  92. package/dist/pr-wait-mergeable/cascade.js +21 -1
  93. package/dist/pr-wait-mergeable/types.d.ts +2 -0
  94. package/dist/pr-wait-mergeable/wrappers.d.ts +6 -0
  95. package/dist/pr-wait-mergeable/wrappers.js +11 -0
  96. package/dist/preflight/evaluate.d.ts +15 -0
  97. package/dist/preflight/evaluate.js +29 -3
  98. package/dist/preflight-cache/evaluate.js +9 -3
  99. package/dist/product-first-done-gate/acceptance-resolver.d.ts +14 -2
  100. package/dist/product-first-done-gate/acceptance-resolver.js +13 -2
  101. package/dist/product-first-done-gate/evaluate.js +53 -12
  102. package/dist/release/cli-drift-report.d.ts +68 -0
  103. package/dist/release/cli-drift-report.js +189 -0
  104. package/dist/release/index.d.ts +1 -0
  105. package/dist/release/index.js +1 -0
  106. package/dist/release/issue-state-fetch.d.ts +5 -3
  107. package/dist/release/issue-state-fetch.js +86 -21
  108. package/dist/release/native-steps.js +1 -0
  109. package/dist/release/pipeline.js +19 -0
  110. package/dist/release/types.d.ts +14 -0
  111. package/dist/render/constants.d.ts +4 -0
  112. package/dist/render/constants.js +11 -8
  113. package/dist/render/export-spec.js +31 -5
  114. package/dist/render/index.d.ts +1 -1
  115. package/dist/render/index.js +1 -1
  116. package/dist/render/prd-render.d.ts +4 -1
  117. package/dist/render/prd-render.js +82 -23
  118. package/dist/render/project-render.js +14 -7
  119. package/dist/render/scope-outlook.d.ts +2 -0
  120. package/dist/render/scope-outlook.js +3 -0
  121. package/dist/render/spec-render.js +2 -2
  122. package/dist/resolution/package-manager.d.ts +33 -4
  123. package/dist/resolution/package-manager.js +210 -9
  124. package/dist/review-monitor/constants.d.ts +7 -0
  125. package/dist/review-monitor/constants.js +7 -0
  126. package/dist/review-monitor/github-lease.d.ts +98 -1
  127. package/dist/review-monitor/github-lease.js +182 -1
  128. package/dist/review-monitor/lease-comment.d.ts +36 -0
  129. package/dist/review-monitor/lease-comment.js +103 -2
  130. package/dist/scm/call.js +12 -3
  131. package/dist/scm/design-critique-chip.d.ts +38 -0
  132. package/dist/scm/design-critique-chip.js +187 -0
  133. package/dist/scm/gh-rest.d.ts +9 -1
  134. package/dist/scm/gh-rest.js +103 -0
  135. package/dist/scm/index.d.ts +1 -0
  136. package/dist/scm/index.js +1 -0
  137. package/dist/scm/main.d.ts +3 -0
  138. package/dist/scm/main.js +15 -1
  139. package/dist/scm/readiness-cli.d.ts +2 -0
  140. package/dist/scm/readiness-cli.js +42 -1
  141. package/dist/scm/readiness.d.ts +4 -1
  142. package/dist/scm/readiness.js +35 -5
  143. package/dist/scope/decompose.js +2 -1
  144. package/dist/scope/delivery-evidence.d.ts +27 -1
  145. package/dist/scope/index.d.ts +1 -0
  146. package/dist/scope/index.js +1 -0
  147. package/dist/scope/lifecycle-write.d.ts +24 -0
  148. package/dist/scope/lifecycle-write.js +64 -0
  149. package/dist/scope/main.js +3 -1
  150. package/dist/scope/project-definition-sync.js +14 -7
  151. package/dist/scope/transition.js +90 -11
  152. package/dist/scope-provenance/evaluate.js +5 -1
  153. package/dist/session/git.d.ts +42 -0
  154. package/dist/session/git.js +219 -3
  155. package/dist/session/occupancy.d.ts +239 -12
  156. package/dist/session/occupancy.js +820 -27
  157. package/dist/session/ritual-sentinel.d.ts +5 -0
  158. package/dist/session/ritual-sentinel.js +5 -0
  159. package/dist/session/session-ready.d.ts +5 -1
  160. package/dist/session/session-ready.js +98 -8
  161. package/dist/session/session-start.d.ts +17 -1
  162. package/dist/session/session-start.js +49 -17
  163. package/dist/session/toolchain-preflight.d.ts +13 -3
  164. package/dist/session/toolchain-preflight.js +93 -18
  165. package/dist/session/verify-session-ritual.d.ts +33 -3
  166. package/dist/session/verify-session-ritual.js +89 -19
  167. package/dist/slice/constants.d.ts +1 -1
  168. package/dist/spec-authority/constants.d.ts +10 -2
  169. package/dist/spec-authority/constants.js +53 -8
  170. package/dist/spec-authority/resolver.d.ts +3 -0
  171. package/dist/spec-authority/resolver.js +55 -7
  172. package/dist/subprocess/max-buffer.d.ts +14 -0
  173. package/dist/subprocess/max-buffer.js +15 -0
  174. package/dist/swarm/complete-cohort.d.ts +2 -0
  175. package/dist/swarm/complete-cohort.js +21 -13
  176. package/dist/swarm/index.d.ts +1 -0
  177. package/dist/swarm/index.js +1 -0
  178. package/dist/swarm/launch-cli.js +53 -33
  179. package/dist/swarm/launch.d.ts +77 -0
  180. package/dist/swarm/launch.js +248 -38
  181. package/dist/swarm/pre-dispatch-cli.js +2 -1
  182. package/dist/swarm/pre-dispatch.js +10 -1
  183. package/dist/swarm/subagent-status-dir.d.ts +27 -0
  184. package/dist/swarm/subagent-status-dir.js +42 -0
  185. package/dist/swarm/worktrees.d.ts +17 -3
  186. package/dist/swarm/worktrees.js +48 -4
  187. package/dist/triage/actions/index.d.ts +4 -0
  188. package/dist/triage/actions/index.js +6 -0
  189. package/dist/triage/evaluate/evaluate.d.ts +11 -0
  190. package/dist/triage/evaluate/evaluate.js +169 -0
  191. package/dist/triage/evaluate/github.d.ts +16 -0
  192. package/dist/triage/evaluate/github.js +153 -0
  193. package/dist/triage/evaluate/index.d.ts +9 -0
  194. package/dist/triage/evaluate/index.js +8 -0
  195. package/dist/triage/evaluate/paths.d.ts +7 -0
  196. package/dist/triage/evaluate/paths.js +25 -0
  197. package/dist/triage/evaluate/sink.d.ts +5 -0
  198. package/dist/triage/evaluate/sink.js +52 -0
  199. package/dist/triage/evaluate/types.d.ts +93 -0
  200. package/dist/triage/evaluate/types.js +13 -0
  201. package/dist/triage/evaluate/validity.d.ts +8 -0
  202. package/dist/triage/evaluate/validity.js +87 -0
  203. package/dist/triage/evaluate/value.d.ts +12 -0
  204. package/dist/triage/evaluate/value.js +33 -0
  205. package/dist/triage/evaluate/wip-census.d.ts +5 -0
  206. package/dist/triage/evaluate/wip-census.js +39 -0
  207. package/dist/triage/evaluate/worktrees.d.ts +8 -0
  208. package/dist/triage/evaluate/worktrees.js +68 -0
  209. package/dist/triage/evaluate/xbrief-refs.d.ts +8 -0
  210. package/dist/triage/evaluate/xbrief-refs.js +60 -0
  211. package/dist/triage/help/registry-data.d.ts +21 -7
  212. package/dist/triage/help/registry-data.js +53 -5
  213. package/dist/triage/index.d.ts +1 -0
  214. package/dist/triage/index.js +1 -0
  215. package/dist/triage/scope/mutations-core.d.ts +2 -6
  216. package/dist/triage/scope/mutations-core.js +23 -55
  217. package/dist/triage/scope-drift/add-ignore.js +5 -39
  218. package/dist/triage/subscribe/index.d.ts +2 -6
  219. package/dist/triage/subscribe/index.js +15 -65
  220. package/dist/triage/welcome/writers.js +15 -74
  221. package/dist/value/feedback-file.d.ts +17 -2
  222. package/dist/value/feedback-file.js +80 -6
  223. package/dist/vbrief-build/index.d.ts +2 -1
  224. package/dist/vbrief-build/index.js +2 -1
  225. package/dist/vbrief-build/parity-scenarios.js +5 -5
  226. package/dist/vbrief-build/project-definition-io.d.ts +63 -3
  227. package/dist/vbrief-build/project-definition-io.js +357 -31
  228. package/dist/vbrief-build/project-definition-mutation.d.ts +32 -0
  229. package/dist/vbrief-build/project-definition-mutation.js +43 -0
  230. package/dist/vbrief-reconcile/labels.d.ts +1 -0
  231. package/dist/vbrief-reconcile/labels.js +30 -0
  232. package/dist/vbrief-validate/conformance.d.ts +1 -0
  233. package/dist/vbrief-validate/conformance.js +85 -8
  234. package/dist/vbrief-validate/main.js +2 -0
  235. package/dist/vbrief-validate/precutover.js +5 -10
  236. package/dist/vbrief-validation/story-quality.d.ts +7 -0
  237. package/dist/vbrief-validation/story-quality.js +8 -1
  238. package/dist/verify-ac/clauses.d.ts +62 -2
  239. package/dist/verify-ac/clauses.js +183 -111
  240. package/dist/verify-ac/index.d.ts +1 -1
  241. package/dist/verify-ac/index.js +1 -1
  242. package/dist/verify-env/node-runtime.d.ts +8 -4
  243. package/dist/verify-env/node-runtime.js +9 -6
  244. package/dist/verify-env/toolchain-check.d.ts +35 -3
  245. package/dist/verify-env/toolchain-check.js +155 -36
  246. package/dist/verify-source/project-definition-mutation-boundary.d.ts +104 -0
  247. package/dist/verify-source/project-definition-mutation-boundary.js +326 -0
  248. package/dist/xbrief-migrate/agents-header.js +69 -7
  249. package/package.json +7 -3
@@ -11,6 +11,26 @@ export const ACTIVE_FOLDER = "active";
11
11
  export const ELIGIBLE_STATUS = "running";
12
12
  /** Actionable redirect appended to every reject path (#810 / #2449). */
13
13
  export const ACTIVATE_HINT = "Run `task scope:activate -- {path}` (or legacy `task vbrief:activate -- {path}`) before spawning an implementation agent.";
14
+ /**
15
+ * Recovery for the origin-freshness reject (#3828).
16
+ *
17
+ * Freshness is only reached after the active/ + running checks pass, so
18
+ * `ACTIVATE_HINT` names a transition that cannot apply here: `activate` accepts
19
+ * `pending/` alone and fails with `Invalid transition: 'activate' requires file
20
+ * in pending/`. `block` -> `unblock` is in place (`targetFolder: null`), requires
21
+ * and restores `running`, and stamps `plan.updated` plus `xBRIEFInfo.updated`,
22
+ * the field this reject compares. It is interim: the pair also records a
23
+ * `blocked` -> `running` transition for a brief that was never blocked, a
24
+ * tradeoff accepted knowingly. #3857 owns the dedicated acknowledge verb.
25
+ */
26
+ export const ORIGIN_FRESHNESS_HINT = "Recovery: this xBRIEF is already in active/ at status running. " +
27
+ "Once you have re-read the origin delta, stamp xBRIEFInfo.updated in place with " +
28
+ "`task scope:block -- {path}` then `task scope:unblock -- {path}`. " +
29
+ "That pair is interim: it records a blocked -> running transition for a brief " +
30
+ "that was never blocked, and #3857 owns the verb that records the " +
31
+ "acknowledgement honestly. If the origin could not be fetched or compared, " +
32
+ "restore `gh` REST access to the origin repository first -- a stamp does not " +
33
+ "clear that.";
14
34
  /** Lifecycle folder names eligible for implementation (#810). */
15
35
  export const ELIGIBLE_LIFECYCLE_DIRS = ["xbrief/active", "vbrief/active"];
16
36
  export const PREFLIGHT_USAGE_HINT = "Expected: `task xbrief:preflight -- xbrief/active/<story>.xbrief.json` (legacy: `task vbrief:preflight -- <path>`).";
@@ -18,8 +38,12 @@ export const PREFLIGHT_USAGE_HINT = "Expected: `task xbrief:preflight -- xbrief/
18
38
  export function formatActivateHint(path) {
19
39
  return ACTIVATE_HINT.replace("{path}", () => path);
20
40
  }
21
- function buildReject(path, reason) {
22
- return `${reason}\n ${PREFLIGHT_USAGE_HINT}\n ${formatActivateHint(path)}`;
41
+ /** Substitute every `{path}` without `$`-pattern expansion in user paths (#1721). */
42
+ export function formatOriginFreshnessHint(path) {
43
+ return ORIGIN_FRESHNESS_HINT.split("{path}").join(path);
44
+ }
45
+ function buildReject(path, reason, hint) {
46
+ return `${reason}\n ${PREFLIGHT_USAGE_HINT}\n ${hint ?? formatActivateHint(path)}`;
23
47
  }
24
48
  /** Map Node `JSON.parse` errors to CPython `json.JSONDecodeError.msg` for parity (#1721). */
25
49
  function nodeJsonErrorToPythonMsg(nodeMessage) {
@@ -167,7 +191,9 @@ export function evaluate(vbriefPath, options = {}) {
167
191
  return {
168
192
  exitCode: 1,
169
193
  parentLineage: lineage,
170
- message: buildReject(path, originFreshness.message),
194
+ // #3828: the brief is active + running by this point, so the activate
195
+ // hint would name a transition that hard-errors from the printed state.
196
+ message: buildReject(path, originFreshness.message, formatOriginFreshnessHint(path)),
171
197
  };
172
198
  }
173
199
  // #3425: fail closed when an applicable project invariant has no disposition.
@@ -300,13 +300,19 @@ function formatDriftFailure(repo, drift) {
300
300
  };
301
301
  }
302
302
  function runDriftProbe(resolvedRepo, cacheRoot, source, options) {
303
- if (options.probeDriftFn) {
304
- return options.probeDriftFn(resolvedRepo, cacheRoot, source);
305
- }
306
303
  try {
304
+ if (options.probeDriftFn) {
305
+ return options.probeDriftFn(resolvedRepo, cacheRoot, source);
306
+ }
307
307
  return probeCacheDrift({ repo: resolvedRepo, source, cacheRoot });
308
308
  }
309
309
  catch {
310
+ // Deliberate fail-open (#3738 / #3422): a thrown probe (429 after retry,
311
+ // 502/503, DNS, auth) returns null; evaluate treats null as pass.
312
+ // Work-selection / session:start still inherit this. The write path no
313
+ // longer executes cache_fresh, so this is not a write-authorization bypass.
314
+ // Do not silently convert to fail-closed without revisiting #3422 (local
315
+ // work MAY continue when it does not need the forge).
310
316
  return null;
311
317
  }
312
318
  }
@@ -93,18 +93,30 @@ export declare function resolveAcceptanceVerdict(reading: AcceptanceReading): Ac
93
93
  /** One line naming the predicate and the value it read (#3497). */
94
94
  export declare function formatAcceptanceVerdict(verdictResult: AcceptanceVerdict): string;
95
95
  /**
96
- * Clause-walk composition (#3323 / #3497).
96
+ * Clause-walk composition (#3323 / #3497 / #3826).
97
97
  *
98
98
  * A clause the shipped artifact contradicts (`failed`) always blocks. A clause the
99
99
  * static walk cannot decide (`unverifiable`) is evidence of nothing — it blocks only
100
100
  * when nothing else verified the product. A green executable acceptance run IS that
101
101
  * something: the product-first oracle already ran. Before #3497 an all-unverifiable
102
102
  * clause set refused an artifact whose stated command had just exited 0.
103
+ *
104
+ * #3826 excused a set the walk has no oracle for at all: a clause with no bound
105
+ * artifact path can only ever come back `unverifiable`, so demanding a positive
106
+ * `verified` from a set of them cannot be satisfied by doing the work correctly.
107
+ * Where the walk has no oracle, `failed === 0` is the strongest verdict available
108
+ * and `evaluateAcceptanceEvidenceGate` adjudicates those criteria at `scope:complete`.
109
+ *
110
+ * #3835 makes that excusal per clause. As a *set* predicate, `verified > 0` was
111
+ * re-armed for every clause by any single bound one, so one verified binding
112
+ * covered siblings that had their own oracle and did not meet it.
113
+ * `adjudicableUnverified` counts exactly those siblings.
103
114
  */
104
115
  export declare function clauseWalkBlocks(input: {
105
116
  readonly failed: number;
106
- readonly verified: number;
107
117
  readonly walked: number;
118
+ /** Clauses the walk had an oracle for and that did not come back `verified`. */
119
+ readonly adjudicableUnverified: number;
108
120
  readonly hasGreenExecutableRun: boolean;
109
121
  }): boolean;
110
122
  /** Which consumer is reading acceptance. Selects one shared option profile (#3497). */
@@ -106,19 +106,30 @@ export function formatAcceptanceVerdict(verdictResult) {
106
106
  return `${lead}\n remedy: ${verdictResult.remedy}`;
107
107
  }
108
108
  /**
109
- * Clause-walk composition (#3323 / #3497).
109
+ * Clause-walk composition (#3323 / #3497 / #3826).
110
110
  *
111
111
  * A clause the shipped artifact contradicts (`failed`) always blocks. A clause the
112
112
  * static walk cannot decide (`unverifiable`) is evidence of nothing — it blocks only
113
113
  * when nothing else verified the product. A green executable acceptance run IS that
114
114
  * something: the product-first oracle already ran. Before #3497 an all-unverifiable
115
115
  * clause set refused an artifact whose stated command had just exited 0.
116
+ *
117
+ * #3826 excused a set the walk has no oracle for at all: a clause with no bound
118
+ * artifact path can only ever come back `unverifiable`, so demanding a positive
119
+ * `verified` from a set of them cannot be satisfied by doing the work correctly.
120
+ * Where the walk has no oracle, `failed === 0` is the strongest verdict available
121
+ * and `evaluateAcceptanceEvidenceGate` adjudicates those criteria at `scope:complete`.
122
+ *
123
+ * #3835 makes that excusal per clause. As a *set* predicate, `verified > 0` was
124
+ * re-armed for every clause by any single bound one, so one verified binding
125
+ * covered siblings that had their own oracle and did not meet it.
126
+ * `adjudicableUnverified` counts exactly those siblings.
116
127
  */
117
128
  export function clauseWalkBlocks(input) {
118
129
  if (input.failed > 0) {
119
130
  return true;
120
131
  }
121
- if (input.walked === 0 || input.verified > 0) {
132
+ if (input.walked === 0 || input.adjudicableUnverified === 0) {
122
133
  return false;
123
134
  }
124
135
  return !input.hasGreenExecutableRun;
@@ -10,14 +10,14 @@
10
10
  import { existsSync, readFileSync } from "node:fs";
11
11
  import { basename, isAbsolute, relative, resolve } from "node:path";
12
12
  import { emitAcceptanceStampFromPlan, MISSING_AMBIGUITY_ATTESTATION_CAUSE, stampedAmbiguityAttestationError, } from "../intake/clause-derivation.js";
13
- import { appendLiteralAcceptanceAdvisory, evaluateLiteralAcceptanceFromPlan, isNoopRefusalReason, runLiteralAcceptanceCommands, stripLiteralAcceptanceAdvisory, } from "../literal-acceptance/index.js";
13
+ import { appendLiteralAcceptanceAdvisory, evaluateLiteralAcceptanceFromPlan, isExecutableLiteralSource, isInlineProseMention, isNoopRefusalReason, readNotAcceptanceCommands, readStoredLiteralAcceptanceCommands, runLiteralAcceptanceCommands, stripLiteralAcceptanceAdvisory, } from "../literal-acceptance/index.js";
14
14
  import { ENV_RUN_SUMMARY_PATH, RunSummaryEmitter, } from "../run-summary/index.js";
15
15
  import { maybeBankOnAcPass } from "../session/ac-pass-banking.js";
16
16
  import { resolveAcReuse, resolveScopeIdForAcReuse, snapshotFromReuseFields, } from "../session/ac-pass-reuse.js";
17
17
  import { gitHead } from "../session/git.js";
18
18
  import { hashProductState } from "../session/product-state-hash.js";
19
19
  import { resolveVerifyAcSessionId, writeVerifyAcSessionCache, } from "../session/verify-ac-session-cache.js";
20
- import { formatClauseWalkMessage, walkAcceptanceClauses, } from "../verify-ac/clauses.js";
20
+ import { countUnverifiedAdjudicableClauses, formatClauseWalkMessage, readDeclaredArtifactScope, walkAcceptanceClauses, } from "../verify-ac/clauses.js";
21
21
  import { emitVerifyAcAttempts, evaluateProductOracleIntegrity, mergeOracleVerdict, } from "../verify-ac/evaluate.js";
22
22
  import { readPlanAcceptance, validatePlanAcceptance } from "./acceptance.js";
23
23
  import { clauseWalkBlocks, formatAcceptanceVerdict, resolveAcceptanceVerdict, } from "./acceptance-resolver.js";
@@ -205,9 +205,10 @@ export function evaluateVerifyAcFromPlan(plan, options = {}) {
205
205
  // the #3267 ledger is a parallel store and can be empty while stated commands exist.
206
206
  // Stated was previously excluded, so rung=stated + empty ledger printed "nothing to run".
207
207
  // Do not override a blocking rejected ledger or a config error.
208
- if (shouldRunPlanAcceptanceDirectly(base, acceptance)) {
208
+ if (shouldRunPlanAcceptanceDirectly(base, acceptance, plan)) {
209
209
  const runner = optionsWithScope.runner;
210
- const direct = runLiteralAcceptanceCommands(acceptance.commands.map((c) => ({
210
+ const directCommands = statedAcceptanceCommands(acceptance, plan);
211
+ const direct = runLiteralAcceptanceCommands(directCommands.map((c) => ({
211
212
  command: c.command,
212
213
  cwd: c.cwd ?? null,
213
214
  expectedStdout: c.expectedStdout ?? null,
@@ -271,8 +272,38 @@ export function evaluateVerifyAcFromPlan(plan, options = {}) {
271
272
  }
272
273
  return applyOracle(annotate(base, acceptance, optionsWithScope.quiet), optionsWithScope, plan);
273
274
  }
274
- function shouldRunPlanAcceptanceDirectly(base, acceptance) {
275
- if (acceptance.commands.length === 0)
275
+ function hasNonDefaultAcceptanceContext(command) {
276
+ const cwd = command.cwd !== undefined && command.cwd !== null && String(command.cwd).trim();
277
+ const stdout = command.expectedStdout !== undefined &&
278
+ command.expectedStdout !== null &&
279
+ String(command.expectedStdout).length > 0;
280
+ const exit = typeof command.expectedExitCode === "number" && command.expectedExitCode !== 0;
281
+ return Boolean(cwd) || stdout || exit;
282
+ }
283
+ function hasStructuredAcceptancePeer(plan, command) {
284
+ return readStoredLiteralAcceptanceCommands(plan).some((c) => c.command === command && isExecutableLiteralSource(c.source));
285
+ }
286
+ /**
287
+ * True when a plan.acceptance.commands row is only an ingest copy of an inline
288
+ * prose mention (or an operator not-command disposition) and has no structured
289
+ * peer. Independently authored rows — promoted verify_commands, or a command
290
+ * with its own cwd/expectedStdout/exit — still run (#3721 Greptile).
291
+ */
292
+ function isInlineScrapedAcceptanceCommand(plan, command) {
293
+ if (hasNonDefaultAcceptanceContext(command))
294
+ return false;
295
+ if (hasStructuredAcceptancePeer(plan, command.command))
296
+ return false;
297
+ if (readNotAcceptanceCommands(plan).has(command.command))
298
+ return true;
299
+ return readStoredLiteralAcceptanceCommands(plan).some((c) => c.command === command.command && isInlineProseMention(c));
300
+ }
301
+ /** plan.acceptance.commands minus ingest-copied inline mentions (#3721). */
302
+ function statedAcceptanceCommands(acceptance, plan) {
303
+ return acceptance.commands.filter((c) => !isInlineScrapedAcceptanceCommand(plan, c));
304
+ }
305
+ function shouldRunPlanAcceptanceDirectly(base, acceptance, plan) {
306
+ if (statedAcceptanceCommands(acceptance, plan).length === 0)
276
307
  return false;
277
308
  if (base.runs.length > 0)
278
309
  return false;
@@ -444,21 +475,24 @@ export function emitVerifyAcTerminalOutcome(input) {
444
475
  resolvedCommandCount: 0,
445
476
  }, { projectRoot: input.projectRoot, env: input.env }, resolve(input.projectRoot));
446
477
  }
447
- function applyClauseWalk(result, options) {
478
+ function applyClauseWalk(result, options, plan) {
448
479
  const clauses = result.acceptance.clauses ?? [];
449
480
  if (clauses.length === 0 || result.resolution === "config" || result.resolution === "skipped") {
450
481
  return result;
451
482
  }
452
483
  const projectRoot = resolve(options.projectRoot ?? process.cwd());
453
- const report = walkAcceptanceClauses(clauses, projectRoot);
484
+ // #3835: the walk reads files, so it reads only what this brief declared.
485
+ const report = walkAcceptanceClauses(clauses, projectRoot, {
486
+ declaredScope: readDeclaredArtifactScope(plan),
487
+ });
454
488
  const message = options.quiet ? result.message : formatClauseWalkMessage(report, result.message);
455
489
  // #3497: a clause the static walk cannot decide is evidence of nothing. It blocks
456
490
  // only when nothing else verified the product; a green executable acceptance run
457
491
  // already is the product-first oracle. Failed clauses still block unconditionally.
458
492
  const blocked = clauseWalkBlocks({
459
493
  failed: report.failed.length,
460
- verified: report.verified.length,
461
494
  walked: report.clauses.length,
495
+ adjudicableUnverified: countUnverifiedAdjudicableClauses(report.clauses),
462
496
  hasGreenExecutableRun: result.ok && result.runs.length > 0 && result.runs.every((run) => run.ok),
463
497
  });
464
498
  const ok = result.ok && !blocked;
@@ -467,8 +501,10 @@ function applyClauseWalk(result, options) {
467
501
  ok,
468
502
  code: ok ? result.code : result.code === 2 ? 2 : 1,
469
503
  message,
504
+ // #3835: only a clause that actually verified may relabel an empty pass as
505
+ // verified. A walk that verified nothing has not verified anything.
470
506
  resolution: ok
471
- ? result.resolution === "empty-pass"
507
+ ? result.resolution === "empty-pass" && report.verified.length > 0
472
508
  ? "verified-pass"
473
509
  : result.resolution
474
510
  : "fail",
@@ -518,8 +554,13 @@ function labelVerdict(result) {
518
554
  }
519
555
  function applyOracle(result, options, plan = {}) {
520
556
  const projectRoot = resolve(options.projectRoot ?? process.cwd());
521
- const walked = applyClauseWalk(applyRejectedNoop(result), options);
522
- const gated = walked.clauseWalked === true ? walked : applyEmptyFloorPolicy(walked, options);
557
+ const walked = applyClauseWalk(applyRejectedNoop(result), options, plan);
558
+ // #3835: the clause walk may stand in for the empty floor only when it actually
559
+ // verified something. A walk that verified nothing is not positive evidence, so
560
+ // an otherwise-empty acceptance still answers to the project floor rather than
561
+ // passing on the strength of a clause set nothing adjudicated.
562
+ const walkVerified = (walked.clauseOutcomes ?? []).some((row) => row.outcome === "verified");
563
+ const gated = walked.clauseWalked === true && walkVerified ? walked : applyEmptyFloorPolicy(walked, options);
523
564
  // Emit/read disk only when the caller supplied env (CLI passes process.env).
524
565
  // Tests stay isolated unless they opt in with env or runSummaryText.
525
566
  if (options.env !== undefined) {
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Local-vs-released CLI drift report after a cut (#3753).
3
+ *
4
+ * Report-only: never runs `npm i -g`. Never fails the release. Polls all four
5
+ * workspace packages with `--prefer-online` so a lag-poisoned npm cache cannot
6
+ * masquerade as a missing publish.
7
+ */
8
+ import { type ActiveCliCheckResult } from "../session/active-cli.js";
9
+ import type { ReleaseConfig } from "./types.js";
10
+ /** Workspace packages published by npm-publish.yml (sibling propagate is not atomic). */
11
+ export declare const WORKSPACE_PACKAGES: readonly ["@deftai/directive-types", "@deftai/directive-core", "@deftai/directive-content", "@deftai/directive"];
12
+ export type WorkspacePackageName = (typeof WORKSPACE_PACKAGES)[number];
13
+ /** Observed sibling lag was ~7 minutes; Phase 7 wait sits under this ceiling. */
14
+ export declare const CLI_DRIFT_POLL_TIMEOUT_MS: number;
15
+ export declare const CLI_DRIFT_POLL_INTERVAL_MS = 30000;
16
+ /** Pipeline completion is a single probe — Step 13 returns before npm-publish.yml is green. */
17
+ export declare const CLI_DRIFT_PIPELINE_POLL_TIMEOUT_MS = 0;
18
+ export type RegistryVisibility = "all-visible" | "still-propagating" | "publish-incomplete" | "skipped";
19
+ export interface WorkspacePackageProbe {
20
+ readonly name: WorkspacePackageName;
21
+ readonly visible: boolean;
22
+ readonly version: string | null;
23
+ }
24
+ export interface CliDriftReport {
25
+ readonly releasedVersion: string;
26
+ readonly localVersion: string | null;
27
+ readonly localPath: string | null;
28
+ readonly match: boolean;
29
+ readonly shadowed: boolean;
30
+ readonly registry: RegistryVisibility;
31
+ readonly packages: readonly WorkspacePackageProbe[];
32
+ readonly remediation: string;
33
+ readonly lines: readonly string[];
34
+ }
35
+ export interface CliDriftReportSeams {
36
+ readonly checkActiveCli?: (targetVersion: string) => ActiveCliCheckResult;
37
+ readonly viewPackage?: (name: WorkspacePackageName, version: string) => WorkspacePackageProbe;
38
+ readonly nowMs?: () => number;
39
+ readonly sleepMs?: (ms: number) => void;
40
+ }
41
+ export declare function remediationCommand(version: string): string;
42
+ export declare function npmViewArgs(name: WorkspacePackageName, version: string): string[];
43
+ export declare function defaultViewWorkspacePackage(name: WorkspacePackageName, version: string): WorkspacePackageProbe;
44
+ export declare function classifyRegistryVisibility(opts: {
45
+ readonly probes: readonly WorkspacePackageProbe[];
46
+ readonly waitExhausted: boolean;
47
+ readonly skipped: boolean;
48
+ }): RegistryVisibility;
49
+ export declare function pollWorkspacePackages(version: string, opts: {
50
+ readonly timeoutMs: number;
51
+ readonly intervalMs?: number;
52
+ readonly viewPackage: (name: WorkspacePackageName, version: string) => WorkspacePackageProbe;
53
+ readonly nowMs?: () => number;
54
+ readonly sleepMs?: (ms: number) => void;
55
+ }): {
56
+ readonly probes: readonly WorkspacePackageProbe[];
57
+ readonly waitExhausted: boolean;
58
+ };
59
+ export declare function shouldSkipRegistryPoll(config: Pick<ReleaseConfig, "dryRun" | "skipTag" | "version">, env?: NodeJS.ProcessEnv): boolean;
60
+ export declare function buildCliDriftReport(releasedVersion: string, opts?: {
61
+ readonly skipRegistryPoll: boolean;
62
+ readonly pollTimeoutMs?: number;
63
+ readonly pollIntervalMs?: number;
64
+ } & CliDriftReportSeams): CliDriftReport;
65
+ export declare function formatCliDriftReport(report: CliDriftReport): string;
66
+ /** Best-effort wrapper — a report failure must never fail the cut. */
67
+ export declare function emitCliDriftReportBestEffort(releasedVersion: string, opts: Parameters<typeof buildCliDriftReport>[1], write?: (text: string) => void): void;
68
+ //# sourceMappingURL=cli-drift-report.d.ts.map
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Local-vs-released CLI drift report after a cut (#3753).
3
+ *
4
+ * Report-only: never runs `npm i -g`. Never fails the release. Polls all four
5
+ * workspace packages with `--prefer-online` so a lag-poisoned npm cache cannot
6
+ * masquerade as a missing publish.
7
+ */
8
+ import { spawnSync } from "node:child_process";
9
+ import { PUBLIC_NPM_REGISTRY } from "../doctor/constants.js";
10
+ import { checkActiveCliAgainstTarget } from "../session/active-cli.js";
11
+ import { RELEASE_E2E_ENV } from "./skip-ci-incident.js";
12
+ /** Same sentinel as release-e2e `REHEARSAL_VERSION` — do not poll npm for 0.0.1. */
13
+ const REHEARSAL_VERSION = "0.0.1";
14
+ /** Workspace packages published by npm-publish.yml (sibling propagate is not atomic). */
15
+ export const WORKSPACE_PACKAGES = [
16
+ "@deftai/directive-types",
17
+ "@deftai/directive-core",
18
+ "@deftai/directive-content",
19
+ "@deftai/directive",
20
+ ];
21
+ /** Observed sibling lag was ~7 minutes; Phase 7 wait sits under this ceiling. */
22
+ export const CLI_DRIFT_POLL_TIMEOUT_MS = 10 * 60 * 1000;
23
+ export const CLI_DRIFT_POLL_INTERVAL_MS = 30_000;
24
+ /** Pipeline completion is a single probe — Step 13 returns before npm-publish.yml is green. */
25
+ export const CLI_DRIFT_PIPELINE_POLL_TIMEOUT_MS = 0;
26
+ export function remediationCommand(version) {
27
+ return `npm i -g @deftai/directive@${version} --prefer-online`;
28
+ }
29
+ export function npmViewArgs(name, version) {
30
+ return [
31
+ "view",
32
+ `${name}@${version}`,
33
+ "version",
34
+ "--prefer-online",
35
+ "--ignore-scripts",
36
+ `--registry=${PUBLIC_NPM_REGISTRY}`,
37
+ ];
38
+ }
39
+ export function defaultViewWorkspacePackage(name, version) {
40
+ try {
41
+ const result = spawnSync("npm", npmViewArgs(name, version), {
42
+ encoding: "utf8",
43
+ timeout: 15_000,
44
+ stdio: ["ignore", "pipe", "pipe"],
45
+ windowsHide: true,
46
+ });
47
+ const raw = typeof result.stdout === "string" ? result.stdout : "";
48
+ const seen = raw.trim().split(/\r?\n/)[0]?.trim() ?? "";
49
+ const visible = result.status === 0 && seen === version;
50
+ return { name, visible, version: seen.length > 0 ? seen : null };
51
+ }
52
+ catch {
53
+ return { name, visible: false, version: null };
54
+ }
55
+ }
56
+ export function classifyRegistryVisibility(opts) {
57
+ if (opts.skipped)
58
+ return "skipped";
59
+ const visibleCount = opts.probes.filter((p) => p.visible).length;
60
+ if (opts.probes.length > 0 && visibleCount === opts.probes.length)
61
+ return "all-visible";
62
+ if (visibleCount === 0 && opts.waitExhausted)
63
+ return "publish-incomplete";
64
+ return "still-propagating";
65
+ }
66
+ export function pollWorkspacePackages(version, opts) {
67
+ const now = opts.nowMs ?? Date.now;
68
+ const sleep = opts.sleepMs ?? (() => undefined);
69
+ const interval = opts.intervalMs ?? CLI_DRIFT_POLL_INTERVAL_MS;
70
+ const start = now();
71
+ let probes = WORKSPACE_PACKAGES.map((name) => opts.viewPackage(name, version));
72
+ if (probes.every((p) => p.visible)) {
73
+ return { probes, waitExhausted: false };
74
+ }
75
+ if (opts.timeoutMs <= 0) {
76
+ return { probes, waitExhausted: false };
77
+ }
78
+ while (now() - start < opts.timeoutMs) {
79
+ const remaining = opts.timeoutMs - (now() - start);
80
+ if (remaining <= 0)
81
+ break;
82
+ sleep(Math.min(interval, remaining));
83
+ probes = WORKSPACE_PACKAGES.map((name) => opts.viewPackage(name, version));
84
+ if (probes.every((p) => p.visible)) {
85
+ return { probes, waitExhausted: false };
86
+ }
87
+ }
88
+ return { probes, waitExhausted: true };
89
+ }
90
+ export function shouldSkipRegistryPoll(config, env = process.env) {
91
+ if (config.dryRun || config.skipTag)
92
+ return true;
93
+ if (config.version === REHEARSAL_VERSION)
94
+ return true;
95
+ if (env[RELEASE_E2E_ENV])
96
+ return true;
97
+ if (env.CI === "true" || env.GITHUB_ACTIONS === "true")
98
+ return true;
99
+ // Unit tests never probe the live registry; production `task release` does not set VITEST.
100
+ if (env.VITEST)
101
+ return true;
102
+ return false;
103
+ }
104
+ function formatRegistryLine(registry, packages) {
105
+ if (registry === "skipped") {
106
+ return " registry: skipped (dry-run / skip-tag / rehearsal / CI — no publish to poll)";
107
+ }
108
+ const visible = packages.filter((p) => p.visible).map((p) => p.name);
109
+ const missing = packages.filter((p) => !p.visible).map((p) => p.name);
110
+ const counts = `visible ${visible.length}/${packages.length}`;
111
+ if (registry === "all-visible") {
112
+ return ` registry: all-visible (${counts})`;
113
+ }
114
+ if (registry === "publish-incomplete") {
115
+ return ` registry: publish-incomplete (${counts}; none of the four packages resolved after the wait)`;
116
+ }
117
+ const missingText = missing.length > 0 ? `; missing: ${missing.join(", ")}` : "";
118
+ return ` registry: still-propagating (${counts}${missingText})`;
119
+ }
120
+ export function buildCliDriftReport(releasedVersion, opts = { skipRegistryPoll: false }) {
121
+ const check = opts.checkActiveCli ?? ((target) => checkActiveCliAgainstTarget(target));
122
+ const cli = check(releasedVersion);
123
+ const localVersion = cli.active?.version ?? null;
124
+ const localPath = cli.active?.path ?? null;
125
+ const match = localVersion !== null && localVersion === releasedVersion && cli.ok;
126
+ const shadowed = !cli.ok && cli.candidates.length > 0;
127
+ let packages = [];
128
+ let waitExhausted = false;
129
+ if (!opts.skipRegistryPoll) {
130
+ const view = opts.viewPackage ?? defaultViewWorkspacePackage;
131
+ const polled = pollWorkspacePackages(releasedVersion, {
132
+ timeoutMs: opts.pollTimeoutMs ?? CLI_DRIFT_PIPELINE_POLL_TIMEOUT_MS,
133
+ intervalMs: opts.pollIntervalMs,
134
+ viewPackage: view,
135
+ nowMs: opts.nowMs,
136
+ sleepMs: opts.sleepMs,
137
+ });
138
+ packages = polled.probes;
139
+ waitExhausted = polled.waitExhausted;
140
+ }
141
+ const registry = classifyRegistryVisibility({
142
+ probes: packages,
143
+ waitExhausted,
144
+ skipped: opts.skipRegistryPoll,
145
+ });
146
+ const remediation = remediationCommand(releasedVersion);
147
+ const localLabel = localVersion === null
148
+ ? "none on PATH"
149
+ : `${localVersion}${localPath !== null ? ` (${localPath})` : ""}`;
150
+ const lines = [
151
+ "CLI drift report (#3753):",
152
+ ` released: ${releasedVersion}`,
153
+ ` local global CLI: ${localLabel}`,
154
+ ` match: ${match ? "yes" : "no"}`,
155
+ ...(shadowed
156
+ ? [" note: PATH-shadowed install — bare deft --version is not sufficient (#3233)"]
157
+ : []),
158
+ formatRegistryLine(registry, packages),
159
+ ` remediation: ${remediation}`,
160
+ " note: report-only — this pipeline does not run npm i -g",
161
+ ];
162
+ return {
163
+ releasedVersion,
164
+ localVersion,
165
+ localPath,
166
+ match,
167
+ shadowed,
168
+ registry,
169
+ packages,
170
+ remediation,
171
+ lines,
172
+ };
173
+ }
174
+ export function formatCliDriftReport(report) {
175
+ return `${report.lines.join("\n")}\n`;
176
+ }
177
+ /** Best-effort wrapper — a report failure must never fail the cut. */
178
+ export function emitCliDriftReportBestEffort(releasedVersion, opts, write = (text) => {
179
+ process.stderr.write(text);
180
+ }) {
181
+ try {
182
+ write(formatCliDriftReport(buildCliDriftReport(releasedVersion, opts)));
183
+ }
184
+ catch (err) {
185
+ const reason = err instanceof Error ? err.message : String(err);
186
+ write(`CLI drift report (#3753): skipped (${reason.replace(/\r?\n/g, " ")})\n`);
187
+ }
188
+ }
189
+ //# sourceMappingURL=cli-drift-report.js.map
@@ -1,5 +1,6 @@
1
1
  export * from "./auto-hatch.js";
2
2
  export * from "./build-dist.js";
3
+ export * from "./cli-drift-report.js";
3
4
  export * from "./closed-verb-gate.js";
4
5
  export * from "./constants.js";
5
6
  export * from "./coverage-debt-ledger.js";
@@ -1,6 +1,7 @@
1
1
  /* v8 ignore file -- re-export barrel; covered via main.ts */
2
2
  export * from "./auto-hatch.js";
3
3
  export * from "./build-dist.js";
4
+ export * from "./cli-drift-report.js";
4
5
  export * from "./closed-verb-gate.js";
5
6
  export * from "./constants.js";
6
7
  export * from "./coverage-debt-ledger.js";
@@ -1,4 +1,4 @@
1
- import { type FetchIssueStatesOptions } from "../intake/reconcile-issues.js";
1
+ import { IssueState } from "../intake/reconcile-issues.js";
2
2
  import { type CallOptions, type CompletedProcess } from "../scm/call.js";
3
3
  export type ScmCallFn = (source: string, verb: string, args: readonly string[] | null, options?: CallOptions) => CompletedProcess;
4
4
  /** Max seconds to sleep before the single rate-limit retry (operator-locked cap). */
@@ -9,13 +9,15 @@ export interface RateLimitProbe {
9
9
  readonly coreResetUnix: number | null;
10
10
  readonly graphqlRemaining: number | null;
11
11
  }
12
- export interface FetchIssueStatesForReleaseOptions extends FetchIssueStatesOptions {
12
+ export interface FetchIssueStatesForReleaseOptions {
13
+ readonly cwd?: string;
14
+ readonly scmCall?: ScmCallFn;
13
15
  readonly sleep?: SleepFn;
14
16
  readonly now?: () => number;
15
17
  }
16
18
  export type FetchIssueStatesForReleaseResult = {
17
19
  readonly ok: true;
18
- readonly states: Map<number, import("../intake/reconcile-issues.js").IssueState>;
20
+ readonly states: Map<number, IssueState>;
19
21
  } | {
20
22
  readonly ok: false;
21
23
  readonly reason: string;