@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
@@ -11,7 +11,7 @@ import { readCorePackageVersion } from "../engine-version.js";
11
11
  import { containedWrite } from "../fs/contained-write.js";
12
12
  import { assertWriteTargetSafe } from "../fs/projection-containment.js";
13
13
  import { resolveRunSummaryDestination } from "./path.js";
14
- import { RUN_SUMMARY_SCHEMA_VERSION, RUN_SUMMARY_STDOUT_PREFIX, RUN_SUMMARY_WRITE_WARNING, } from "./types.js";
14
+ import { ENV_TOTAL_TOOL_TURNS, RUN_SUMMARY_SCHEMA_VERSION, RUN_SUMMARY_STDOUT_PREFIX, RUN_SUMMARY_WRITE_WARNING, } from "./types.js";
15
15
  function sortKeysDeep(value) {
16
16
  if (Array.isArray(value)) {
17
17
  return value.map(sortKeysDeep);
@@ -90,6 +90,7 @@ export class RunSummaryEmitter {
90
90
  frameworkVersion;
91
91
  now;
92
92
  destination;
93
+ env;
93
94
  writeStdout;
94
95
  writeStderr;
95
96
  constructor(options) {
@@ -97,10 +98,11 @@ export class RunSummaryEmitter {
97
98
  this.sessionId = options.sessionId;
98
99
  this.frameworkVersion = options.frameworkVersion ?? readCorePackageVersion();
99
100
  this.now = options.now ?? (() => new Date());
101
+ this.env = options.env ?? process.env;
100
102
  this.destination =
101
103
  options.destination ??
102
104
  resolveRunSummaryDestination(options.projectRoot, {
103
- env: options.env,
105
+ env: this.env,
104
106
  gitignoreCovers: options.gitignoreCovers,
105
107
  });
106
108
  this.writeStdout = options.writeStdout ?? ((line) => process.stdout.write(`${line}\n`));
@@ -115,6 +117,7 @@ export class RunSummaryEmitter {
115
117
  return { emitted: false, destination: this.destination, line: null, warning: false };
116
118
  }
117
119
  this.seq += 1;
120
+ const denominator = readPayloadToolTurnDenominator(payload) ?? readEnvToolTurnDenominator(this.env);
118
121
  const line = {
119
122
  schema_version: RUN_SUMMARY_SCHEMA_VERSION,
120
123
  session_id: this.sessionId,
@@ -123,6 +126,7 @@ export class RunSummaryEmitter {
123
126
  ts: this.now().toISOString(),
124
127
  event,
125
128
  payload,
129
+ ...(denominator !== undefined ? { total_tool_turns: denominator } : {}),
126
130
  };
127
131
  const text = lineToJson(line);
128
132
  if (this.destination.kind === "stdout") {
@@ -156,9 +160,51 @@ export class RunSummaryEmitter {
156
160
  emitDialTransition(payload) {
157
161
  return this.emit("dial_transition", payload);
158
162
  }
163
+ emitDialEscalationEvaluation(payload) {
164
+ return this.emit("dial_escalation_evaluation", payload);
165
+ }
159
166
  emitCheckInvocation(payload) {
160
167
  return this.emit("check_invocation", payload);
161
168
  }
169
+ emitToolTurnDenominator(payload) {
170
+ return this.emit("tool_turn_denominator", payload);
171
+ }
172
+ emitVerification(payload) {
173
+ return this.emit("verification", payload);
174
+ }
175
+ emitAcceptance(payload) {
176
+ return this.emit("acceptance", payload);
177
+ }
178
+ emitAcceptanceStamp(payload) {
179
+ return this.emit("acceptance_stamp", payload);
180
+ }
181
+ /** Emit the harness-supplied denominator when DEFT_TOTAL_TOOL_TURNS is set. */
182
+ emitKnownToolTurnDenominator() {
183
+ const n = readEnvToolTurnDenominator(this.env);
184
+ if (n === undefined) {
185
+ return { emitted: false, destination: this.destination, line: null, warning: false };
186
+ }
187
+ return this.emitToolTurnDenominator({ total_tool_turns: n });
188
+ }
189
+ }
190
+ function readPayloadToolTurnDenominator(payload) {
191
+ if (!("total_tool_turns" in payload)) {
192
+ return undefined;
193
+ }
194
+ const value = payload.total_tool_turns;
195
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
196
+ }
197
+ /** Parse harness-supplied DEFT_TOTAL_TOOL_TURNS (integer > 0). Invalid/unset → omit. */
198
+ export function readEnvToolTurnDenominator(env) {
199
+ const raw = env[ENV_TOTAL_TOOL_TURNS];
200
+ if (raw === undefined || raw.trim().length === 0) {
201
+ return undefined;
202
+ }
203
+ const n = Number(raw.trim());
204
+ if (!Number.isInteger(n) || !Number.isFinite(n) || n <= 0) {
205
+ return undefined;
206
+ }
207
+ return n;
162
208
  }
163
209
  /**
164
210
  * One-shot helper for call sites that do not hold an emitter (e.g. dial escalate).
@@ -1,4 +1,5 @@
1
1
  export { type EmitRunSummaryResult, emitRunSummaryEvent, RunSummaryEmitter, type RunSummaryEmitterOptions, } from "./emit.js";
2
2
  export { gitignoreCoversRunSummary, type ResolveRunSummaryDestinationOptions, resolveRunSummaryDestination, } from "./path.js";
3
- export { type CheckGateOutcome, type CheckInvocationRunSummaryPayload, DEFAULT_RUN_SUMMARY_BASENAME, type DialTransitionRunSummaryPayload, ENV_RUN_SUMMARY_PATH, RUN_SUMMARY_EVENT_KINDS, RUN_SUMMARY_SCHEMA_VERSION, RUN_SUMMARY_STDOUT_PREFIX, RUN_SUMMARY_WRITE_WARNING, type RunSummaryBaseFields, type RunSummaryDestination, type RunSummaryEventKind, type RunSummaryLine, type RunSummaryPayload, type SessionStartRunSummaryPayload, } from "./types.js";
3
+ export { computeRitualGateShare, parseRunSummaryJsonl, type RitualGateShare, readToolTurnDenominator, } from "./share.js";
4
+ export { type AcceptanceClauseOutcomeRow, type AcceptanceRunSummaryOutcome, type AcceptanceRunSummaryPayload, type AcceptanceStampRunSummaryPayload, type CheckGateOutcome, type CheckInvocationRunSummaryPayload, DEFAULT_RUN_SUMMARY_BASENAME, type DialEscalationEvaluationOutcome, type DialEscalationEvaluationRunSummaryPayload, type DialTransitionRunSummaryPayload, ENV_RUN_SUMMARY_PATH, ENV_TOTAL_TOOL_TURNS, RUN_SUMMARY_EVENT_KINDS, RUN_SUMMARY_SCHEMA_VERSION, RUN_SUMMARY_STDOUT_PREFIX, RUN_SUMMARY_WRITE_WARNING, type RunSummaryBaseFields, type RunSummaryDestination, type RunSummaryEventKind, type RunSummaryLine, type RunSummaryPayload, type SessionStartRunSummaryPayload, type ToolTurnDenominatorRunSummaryPayload, type VerificationOutcome, type VerificationRunSummaryPayload, } from "./types.js";
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1,4 +1,5 @@
1
1
  export { emitRunSummaryEvent, RunSummaryEmitter, } from "./emit.js";
2
2
  export { gitignoreCoversRunSummary, resolveRunSummaryDestination, } from "./path.js";
3
- export { DEFAULT_RUN_SUMMARY_BASENAME, ENV_RUN_SUMMARY_PATH, RUN_SUMMARY_EVENT_KINDS, RUN_SUMMARY_SCHEMA_VERSION, RUN_SUMMARY_STDOUT_PREFIX, RUN_SUMMARY_WRITE_WARNING, } from "./types.js";
3
+ export { computeRitualGateShare, parseRunSummaryJsonl, readToolTurnDenominator, } from "./share.js";
4
+ export { DEFAULT_RUN_SUMMARY_BASENAME, ENV_RUN_SUMMARY_PATH, ENV_TOTAL_TOOL_TURNS, RUN_SUMMARY_EVENT_KINDS, RUN_SUMMARY_SCHEMA_VERSION, RUN_SUMMARY_STDOUT_PREFIX, RUN_SUMMARY_WRITE_WARNING, } from "./types.js";
4
5
  //# sourceMappingURL=index.js.map
@@ -6,7 +6,7 @@
6
6
  * - unset → repo-root `.deft-run-summary.json` only when .gitignore covers it; else silent
7
7
  */
8
8
  import { type RunSummaryDestination } from "./types.js";
9
- export { DEFAULT_RUN_SUMMARY_BASENAME, ENV_RUN_SUMMARY_PATH, RUN_SUMMARY_STDOUT_PREFIX, RUN_SUMMARY_WRITE_WARNING, } from "./types.js";
9
+ export { DEFAULT_RUN_SUMMARY_BASENAME, ENV_RUN_SUMMARY_PATH, ENV_TOTAL_TOOL_TURNS, RUN_SUMMARY_STDOUT_PREFIX, RUN_SUMMARY_WRITE_WARNING, } from "./types.js";
10
10
  /**
11
11
  * True when a .gitignore line covers the default run-summary basename.
12
12
  * Matches exact line or trailing slash variants; ignores comments.
@@ -8,7 +8,7 @@
8
8
  import { existsSync, readFileSync } from "node:fs";
9
9
  import { isAbsolute, join, resolve } from "node:path";
10
10
  import { DEFAULT_RUN_SUMMARY_BASENAME, ENV_RUN_SUMMARY_PATH, } from "./types.js";
11
- export { DEFAULT_RUN_SUMMARY_BASENAME, ENV_RUN_SUMMARY_PATH, RUN_SUMMARY_STDOUT_PREFIX, RUN_SUMMARY_WRITE_WARNING, } from "./types.js";
11
+ export { DEFAULT_RUN_SUMMARY_BASENAME, ENV_RUN_SUMMARY_PATH, ENV_TOTAL_TOOL_TURNS, RUN_SUMMARY_STDOUT_PREFIX, RUN_SUMMARY_WRITE_WARNING, } from "./types.js";
12
12
  /**
13
13
  * True when a .gitignore line covers the default run-summary basename.
14
14
  * Matches exact line or trailing slash variants; ignores comments.
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Ritual+gate share from a #3282 run-summary stream (#3320).
3
+ *
4
+ * Share is ritual+gate invocations over the emitted tool/turn denominator.
5
+ * Absence of the denominator is unevaluable — never invent a share from counts.
6
+ */
7
+ import { type RunSummaryLine } from "./types.js";
8
+ export interface RitualGateShare {
9
+ readonly evaluable: boolean;
10
+ readonly ritualGateCount: number;
11
+ readonly totalToolTurns: number | null;
12
+ readonly share: number | null;
13
+ }
14
+ /** True when n is a finite integer usable as a share denominator. */
15
+ export declare function isValidToolTurnDenominator(value: unknown): value is number;
16
+ /** Read total_tool_turns from a line (top-level field or payload). */
17
+ export declare function readToolTurnDenominator(line: unknown): number | undefined;
18
+ /** Parse JSONL (or DEFT-TLM: prefixed stdout capture) into run-summary lines. */
19
+ export declare function parseRunSummaryJsonl(text: string): RunSummaryLine[];
20
+ /**
21
+ * Compute ritual+gate share from summary lines alone.
22
+ * Uses the latest session in the stream. Missing/invalid denominator → unevaluable.
23
+ */
24
+ export declare function computeRitualGateShare(lines: readonly RunSummaryLine[]): RitualGateShare;
25
+ //# sourceMappingURL=share.d.ts.map
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Ritual+gate share from a #3282 run-summary stream (#3320).
3
+ *
4
+ * Share is ritual+gate invocations over the emitted tool/turn denominator.
5
+ * Absence of the denominator is unevaluable — never invent a share from counts.
6
+ */
7
+ import { RUN_SUMMARY_STDOUT_PREFIX } from "./types.js";
8
+ function asRecord(value) {
9
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
10
+ return null;
11
+ }
12
+ return value;
13
+ }
14
+ /** True when n is a finite integer usable as a share denominator. */
15
+ export function isValidToolTurnDenominator(value) {
16
+ return (typeof value === "number" && Number.isInteger(value) && Number.isFinite(value) && value > 0);
17
+ }
18
+ /** Read total_tool_turns from a line (top-level field or payload). */
19
+ export function readToolTurnDenominator(line) {
20
+ const rec = asRecord(line);
21
+ if (rec === null) {
22
+ return undefined;
23
+ }
24
+ if (isValidToolTurnDenominator(rec.total_tool_turns)) {
25
+ return rec.total_tool_turns;
26
+ }
27
+ const payload = asRecord(rec.payload);
28
+ if (payload !== null && isValidToolTurnDenominator(payload.total_tool_turns)) {
29
+ return payload.total_tool_turns;
30
+ }
31
+ return undefined;
32
+ }
33
+ function parseLine(raw) {
34
+ const trimmed = raw.trim();
35
+ if (trimmed.length === 0) {
36
+ return null;
37
+ }
38
+ const body = trimmed.startsWith(RUN_SUMMARY_STDOUT_PREFIX)
39
+ ? trimmed.slice(RUN_SUMMARY_STDOUT_PREFIX.length)
40
+ : trimmed;
41
+ try {
42
+ const parsed = JSON.parse(body);
43
+ const rec = asRecord(parsed);
44
+ if (rec === null) {
45
+ return null;
46
+ }
47
+ if (typeof rec.event !== "string" || typeof rec.session_id !== "string") {
48
+ return null;
49
+ }
50
+ return parsed;
51
+ }
52
+ catch {
53
+ return null;
54
+ }
55
+ }
56
+ /** Parse JSONL (or DEFT-TLM: prefixed stdout capture) into run-summary lines. */
57
+ export function parseRunSummaryJsonl(text) {
58
+ const lines = [];
59
+ for (const raw of text.split(/\r?\n/)) {
60
+ const line = parseLine(raw);
61
+ if (line !== null) {
62
+ lines.push(line);
63
+ }
64
+ }
65
+ return lines;
66
+ }
67
+ function lastSessionId(lines) {
68
+ let lastStart = null;
69
+ let lastAny = null;
70
+ for (const line of lines) {
71
+ lastAny = line.session_id;
72
+ if (line.event === "session_start") {
73
+ lastStart = line.session_id;
74
+ }
75
+ }
76
+ return lastStart ?? lastAny;
77
+ }
78
+ /**
79
+ * Compute ritual+gate share from summary lines alone.
80
+ * Uses the latest session in the stream. Missing/invalid denominator → unevaluable.
81
+ */
82
+ export function computeRitualGateShare(lines) {
83
+ const sessionId = lastSessionId(lines);
84
+ const sessionLines = sessionId === null ? [] : lines.filter((line) => line.session_id === sessionId);
85
+ let ritualGateCount = 0;
86
+ let totalToolTurns = null;
87
+ for (const line of sessionLines) {
88
+ if (line.event === "check_invocation") {
89
+ ritualGateCount += 1;
90
+ }
91
+ const denom = readToolTurnDenominator(line);
92
+ if (denom !== undefined) {
93
+ totalToolTurns = denom;
94
+ }
95
+ }
96
+ if (totalToolTurns === null) {
97
+ return { evaluable: false, ritualGateCount, totalToolTurns: null, share: null };
98
+ }
99
+ return {
100
+ evaluable: true,
101
+ ritualGateCount,
102
+ totalToolTurns,
103
+ share: ritualGateCount / totalToolTurns,
104
+ };
105
+ }
106
+ //# sourceMappingURL=share.js.map
@@ -7,13 +7,15 @@
7
7
  export declare const RUN_SUMMARY_SCHEMA_VERSION: 1;
8
8
  /** Env var that opts into / redirects run-summary JSONL (#3282). */
9
9
  export declare const ENV_RUN_SUMMARY_PATH = "DEFT_RUN_SUMMARY_PATH";
10
+ /** Harness-supplied session tool/turn total stamped onto run-summary lines (#3320). */
11
+ export declare const ENV_TOTAL_TOOL_TURNS = "DEFT_TOTAL_TOOL_TURNS";
10
12
  /** Repo-root default collectible path when gitignore coverage is present. */
11
13
  export declare const DEFAULT_RUN_SUMMARY_BASENAME = ".deft-run-summary.json";
12
14
  /** Prefix for stdout lines when `DEFT_RUN_SUMMARY_PATH=-`. */
13
15
  export declare const RUN_SUMMARY_STDOUT_PREFIX = "DEFT-TLM:";
14
16
  /** One warning when an explicitly requested write fails. */
15
17
  export declare const RUN_SUMMARY_WRITE_WARNING = "[deft run-summary] failed to write DEFT_RUN_SUMMARY_PATH telemetry (fail-open; exit codes unchanged)";
16
- export declare const RUN_SUMMARY_EVENT_KINDS: readonly ["session_start", "dial_transition", "check_invocation"];
18
+ export declare const RUN_SUMMARY_EVENT_KINDS: readonly ["session_start", "dial_transition", "dial_escalation_evaluation", "check_invocation", "tool_turn_denominator", "verification", "acceptance", "acceptance_stamp"];
17
19
  export type RunSummaryEventKind = (typeof RUN_SUMMARY_EVENT_KINDS)[number];
18
20
  export interface RunSummaryBaseFields {
19
21
  readonly schema_version: typeof RUN_SUMMARY_SCHEMA_VERSION;
@@ -23,6 +25,11 @@ export interface RunSummaryBaseFields {
23
25
  readonly seq: number;
24
26
  readonly ts: string;
25
27
  readonly event: RunSummaryEventKind;
28
+ /**
29
+ * Session total tool/turn count (#3320). Present so ritual+gate share is
30
+ * computable from the summary alone. Absence means the #3286 trigger is unevaluable.
31
+ */
32
+ readonly total_tool_turns?: number;
26
33
  }
27
34
  export interface SessionStartRunSummaryPayload {
28
35
  readonly ceremony_dial?: Record<string, unknown>;
@@ -43,6 +50,14 @@ export interface DialTransitionRunSummaryPayload {
43
50
  readonly reason: string;
44
51
  readonly evidence?: string;
45
52
  }
53
+ /** One evaluation of escalate-on-evidence (#3319). Absence of this event ≠ declined. */
54
+ export type DialEscalationEvaluationOutcome = "escalated" | "declined";
55
+ export interface DialEscalationEvaluationRunSummaryPayload {
56
+ /** Ceremony depth considered (the start/current tier under evaluation). */
57
+ readonly tier: string;
58
+ readonly outcome: DialEscalationEvaluationOutcome;
59
+ readonly reason: string;
60
+ }
46
61
  export interface CheckGateOutcome {
47
62
  readonly id: string;
48
63
  readonly status: "run" | "skipped" | "failed";
@@ -51,13 +66,56 @@ export interface CheckGateOutcome {
51
66
  readonly remedy?: string;
52
67
  readonly from_cache?: boolean;
53
68
  }
69
+ /** Per-clause coverage row carried on verify:ac telemetry (#3323). */
70
+ export interface AcceptanceClauseOutcomeRow {
71
+ readonly id: number;
72
+ readonly outcome: "verified" | "unverifiable" | "failed";
73
+ }
54
74
  export interface CheckInvocationRunSummaryPayload {
55
75
  readonly target: string;
56
76
  readonly exit_code: number;
57
77
  readonly degraded?: boolean;
58
78
  readonly gates: readonly CheckGateOutcome[];
79
+ /** Additive verify:ac fields (#3323). Consumers ignore unknown keys. */
80
+ readonly source_rung?: string;
81
+ readonly none_stated?: boolean;
82
+ readonly clause_outcomes?: readonly AcceptanceClauseOutcomeRow[];
83
+ }
84
+ /** Total tool/turn count for the session (#3320). */
85
+ export interface ToolTurnDenominatorRunSummaryPayload {
86
+ readonly total_tool_turns: number;
87
+ }
88
+ /** Product-oracle attempt (#3322). Same emitter as #3319 / #3320. */
89
+ export type VerificationOutcome = "pass" | "fail";
90
+ /** verify:ac resolution telemetry (#3334). Distinct from verification pass/fail. */
91
+ export type AcceptanceRunSummaryOutcome = "verified-pass" | "empty-pass" | "soft_empty" | "fail";
92
+ export interface AcceptanceRunSummaryPayload {
93
+ readonly resolved_command_count: number;
94
+ readonly outcome: AcceptanceRunSummaryOutcome;
95
+ readonly source_rung?: string;
96
+ readonly none_stated?: boolean;
97
+ readonly clause_count?: number;
98
+ readonly clause_outcomes?: readonly AcceptanceClauseOutcomeRow[];
99
+ }
100
+ /** Intake-time stamp: which rung locked, whether commands were stated, counts (#3323). */
101
+ export interface AcceptanceStampRunSummaryPayload {
102
+ readonly rung: string;
103
+ readonly none_stated: boolean;
104
+ readonly command_count: number;
105
+ readonly clause_count: number;
106
+ }
107
+ export interface VerificationRunSummaryPayload {
108
+ readonly check_id: string;
109
+ readonly method_fingerprint: string;
110
+ readonly outcome: VerificationOutcome;
111
+ /**
112
+ * True when both sides of the comparison were rebuilt from scratch by a
113
+ * different method than the one that failed. Required to resolve
114
+ * fail → method-change → pass (#3322).
115
+ */
116
+ readonly independent_rederivation?: boolean;
59
117
  }
60
- export type RunSummaryPayload = SessionStartRunSummaryPayload | DialTransitionRunSummaryPayload | CheckInvocationRunSummaryPayload;
118
+ export type RunSummaryPayload = SessionStartRunSummaryPayload | DialTransitionRunSummaryPayload | DialEscalationEvaluationRunSummaryPayload | CheckInvocationRunSummaryPayload | ToolTurnDenominatorRunSummaryPayload | VerificationRunSummaryPayload | AcceptanceRunSummaryPayload | AcceptanceStampRunSummaryPayload;
61
119
  export type RunSummaryLine = RunSummaryBaseFields & {
62
120
  readonly payload: RunSummaryPayload;
63
121
  };
@@ -7,6 +7,8 @@
7
7
  export const RUN_SUMMARY_SCHEMA_VERSION = 1;
8
8
  /** Env var that opts into / redirects run-summary JSONL (#3282). */
9
9
  export const ENV_RUN_SUMMARY_PATH = "DEFT_RUN_SUMMARY_PATH";
10
+ /** Harness-supplied session tool/turn total stamped onto run-summary lines (#3320). */
11
+ export const ENV_TOTAL_TOOL_TURNS = "DEFT_TOTAL_TOOL_TURNS";
10
12
  /** Repo-root default collectible path when gitignore coverage is present. */
11
13
  export const DEFAULT_RUN_SUMMARY_BASENAME = ".deft-run-summary.json";
12
14
  /** Prefix for stdout lines when `DEFT_RUN_SUMMARY_PATH=-`. */
@@ -16,6 +18,11 @@ export const RUN_SUMMARY_WRITE_WARNING = "[deft run-summary] failed to write DEF
16
18
  export const RUN_SUMMARY_EVENT_KINDS = [
17
19
  "session_start",
18
20
  "dial_transition",
21
+ "dial_escalation_evaluation",
19
22
  "check_invocation",
23
+ "tool_turn_denominator",
24
+ "verification",
25
+ "acceptance",
26
+ "acceptance_stamp",
20
27
  ];
21
28
  //# sourceMappingURL=types.js.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * scope:activate refuses acceptance-shaped narratives without plan.acceptance (#3334).
3
+ *
4
+ * Agents author Test / AcceptanceCriteria / Verification prose in narratives
5
+ * and then skip the executable plan.acceptance block. Activation fails closed
6
+ * with one remediation: stamp the acceptance block.
7
+ */
8
+ export interface AcceptanceActivateHit {
9
+ readonly key: string;
10
+ }
11
+ export interface AcceptanceActivateGateResult {
12
+ readonly ok: boolean;
13
+ readonly message: string;
14
+ readonly hits: readonly AcceptanceActivateHit[];
15
+ }
16
+ /** Narrative keys that look like authored acceptance (non-empty string values). */
17
+ export declare function collectAcceptanceShapedNarrativeKeys(narratives: Record<string, unknown>): AcceptanceActivateHit[];
18
+ /**
19
+ * Fail closed when narratives carry acceptance-shaped keys and plan.acceptance
20
+ * is absent. A stamped (even empty/none_stated) block is enough.
21
+ */
22
+ export declare function evaluateAcceptanceActivateGate(plan: Record<string, unknown>): AcceptanceActivateGateResult;
23
+ //# sourceMappingURL=acceptance-activate-gate.d.ts.map
@@ -0,0 +1,70 @@
1
+ /**
2
+ * scope:activate refuses acceptance-shaped narratives without plan.acceptance (#3334).
3
+ *
4
+ * Agents author Test / AcceptanceCriteria / Verification prose in narratives
5
+ * and then skip the executable plan.acceptance block. Activation fails closed
6
+ * with one remediation: stamp the acceptance block.
7
+ */
8
+ import { validatePlanAcceptance } from "../product-first-done-gate/acceptance.js";
9
+ function asRecord(value) {
10
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
11
+ return value;
12
+ }
13
+ return null;
14
+ }
15
+ const ACCEPTANCE_SHAPED_KEYS = new Set(["test", "acceptancecriteria", "verification"]);
16
+ function normalizeNarrativeKey(key) {
17
+ return key.replace(/[\s_-]+/g, "").toLowerCase();
18
+ }
19
+ /** Narrative keys that look like authored acceptance (non-empty string values). */
20
+ export function collectAcceptanceShapedNarrativeKeys(narratives) {
21
+ const hits = [];
22
+ for (const key of Object.keys(narratives)) {
23
+ if (!ACCEPTANCE_SHAPED_KEYS.has(normalizeNarrativeKey(key))) {
24
+ continue;
25
+ }
26
+ const value = narratives[key];
27
+ if (typeof value !== "string" || value.trim().length === 0) {
28
+ continue;
29
+ }
30
+ hits.push({ key });
31
+ }
32
+ return hits;
33
+ }
34
+ /**
35
+ * Fail closed when narratives carry acceptance-shaped keys and plan.acceptance
36
+ * is absent. A stamped (even empty/none_stated) block is enough.
37
+ */
38
+ export function evaluateAcceptanceActivateGate(plan) {
39
+ const narratives = asRecord(plan.narratives);
40
+ if (narratives === null) {
41
+ return { ok: true, message: "", hits: [] };
42
+ }
43
+ const hits = collectAcceptanceShapedNarrativeKeys(narratives);
44
+ if (hits.length === 0) {
45
+ return { ok: true, message: "", hits: [] };
46
+ }
47
+ if (plan.acceptance !== undefined && plan.acceptance !== null) {
48
+ const schemaErrors = validatePlanAcceptance(plan.acceptance);
49
+ if (schemaErrors.length > 0) {
50
+ return {
51
+ ok: false,
52
+ message: `Refusing activate: plan.acceptance is present but invalid (#3334): ` +
53
+ `${schemaErrors.join("; ")}. Stamp a valid plan.acceptance block ` +
54
+ `(author commands or none_stated via intake capture / task issue:ingest).`,
55
+ hits,
56
+ };
57
+ }
58
+ return { ok: true, message: "", hits };
59
+ }
60
+ const keys = hits.map((h) => h.key).join(", ");
61
+ return {
62
+ ok: false,
63
+ message: `Refusing activate: narratives contain acceptance-shaped keys (${keys}) ` +
64
+ `but plan.acceptance is absent (#3334). Stamp plan.acceptance ` +
65
+ `(author commands or none_stated via intake capture / task issue:ingest) — ` +
66
+ `the criteria are in the wrong, non-executable field.`,
67
+ hits,
68
+ };
69
+ }
70
+ //# sourceMappingURL=acceptance-activate-gate.js.map
@@ -1,3 +1,4 @@
1
+ export * from "./acceptance-activate-gate.js";
1
2
  export * from "./acceptance-evidence.js";
2
3
  export * from "./audit-log.js";
3
4
  export * from "./batch-promote.js";
@@ -1,3 +1,4 @@
1
+ export * from "./acceptance-activate-gate.js";
1
2
  export * from "./acceptance-evidence.js";
2
3
  export * from "./audit-log.js";
3
4
  export * from "./batch-promote.js";
@@ -5,6 +5,7 @@ import { assertProjectionContained, ProjectionContainmentError, } from "../fs/pr
5
5
  import { hasArtifactSuffix } from "../layout/resolve.js";
6
6
  import { evaluateCompletedPlanConsistency } from "../lifecycle/completed-consistency.js";
7
7
  import { evaluateLiteralAcceptanceFromPlan } from "../literal-acceptance/index.js";
8
+ import { evaluateAcceptanceActivateGate } from "./acceptance-activate-gate.js";
8
9
  import { evaluateAcceptanceEvidenceGate, formatAcceptanceCompletionListing, } from "./acceptance-evidence.js";
9
10
  import { append, canonicalLogPath, newDecisionId } from "./audit-log.js";
10
11
  import { atomicWriteBrief, formatBriefJson, readBriefForMutation } from "./brief-io.js";
@@ -148,6 +149,10 @@ export function runTransition(action, filePath, now = new Date(), options = {})
148
149
  if (!effortGate.ok) {
149
150
  return { ok: false, message: effortGate.message };
150
151
  }
152
+ const acceptanceGate = evaluateAcceptanceActivateGate(planObj);
153
+ if (!acceptanceGate.ok) {
154
+ return { ok: false, message: acceptanceGate.message };
155
+ }
151
156
  }
152
157
  // #3041: fail closed before mutating a code-bearing complete without delivery evidence.
153
158
  if (act === "complete") {
@@ -1,7 +1,6 @@
1
1
  export * from "./ac-pass-banking.js";
2
2
  export * from "./active-cli.js";
3
3
  export * from "./compact-ritual.js";
4
- export * from "./coverage-check-resume-nudge.js";
5
4
  export * from "./deposit-sha.js";
6
5
  export * from "./effort-budget.js";
7
6
  export * from "./git.js";
@@ -1,7 +1,6 @@
1
1
  export * from "./ac-pass-banking.js";
2
2
  export * from "./active-cli.js";
3
3
  export * from "./compact-ritual.js";
4
- export * from "./coverage-check-resume-nudge.js";
5
4
  export * from "./deposit-sha.js";
6
5
  export * from "./effort-budget.js";
7
6
  export * from "./git.js";
@@ -6,6 +6,7 @@
6
6
  * agents:refresh and verify:cache-fresh; formats compact (opt-in) vs verbose
7
7
  * (default). Does NOT implement unified `deft orient` (Later remains open).
8
8
  */
9
+ import { isFrameworkRepoRoot } from "../check/context.js";
9
10
  import { decideThrottle, readState as readDoctorState, renderDoctorStatusLine, } from "../doctor/doctor-state.js";
10
11
  import { cmdDoctor } from "../doctor/main.js";
11
12
  import { applyAgentsRefresh } from "../platform/agents-md.js";
@@ -271,16 +272,23 @@ export function runCacheFreshOrientationSection(projectRoot, options) {
271
272
  void options.now;
272
273
  try {
273
274
  const evaluate = options.evaluateFn ?? evaluateCacheFresh;
274
- // #3286 Greptile: do not treat missing cache/candidates as a successful
275
- // cache-fresh for orientation / gated_steps (bootstrap must not look green).
276
- // Session:start still continues; gated verify will re-run cache_fresh when
277
- // ok is false.
275
+ const consumerDeposit = !isFrameworkRepoRoot(projectRoot);
276
+ // Consumer deposits often have no triage cache. Treat that as a named
277
+ // bootstrap cause (dirty), never a generic orientation `error` (#3335).
278
+ // Framework source still evaluates live; missing cache stays non-green
279
+ // (#3286) so gated verify re-runs cache_fresh.
278
280
  const result = evaluate(projectRoot, {
279
281
  allowMissingBootstrap: false,
280
282
  autoPopulateEmpty: false,
281
283
  ...options.evaluateOptions,
282
284
  });
283
285
  const ok = result.code === 0;
286
+ const missingCache = result.code === 2 && /not found|bootstrap/i.test(result.message);
287
+ const namedCause = missingCache
288
+ ? consumerDeposit
289
+ ? `[deft cache-fresh] triage cache not populated (consumer deposit) — not a toolchain failure. Recovery: run \`deft triage:bootstrap\` if you need the queue.`
290
+ : result.message
291
+ : result.message;
284
292
  // Tag as sha_match only when message already uses the canonical phrase
285
293
  // (e.g. tests inject it) — never invent a shortcut past evaluate.
286
294
  const shaMatch = ok && result.message.includes(DEPOSIT_SHA_MATCH_NOOP);
@@ -288,19 +296,21 @@ export function runCacheFreshOrientationSection(projectRoot, options) {
288
296
  ? "sha_match"
289
297
  : ok
290
298
  ? "ok"
291
- : result.code === 2
292
- ? "error"
293
- : "dirty";
299
+ : missingCache
300
+ ? "dirty"
301
+ : result.code === 2
302
+ ? "error"
303
+ : "dirty";
294
304
  return {
295
305
  name: "cache_fresh",
296
306
  status,
297
307
  ok,
298
308
  exitCode: result.code,
299
- lines: [result.message],
309
+ lines: [namedCause],
300
310
  shaMatch,
301
311
  depositSha: options.depositSha,
302
312
  durationMs: elapsedMs(started),
303
- detail: { code: result.code },
313
+ detail: { code: result.code, missing_cache: missingCache },
304
314
  };
305
315
  }
306
316
  catch (exc) {
@@ -1,6 +1,7 @@
1
1
  import { type HostContentSurfaceSeams } from "../platform/host-content-surface.js";
2
2
  import { type EnvironmentContext } from "../platform/shell-context.js";
3
3
  import { type CeremonyDialInputs, type CeremonyDialSelection, type ProvisionalCeremonyEstimateHints } from "../policy/ceremony-dial.js";
4
+ import { type CeremonyStartTierProvenance } from "../policy/ceremony-dial-escalation.js";
4
5
  import { type ProbeScmReadinessOptions, type ScmReadinessReport } from "../scm/readiness.js";
5
6
  import { type ResolveUserMdResult } from "../user-config/resolve-user-md.js";
6
7
  import { type DetectHardEffortBudgetInput } from "./effort-budget.js";
@@ -125,6 +126,11 @@ export interface SessionStartOptions {
125
126
  * loads plan.policy.ceremonyDial and applies inputs (after provisional fill).
126
127
  */
127
128
  readonly ceremonyDial?: CeremonyDialSelection;
129
+ /**
130
+ * #3319: start-tier provenance hint. CLI `--ceremony-depth` / harness pin
131
+ * should pass `external-pin`. Policy override is inferred as `operator`.
132
+ */
133
+ readonly ceremonyDialStartTierProvenance?: CeremonyStartTierProvenance;
128
134
  /**
129
135
  * #3282: done-gate toolchain preflight seams (tests inject result or probe).
130
136
  * When omitted, runs live preflight after verify_tools on cold path.