@deftai/directive-core 0.101.0 → 0.102.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 (71) hide show
  1. package/dist/authz/classify.js +97 -8
  2. package/dist/check/cached-orchestrator.d.ts +4 -0
  3. package/dist/check/cached-orchestrator.js +70 -5
  4. package/dist/check/cli-native-gates.d.ts +43 -0
  5. package/dist/check/cli-native-gates.js +84 -0
  6. package/dist/check/index.d.ts +1 -0
  7. package/dist/check/index.js +1 -0
  8. package/dist/check/named-cause.js +10 -0
  9. package/dist/doctor/checks.d.ts +5 -6
  10. package/dist/doctor/checks.js +13 -56
  11. package/dist/eval/later-graduation.d.ts +39 -0
  12. package/dist/eval/later-graduation.js +91 -0
  13. package/dist/eval/report.d.ts +6 -0
  14. package/dist/eval/report.js +27 -8
  15. package/dist/index.d.ts +1 -0
  16. package/dist/index.js +1 -0
  17. package/dist/intake/issue-ingest.js +42 -0
  18. package/dist/policy/ceremony-dial-escalation.d.ts +52 -0
  19. package/dist/policy/ceremony-dial-escalation.js +92 -0
  20. package/dist/policy/ceremony-dial.d.ts +3 -1
  21. package/dist/policy/ceremony-dial.js +52 -1
  22. package/dist/policy/check-resume.d.ts +9 -38
  23. package/dist/policy/check-resume.js +18 -156
  24. package/dist/policy/coverage-debt.d.ts +18 -37
  25. package/dist/policy/coverage-debt.js +38 -147
  26. package/dist/policy/index.d.ts +1 -1
  27. package/dist/policy/index.js +1 -1
  28. package/dist/product-first-done-gate/acceptance.js +22 -0
  29. package/dist/product-first-done-gate/empty-resolution.d.ts +26 -0
  30. package/dist/product-first-done-gate/empty-resolution.js +38 -0
  31. package/dist/product-first-done-gate/evaluate.d.ts +28 -2
  32. package/dist/product-first-done-gate/evaluate.js +209 -53
  33. package/dist/product-first-done-gate/index.d.ts +1 -0
  34. package/dist/product-first-done-gate/index.js +1 -0
  35. package/dist/product-first-done-gate/types.d.ts +3 -0
  36. package/dist/product-first-done-gate/types.js +0 -7
  37. package/dist/run-summary/emit.d.ts +11 -1
  38. package/dist/run-summary/emit.js +48 -2
  39. package/dist/run-summary/index.d.ts +2 -1
  40. package/dist/run-summary/index.js +2 -1
  41. package/dist/run-summary/path.d.ts +1 -1
  42. package/dist/run-summary/path.js +1 -1
  43. package/dist/run-summary/share.d.ts +25 -0
  44. package/dist/run-summary/share.js +106 -0
  45. package/dist/run-summary/types.d.ts +60 -2
  46. package/dist/run-summary/types.js +7 -0
  47. package/dist/scope/acceptance-activate-gate.d.ts +23 -0
  48. package/dist/scope/acceptance-activate-gate.js +70 -0
  49. package/dist/scope/index.d.ts +1 -0
  50. package/dist/scope/index.js +1 -0
  51. package/dist/scope/transition.js +5 -0
  52. package/dist/session/index.d.ts +0 -1
  53. package/dist/session/index.js +0 -1
  54. package/dist/session/orientation-compression.js +19 -9
  55. package/dist/session/session-start.d.ts +6 -0
  56. package/dist/session/session-start.js +44 -22
  57. package/dist/session/toolchain-preflight.d.ts +15 -0
  58. package/dist/session/toolchain-preflight.js +65 -5
  59. package/dist/verify-ac/clauses.d.ts +51 -0
  60. package/dist/verify-ac/clauses.js +490 -0
  61. package/dist/verify-ac/evaluate.d.ts +55 -0
  62. package/dist/verify-ac/evaluate.js +145 -0
  63. package/dist/verify-ac/flag.d.ts +35 -0
  64. package/dist/verify-ac/flag.js +104 -0
  65. package/dist/verify-ac/index.d.ts +10 -0
  66. package/dist/verify-ac/index.js +10 -0
  67. package/package.json +7 -3
  68. package/dist/policy/coverage-check-resume-presets.d.ts +0 -46
  69. package/dist/policy/coverage-check-resume-presets.js +0 -228
  70. package/dist/session/coverage-check-resume-nudge.d.ts +0 -34
  71. package/dist/session/coverage-check-resume-nudge.js +0 -66
@@ -5,6 +5,7 @@
5
5
  * Interoperates with #3267 plan.metadata.literal_acceptance_commands.
6
6
  */
7
7
  import { attachLiteralAcceptanceCommands, readStoredLiteralAcceptanceCommands, } from "../literal-acceptance/index.js";
8
+ import { readAcceptanceClauses, serializeAcceptanceClauses } from "../verify-ac/clauses.js";
8
9
  import { PLAN_ACCEPTANCE_KEY, } from "./types.js";
9
10
  function asRecord(value) {
10
11
  if (typeof value === "object" && value !== null && !Array.isArray(value)) {
@@ -73,6 +74,22 @@ export function validatePlanAcceptance(value) {
73
74
  if ("source_rung" in rec && rec.source_rung !== undefined && !isAcSourceRung(rec.source_rung)) {
74
75
  errors.push('plan.acceptance.source_rung must be "stated" | "derived" | "project_floor"');
75
76
  }
77
+ if ("clauses" in rec && rec.clauses !== undefined && !Array.isArray(rec.clauses)) {
78
+ errors.push("plan.acceptance.clauses must be an array");
79
+ }
80
+ if (Array.isArray(rec.clauses)) {
81
+ rec.clauses.forEach((entry, index) => {
82
+ const row = asRecord(entry);
83
+ if (row === null) {
84
+ errors.push(`plan.acceptance.clauses[${index}] must be an object`);
85
+ return;
86
+ }
87
+ const text = isNonEmptyString(row.text) ? row.text.trim() : "";
88
+ if (text.length === 0) {
89
+ errors.push(`plan.acceptance.clauses[${index}].text must be a non-empty string`);
90
+ }
91
+ });
92
+ }
76
93
  const commands = coerceCommands(rec.commands);
77
94
  const noneStated = rec.none_stated === true;
78
95
  if (commands.length === 0 && !noneStated) {
@@ -110,6 +127,7 @@ export function readPlanAcceptance(plan) {
110
127
  if (noneStated && commands.length === 0) {
111
128
  sourceRung = isAcSourceRung(rec.source_rung) ? rec.source_rung : "project_floor";
112
129
  }
130
+ const clauses = readAcceptanceClauses(rec);
113
131
  return {
114
132
  commands,
115
133
  none_stated: noneStated,
@@ -119,6 +137,7 @@ export function readPlanAcceptance(plan) {
119
137
  : isNonEmptyString(rec.derivedReason)
120
138
  ? rec.derivedReason
121
139
  : null,
140
+ ...(clauses.length > 0 ? { clauses } : {}),
122
141
  };
123
142
  }
124
143
  }
@@ -181,6 +200,9 @@ export function attachPlanAcceptance(plan, acceptance) {
181
200
  if (acceptance.derived_reason) {
182
201
  serializable.derived_reason = acceptance.derived_reason;
183
202
  }
203
+ if (acceptance.clauses !== undefined && acceptance.clauses.length > 0) {
204
+ serializable.clauses = serializeAcceptanceClauses(acceptance.clauses);
205
+ }
184
206
  let next = {
185
207
  ...plan,
186
208
  [PLAN_ACCEPTANCE_KEY]: serializable,
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Empty verify:ac resolution is not a green run when there is no suite floor (#3334).
3
+ *
4
+ * Framework source keeps today's floor-pass (suite gates own the floor).
5
+ * Consumer / no-suite projects fail closed with a distinct soft_empty outcome.
6
+ */
7
+ import type { PlanAcceptance } from "./types.js";
8
+ export declare const EMPTY_AC_OUTCOME: "soft_empty";
9
+ export declare const EMPTY_AC_CAUSE = "no acceptance stamped \u2014 floor is empty in this project";
10
+ export declare const EMPTY_AC_REMEDY = "stamp plan.acceptance (author commands or run intake capture / task issue:ingest)";
11
+ export type VerifyAcResolution = "verified-pass" | "empty-pass" | "soft_empty" | "fail" | "config" | "skipped";
12
+ /** True when this project check composition includes a suite gate (#3188). */
13
+ export declare function projectHasSuiteFloor(projectRoot: string): boolean;
14
+ export declare function isSoftEmptyAcText(text: string): boolean;
15
+ export interface EmptyAcResolutionInput {
16
+ readonly ok: boolean;
17
+ readonly code: number;
18
+ readonly runsLength: number;
19
+ readonly commandCount: number;
20
+ readonly rejectedCount: number;
21
+ readonly resolution?: VerifyAcResolution;
22
+ }
23
+ /** Zero executable commands, no rejected/unpromoted ledger, not already classified. */
24
+ export declare function isEmptyAcResolution(input: EmptyAcResolutionInput): boolean;
25
+ export declare function formatSoftEmptyMessage(acceptance: PlanAcceptance, quiet?: boolean): string;
26
+ //# sourceMappingURL=empty-resolution.d.ts.map
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Empty verify:ac resolution is not a green run when there is no suite floor (#3334).
3
+ *
4
+ * Framework source keeps today's floor-pass (suite gates own the floor).
5
+ * Consumer / no-suite projects fail closed with a distinct soft_empty outcome.
6
+ */
7
+ import { isFrameworkRepoRoot } from "../check/context.js";
8
+ export const EMPTY_AC_OUTCOME = "soft_empty";
9
+ export const EMPTY_AC_CAUSE = "no acceptance stamped — floor is empty in this project";
10
+ export const EMPTY_AC_REMEDY = "stamp plan.acceptance (author commands or run intake capture / task issue:ingest)";
11
+ /** True when this project check composition includes a suite gate (#3188). */
12
+ export function projectHasSuiteFloor(projectRoot) {
13
+ return isFrameworkRepoRoot(projectRoot);
14
+ }
15
+ export function isSoftEmptyAcText(text) {
16
+ return /soft_empty\s*\(#3334\)/i.test(text);
17
+ }
18
+ /** Zero executable commands, no rejected/unpromoted ledger, not already classified. */
19
+ export function isEmptyAcResolution(input) {
20
+ if (input.resolution === "config" || input.resolution === "skipped") {
21
+ return false;
22
+ }
23
+ if (input.code === 2) {
24
+ return false;
25
+ }
26
+ if (input.rejectedCount > 0 || input.commandCount > 0 || input.runsLength > 0) {
27
+ return false;
28
+ }
29
+ return true;
30
+ }
31
+ export function formatSoftEmptyMessage(acceptance, quiet) {
32
+ if (quiet) {
33
+ return "";
34
+ }
35
+ return (`verify:ac ${EMPTY_AC_OUTCOME} (#3334) [rung=${acceptance.source_rung}]: ` +
36
+ `${EMPTY_AC_CAUSE}. ${EMPTY_AC_REMEDY}.`);
37
+ }
38
+ //# sourceMappingURL=empty-resolution.js.map
@@ -3,15 +3,22 @@
3
3
  *
4
4
  * Runs plan.acceptance.commands (or #3267 literal ledger) via the shared
5
5
  * literal-acceptance runner. Records source_rung in the result message.
6
- * Project floor with empty commands is a soft pass (suite gates own the floor
7
- * inside full `task check`); standalone done paths still surface the rung.
6
+ * Project floor with empty commands is a soft pass only when a suite floor
7
+ * exists (suite gates own the floor inside full `task check`). With no suite
8
+ * floor, empty resolution is soft_empty — not a green run (#3334).
8
9
  */
9
10
  import { type EvaluateLiteralAcceptanceOptions, type LiteralAcceptanceGateResult } from "../literal-acceptance/index.js";
11
+ import { type ClauseWalkResult } from "../verify-ac/clauses.js";
12
+ import { type VerifyAcResolution } from "./empty-resolution.js";
10
13
  import type { AcSourceRung, PlanAcceptance } from "./types.js";
11
14
  export interface VerifyAcResult extends LiteralAcceptanceGateResult {
12
15
  readonly sourceRung: AcSourceRung;
13
16
  readonly noneStated: boolean;
14
17
  readonly acceptance: PlanAcceptance;
18
+ readonly resolution: VerifyAcResolution;
19
+ readonly resolvedCommandCount: number;
20
+ readonly clauseOutcomes?: readonly ClauseWalkResult[];
21
+ readonly clauseWalked?: boolean;
15
22
  }
16
23
  export interface EvaluateVerifyAcOptions extends EvaluateLiteralAcceptanceOptions {
17
24
  /**
@@ -36,6 +43,25 @@ export interface EvaluateVerifyAcOptions extends EvaluateLiteralAcceptanceOption
36
43
  readonly bankOnPass?: boolean;
37
44
  /** Optional scope id override for the bank ledger (default plan.id / path). */
38
45
  readonly bankScopeId?: string | null;
46
+ /**
47
+ * Injected run-summary JSONL for product-oracle integrity (#3322).
48
+ * Undefined → read DEFT_RUN_SUMMARY_PATH / default dest; null → skip disk.
49
+ */
50
+ readonly runSummaryText?: string | null;
51
+ /** When false, skip #3322 oracle integrity. Default true. */
52
+ readonly applyOracleIntegrity?: boolean;
53
+ /** Env seam for run-summary dest resolution (#3322 / #3334). */
54
+ readonly env?: NodeJS.ProcessEnv;
55
+ /**
56
+ * Inject suite-floor detection (tests). Default: framework source has a
57
+ * suite floor; consumer projects do not (#3334).
58
+ */
59
+ readonly hasSuiteFloor?: boolean;
60
+ /**
61
+ * When true, skip the acceptance run-summary emit so the path helper can
62
+ * emit after the #3285 bank checkpoint.
63
+ */
64
+ readonly skipAcceptanceEmit?: boolean;
39
65
  }
40
66
  /**
41
67
  * Evaluate product AC from an in-memory plan.
@@ -3,14 +3,19 @@
3
3
  *
4
4
  * Runs plan.acceptance.commands (or #3267 literal ledger) via the shared
5
5
  * literal-acceptance runner. Records source_rung in the result message.
6
- * Project floor with empty commands is a soft pass (suite gates own the floor
7
- * inside full `task check`); standalone done paths still surface the rung.
6
+ * Project floor with empty commands is a soft pass only when a suite floor
7
+ * exists (suite gates own the floor inside full `task check`). With no suite
8
+ * floor, empty resolution is soft_empty — not a green run (#3334).
8
9
  */
9
10
  import { existsSync, readFileSync } from "node:fs";
10
11
  import { basename, resolve } from "node:path";
11
12
  import { evaluateLiteralAcceptanceFromPlan, runLiteralAcceptanceCommands, } from "../literal-acceptance/index.js";
13
+ import { ENV_RUN_SUMMARY_PATH, RunSummaryEmitter, } from "../run-summary/index.js";
12
14
  import { maybeBankOnAcPass } from "../session/ac-pass-banking.js";
15
+ import { formatClauseWalkMessage, walkAcceptanceClauses, } from "../verify-ac/clauses.js";
16
+ import { emitVerifyAcAttempts, evaluateProductOracleIntegrity, mergeOracleVerdict, } from "../verify-ac/evaluate.js";
13
17
  import { readPlanAcceptance, validatePlanAcceptance } from "./acceptance.js";
18
+ import { formatSoftEmptyMessage, isEmptyAcResolution, projectHasSuiteFloor, } from "./empty-resolution.js";
14
19
  function asRecord(value) {
15
20
  if (typeof value === "object" && value !== null && !Array.isArray(value)) {
16
21
  return value;
@@ -25,7 +30,7 @@ export function evaluateVerifyAcFromPlan(plan, options = {}) {
25
30
  const schemaErrors = validatePlanAcceptance(plan.acceptance ?? acceptance);
26
31
  // Only hard-fail schema when an explicit plan.acceptance object exists.
27
32
  if (plan.acceptance !== undefined && schemaErrors.length > 0) {
28
- return {
33
+ return applyOracle({
29
34
  ok: false,
30
35
  code: 2,
31
36
  message: `verify:ac config error (#3284): ${schemaErrors.join("; ")}`,
@@ -34,7 +39,9 @@ export function evaluateVerifyAcFromPlan(plan, options = {}) {
34
39
  sourceRung: acceptance.source_rung,
35
40
  noneStated: acceptance.none_stated,
36
41
  acceptance,
37
- };
42
+ resolution: "config",
43
+ resolvedCommandCount: 0,
44
+ }, options);
38
45
  }
39
46
  const projectRoot = resolve(options.projectRoot ?? process.cwd());
40
47
  // Prefer shared literal-acceptance path so safety / promotion rules stay one place.
@@ -45,7 +52,10 @@ export function evaluateVerifyAcFromPlan(plan, options = {}) {
45
52
  const base = evaluateLiteralAcceptanceFromPlan(plan, {
46
53
  projectRoot,
47
54
  runner: options.runner,
48
- captureFromNarratives: options.captureFromNarratives,
55
+ // Check composition uses the stamped ledger only. Re-scanning issue prose
56
+ // during `task check` re-captures backtick `verify:ac` lines as rejected
57
+ // and deadlocks the graph (#3323 / #3284 check-integrated).
58
+ captureFromNarratives: options.captureFromNarratives ?? (options.checkIntegrated === true ? false : undefined),
49
59
  quiet: options.quiet,
50
60
  });
51
61
  // When literal path had nothing executable but plan.acceptance has derived commands,
@@ -63,7 +73,7 @@ export function evaluateVerifyAcFromPlan(plan, options = {}) {
63
73
  source: "explicit",
64
74
  sourceSpan: "plan.acceptance.commands",
65
75
  })), { projectRoot, runner, allowTaskStatement: options.allowTaskStatement });
66
- return annotate(direct, acceptance, options.quiet);
76
+ return applyOracle(annotate(direct, acceptance, options.quiet), options);
67
77
  }
68
78
  // Check composition: mid-story unpromoted capture-only may soft-pass so the
69
79
  // framework graph is not deadlocked before agents promote peers.
@@ -72,12 +82,12 @@ export function evaluateVerifyAcFromPlan(plan, options = {}) {
72
82
  if (options.checkIntegrated === true && !base.ok && base.runs.length === 0) {
73
83
  const hasRejected = (base.rejected?.length ?? 0) > 0;
74
84
  if (hasRejected) {
75
- return annotate(base, acceptance, options.quiet);
85
+ return applyOracle(annotate(base, acceptance, options.quiet), options);
76
86
  }
77
87
  const unpromoted = /capture-only|task_statement|no matching agent-promoted/i.test(base.message) ||
78
88
  (base.commands.length > 0 && base.commands.every((c) => c.source === "task_statement"));
79
89
  if (unpromoted || base.commands.length === 0) {
80
- return {
90
+ return applyOracle({
81
91
  ok: true,
82
92
  code: 0,
83
93
  message: options.quiet
@@ -92,10 +102,164 @@ export function evaluateVerifyAcFromPlan(plan, options = {}) {
92
102
  sourceRung: acceptance.source_rung,
93
103
  noneStated: acceptance.none_stated,
94
104
  acceptance,
95
- };
105
+ resolution: classifyResolution({
106
+ ok: true,
107
+ code: 0,
108
+ runsLength: 0,
109
+ commandCount: base.commands.length,
110
+ rejectedCount: base.rejected?.length ?? 0,
111
+ }),
112
+ resolvedCommandCount: base.commands.length,
113
+ }, options);
114
+ }
115
+ }
116
+ return applyOracle(annotate(base, acceptance, options.quiet), options);
117
+ }
118
+ function classifyResolution(input) {
119
+ if (input.resolution !== undefined) {
120
+ return input.resolution;
121
+ }
122
+ if (input.code === 2) {
123
+ return "config";
124
+ }
125
+ if (!input.ok) {
126
+ return "fail";
127
+ }
128
+ if (input.runsLength > 0) {
129
+ return "verified-pass";
130
+ }
131
+ if (isEmptyAcResolution(input)) {
132
+ return "empty-pass";
133
+ }
134
+ return "empty-pass";
135
+ }
136
+ function applyEmptyFloorPolicy(result, options) {
137
+ if (!isEmptyAcResolution({
138
+ ok: result.ok,
139
+ code: result.code,
140
+ runsLength: result.runs.length,
141
+ commandCount: Math.max(result.commands.length, result.acceptance.commands.length),
142
+ rejectedCount: result.rejected?.length ?? 0,
143
+ resolution: result.resolution,
144
+ })) {
145
+ return result;
146
+ }
147
+ const projectRoot = resolve(options.projectRoot ?? process.cwd());
148
+ const suiteFloor = options.hasSuiteFloor ?? projectHasSuiteFloor(projectRoot);
149
+ if (suiteFloor) {
150
+ return {
151
+ ...result,
152
+ resolution: "empty-pass",
153
+ resolvedCommandCount: 0,
154
+ };
155
+ }
156
+ return {
157
+ ...result,
158
+ ok: false,
159
+ code: 1,
160
+ message: formatSoftEmptyMessage(result.acceptance),
161
+ resolution: "soft_empty",
162
+ resolvedCommandCount: 0,
163
+ };
164
+ }
165
+ function acceptanceOutcomeOf(result) {
166
+ if (result.resolution === "verified-pass")
167
+ return "verified-pass";
168
+ if (result.resolution === "empty-pass")
169
+ return "empty-pass";
170
+ if (result.resolution === "soft_empty")
171
+ return "soft_empty";
172
+ if (result.resolution === "fail")
173
+ return "fail";
174
+ return null;
175
+ }
176
+ function emitAcceptanceOutcome(result, options, projectRoot) {
177
+ if (options.env === undefined) {
178
+ return;
179
+ }
180
+ const dest = options.env[ENV_RUN_SUMMARY_PATH];
181
+ if (dest === undefined || dest.trim().length === 0) {
182
+ return;
183
+ }
184
+ const outcome = acceptanceOutcomeOf(result);
185
+ if (outcome === null) {
186
+ return;
187
+ }
188
+ try {
189
+ const emitter = new RunSummaryEmitter({
190
+ projectRoot,
191
+ sessionId: (typeof options.env.DEFT_SESSION_ID === "string" && options.env.DEFT_SESSION_ID.trim()) ||
192
+ "verify-ac",
193
+ env: options.env,
194
+ });
195
+ emitter.emitAcceptance({
196
+ resolved_command_count: result.resolvedCommandCount,
197
+ outcome,
198
+ source_rung: result.sourceRung,
199
+ none_stated: result.noneStated,
200
+ clause_count: result.clauseOutcomes?.length,
201
+ clause_outcomes: result.clauseOutcomes?.map((row) => ({
202
+ id: row.id,
203
+ outcome: row.outcome,
204
+ })),
205
+ });
206
+ }
207
+ catch {
208
+ // fail-open
209
+ }
210
+ }
211
+ function applyClauseWalk(result, options) {
212
+ const clauses = result.acceptance.clauses ?? [];
213
+ if (clauses.length === 0 || result.resolution === "config" || result.resolution === "skipped") {
214
+ return result;
215
+ }
216
+ const projectRoot = resolve(options.projectRoot ?? process.cwd());
217
+ const report = walkAcceptanceClauses(clauses, projectRoot);
218
+ const message = options.quiet ? result.message : formatClauseWalkMessage(report, result.message);
219
+ const ok = result.ok && report.ok;
220
+ return {
221
+ ...result,
222
+ ok,
223
+ code: ok ? result.code : result.code === 2 ? 2 : 1,
224
+ message,
225
+ resolution: ok
226
+ ? result.resolution === "empty-pass"
227
+ ? "verified-pass"
228
+ : result.resolution
229
+ : "fail",
230
+ clauseOutcomes: report.clauses,
231
+ clauseWalked: true,
232
+ };
233
+ }
234
+ function applyOracle(result, options) {
235
+ const projectRoot = resolve(options.projectRoot ?? process.cwd());
236
+ const walked = applyClauseWalk(result, options);
237
+ const gated = walked.clauseWalked === true ? walked : applyEmptyFloorPolicy(walked, options);
238
+ // Emit/read disk only when the caller supplied env (CLI passes process.env).
239
+ // Tests stay isolated unless they opt in with env or runSummaryText.
240
+ if (options.env !== undefined) {
241
+ emitVerifyAcAttempts({
242
+ projectRoot,
243
+ runs: gated.runs,
244
+ env: options.env,
245
+ });
246
+ }
247
+ let next = gated;
248
+ if (options.applyOracleIntegrity !== false) {
249
+ const verdict = evaluateProductOracleIntegrity({
250
+ projectRoot,
251
+ runSummaryText: options.runSummaryText,
252
+ env: options.env,
253
+ });
254
+ next = mergeOracleVerdict(gated, verdict);
255
+ if (!verdict.ok && next.resolution !== "soft_empty" && next.resolution !== "config") {
256
+ next = { ...next, resolution: "fail" };
96
257
  }
97
258
  }
98
- return annotate(base, acceptance, options.quiet);
259
+ if (options.skipAcceptanceEmit !== true) {
260
+ emitAcceptanceOutcome(next, options, projectRoot);
261
+ }
262
+ return next;
99
263
  }
100
264
  function annotate(result, acceptance, quiet) {
101
265
  const prefix = result.ok
@@ -116,12 +280,21 @@ function annotate(result, acceptance, quiet) {
116
280
  else if (result.ok) {
117
281
  message = "";
118
282
  }
283
+ const commandCount = Math.max(result.commands.length, acceptance.commands.length);
119
284
  return {
120
285
  ...result,
121
286
  message,
122
287
  sourceRung: acceptance.source_rung,
123
288
  noneStated: acceptance.none_stated,
124
289
  acceptance,
290
+ resolution: classifyResolution({
291
+ ok: result.ok,
292
+ code: result.code,
293
+ runsLength: result.runs.length,
294
+ commandCount,
295
+ rejectedCount: result.rejected?.length ?? 0,
296
+ }),
297
+ resolvedCommandCount: result.runs.length > 0 ? result.runs.length : commandCount,
125
298
  };
126
299
  }
127
300
  /**
@@ -131,18 +304,9 @@ export function evaluateVerifyAcFromPath(xbriefPath, options = {}) {
131
304
  const abs = resolve(xbriefPath);
132
305
  if (!existsSync(abs)) {
133
306
  if (options.softMissingXbrief) {
134
- return softSkip(`xBRIEF not found: ${abs}`, options.quiet);
307
+ return applyOracle(softSkip(`xBRIEF not found: ${abs}`, options.quiet), options);
135
308
  }
136
- return {
137
- ok: false,
138
- code: 2,
139
- message: `verify:ac: xBRIEF not found: ${abs}`,
140
- commands: [],
141
- runs: [],
142
- sourceRung: "project_floor",
143
- noneStated: true,
144
- acceptance: { commands: [], none_stated: true, source_rung: "project_floor" },
145
- };
309
+ return applyOracle(configResult(`verify:ac: xBRIEF not found: ${abs}`), options);
146
310
  }
147
311
  let parsed;
148
312
  try {
@@ -150,45 +314,20 @@ export function evaluateVerifyAcFromPath(xbriefPath, options = {}) {
150
314
  }
151
315
  catch (err) {
152
316
  const msg = err instanceof Error ? err.message : String(err);
153
- return {
154
- ok: false,
155
- code: 2,
156
- message: `verify:ac: unreadable xBRIEF (${msg}): ${abs}`,
157
- commands: [],
158
- runs: [],
159
- sourceRung: "project_floor",
160
- noneStated: true,
161
- acceptance: { commands: [], none_stated: true, source_rung: "project_floor" },
162
- };
317
+ return applyOracle(configResult(`verify:ac: unreadable xBRIEF (${msg}): ${abs}`), options);
163
318
  }
164
319
  const data = asRecord(parsed);
165
320
  if (data === null) {
166
- return {
167
- ok: false,
168
- code: 2,
169
- message: `verify:ac: xBRIEF top-level is not an object: ${abs}`,
170
- commands: [],
171
- runs: [],
172
- sourceRung: "project_floor",
173
- noneStated: true,
174
- acceptance: { commands: [], none_stated: true, source_rung: "project_floor" },
175
- };
321
+ return applyOracle(configResult(`verify:ac: xBRIEF top-level is not an object: ${abs}`), options);
176
322
  }
177
323
  const plan = asRecord(data.plan);
178
324
  if (plan === null) {
179
- return {
180
- ok: false,
181
- code: 2,
182
- message: `verify:ac: xBRIEF missing plan object: ${abs}`,
183
- commands: [],
184
- runs: [],
185
- sourceRung: "project_floor",
186
- noneStated: true,
187
- acceptance: { commands: [], none_stated: true, source_rung: "project_floor" },
188
- };
325
+ return applyOracle(configResult(`verify:ac: xBRIEF missing plan object: ${abs}`), options);
189
326
  }
190
- const result = evaluateVerifyAcFromPlan(plan, options);
191
- return maybeAttachAcPassBank(result, plan, abs, options);
327
+ const result = evaluateVerifyAcFromPlan(plan, { ...options, skipAcceptanceEmit: true });
328
+ const banked = maybeAttachAcPassBank(result, plan, abs, options);
329
+ emitAcceptanceOutcome(banked, options, resolve(options.projectRoot ?? process.cwd()));
330
+ return banked;
192
331
  }
193
332
  /**
194
333
  * After executable AC pass, FINALIZE the banking checkpoint (#3285).
@@ -231,11 +370,26 @@ function maybeAttachAcPassBank(result, plan, xbriefPath, options) {
231
370
  ...result,
232
371
  ok: false,
233
372
  code: 1,
373
+ resolution: "fail",
234
374
  message: `verify:ac bank checkpoint failed (#3285): ${msg}` +
235
375
  (result.message ? `\n${result.message}` : ""),
236
376
  };
237
377
  }
238
378
  }
379
+ function configResult(message) {
380
+ return {
381
+ ok: false,
382
+ code: 2,
383
+ message,
384
+ commands: [],
385
+ runs: [],
386
+ sourceRung: "project_floor",
387
+ noneStated: true,
388
+ acceptance: { commands: [], none_stated: true, source_rung: "project_floor" },
389
+ resolution: "config",
390
+ resolvedCommandCount: 0,
391
+ };
392
+ }
239
393
  function softSkip(detail, quiet) {
240
394
  return {
241
395
  ok: true,
@@ -246,6 +400,8 @@ function softSkip(detail, quiet) {
246
400
  sourceRung: "project_floor",
247
401
  noneStated: true,
248
402
  acceptance: { commands: [], none_stated: true, source_rung: "project_floor" },
403
+ resolution: "skipped",
404
+ resolvedCommandCount: 0,
249
405
  };
250
406
  }
251
407
  /** Pure: product AC is required at every ceremony depth (#3284 / #3267 / #3156). */
@@ -6,6 +6,7 @@
6
6
  */
7
7
  export { attachPlanAcceptance, buildAcceptanceFromIntakeCapture, readPlanAcceptance, stampAcceptanceFromLiteralCapture, validatePlanAcceptance, } from "./acceptance.js";
8
8
  export { applyProductFirstGateMode, isHygieneGate, isProductAcGate, type ProductFirstCheckModeResolution, type ResolveProductFirstCheckModeInput, resolveProductFirstCheckMode, } from "./check-mode.js";
9
+ export { EMPTY_AC_CAUSE, EMPTY_AC_OUTCOME, EMPTY_AC_REMEDY, formatSoftEmptyMessage, isEmptyAcResolution, isSoftEmptyAcText, projectHasSuiteFloor, type VerifyAcResolution, } from "./empty-resolution.js";
9
10
  export { type EvaluateVerifyAcOptions, evaluateVerifyAcFromPath, evaluateVerifyAcFromPlan, isVerifyAcRequiredAtCeremonyDepth, type VerifyAcResult, } from "./evaluate.js";
10
11
  export { type AcceptanceCommand, type AcSourceRung, ENV_CHECK_AC_ONLY, ENV_CHECK_MODE, ENV_HYGIENE_ADVISORY, HYGIENE_GATE_ID_PREFIXES, PLAN_ACCEPTANCE_KEY, type PlanAcceptance, PRODUCT_AC_GATE_ID, type ProductFirstCheckMode, } from "./types.js";
11
12
  //# sourceMappingURL=index.d.ts.map
@@ -6,6 +6,7 @@
6
6
  */
7
7
  export { attachPlanAcceptance, buildAcceptanceFromIntakeCapture, readPlanAcceptance, stampAcceptanceFromLiteralCapture, validatePlanAcceptance, } from "./acceptance.js";
8
8
  export { applyProductFirstGateMode, isHygieneGate, isProductAcGate, resolveProductFirstCheckMode, } from "./check-mode.js";
9
+ export { EMPTY_AC_CAUSE, EMPTY_AC_OUTCOME, EMPTY_AC_REMEDY, formatSoftEmptyMessage, isEmptyAcResolution, isSoftEmptyAcText, projectHasSuiteFloor, } from "./empty-resolution.js";
9
10
  export { evaluateVerifyAcFromPath, evaluateVerifyAcFromPlan, isVerifyAcRequiredAtCeremonyDepth, } from "./evaluate.js";
10
11
  export { ENV_CHECK_AC_ONLY, ENV_CHECK_MODE, ENV_HYGIENE_ADVISORY, HYGIENE_GATE_ID_PREFIXES, PLAN_ACCEPTANCE_KEY, PRODUCT_AC_GATE_ID, } from "./types.js";
11
12
  //# sourceMappingURL=index.js.map
@@ -1,3 +1,4 @@
1
+ import type { AcceptanceClause } from "../verify-ac/clauses.js";
1
2
  /**
2
3
  * Product-first done-gate (#3284): stated acceptance criteria run FIRST and are
3
4
  * never skippable; hygiene gates run second and may degrade under pressure.
@@ -31,6 +32,8 @@ export interface PlanAcceptance {
31
32
  readonly source_rung: AcSourceRung;
32
33
  /** Why derived/floor was chosen (required for derived when recorded). */
33
34
  readonly derived_reason?: string | null;
35
+ /** Rung-2 independently testable clauses (#3323). Parallel to commands[]. */
36
+ readonly clauses?: readonly AcceptanceClause[];
34
37
  }
35
38
  /** How `task check` composes product vs hygiene gates under pressure (#3284). */
36
39
  export type ProductFirstCheckMode =
@@ -1,10 +1,3 @@
1
- /**
2
- * Product-first done-gate (#3284): stated acceptance criteria run FIRST and are
3
- * never skippable; hygiene gates run second and may degrade under pressure.
4
- *
5
- * Architecture for #3267 (literal acceptance-command mechanism). Composes
6
- * #3214 ceremony dial (rapid = AC-only) and #3266 bank-the-pass.
7
- */
8
1
  /** Canonical product AC gate id used in check gate lists. */
9
2
  export const PRODUCT_AC_GATE_ID = "verify:ac";
10
3
  /** plan.acceptance object key on the plan root. */
@@ -6,7 +6,7 @@
6
6
  * Symlink destinations are refused (no-follow / containment) — fail-open.
7
7
  */
8
8
  import { type ResolveRunSummaryDestinationOptions } from "./path.js";
9
- import { type CheckInvocationRunSummaryPayload, type DialTransitionRunSummaryPayload, type RunSummaryDestination, type RunSummaryEventKind, type RunSummaryLine, type RunSummaryPayload, type SessionStartRunSummaryPayload } from "./types.js";
9
+ import { type AcceptanceRunSummaryPayload, type AcceptanceStampRunSummaryPayload, type CheckInvocationRunSummaryPayload, type DialEscalationEvaluationRunSummaryPayload, type DialTransitionRunSummaryPayload, type RunSummaryDestination, type RunSummaryEventKind, type RunSummaryLine, type RunSummaryPayload, type SessionStartRunSummaryPayload, type ToolTurnDenominatorRunSummaryPayload, type VerificationRunSummaryPayload } from "./types.js";
10
10
  export interface RunSummaryEmitterOptions extends ResolveRunSummaryDestinationOptions {
11
11
  readonly projectRoot: string;
12
12
  readonly sessionId: string;
@@ -36,6 +36,7 @@ export declare class RunSummaryEmitter {
36
36
  private readonly frameworkVersion;
37
37
  private readonly now;
38
38
  private readonly destination;
39
+ private readonly env;
39
40
  private readonly writeStdout;
40
41
  private readonly writeStderr;
41
42
  constructor(options: RunSummaryEmitterOptions);
@@ -43,8 +44,17 @@ export declare class RunSummaryEmitter {
43
44
  emit(event: RunSummaryEventKind, payload: RunSummaryPayload): EmitRunSummaryResult;
44
45
  emitSessionStart(payload: SessionStartRunSummaryPayload): EmitRunSummaryResult;
45
46
  emitDialTransition(payload: DialTransitionRunSummaryPayload): EmitRunSummaryResult;
47
+ emitDialEscalationEvaluation(payload: DialEscalationEvaluationRunSummaryPayload): EmitRunSummaryResult;
46
48
  emitCheckInvocation(payload: CheckInvocationRunSummaryPayload): EmitRunSummaryResult;
49
+ emitToolTurnDenominator(payload: ToolTurnDenominatorRunSummaryPayload): EmitRunSummaryResult;
50
+ emitVerification(payload: VerificationRunSummaryPayload): EmitRunSummaryResult;
51
+ emitAcceptance(payload: AcceptanceRunSummaryPayload): EmitRunSummaryResult;
52
+ emitAcceptanceStamp(payload: AcceptanceStampRunSummaryPayload): EmitRunSummaryResult;
53
+ /** Emit the harness-supplied denominator when DEFT_TOTAL_TOOL_TURNS is set. */
54
+ emitKnownToolTurnDenominator(): EmitRunSummaryResult;
47
55
  }
56
+ /** Parse harness-supplied DEFT_TOTAL_TOOL_TURNS (integer > 0). Invalid/unset → omit. */
57
+ export declare function readEnvToolTurnDenominator(env: NodeJS.ProcessEnv): number | undefined;
48
58
  /**
49
59
  * One-shot helper for call sites that do not hold an emitter (e.g. dial escalate).
50
60
  * Still fail-open; creates a fresh seq=1 emitter unless `seqSeed` is passed via reuse.