create-agdf 0.14.2 → 0.14.4

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 (103) hide show
  1. package/README.md +39 -3
  2. package/generated/.opencode/contracts/control-scaffold.md +17 -2
  3. package/generated/.opencode/contracts/interaction.md +2 -2
  4. package/generated/.opencode/skills/agdf-delivery-path-search/SKILL.md +16 -4
  5. package/generated/.opencode/skills/agdf-gate-check/SKILL.md +6 -4
  6. package/generated/plugins/agdf/.claude-plugin/plugin.json +1 -1
  7. package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
  8. package/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
  9. package/generated/plugins/agdf/meta/contracts/control-scaffold.md +17 -2
  10. package/generated/plugins/agdf/meta/contracts/interaction.md +2 -2
  11. package/generated/plugins/agdf/meta/copilot-payload-baseline.json +3 -3
  12. package/generated/plugins/agdf/meta/distribution-profile-history.json +125 -0
  13. package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
  14. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/command-registry.js +8 -1
  15. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/delivery-path-search-command.js +51 -19
  16. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/parse-args.js +3 -0
  17. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/doctor.js +13 -0
  18. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/gate-check.js +64 -13
  19. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/run-state.js +6 -0
  20. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-identity.js +9 -0
  21. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-parser.js +4 -2
  22. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/contracts.js +37 -0
  23. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/persistence.js +14 -2
  24. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/search-engine.js +126 -36
  25. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/state-adapter.js +40 -92
  26. package/generated/plugins/agdf/runtime/create-agdf/lib/interaction-presentation.js +57 -7
  27. package/generated/plugins/agdf/runtime/create-agdf/lib/runtime/plugin-provenance.js +46 -3
  28. package/generated/plugins/agdf/runtime/create-agdf/package.json +1 -1
  29. package/generated/plugins/agdf/runtime/runtime-manifest.json +2 -2
  30. package/generated/plugins/agdf/scripts/check-runtime-integrity.mjs +2 -0
  31. package/generated/plugins/agdf/skills/delivery-path-search/SKILL.md +16 -4
  32. package/generated/plugins/agdf/skills/gate-check/SKILL.md +6 -4
  33. package/generated/plugins/agdf/submission/openai/availability.md +1 -1
  34. package/generated/plugins/agdf/submission/openai/capability-matrix.json +1 -1
  35. package/generated/plugins/agdf/submission/openai/release-notes.md +7 -1
  36. package/generated/plugins/agdf/submission/openai/reviewer-cases.json +1 -1
  37. package/generated/plugins/copilot/agdf/.agdf-payload-inventory.json +71 -51
  38. package/generated/plugins/copilot/agdf/copilot-skills/agdf-delivery-path-search/SKILL.md +16 -4
  39. package/generated/plugins/copilot/agdf/copilot-skills/agdf-gate-check/SKILL.md +6 -4
  40. package/generated/plugins/copilot/agdf/copilot-skills/contracts/control-scaffold.md +17 -2
  41. package/generated/plugins/copilot/agdf/copilot-skills/contracts/interaction.md +2 -2
  42. package/generated/plugins/copilot/agdf/meta/agdf-plugin.definition.json +1 -1
  43. package/generated/plugins/copilot/agdf/meta/distribution-profile-history.json +125 -0
  44. package/generated/plugins/copilot/agdf/plugin.json +1 -1
  45. package/generated/plugins/copilot/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
  46. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/cli/command-registry.js +8 -1
  47. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/cli/delivery-path-search-command.js +51 -19
  48. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/cli/parse-args.js +3 -0
  49. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/doctor.js +13 -0
  50. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/gate-check.js +64 -13
  51. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/run-state.js +6 -0
  52. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-state/run-identity.js +9 -0
  53. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-state/run-state-parser.js +4 -2
  54. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/contracts.js +37 -0
  55. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/persistence.js +14 -2
  56. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/search-engine.js +126 -36
  57. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/state-adapter.js +40 -92
  58. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/interaction-presentation.js +57 -7
  59. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/runtime/plugin-provenance.js +46 -3
  60. package/generated/plugins/copilot/agdf/runtime/create-agdf/package.json +1 -1
  61. package/generated/plugins/copilot/agdf/runtime/runtime-manifest.json +2 -2
  62. package/generated/submissions/openai/agdf/.codex-plugin/plugin.json +1 -1
  63. package/generated/submissions/openai/agdf/meta/agdf-plugin.definition.json +1 -1
  64. package/generated/submissions/openai/agdf/meta/contracts/control-scaffold.md +17 -2
  65. package/generated/submissions/openai/agdf/meta/contracts/interaction.md +2 -2
  66. package/generated/submissions/openai/agdf/meta/copilot-payload-baseline.json +3 -3
  67. package/generated/submissions/openai/agdf/meta/distribution-profile-history.json +125 -0
  68. package/generated/submissions/openai/agdf/skills/delivery-path-search/SKILL.md +16 -4
  69. package/generated/submissions/openai/agdf/skills/gate-check/SKILL.md +6 -4
  70. package/generated/submissions/openai/agdf/submission/openai/availability.md +1 -1
  71. package/generated/submissions/openai/agdf/submission/openai/capability-matrix.json +1 -1
  72. package/generated/submissions/openai/agdf/submission/openai/inventory.json +18 -14
  73. package/generated/submissions/openai/agdf/submission/openai/listing.json +1 -1
  74. package/generated/submissions/openai/agdf/submission/openai/readiness.json +2 -2
  75. package/generated/submissions/openai/agdf/submission/openai/readiness.md +2 -2
  76. package/generated/submissions/openai/agdf/submission/openai/release-notes.md +7 -1
  77. package/generated/submissions/openai/agdf/submission/openai/reviewer-cases.json +1 -1
  78. package/lib/cli/application.js +24 -6
  79. package/lib/cli/command-registry.js +8 -1
  80. package/lib/cli/delivery-path-search-command.js +51 -19
  81. package/lib/cli/parse-args.js +3 -0
  82. package/lib/control-evaluation/doctor.js +13 -0
  83. package/lib/control-evaluation/gate-check.js +64 -13
  84. package/lib/control-evaluation/run-state.js +6 -0
  85. package/lib/control-state/run-identity.js +9 -0
  86. package/lib/control-state/run-state-parser.js +4 -2
  87. package/lib/delivery-path-search/contracts.js +37 -0
  88. package/lib/delivery-path-search/persistence.js +14 -2
  89. package/lib/delivery-path-search/search-engine.js +126 -36
  90. package/lib/delivery-path-search/state-adapter.js +40 -92
  91. package/lib/installers/claude-cache-recovery.js +76 -0
  92. package/lib/installers/copilot-settings.js +146 -16
  93. package/lib/installers/local-marketplace.js +169 -14
  94. package/lib/installers/plugin-installers.js +96 -9
  95. package/lib/interaction-presentation.js +57 -7
  96. package/lib/lifecycle/operations.js +28 -4
  97. package/lib/lifecycle/result.js +4 -4
  98. package/lib/release/profile-history.js +222 -0
  99. package/lib/release/version-bump.js +527 -0
  100. package/lib/release/version-coherence.js +92 -38
  101. package/lib/runtime/distribution-profile-history.js +173 -0
  102. package/lib/runtime/plugin-provenance.js +46 -3
  103. package/package.json +7 -3
@@ -12,21 +12,25 @@ import { enforcementForSurface } from '../delivery-path-search/surfaces/capabili
12
12
  import { persistSearchResult } from '../delivery-path-search/persistence.js';
13
13
 
14
14
  function unavailableOpenCodeResult({ options, preflight, input = null, error = null }) {
15
+ const surface = options.surface ?? "opencode";
15
16
  const mutation = error?.code === "OPENCODE_EVALUATOR_MUTATION_DETECTED";
16
- const attempted = Boolean(input);
17
+ const attempted = Boolean(input && error);
17
18
  const nextAction = mutation
18
19
  ? "Investigate and revert the detected repository mutation before any evaluator fallback or retry."
19
20
  : "Repair the reported OpenCode capability and retry, or use the existing instruction-only Delivery Path Search workflow.";
20
21
  return {
21
22
  contract_version: "1",
22
- surface: "opencode",
23
+ surface,
23
24
  scope_key: input?.scope_key ?? options.runId ?? null,
25
+ scope_revision: input?.scope_revision ?? "unversioned",
26
+ objective: input?.objective ?? "Delivery Path Search evaluator capability",
24
27
  current_gate: input?.current_gate ?? null,
28
+ outcome_phase: "evaluation",
25
29
  status: mutation ? "evaluator_error" : "evaluator_unavailable",
26
30
  recommendation: null,
27
- enforcement: enforcementForSurface("opencode"),
31
+ enforcement: enforcementForSurface(surface),
28
32
  evaluator: {
29
- name: "opencode",
33
+ name: surface,
30
34
  preflight: preflight?.code ?? "not_run",
31
35
  failure_code: error?.code ?? preflight?.code,
32
36
  detail: error?.message ?? preflight?.detail,
@@ -38,6 +42,16 @@ function unavailableOpenCodeResult({ options, preflight, input = null, error = n
38
42
  failure_code: error?.code ?? preflight?.code,
39
43
  evidence: preflight?.evidence ?? [],
40
44
  },
45
+ provenance: {
46
+ baseline_candidates: Math.min(input?.allowed_actions?.length ?? 0, input?.budgets?.max_candidates ?? 0),
47
+ generated_candidates: 0,
48
+ legal_candidates: attempted ? 1 : 0,
49
+ rejected_candidates: 0,
50
+ evaluation_attempts: attempted ? 1 : 0,
51
+ valid_evaluations: 0,
52
+ invalid_evaluations: attempted ? 1 : 0,
53
+ },
54
+ failure_code: error?.code ?? preflight?.code,
41
55
  stopping_reason: mutation ? "opencode_mutation_detected" : attempted ? "opencode_evaluator_failed" : "opencode_preflight_failed",
42
56
  next_action: nextAction,
43
57
  next_gate_action: nextAction,
@@ -46,11 +60,26 @@ function unavailableOpenCodeResult({ options, preflight, input = null, error = n
46
60
 
47
61
  export async function executeDeliveryPathSearch(options, io = console, dependencies = {}) {
48
62
  const fixture = options.fixture ? JSON.parse(readFileSync(options.fixture, "utf8")) : null;
63
+ const fixtureGeneration = fixture?.generator_response;
64
+ const generationEnabled = Boolean(fixtureGeneration || options.generateCandidates);
65
+ const initialEnforcement = fixture?.input?.enforcement ?? enforcementForSurface(options.surface);
66
+ const initialInput = fixture?.input ?? (dependencies.searchInputFromControl ?? searchInputFromControl)(options.dir, {
67
+ scopeKey: options.runId,
68
+ enforcement: initialEnforcement,
69
+ generation: generationEnabled ? {
70
+ enabled: true,
71
+ maxProposals: options.maxGeneratedCandidates,
72
+ maxDurationMs: options.generationTimeoutMs,
73
+ maxCostUnits: options.generationCostUnits,
74
+ } : undefined,
75
+ });
49
76
  const preflight = !fixture && options.surface === "opencode"
50
77
  ? (dependencies.preflightOpenCodeEvaluator ?? preflightOpenCodeEvaluator)({ cwd: options.dir })
51
78
  : null;
52
79
  if (preflight?.status === "failed") {
53
- const result = unavailableOpenCodeResult({ options, preflight });
80
+ const result = initialInput.input_failure_code
81
+ ? await runDeliveryPathSearch(initialInput, { name: "not-invoked", async evaluate() { throw new Error("input failure must stop before evaluation"); } })
82
+ : unavailableOpenCodeResult({ options, preflight, input: initialInput });
54
83
  printDeliveryPathSearchResult(result, options, io);
55
84
  return result;
56
85
  }
@@ -58,18 +87,7 @@ export async function executeDeliveryPathSearch(options, io = console, dependenc
58
87
  ?? (preflight
59
88
  ? enforcementForSurface("opencode", preflight.evidence, { preflight })
60
89
  : enforcementForSurface(options.surface));
61
- const fixtureGeneration = fixture?.generator_response;
62
- const generationEnabled = Boolean(fixtureGeneration || options.generateCandidates);
63
- const baseInput = fixture?.input ?? (dependencies.searchInputFromControl ?? searchInputFromControl)(options.dir, {
64
- scopeKey: options.runId,
65
- enforcement,
66
- generation: generationEnabled ? {
67
- enabled: true,
68
- maxProposals: options.maxGeneratedCandidates,
69
- maxDurationMs: options.generationTimeoutMs,
70
- maxCostUnits: options.generationCostUnits,
71
- } : undefined,
72
- });
90
+ const baseInput = fixture ? initialInput : { ...initialInput, enforcement };
73
91
  const input = generationEnabled && !baseInput.generation ? {
74
92
  ...baseInput,
75
93
  generation: {
@@ -90,7 +108,13 @@ export async function executeDeliveryPathSearch(options, io = console, dependenc
90
108
  ? (dependencies.openCodeEvaluator ?? openCodeEvaluator)({ cwd: options.dir, model: options.model, preflight })
91
109
  : null;
92
110
  if (!evaluator) {
93
- throw new Error(`${options.surface} has no executable evaluator in this release. Codex, Claude and preflight-qualified OpenCode are the executable adapters; --fixture is available for deterministic contract testing.`);
111
+ const detail = `${options.surface} has no executable evaluator in this release. Codex, Claude and preflight-qualified OpenCode are the executable adapters; --fixture is available for deterministic contract testing.`;
112
+ const result = {
113
+ ...unavailableOpenCodeResult({ options, preflight: { code: "surface_evaluator_unavailable", detail, evidence: [] }, input }),
114
+ surface: options.surface,
115
+ };
116
+ printDeliveryPathSearchResult(result, options, io);
117
+ return result;
94
118
  }
95
119
  const generator = fixtureGeneration
96
120
  ? fixtureGenerator(fixtureGeneration)
@@ -125,15 +149,23 @@ function printDeliveryPathSearchResult(result, options, io) {
125
149
  return;
126
150
  }
127
151
  io.log(`AGDF Delivery Path Search: ${result.status}`);
152
+ io.log(`Scope: ${result.scope_key ?? "unknown"} @ ${result.scope_revision ?? "unversioned"}`);
153
+ if (result.objective) io.log(`Objective: ${result.objective}`);
128
154
  io.log(`Current gate: ${result.current_gate ?? "unknown"}`);
155
+ io.log(`Outcome phase: ${result.outcome_phase ?? "unknown"}`);
129
156
  io.log(`Enforcement: ${result.enforcement.level}`);
130
157
  io.log(`Recommendation: ${result.recommendation?.action ?? "none"}`);
131
158
  if (result.budgets) io.log(`Evaluations: ${result.budgets.evaluations}`);
159
+ if (result.provenance) {
160
+ io.log(`Candidates: ${result.provenance.legal_candidates} legal, ${result.provenance.rejected_candidates} rejected`);
161
+ io.log(`Evaluation provenance: ${result.provenance.valid_evaluations}/${result.provenance.evaluation_attempts} valid, ${result.provenance.invalid_evaluations} invalid`);
162
+ }
132
163
  if (result.generation) {
133
164
  io.log(`Generation: ${result.generation.status} (${result.generation.accepted}/${result.generation.returned} accepted)`);
134
165
  io.log(`Generation budget: ${result.generation.cost_units} cost units, ${result.generation.duration_ms} ms`);
135
166
  if (result.generation.failure_code) io.log(`Generation failure: ${result.generation.failure_code}`);
136
167
  }
137
168
  io.log(`Stopping reason: ${result.stopping_reason}`);
138
- io.log(result.next_gate_action);
169
+ if (result.failure_code) io.log(`Failure: ${result.failure_code}`);
170
+ io.log(result.next_action ?? result.next_gate_action);
139
171
  }
@@ -45,6 +45,7 @@ export function parseArgs(argv, dependencies = {}) {
45
45
  let allActive = false;
46
46
  let scope;
47
47
  let confirm = false;
48
+ let shared = false;
48
49
  let runtimeChecksDecision;
49
50
  let runtimeChecksAction;
50
51
 
@@ -61,6 +62,7 @@ export function parseArgs(argv, dependencies = {}) {
61
62
  if (arg === "--generate-candidates") { generateCandidates = true; continue; }
62
63
  if (arg === "--all-active") { allActive = true; continue; }
63
64
  if (arg === "--confirm") { confirm = true; continue; }
65
+ if (arg === "--shared") { shared = true; continue; }
64
66
 
65
67
  if (arg === "--runtime-checks") {
66
68
  const next = requiredValue(args, i, arg);
@@ -162,6 +164,7 @@ export function parseArgs(argv, dependencies = {}) {
162
164
  allActive,
163
165
  scope,
164
166
  confirm,
167
+ shared,
165
168
  runtimeChecksDecision,
166
169
  runtimeChecksAction: runtimeChecksAction ?? "status",
167
170
  generatorModel,
@@ -130,6 +130,19 @@ export function evaluateDoctor(targetDir, selection = {}) {
130
130
  "Add at least one evidence row or explicitly document that no evidence exists yet.",
131
131
  );
132
132
  }
133
+
134
+ for (const identityFinding of selectedRunState.identity_findings ?? []) {
135
+ addFinding(
136
+ findings,
137
+ "revise",
138
+ identityFinding.code,
139
+ identityFinding.code === "AGDF_RUN_ID_INVALID"
140
+ ? "The selected run state has a missing or invalid run_id; interaction cards cannot be rendered for it."
141
+ : "The selected run state has a missing or invalid revision_id; approval interactions cannot be rendered for it.",
142
+ runPath,
143
+ "Run run-migrate to repair the run identity, or migrate the legacy state to a canonical run record.",
144
+ );
145
+ }
133
146
  }
134
147
 
135
148
  const backlogPath = join(".agdf", "control", "MASTER_BACKLOG.md");
@@ -1,4 +1,4 @@
1
- import { attachApprovalOrientationSnapshot, buildArtefactRefs, buildQualityReadiness, gateTitle, localePack, renderApprovalOrientationSnapshot, renderOperationalStatusCard, resolveHumanRunTitle } from '../interaction-presentation.js';
1
+ import { attachApprovalOrientationSnapshot, buildArtefactRefs, buildQualityReadiness, gateTitle, localePack, renderApprovalOrientationSnapshot, renderOperationalStatusCard, resolveHumanRunTitle, validateApprovalOrientationPreconditions, validateApprovalOrientationSnapshot, validateOperationalStatusCardPreconditions } from '../interaction-presentation.js';
2
2
  import { interactionLocales, resolveConfiguredChatLanguage } from '../cli/runtime-context.js';
3
3
  import { evaluateDoctor } from './doctor.js';
4
4
  import { analyzeDeliveryMap, deriveQualityOutlook } from './delivery-map.js';
@@ -300,8 +300,9 @@ export function evaluateGateCheck(targetDir, selection = {}) {
300
300
  humanPresentation,
301
301
  revisionId,
302
302
  });
303
+ const readyForApproval = isReadyUserGateApproval({ status, currentGate, missingApproval });
303
304
  const approvalOrientation = attachApprovalOrientationSnapshot(statusCard, {
304
- ready: isReadyUserGateApproval({ status, currentGate, missingApproval }),
305
+ ready: readyForApproval,
305
306
  humanPresentation,
306
307
  revisionId,
307
308
  registry: interactionLocales,
@@ -312,6 +313,50 @@ export function evaluateGateCheck(targetDir, selection = {}) {
312
313
  enumerable: false,
313
314
  });
314
315
 
316
+ const approvalPresentation = renderApprovalOrientationSnapshot(approvalOrientation, {
317
+ registry: interactionLocales,
318
+ expectedIdentity: {
319
+ run_id: statusCard.run_id,
320
+ revision_id: revisionId,
321
+ current_gate: currentGate,
322
+ presentation_language: statusCard.presentation_language,
323
+ },
324
+ });
325
+ const presentationDiagnostics = {};
326
+ if (!statusPresentation) {
327
+ presentationDiagnostics.status_presentation_errors = [
328
+ ...validateOperationalStatusCardPreconditions(statusCard, {
329
+ registry: interactionLocales,
330
+ humanPresentation,
331
+ }).errors,
332
+ ];
333
+ }
334
+ if (readyForApproval && !approvalPresentation) {
335
+ presentationDiagnostics.approval_presentation_errors = approvalOrientation
336
+ ? [
337
+ ...validateApprovalOrientationSnapshot(approvalOrientation, {
338
+ registry: interactionLocales,
339
+ expectedIdentity: {
340
+ run_id: statusCard.run_id,
341
+ revision_id: revisionId,
342
+ current_gate: currentGate,
343
+ presentation_language: statusCard.presentation_language,
344
+ },
345
+ }).errors,
346
+ ]
347
+ : [
348
+ ...validateApprovalOrientationPreconditions({
349
+ statusCard,
350
+ humanPresentation,
351
+ registry: interactionLocales,
352
+ requestedLocale: presentationLocale,
353
+ }).errors,
354
+ ];
355
+ if (!presentationDiagnostics.approval_presentation_errors.length) {
356
+ presentationDiagnostics.approval_presentation_errors = ["snapshot_unavailable"];
357
+ }
358
+ }
359
+
315
360
  return {
316
361
  schema_version: "1",
317
362
  status,
@@ -332,15 +377,8 @@ export function evaluateGateCheck(targetDir, selection = {}) {
332
377
  candidate_runs: runState.candidate_runs ?? [],
333
378
  status_card: statusCard,
334
379
  status_presentation: statusPresentation,
335
- approval_presentation: renderApprovalOrientationSnapshot(approvalOrientation, {
336
- registry: interactionLocales,
337
- expectedIdentity: {
338
- run_id: statusCard.run_id,
339
- revision_id: revisionId,
340
- current_gate: currentGate,
341
- presentation_language: statusCard.presentation_language,
342
- },
343
- }),
380
+ approval_presentation: approvalPresentation,
381
+ ...(Object.keys(presentationDiagnostics).length ? { presentation_diagnostics: presentationDiagnostics } : {}),
344
382
  delivery_map: {
345
383
  relationships: deliveryMap.relationships,
346
384
  mode_slice_decision: runState.mode_slice_decision,
@@ -368,6 +406,14 @@ export function printApprovalEnvelope(report, { io = console, reEvaluate } = {})
368
406
  if (report?.approval_presentation) {
369
407
  io.log(report.approval_presentation.blocks.run_status_card.markdown);
370
408
  io.log("");
409
+ if (report.status_presentation?.markdown) {
410
+ io.log(report.status_presentation.markdown);
411
+ } else {
412
+ const pack = localePack(interactionLocales, report.approval_presentation.presentation_language || "en");
413
+ const codes = report.presentation_diagnostics?.status_presentation_errors ?? [];
414
+ io.log(codes.length ? `${pack.interaction.statusPresentationFailure} (${codes.join(", ")})` : pack.interaction.statusPresentationFailure);
415
+ }
416
+ io.log("");
371
417
  io.log(report.approval_presentation.blocks.gate_transition_card.markdown);
372
418
  io.log("");
373
419
  io.log(report.approval_presentation.approval_interaction.exact_text_fallback);
@@ -386,7 +432,10 @@ export function printApprovalEnvelope(report, { io = console, reEvaluate } = {})
386
432
  const pack = localePack(interactionLocales, refreshed?.status_card?.presentation_language || report?.status_card?.presentation_language || "en");
387
433
  const refreshedApproval = canonicalApprovalForReport(refreshed);
388
434
  if (initialApproval && refreshedApproval) {
389
- io.log(pack.interaction.presentationFailure);
435
+ const codes = refreshed?.presentation_diagnostics?.approval_presentation_errors
436
+ ?? report?.presentation_diagnostics?.approval_presentation_errors
437
+ ?? [];
438
+ io.log(codes.length ? `${pack.interaction.presentationFailure} (${codes.join(", ")})` : pack.interaction.presentationFailure);
390
439
  io.log(pack.interaction.exactTextRequest.replace("{approval}", refreshedApproval));
391
440
  return Object.freeze({ outcome: "exact_text_recovery", requested_decision: true, status: refreshed.status });
392
441
  }
@@ -404,7 +453,9 @@ export function printApprovalEnvelope(report, { io = console, reEvaluate } = {})
404
453
  function printGateCheckStatusCard(report, io) {
405
454
  const card = report.status_card;
406
455
  if (!report.status_presentation?.markdown) {
407
- io.log(localePack(interactionLocales, card?.presentation_language || "en").interaction.statusPresentationFailure);
456
+ const failure = localePack(interactionLocales, card?.presentation_language || "en").interaction.statusPresentationFailure;
457
+ const codes = report.presentation_diagnostics?.status_presentation_errors ?? [];
458
+ io.log(codes.length ? `${failure} (${codes.join(", ")})` : failure);
408
459
  return false;
409
460
  }
410
461
  io.log(report.status_presentation.markdown);
@@ -2,6 +2,7 @@ import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
2
2
  import { isAbsolute, join, relative, resolve } from "node:path";
3
3
  import process from "node:process";
4
4
  import { parseControlState, resolveRuns } from "../control-state/index.js";
5
+ import { validateRunIdentity } from "../control-state/run-identity.js";
5
6
  import { buildRunCandidates } from "../interaction-presentation.js";
6
7
  import { cleanStatusCell, filled, isPlaceholderValue, readTargetFile } from "./shared.js";
7
8
  import { extractField, isSafeRepoRelativePath, readVerifiedChangeRecord } from "./verified-change.js";
@@ -87,6 +88,7 @@ export function readRunState(targetDir, selection = {}) {
87
88
  memory: {},
88
89
  candidate_runs: candidateRuns,
89
90
  resolution_error: resolutionError,
91
+ identity_findings: [],
90
92
  };
91
93
  }
92
94
 
@@ -101,6 +103,10 @@ export function readRunState(targetDir, selection = {}) {
101
103
  path: runPath,
102
104
  content,
103
105
  ...parsed,
106
+ identity_findings: validateRunIdentity({
107
+ runId: extractField(content, "run_id").replace(/^`|`$/g, ""),
108
+ revisionId: extractField(content, "revision_id").replace(/^`|`$/g, ""),
109
+ }),
104
110
  };
105
111
  }
106
112
 
@@ -0,0 +1,9 @@
1
+ export const RUN_ID_PATTERN = /^[a-z0-9][a-z0-9._-]{0,127}$/;
2
+ export const REVISION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3
+
4
+ export function validateRunIdentity({ runId, revisionId } = {}) {
5
+ const findings = [];
6
+ if (!RUN_ID_PATTERN.test(String(runId ?? ""))) findings.push({ code: "AGDF_RUN_ID_INVALID" });
7
+ if (!REVISION_ID_PATTERN.test(String(revisionId ?? ""))) findings.push({ code: "AGDF_RUN_REVISION_ID_INVALID" });
8
+ return findings;
9
+ }
@@ -1,4 +1,6 @@
1
- export const RUN_ID_PATTERN = /^[a-z0-9][a-z0-9._-]{0,127}$/;
1
+ import { REVISION_ID_PATTERN, RUN_ID_PATTERN } from "./run-identity.js";
2
+
3
+ export { RUN_ID_PATTERN } from "./run-identity.js";
2
4
  export const LIFECYCLES = new Set([
3
5
  "active",
4
6
  "completed",
@@ -34,7 +36,7 @@ export function parseRunState(content, expected) {
34
36
  findings.push({ code: "AGDF_RUN_LIFECYCLE_INVALID" });
35
37
  if (!/^[1-9]\d*$/.test(values.get("revision") ?? ""))
36
38
  findings.push({ code: "AGDF_RUN_REVISION_INVALID" });
37
- if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(values.get("revision_id") ?? ""))
39
+ if (!REVISION_ID_PATTERN.test(values.get("revision_id") ?? ""))
38
40
  findings.push({ code: "AGDF_RUN_REVISION_ID_INVALID" });
39
41
  return {
40
42
  content,
@@ -68,6 +68,10 @@ export function validateSearchInput(value) {
68
68
  }
69
69
  requireString(input.objective, "objective");
70
70
  requireString(input.current_gate, "current_gate");
71
+ optionalBoundedString(input.scope_revision, "scope_revision", 200);
72
+ optionalBoundedString(input.input_failure_code, "input_failure_code", 200);
73
+ optionalBoundedString(input.input_failure_detail, "input_failure_detail", 1000);
74
+ optionalBoundedString(input.input_recovery_action, "input_recovery_action", 1000);
71
75
  stringArray(input.allowed_actions, "allowed_actions");
72
76
  stringArray(input.forbidden_actions, "forbidden_actions");
73
77
  stringArray(input.evidence_refs ?? [], "evidence_refs");
@@ -81,6 +85,39 @@ export function validateSearchInput(value) {
81
85
  return structuredClone(input);
82
86
  }
83
87
 
88
+ const RESULT_PHASE_BY_STATUS = new Map([
89
+ ["input_unavailable", "input"],
90
+ ["no_legal_candidates", "candidate"],
91
+ ["evaluator_unavailable", "evaluation"],
92
+ ["evaluator_error", "evaluation"],
93
+ ["recommendation", "search"],
94
+ ["no_safe_recommendation", "search"],
95
+ ]);
96
+
97
+ export function validateSearchResult(value) {
98
+ const result = requireObject(value, "search result");
99
+ if (result.contract_version !== CONTRACT_VERSION) throw new Error(`unsupported search result contract_version: ${result.contract_version}`);
100
+ const status = requireString(result.status, "result.status");
101
+ const expectedPhase = RESULT_PHASE_BY_STATUS.get(status);
102
+ if (!expectedPhase) throw new Error(`unsupported search result status: ${status}`);
103
+ if (result.outcome_phase !== expectedPhase) throw new Error(`${status} requires outcome_phase ${expectedPhase}`);
104
+ requireString(result.scope_key, "result.scope_key");
105
+ optionalBoundedString(result.scope_revision, "result.scope_revision", 200);
106
+ const provenance = requireObject(result.provenance, "result.provenance");
107
+ for (const key of ["baseline_candidates", "generated_candidates", "legal_candidates", "rejected_candidates", "evaluation_attempts", "valid_evaluations", "invalid_evaluations"]) {
108
+ if (!Number.isInteger(provenance[key]) || provenance[key] < 0) throw new Error(`result.provenance.${key} must be a non-negative integer`);
109
+ }
110
+ if (provenance.evaluation_attempts !== provenance.valid_evaluations + provenance.invalid_evaluations) throw new Error("result evaluation provenance counts are inconsistent");
111
+ const recommendationFacing = status === "recommendation" || status === "no_safe_recommendation";
112
+ if (recommendationFacing && provenance.valid_evaluations < 1) throw new Error(`${status} requires at least one valid evaluation`);
113
+ if (status === "recommendation" && !result.recommendation) throw new Error("recommendation status requires recommendation content");
114
+ if (status !== "recommendation" && result.recommendation != null) throw new Error(`${status} must not contain recommendation content`);
115
+ if (status === "no_legal_candidates" && (provenance.legal_candidates !== 0 || provenance.evaluation_attempts !== 0)) throw new Error("no_legal_candidates requires zero legal candidates and evaluation attempts");
116
+ if (status === "input_unavailable" && provenance.evaluation_attempts !== 0) throw new Error("input_unavailable requires zero evaluation attempts");
117
+ if ((status === "evaluator_unavailable" || status === "evaluator_error") && provenance.valid_evaluations !== 0) throw new Error(`${status} requires zero valid evaluations`);
118
+ return structuredClone(result);
119
+ }
120
+
84
121
  export function validateCandidate(value) {
85
122
  const candidate = requireObject(value, "candidate");
86
123
  requireString(candidate.id, "candidate.id");
@@ -1,5 +1,6 @@
1
1
  import { mkdirSync, writeFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
+ import { validateSearchResult } from "./contracts.js";
3
4
 
4
5
  function safeResult(result) {
5
6
  return JSON.parse(JSON.stringify(result, (key, value) => {
@@ -9,22 +10,33 @@ function safeResult(result) {
9
10
  }
10
11
 
11
12
  export function persistSearchResult(targetDir, result) {
12
- const root = join(targetDir, ".agdf", "control", "artefacts", result.scope_key);
13
+ const validated = validateSearchResult(result);
14
+ if (!["recommendation", "no_safe_recommendation"].includes(validated.status)) {
15
+ throw new Error(`search result status ${validated.status} is not persistable`);
16
+ }
17
+ if (validated.provenance.valid_evaluations < 1) {
18
+ throw new Error("search result requires at least one valid evaluation before persistence");
19
+ }
20
+ const root = join(targetDir, ".agdf", "control", "artefacts", validated.scope_key);
13
21
  const jsonPath = join(root, "DELIVERY_PATH_SEARCH.json");
14
22
  const markdownPath = join(root, "DELIVERY_PATH_SEARCH.md");
15
23
  mkdirSync(dirname(jsonPath), { recursive: true });
16
- const safe = safeResult(result);
24
+ const safe = safeResult(validated);
17
25
  writeFileSync(jsonPath, `${JSON.stringify(safe, null, 2)}\n`, "utf8");
18
26
  const recommendation = safe.recommendation?.action ?? "No safe recommendation";
19
27
  writeFileSync(markdownPath, [
20
28
  "# Delivery Path Search Decision",
21
29
  "",
22
30
  `- status: ${safe.status}`,
31
+ `- outcome_phase: ${safe.outcome_phase}`,
32
+ `- scope_revision: ${safe.scope_revision}`,
23
33
  `- current_gate: ${safe.current_gate}`,
24
34
  `- enforcement: ${safe.enforcement.level}`,
25
35
  `- recommendation: ${recommendation}`,
26
36
  `- stopping_reason: ${safe.stopping_reason}`,
27
37
  `- evaluations: ${safe.budgets.evaluations}`,
38
+ `- evaluation_attempts: ${safe.provenance.evaluation_attempts}`,
39
+ `- invalid_evaluations: ${safe.provenance.invalid_evaluations}`,
28
40
  `- generation_status: ${safe.generation?.status ?? "legacy_deterministic"}`,
29
41
  `- generated_candidates: ${safe.generation ? `${safe.generation.accepted}/${safe.generation.returned}` : "0/0"}`,
30
42
  `- generation_cost_units: ${safe.generation?.cost_units ?? 0}`,