@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
@@ -0,0 +1,91 @@
1
+ /**
2
+ * #3286 Later graduation trigger from a #3282 run summary (#3320).
3
+ *
4
+ * Named surface: eval:report prints graduate / do-not-graduate / unevaluable.
5
+ * A graduation or decline MUST cite an emitted share. Counts without a
6
+ * denominator are unevaluable — never invent a ratio.
7
+ */
8
+ import { existsSync, readFileSync } from "node:fs";
9
+ import { isAbsolute, resolve } from "node:path";
10
+ import { computeRitualGateShare, parseRunSummaryJsonl, } from "../run-summary/share.js";
11
+ import { DEFAULT_RUN_SUMMARY_BASENAME, ENV_RUN_SUMMARY_PATH } from "../run-summary/types.js";
12
+ /** #3286 Later trigger: graduate when ritual+gate share is at least this fraction. */
13
+ export const LATER_GRADUATION_SHARE_THRESHOLD = 0.25;
14
+ function formatPercent(value) {
15
+ return `${(value * 100).toFixed(1)}%`;
16
+ }
17
+ export function laterGraduationFromShare(share, threshold = LATER_GRADUATION_SHARE_THRESHOLD) {
18
+ if (!share.evaluable || share.share === null || share.totalToolTurns === null) {
19
+ return {
20
+ evaluable: false,
21
+ verdict: "unevaluable",
22
+ summary: "trigger unevaluable (no tool/turn denominator on run summary)",
23
+ ritualGateCount: share.ritualGateCount,
24
+ totalToolTurns: share.totalToolTurns,
25
+ share: null,
26
+ threshold,
27
+ };
28
+ }
29
+ const graduate = share.share >= threshold;
30
+ const verdict = graduate ? "graduate" : "do-not-graduate";
31
+ return {
32
+ evaluable: true,
33
+ verdict,
34
+ summary: `ritual+gate share ${formatPercent(share.share)} (${share.ritualGateCount}/${share.totalToolTurns}) — ${verdict} (threshold ${formatPercent(threshold)})`,
35
+ ritualGateCount: share.ritualGateCount,
36
+ totalToolTurns: share.totalToolTurns,
37
+ share: share.share,
38
+ threshold,
39
+ };
40
+ }
41
+ /**
42
+ * Resolve a readable run-summary path. Unset env + missing default file → null
43
+ * (fail-open / unevaluable). stdout dest (`-`) is not a file.
44
+ */
45
+ export function resolveRunSummaryReadPath(projectRoot, options = {}) {
46
+ if (options.runSummaryPath !== undefined && options.runSummaryPath.trim().length > 0) {
47
+ return resolve(options.runSummaryPath.trim());
48
+ }
49
+ const env = options.env ?? process.env;
50
+ const raw = env[ENV_RUN_SUMMARY_PATH];
51
+ if (raw !== undefined && raw.trim() === "-") {
52
+ return null;
53
+ }
54
+ if (raw !== undefined && raw.trim().length > 0) {
55
+ const trimmed = raw.trim();
56
+ return isAbsolute(trimmed) ? resolve(trimmed) : resolve(projectRoot, trimmed);
57
+ }
58
+ const fallback = resolve(projectRoot, DEFAULT_RUN_SUMMARY_BASENAME);
59
+ return existsSync(fallback) ? fallback : null;
60
+ }
61
+ function readSummaryText(path) {
62
+ try {
63
+ if (!existsSync(path)) {
64
+ return null;
65
+ }
66
+ return readFileSync(path, "utf8");
67
+ }
68
+ catch {
69
+ return null;
70
+ }
71
+ }
72
+ /** Evaluate #3286 Later from the run-summary file (or unevaluable). */
73
+ export function evaluateLaterGraduationTrigger(options) {
74
+ const threshold = options.threshold ?? LATER_GRADUATION_SHARE_THRESHOLD;
75
+ const path = resolveRunSummaryReadPath(options.projectRoot, {
76
+ runSummaryPath: options.runSummaryPath,
77
+ env: options.env,
78
+ });
79
+ if (path === null) {
80
+ return laterGraduationFromShare({ evaluable: false, ritualGateCount: 0, totalToolTurns: null, share: null }, threshold);
81
+ }
82
+ const text = readSummaryText(path);
83
+ if (text === null) {
84
+ return laterGraduationFromShare({ evaluable: false, ritualGateCount: 0, totalToolTurns: null, share: null }, threshold);
85
+ }
86
+ return laterGraduationFromShare(computeRitualGateShare(parseRunSummaryJsonl(text)), threshold);
87
+ }
88
+ export function formatLaterGraduationLine(trigger) {
89
+ return `#3286 Later: ${trigger.summary}`;
90
+ }
91
+ //# sourceMappingURL=later-graduation.js.map
@@ -1,3 +1,4 @@
1
+ import { type LaterGraduationTrigger } from "./later-graduation.js";
1
2
  import { type GoldenRunRecord } from "./run.js";
2
3
  import { type CellVersionPurity, type MixedVersionPolicy } from "./version-pin.js";
3
4
  export declare const REPORT_SCHEMA_VERSION: 1;
@@ -39,6 +40,8 @@ export interface GoldenEvalReport {
39
40
  readonly holdoutTripwire: HoldoutTripwire;
40
41
  /** Cell-level framework version purity for the reported model (#3215). */
41
42
  readonly versionPurity: VersionPurityEvidence;
43
+ /** #3286 Later graduation trigger from the run-summary denominator (#3320). */
44
+ readonly laterGraduation: LaterGraduationTrigger;
42
45
  }
43
46
  export interface ReportGoldenEvalOptions {
44
47
  readonly projectRoot?: string;
@@ -50,6 +53,9 @@ export interface ReportGoldenEvalOptions {
50
53
  * ledger runs for this model disagree on framework version within a treatment.
51
54
  */
52
55
  readonly mixedVersionPolicy?: MixedVersionPolicy;
56
+ /** Override run-summary JSONL path (#3320). Default: DEFT_RUN_SUMMARY_PATH or repo default. */
57
+ readonly runSummaryPath?: string;
58
+ readonly env?: NodeJS.ProcessEnv;
53
59
  }
54
60
  export interface ReportGoldenEvalResult {
55
61
  readonly code: 0 | 1 | 2;
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { resolveEvalPath } from "../layout/resolve.js";
3
+ import { evaluateLaterGraduationTrigger, formatLaterGraduationLine, } from "./later-graduation.js";
3
4
  import { GOLDEN_RUNS_HISTORY_REL } from "./run.js";
4
5
  import { aggregateCellWithVersionPurity, evaluateLedgerVersionPurity, } from "./version-pin.js";
5
6
  export const REPORT_SCHEMA_VERSION = 1;
@@ -107,17 +108,33 @@ export function goldenRunToVersionedRun(record) {
107
108
  }
108
109
  /** Diff two directive versions with metric deltas and significance (#1703 Tier 2). */
109
110
  export function reportGoldenEval(options) {
110
- if (!options.championVersion.trim() || !options.challengerVersion.trim()) {
111
+ const projectRoot = options.projectRoot ?? process.cwd();
112
+ const laterGraduation = evaluateLaterGraduationTrigger({
113
+ projectRoot,
114
+ runSummaryPath: options.runSummaryPath,
115
+ env: options.env,
116
+ });
117
+ const laterLine = formatLaterGraduationLine(laterGraduation);
118
+ const championMissing = !options.championVersion.trim();
119
+ const challengerMissing = !options.challengerVersion.trim();
120
+ const modelMissing = !options.model.trim();
121
+ if (championMissing && challengerMissing && modelMissing) {
122
+ return {
123
+ code: 0,
124
+ report: null,
125
+ message: `eval:report ${laterLine}`,
126
+ };
127
+ }
128
+ if (championMissing || challengerMissing) {
111
129
  return {
112
130
  code: 2,
113
131
  report: null,
114
- message: "eval:report: --champion and --challenger are required",
132
+ message: `eval:report: --champion and --challenger are required\n ${laterLine}`,
115
133
  };
116
134
  }
117
- if (!options.model.trim()) {
118
- return { code: 2, report: null, message: "eval:report: --model is required" };
135
+ if (modelMissing) {
136
+ return { code: 2, report: null, message: `eval:report: --model is required\n ${laterLine}` };
119
137
  }
120
- const projectRoot = options.projectRoot ?? process.cwd();
121
138
  const policy = options.mixedVersionPolicy ?? "refuse";
122
139
  const records = readGoldenRuns(projectRoot);
123
140
  const modelRecords = records.filter((r) => r.model === options.model);
@@ -157,7 +174,7 @@ export function reportGoldenEval(options) {
157
174
  return {
158
175
  code: 1,
159
176
  report: null,
160
- message: `eval:report: mixed framework versions in treatment cell(s) — aggregation refused (#3215)\n ${versionPurity.summary}`,
177
+ message: `eval:report: mixed framework versions in treatment cell(s) — aggregation refused (#3215)\n ${versionPurity.summary}\n ${laterLine}`,
161
178
  };
162
179
  }
163
180
  const champion = findLatestGoldenRun(records, options.championVersion, options.model);
@@ -166,14 +183,14 @@ export function reportGoldenEval(options) {
166
183
  return {
167
184
  code: 1,
168
185
  report: null,
169
- message: `eval:report: no golden run found for champion v${options.championVersion} model=${options.model}`,
186
+ message: `eval:report: no golden run found for champion v${options.championVersion} model=${options.model}\n ${laterLine}`,
170
187
  };
171
188
  }
172
189
  if (challenger === null) {
173
190
  return {
174
191
  code: 1,
175
192
  report: null,
176
- message: `eval:report: no golden run found for challenger v${options.challengerVersion} model=${options.model}`,
193
+ message: `eval:report: no golden run found for challenger v${options.challengerVersion} model=${options.model}\n ${laterLine}`,
177
194
  };
178
195
  }
179
196
  const championPrimary = champion.results.filter((r) => !r.holdout);
@@ -208,6 +225,7 @@ export function reportGoldenEval(options) {
208
225
  deltas,
209
226
  holdoutTripwire,
210
227
  versionPurity,
228
+ laterGraduation,
211
229
  };
212
230
  const lines = [
213
231
  `eval:report champion=v${options.championVersion} challenger=v${options.challengerVersion} model=${options.model}`,
@@ -223,6 +241,7 @@ export function reportGoldenEval(options) {
223
241
  ` version purity: ${versionPurity.pure ? "ok" : "mixed"} — ${versionPurity.summary}`,
224
242
  ` champion cell pin: v${champion.frameworkVersionPin?.frameworkVersion ?? champion.directiveVersion}`,
225
243
  ` challenger cell pin: v${challenger.frameworkVersionPin?.frameworkVersion ?? challenger.directiveVersion}`,
244
+ ` ${laterLine}`,
226
245
  ];
227
246
  const code = holdoutTripwire.triggered ? 1 : 0;
228
247
  return { code, report, message: lines.join("\n") };
package/dist/index.d.ts CHANGED
@@ -74,6 +74,7 @@ export * as vbriefBuild from "./vbrief-build/index.js";
74
74
  export * as vbriefReconcile from "./vbrief-reconcile/index.js";
75
75
  export * as vbriefValidate from "./vbrief-validate/index.js";
76
76
  export * as vbriefValidation from "./vbrief-validation/index.js";
77
+ export * as verifyAc from "./verify-ac/index.js";
77
78
  export * as verifyEnv from "./verify-env/index.js";
78
79
  export * as verifySource from "./verify-source/index.js";
79
80
  export * as wipCap from "./wip-cap/index.js";
package/dist/index.js CHANGED
@@ -75,6 +75,7 @@ export * as vbriefBuild from "./vbrief-build/index.js";
75
75
  export * as vbriefReconcile from "./vbrief-reconcile/index.js";
76
76
  export * as vbriefValidate from "./vbrief-validate/index.js";
77
77
  export * as vbriefValidation from "./vbrief-validation/index.js";
78
+ export * as verifyAc from "./verify-ac/index.js";
78
79
  export * as verifyEnv from "./verify-env/index.js";
79
80
  export * as verifySource from "./verify-source/index.js";
80
81
  export * as wipCap from "./wip-cap/index.js";
@@ -6,12 +6,14 @@ import { assertWriteTargetSafe, ProjectionContainmentError } from "../fs/project
6
6
  import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
7
7
  import { captureAndAttachLiteralAcceptance } from "../literal-acceptance/index.js";
8
8
  import { stampAcceptanceFromLiteralCapture } from "../product-first-done-gate/index.js";
9
+ import { ENV_RUN_SUMMARY_PATH, RunSummaryEmitter } from "../run-summary/index.js";
9
10
  import { call } from "../scm/call.js";
10
11
  import { resolveProjectRoot } from "../scope/project-context.js";
11
12
  import { resolveProjectRepo } from "../slice/project-context.js";
12
13
  import { countMaintainerCurrentShapeComments, mapIssueCommentEntry, RAW_ISSUE_COMMENTS_KEY, selectCurrentShapeComment, } from "../umbrella-current-shape/index.js";
13
14
  import { slugify, TODAY } from "../vbrief-build/build.js";
14
15
  import { EMITTED_VBRIEF_VERSION } from "../vbrief-build/constants.js";
16
+ import { stampDerivedClausesOnAcceptance } from "../verify-ac/clauses.js";
15
17
  import { LEGACY_ARTIFACT_SUFFIX, LEGACY_INFO_ROOT_KEY, MIGRATED_ARTIFACT_DIR, MIGRATED_ARTIFACT_SUFFIX, MIGRATED_INFO_ROOT_KEY, VBRIEF_VERSION, } from "../xbrief-migrate/constants.js";
16
18
  import { findAcHeading, parseCheckboxItems, parseListItems, sliceAcSection, stripCodeBlocks, stripFencedCodeBlocks, } from "./markdown-scanners.js";
17
19
  import { detectRepo, extractReferencesFromVbrief, fetchOpenIssues, GITHUB_ISSUE_REF_TYPES, LIFECYCLE_FOLDERS, parseIssueNumber, } from "./reconcile-issues.js";
@@ -475,6 +477,9 @@ export function buildIssueVbrief(issue, status, repoUrl, options = {}) {
475
477
  Object.assign(plan, attached.plan);
476
478
  // #3284: stamp plan.acceptance from captured literals (stated or none_stated floor).
477
479
  Object.assign(plan, stampAcceptanceFromLiteralCapture(plan));
480
+ // #3323: when no commands were stated, derive numbered clauses before product edit.
481
+ const derived = stampDerivedClausesOnAcceptance(plan, [title, overviewSource].filter((s) => s.length > 0).join("\n\n"));
482
+ Object.assign(plan, derived.plan);
478
483
  }
479
484
  else {
480
485
  // No body: still record none_stated acceptance so absence is a decision.
@@ -718,8 +723,45 @@ export function ingestOne(issue, options) {
718
723
  assertWriteTargetSafe(projectRoot, target);
719
724
  mkdirSync(folderPath, { recursive: true });
720
725
  writeFileSync(target, `${JSON.stringify(vbrief, null, 2)}\n`, "utf8");
726
+ emitIntakeAcceptanceStamp(projectRoot, vbrief.plan);
721
727
  return ["created", target, `CREATED ${folder}/${filename}`];
722
728
  }
729
+ function emitIntakeAcceptanceStamp(projectRoot, plan) {
730
+ const rec = plan !== null && typeof plan === "object" && !Array.isArray(plan)
731
+ ? plan
732
+ : null;
733
+ if (rec === null) {
734
+ return;
735
+ }
736
+ const acceptance = rec.acceptance !== null && typeof rec.acceptance === "object" && !Array.isArray(rec.acceptance)
737
+ ? rec.acceptance
738
+ : null;
739
+ if (acceptance === null) {
740
+ return;
741
+ }
742
+ const env = process.env;
743
+ const dest = env[ENV_RUN_SUMMARY_PATH];
744
+ if (dest === undefined || dest.trim().length === 0) {
745
+ return;
746
+ }
747
+ try {
748
+ const sessionId = typeof env.DEFT_SESSION_ID === "string" && env.DEFT_SESSION_ID.trim().length > 0
749
+ ? env.DEFT_SESSION_ID.trim()
750
+ : "issue-ingest";
751
+ const emitter = new RunSummaryEmitter({ projectRoot, sessionId, env });
752
+ const commands = Array.isArray(acceptance.commands) ? acceptance.commands.length : 0;
753
+ const clauses = Array.isArray(acceptance.clauses) ? acceptance.clauses.length : 0;
754
+ emitter.emitAcceptanceStamp({
755
+ rung: typeof acceptance.source_rung === "string" ? acceptance.source_rung : "project_floor",
756
+ none_stated: acceptance.none_stated === true,
757
+ command_count: commands,
758
+ clause_count: clauses,
759
+ });
760
+ }
761
+ catch {
762
+ // fail-open
763
+ }
764
+ }
723
765
  export function ingestBulk(issues, options) {
724
766
  let filtered = issues;
725
767
  if (options.label !== undefined && options.label !== null) {
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Ceremony-dial escalation evaluation + start-tier provenance (#3319).
3
+ *
4
+ * Observability only: does not change escalate-on-evidence thresholds or the
5
+ * default start tier. A declined evaluation is distinct from never-evaluated.
6
+ */
7
+ import type { DialEscalationEvaluationOutcome } from "../run-summary/types.js";
8
+ import { type CeremonyDepth, type CeremonyDialInputs, type CeremonyDialSelection, type CeremonyModelTier } from "./ceremony-dial.js";
9
+ export declare const CEREMONY_START_TIER_PROVENANCES: readonly ["cold-start", "external-pin", "operator"];
10
+ export type CeremonyStartTierProvenance = (typeof CEREMONY_START_TIER_PROVENANCES)[number];
11
+ export declare const CEREMONY_DEPTH_RANK: Readonly<Record<CeremonyDepth, number>>;
12
+ export interface ResolveCeremonyStartTierProvenanceInput {
13
+ readonly selection: CeremonyDialSelection;
14
+ /** True when session:start received a pre-resolved selection (CLI/harness). */
15
+ readonly injectedSelection: boolean;
16
+ readonly hint?: CeremonyStartTierProvenance;
17
+ }
18
+ export declare function resolveCeremonyStartTierProvenance(input: ResolveCeremonyStartTierProvenanceInput): CeremonyStartTierProvenance;
19
+ export declare function isCeremonyStartTierPinned(provenance: CeremonyStartTierProvenance): boolean;
20
+ /** Session-start fail-closed line when a pin bypasses #3274 cold-start selection. */
21
+ export declare function formatCeremonyDialPinBypassLine(provenance: CeremonyStartTierProvenance): string | null;
22
+ export interface CeremonyDialEscalationEvaluation {
23
+ readonly tier: CeremonyDepth;
24
+ readonly outcome: DialEscalationEvaluationOutcome;
25
+ readonly reason: string;
26
+ readonly from: CeremonyDepth;
27
+ readonly to: CeremonyDepth;
28
+ }
29
+ export interface EvaluateCeremonyDialEscalationInput {
30
+ readonly from: CeremonyDepth;
31
+ readonly to: CeremonyDepth;
32
+ readonly inputs?: CeremonyDialInputs;
33
+ }
34
+ /**
35
+ * Pure escalate-on-evidence evaluation. `to > from` → escalated; else declined.
36
+ * Does not persist a transition.
37
+ */
38
+ export declare function evaluateCeremonyDialEscalation(input: EvaluateCeremonyDialEscalationInput): CeremonyDialEscalationEvaluation;
39
+ /** Cold-start vs selected depth at session:start (unpinned path only). */
40
+ export declare function evaluateSessionStartCeremonyDialEscalation(input: {
41
+ readonly selection: CeremonyDialSelection;
42
+ readonly modelTier?: CeremonyModelTier | null;
43
+ }): CeremonyDialEscalationEvaluation;
44
+ export interface EmitCeremonyDialEscalationEvaluationOptions {
45
+ readonly projectRoot: string;
46
+ readonly sessionId: string;
47
+ readonly env?: NodeJS.ProcessEnv;
48
+ readonly evaluation: CeremonyDialEscalationEvaluation;
49
+ }
50
+ /** Fail-open emit of a #3319 evaluation event. */
51
+ export declare function emitCeremonyDialEscalationEvaluation(options: EmitCeremonyDialEscalationEvaluationOptions): void;
52
+ //# sourceMappingURL=ceremony-dial-escalation.d.ts.map
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Ceremony-dial escalation evaluation + start-tier provenance (#3319).
3
+ *
4
+ * Observability only: does not change escalate-on-evidence thresholds or the
5
+ * default start tier. A declined evaluation is distinct from never-evaluated.
6
+ */
7
+ import { RunSummaryEmitter } from "../run-summary/emit.js";
8
+ import { selectCeremonyColdStartDepth, } from "./ceremony-dial.js";
9
+ export const CEREMONY_START_TIER_PROVENANCES = ["cold-start", "external-pin", "operator"];
10
+ export const CEREMONY_DEPTH_RANK = {
11
+ minimal: 0,
12
+ rapid: 1,
13
+ standard: 2,
14
+ elevated: 3,
15
+ };
16
+ export function resolveCeremonyStartTierProvenance(input) {
17
+ if (input.hint !== undefined) {
18
+ return input.hint;
19
+ }
20
+ if (input.selection.source === "override") {
21
+ return input.injectedSelection ? "external-pin" : "operator";
22
+ }
23
+ return "cold-start";
24
+ }
25
+ export function isCeremonyStartTierPinned(provenance) {
26
+ return provenance === "external-pin" || provenance === "operator";
27
+ }
28
+ /** Session-start fail-closed line when a pin bypasses #3274 cold-start selection. */
29
+ export function formatCeremonyDialPinBypassLine(provenance) {
30
+ if (!isCeremonyStartTierPinned(provenance)) {
31
+ return null;
32
+ }
33
+ const pinLabel = provenance === "external-pin" ? "external-pin" : "operator pin";
34
+ return (`[deft ceremony-dial] #3274 cold-start selection is bypassed (${pinLabel}). ` +
35
+ "Unset the pin (--ceremony-depth or plan.policy.ceremonyDial.override) to exercise #3274.");
36
+ }
37
+ /**
38
+ * Pure escalate-on-evidence evaluation. `to > from` → escalated; else declined.
39
+ * Does not persist a transition.
40
+ */
41
+ export function evaluateCeremonyDialEscalation(input) {
42
+ const size = input.inputs?.taskSize ?? null;
43
+ const modelTier = input.inputs?.modelTier ?? null;
44
+ const evidence = `size=${size ?? "-"} modelTier=${modelTier ?? "-"}`;
45
+ const raised = CEREMONY_DEPTH_RANK[input.to] > CEREMONY_DEPTH_RANK[input.from];
46
+ if (raised) {
47
+ return {
48
+ tier: input.to,
49
+ outcome: "escalated",
50
+ reason: `evidence raised ${input.from} -> ${input.to} (${evidence})`,
51
+ from: input.from,
52
+ to: input.to,
53
+ };
54
+ }
55
+ return {
56
+ tier: input.from,
57
+ outcome: "declined",
58
+ reason: `insufficient evidence to raise above ${input.from} (${evidence})`,
59
+ from: input.from,
60
+ to: input.to,
61
+ };
62
+ }
63
+ /** Cold-start vs selected depth at session:start (unpinned path only). */
64
+ export function evaluateSessionStartCeremonyDialEscalation(input) {
65
+ const modelTier = input.modelTier ?? input.selection.inputs.modelTier;
66
+ const from = selectCeremonyColdStartDepth(modelTier);
67
+ return evaluateCeremonyDialEscalation({
68
+ from,
69
+ to: input.selection.depth,
70
+ inputs: input.selection.inputs,
71
+ });
72
+ }
73
+ /** Fail-open emit of a #3319 evaluation event. */
74
+ export function emitCeremonyDialEscalationEvaluation(options) {
75
+ try {
76
+ const emitter = new RunSummaryEmitter({
77
+ projectRoot: options.projectRoot,
78
+ sessionId: options.sessionId,
79
+ env: options.env,
80
+ });
81
+ emitter.emitDialEscalationEvaluation({
82
+ tier: options.evaluation.tier,
83
+ outcome: options.evaluation.outcome,
84
+ reason: options.evaluation.reason,
85
+ });
86
+ emitter.emitKnownToolTurnDenominator();
87
+ }
88
+ catch {
89
+ // fail-open
90
+ }
91
+ }
92
+ //# sourceMappingURL=ceremony-dial-escalation.js.map
@@ -161,7 +161,9 @@ export interface ResolveCeremonyDialOptions {
161
161
  */
162
162
  export declare function resolveCeremonyDial(projectRoot: string, options?: ResolveCeremonyDialOptions): CeremonyDialSelection;
163
163
  /** Human-readable status line for session:start / policy:show. */
164
- export declare function formatCeremonyDialStatusLine(selection: CeremonyDialSelection): string;
164
+ export declare function formatCeremonyDialStatusLine(selection: CeremonyDialSelection, extras?: {
165
+ readonly startTierProvenance?: string;
166
+ }): string;
165
167
  /** JSON-serializable dial snapshot for ritual-state / session payload. */
166
168
  export declare function ceremonyDialToDict(selection: CeremonyDialSelection): Record<string, unknown>;
167
169
  export interface CeremonyDialPolicyField {
@@ -401,7 +401,7 @@ export function resolveCeremonyDial(projectRoot, options = {}) {
401
401
  return selected;
402
402
  }
403
403
  /** Human-readable status line for session:start / policy:show. */
404
- export function formatCeremonyDialStatusLine(selection) {
404
+ export function formatCeremonyDialStatusLine(selection, extras) {
405
405
  const parts = [
406
406
  `[deft ceremony-dial] depth=${selection.depth}`,
407
407
  `source=${selection.source}`,
@@ -409,6 +409,10 @@ export function formatCeremonyDialStatusLine(selection) {
409
409
  `modelTier=${selection.inputs.modelTier ?? "-"}`,
410
410
  `projectShape=${selection.inputs.projectShape ?? "-"}`,
411
411
  ];
412
+ if (extras?.startTierProvenance !== undefined && extras.startTierProvenance.length > 0) {
413
+ parts.push(`start-tier=${selection.depth}`);
414
+ parts.push(`provenance=${extras.startTierProvenance}`);
415
+ }
412
416
  if (selection.composition.rapidStrategy) {
413
417
  parts.push(`compose=${selection.composition.rapidStrategy}`);
414
418
  }
@@ -981,7 +985,49 @@ export function formatCeremonyDialAuditLine(audit) {
981
985
  export function escalateCeremonyDial(projectRoot, options) {
982
986
  const prior = resolveCeremonyDial(projectRoot);
983
987
  const from = prior.depth;
988
+ const emitEscalationEvaluation = (applied, reasonSuffix) => {
989
+ if (options.emitRunSummary === false) {
990
+ return;
991
+ }
992
+ try {
993
+ const audit = readCeremonyDialAudit(projectRoot);
994
+ const rawSid = audit.raw?.session_id;
995
+ const sid = options.sessionId ??
996
+ (typeof rawSid === "string" && rawSid.length > 0
997
+ ? rawSid
998
+ : `dial-${Date.now().toString(36)}`);
999
+ const rank = {
1000
+ minimal: 0,
1001
+ rapid: 1,
1002
+ standard: 2,
1003
+ elevated: 3,
1004
+ };
1005
+ const raised = rank[options.to] > rank[from];
1006
+ const outcome = applied && raised ? "escalated" : "declined";
1007
+ const baseReason = options.confirm === true
1008
+ ? options.reason
1009
+ : `${options.reason}; not applied (need --confirm)`;
1010
+ const reason = reasonSuffix !== undefined && reasonSuffix.length > 0
1011
+ ? `${baseReason}; ${reasonSuffix}`
1012
+ : baseReason;
1013
+ const emitter = new RunSummaryEmitter({
1014
+ projectRoot,
1015
+ sessionId: sid,
1016
+ env: options.env,
1017
+ });
1018
+ emitter.emitDialEscalationEvaluation({
1019
+ tier: outcome === "escalated" ? options.to : from,
1020
+ outcome,
1021
+ reason,
1022
+ });
1023
+ emitter.emitKnownToolTurnDenominator();
1024
+ }
1025
+ catch {
1026
+ // fail-open
1027
+ }
1028
+ };
984
1029
  if (options.confirm !== true) {
1030
+ emitEscalationEvaluation(false);
985
1031
  return {
986
1032
  exitCode: 1,
987
1033
  from,
@@ -1001,6 +1047,8 @@ export function escalateCeremonyDial(projectRoot, options) {
1001
1047
  note: options.note ?? `escalate: ${options.reason}`,
1002
1048
  });
1003
1049
  if (setResult.exitCode !== 0) {
1050
+ const persistDetail = oneLineAuditToken(setResult.stdout.trim().split("\n")[0] ?? `exit ${setResult.exitCode}`);
1051
+ emitEscalationEvaluation(false, `persist failed (exit ${setResult.exitCode}): ${persistDetail}`);
1004
1052
  return {
1005
1053
  exitCode: setResult.exitCode,
1006
1054
  from,
@@ -1012,6 +1060,8 @@ export function escalateCeremonyDial(projectRoot, options) {
1012
1060
  .filter((l) => l.length > 0),
1013
1061
  };
1014
1062
  }
1063
+ // #3319: evaluation event on every escalate path (applied or not).
1064
+ emitEscalationEvaluation(true);
1015
1065
  // #3282: event-driven dial_transition line (fail-open).
1016
1066
  if (options.emitRunSummary !== false) {
1017
1067
  try {
@@ -1032,6 +1082,7 @@ export function escalateCeremonyDial(projectRoot, options) {
1032
1082
  reason: options.reason,
1033
1083
  evidence: options.evidence,
1034
1084
  });
1085
+ emitter.emitKnownToolTurnDenominator();
1035
1086
  }
1036
1087
  catch {
1037
1088
  // fail-open
@@ -1,52 +1,36 @@
1
1
  /**
2
- * Check-resume / local suite-stamp policy (#3189).
2
+ * Check-resume / local suite-stamp policy (#3189 / #3314).
3
3
  *
4
- * Consent / SoT surface for consumer expansion of local suite-stamp resume
5
- * (#3188). Unset is fail-closed (localStamp off). CI never trusts a local
6
- * stamp in v1 — `ciTrustsLocalStamp` is fixed false.
4
+ * Plain optional setting. Default off. Fail-closed when absent or invalid.
5
+ * Reserved consumer expansion not implemented. CI never trusts a laptop stamp.
7
6
  */
8
7
  /** Canonical registered policy field name. */
9
8
  export declare const FIELD_CHECK_RESUME = "plan.policy.checkResume";
10
9
  /** Short alias for `policy:show --field=checkResume`. */
11
10
  export declare const FIELD_CHECK_RESUME_CLI_ALIAS = "checkResume";
12
- export declare const CHECK_RESUME_STATUSES: readonly ["unset", "decided"];
13
- export type CheckResumeStatus = (typeof CHECK_RESUME_STATUSES)[number];
14
11
  export declare const CHECK_RESUME_LOCAL_STAMP: readonly ["off", "on"];
15
12
  export type CheckResumeLocalStamp = (typeof CHECK_RESUME_LOCAL_STAMP)[number];
16
- /** Fail-closed when unset / missing. */
13
+ /** Fail-closed when absent / missing / invalid. */
17
14
  export declare const DEFAULT_CHECK_RESUME_LOCAL_STAMP: CheckResumeLocalStamp;
18
- /**
19
- * FIXED false in v1 (#3189 non-goal). Separate RFC required to ever allow CI
20
- * to trust a laptop suite stamp.
21
- */
22
- export declare const CHECK_RESUME_CI_TRUSTS_LOCAL_STAMP_V1: false;
23
15
  export interface CheckResumeConfig {
24
- readonly status: CheckResumeStatus;
25
16
  readonly localStamp: CheckResumeLocalStamp;
26
- /** Always false in v1; present for schema visibility. */
27
- readonly ciTrustsLocalStamp: false;
28
- /** Present when decided via dismiss-with-reason. */
29
- readonly dismissReason: string | null;
30
17
  }
31
18
  export type CheckResumeSource = "typed" | "default" | "default-on-error";
32
19
  export interface CheckResumeResolved extends CheckResumeConfig {
33
20
  readonly source: CheckResumeSource;
34
21
  readonly error: string | null;
35
22
  }
36
- /** Validate a `plan.policy.checkResume` payload. */
23
+ /** Validate a `plan.policy.checkResume` payload. Extra keys are ignored. */
37
24
  export declare function validateCheckResume(value: unknown): string[];
38
25
  /**
39
- * Resolve a typed block. Missing/invalid → localStamp off, status unset.
40
- * `ciTrustsLocalStamp` is always forced false regardless of input truthiness
41
- * (malformed non-false is validation error → fail-closed default).
26
+ * Resolve a typed block. Missing/invalid → localStamp off.
27
+ * Leftover ritual fields (status/dismissReason/ciTrustsLocalStamp) are ignored.
42
28
  */
43
29
  export declare function resolveCheckResumeFromTypedBlock(raw: unknown): CheckResumeResolved;
44
- /** Resolve `plan.policy.checkResume` from PROJECT-DEFINITION (#3189). */
30
+ /** Resolve `plan.policy.checkResume` from PROJECT-DEFINITION (#3314). */
45
31
  export declare function resolveCheckResume(projectRoot: string): CheckResumeResolved;
46
- /** Local suite stamp resume only when decided localStamp=on. */
32
+ /** Local suite stamp resume only when localStamp=on. Reserved — unused by check/release. */
47
33
  export declare function isLocalStampResumeAllowed(policy: CheckResumeResolved): boolean;
48
- /** Always false in v1 — CI must re-run suite, never trust a laptop stamp. */
49
- export declare function isCiTrustsLocalStampAllowed(_policy?: CheckResumeResolved): false;
50
34
  export declare function formatCheckResumeStatusLine(policy: CheckResumeResolved): string;
51
35
  export interface CheckResumePolicyField {
52
36
  readonly name: typeof FIELD_CHECK_RESUME;
@@ -56,17 +40,4 @@ export interface CheckResumePolicyField {
56
40
  }
57
41
  /** Inspector row for `policy:show --field=checkResume`. */
58
42
  export declare function inspectCheckResume(data: Record<string, unknown> | null, projectRoot?: string): CheckResumePolicyField;
59
- export interface WriteCheckResumeOptions {
60
- readonly localStamp: CheckResumeLocalStamp;
61
- readonly dismissReason?: string | null;
62
- readonly actor?: string;
63
- readonly note?: string;
64
- }
65
- export interface WriteCheckResumeResult {
66
- readonly exitCode: 0 | 2;
67
- readonly stdout: string;
68
- readonly changed: boolean;
69
- }
70
- /** Persist decided checkResume (preset or dismiss-with-reason). */
71
- export declare function writeCheckResume(projectRoot: string, options: WriteCheckResumeOptions): WriteCheckResumeResult;
72
43
  //# sourceMappingURL=check-resume.d.ts.map