create-agdf 0.31.0 → 0.32.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 (121) hide show
  1. package/generated/.opencode/agdf-interaction-locales.json +148 -4
  2. package/generated/plugins/agdf/.claude-plugin/plugin.json +1 -1
  3. package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
  4. package/generated/plugins/agdf/companion/events.mjs +5 -3
  5. package/generated/plugins/agdf/companion/figure.mjs +241 -110
  6. package/generated/plugins/agdf/companion/localization.mjs +5 -0
  7. package/generated/plugins/agdf/companion/notice.mjs +10 -2
  8. package/generated/plugins/agdf/companion/register.mjs +90 -32
  9. package/generated/plugins/agdf/companion/run-state.mjs +13 -1
  10. package/generated/plugins/agdf/companion/view.mjs +149 -19
  11. package/generated/plugins/agdf/mcp/server/package.json +2 -2
  12. package/generated/plugins/agdf/mcp/server/src/generation-runtime.js +6 -4
  13. package/generated/plugins/agdf/mcp/server/src/generation.js +8 -2
  14. package/generated/plugins/agdf/mcp/server/src/main.js +4 -1
  15. package/generated/plugins/agdf/mcp/server/src/server.js +19 -5
  16. package/generated/plugins/agdf/mcp/server/ui/cockpit.html +130 -33
  17. package/generated/plugins/agdf/mcp/server/ui/decision-manifest.json +3 -3
  18. package/generated/plugins/agdf/mcp/server/ui/decision.html +3 -3
  19. package/generated/plugins/agdf/mcp/server/ui/manifest.json +3 -3
  20. package/generated/plugins/agdf/meta/agdf-interaction-locales.json +148 -4
  21. package/generated/plugins/agdf/meta/agdf-mcp-capability.json +1 -1
  22. package/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
  23. package/generated/plugins/agdf/meta/copilot-payload-baseline.json +3 -3
  24. package/generated/plugins/agdf/meta/distribution-profile-history.json +7 -0
  25. package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-interaction-locales.json +148 -4
  26. package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
  27. package/generated/plugins/agdf/runtime/create-agdf/lib/mcp-dispatch-runtime.js +4 -1
  28. package/generated/plugins/agdf/runtime/create-agdf/lib/mcp-worker-runtime.js +77 -77
  29. package/generated/plugins/agdf/runtime/create-agdf/package.json +1 -1
  30. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/companion/events.js +4 -1
  31. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/companion/figure-state.js +6 -4
  32. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/companion/journal.js +6 -0
  33. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/companion/run-view.js +54 -11
  34. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-evaluation/decision-content-placeholder.js +4 -1
  35. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-evaluation/gate-check.js +5 -1
  36. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-inspect/cockpit-list.js +10 -8
  37. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-inspect/cockpit-session.js +11 -3
  38. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-inspect/cockpit-work.js +4 -2
  39. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-inspect/cockpit.js +43 -13
  40. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-inspect/work-situation.js +25 -0
  41. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-read/cockpit-worker.js +1 -0
  42. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-state/run-create-command.js +6 -1
  43. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-state/run-inventory-reader.js +50 -3
  44. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-state/run-inventory.js +12 -27
  45. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-state/run-presentation-render.js +29 -0
  46. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-state/run-title.js +43 -0
  47. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/interaction-catalog-validation.js +2 -1
  48. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/interaction-presentation.js +5 -9
  49. package/generated/plugins/agdf/runtime/runtime-manifest.json +2 -2
  50. package/generated/plugins/agdf/submission/openai/availability.md +1 -1
  51. package/generated/plugins/agdf/submission/openai/capability-matrix.json +1 -1
  52. package/generated/plugins/agdf/submission/openai/release-notes.md +5 -8
  53. package/generated/plugins/agdf/submission/openai/reviewer-cases.json +1 -1
  54. package/generated/plugins/agdf-planning/.codex-plugin/plugin.json +1 -1
  55. package/generated/plugins/agdf-planning/plugin.json +1 -1
  56. package/generated/plugins/copilot/agdf/.agdf-payload-inventory.json +76 -58
  57. package/generated/plugins/copilot/agdf/mcp/server/package.json +2 -2
  58. package/generated/plugins/copilot/agdf/mcp/server/src/generation-runtime.js +6 -4
  59. package/generated/plugins/copilot/agdf/mcp/server/src/generation.js +8 -2
  60. package/generated/plugins/copilot/agdf/mcp/server/src/main.js +4 -1
  61. package/generated/plugins/copilot/agdf/mcp/server/src/server.js +19 -5
  62. package/generated/plugins/copilot/agdf/meta/agdf-plugin.definition.json +1 -1
  63. package/generated/plugins/copilot/agdf/meta/distribution-profile-history.json +7 -0
  64. package/generated/plugins/copilot/agdf/plugin.json +1 -1
  65. package/generated/plugins/copilot/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-interaction-locales.json +148 -4
  66. package/generated/plugins/copilot/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
  67. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/mcp-dispatch-runtime.js +4 -1
  68. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/mcp-worker-runtime.js +62 -62
  69. package/generated/plugins/copilot/agdf/runtime/create-agdf/package.json +1 -1
  70. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/companion/events.js +4 -1
  71. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/companion/figure-state.js +6 -4
  72. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/companion/journal.js +6 -0
  73. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/companion/run-view.js +54 -11
  74. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/control-evaluation/decision-content-placeholder.js +4 -1
  75. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/control-evaluation/gate-check.js +5 -1
  76. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/control-inspect/work-situation.js +25 -0
  77. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/control-state/run-create-command.js +6 -1
  78. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/control-state/run-inventory-reader.js +50 -3
  79. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/control-state/run-inventory.js +12 -27
  80. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/control-state/run-presentation-render.js +29 -0
  81. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/control-state/run-title.js +43 -0
  82. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/interaction-catalog-validation.js +2 -1
  83. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/interaction-presentation.js +5 -9
  84. package/generated/plugins/copilot/agdf/runtime/runtime-manifest.json +2 -2
  85. package/generated/plugins/copilot/agdf/skills/agdf-interaction-locales.json +1 -1
  86. package/generated/submissions/openai/agdf/.codex-plugin/plugin.json +1 -1
  87. package/generated/submissions/openai/agdf/meta/agdf-interaction-locales.json +148 -4
  88. package/generated/submissions/openai/agdf/meta/agdf-plugin.definition.json +1 -1
  89. package/generated/submissions/openai/agdf/meta/copilot-payload-baseline.json +3 -3
  90. package/generated/submissions/openai/agdf/meta/distribution-profile-history.json +7 -0
  91. package/generated/submissions/openai/agdf/plugin.json +1 -1
  92. package/generated/submissions/openai/agdf/submission/openai/availability.md +1 -1
  93. package/generated/submissions/openai/agdf/submission/openai/capability-matrix.json +1 -1
  94. package/generated/submissions/openai/agdf/submission/openai/inventory.json +13 -13
  95. package/generated/submissions/openai/agdf/submission/openai/listing.json +1 -1
  96. package/generated/submissions/openai/agdf/submission/openai/readiness.json +2 -2
  97. package/generated/submissions/openai/agdf/submission/openai/readiness.md +2 -2
  98. package/generated/submissions/openai/agdf/submission/openai/release-notes.md +5 -8
  99. package/generated/submissions/openai/agdf/submission/openai/reviewer-cases.json +1 -1
  100. package/lib/mcp-dispatch-runtime.js +4 -1
  101. package/lib/mcp-worker-runtime.js +77 -77
  102. package/package.json +1 -1
  103. package/runtime/core/lib/companion/events.js +4 -1
  104. package/runtime/core/lib/companion/figure-state.js +6 -4
  105. package/runtime/core/lib/companion/journal.js +6 -0
  106. package/runtime/core/lib/companion/run-view.js +54 -11
  107. package/runtime/core/lib/control-evaluation/decision-content-placeholder.js +4 -1
  108. package/runtime/core/lib/control-evaluation/gate-check.js +5 -1
  109. package/runtime/core/lib/control-inspect/cockpit-list.js +10 -8
  110. package/runtime/core/lib/control-inspect/cockpit-session.js +11 -3
  111. package/runtime/core/lib/control-inspect/cockpit-work.js +4 -2
  112. package/runtime/core/lib/control-inspect/cockpit.js +43 -13
  113. package/runtime/core/lib/control-inspect/work-situation.js +25 -0
  114. package/runtime/core/lib/control-read/cockpit-worker.js +1 -0
  115. package/runtime/core/lib/control-state/run-create-command.js +6 -1
  116. package/runtime/core/lib/control-state/run-inventory-reader.js +50 -3
  117. package/runtime/core/lib/control-state/run-inventory.js +12 -27
  118. package/runtime/core/lib/control-state/run-presentation-render.js +29 -0
  119. package/runtime/core/lib/control-state/run-title.js +43 -0
  120. package/runtime/core/lib/interaction-catalog-validation.js +2 -1
  121. package/runtime/core/lib/interaction-presentation.js +5 -9
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agdf/local-validator-runtime",
3
- "version": "0.31.0",
3
+ "version": "0.32.0",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "imports": {
@@ -70,6 +70,8 @@ export function commandGate(command = {}) {
70
70
  const payload = command.payload ?? {};
71
71
  if (command.kind === 'closeout.record') return 'OR';
72
72
  if (command.kind === 'step.record') return payload.internal_step ?? RUN_STEP_SLOTS[payload.step] ?? null;
73
+ if (command.kind === 'verified_change.record') return 'Verified Change';
74
+ if (command.kind === 'ur_draft.replace') return 'UR';
73
75
  if (command.kind === 'operation.recover') return null;
74
76
  return typeof payload.gate === 'string' ? payload.gate : null;
75
77
  }
@@ -83,7 +85,8 @@ function successKind(command, result) {
83
85
  if (kind === 'approval.record') return 'approval_recorded';
84
86
  if (kind === 'closeout.record') return 'run_completed';
85
87
  if (kind === 'material.stage' && command.payload?.action === 'seal') return 'material_staged';
86
- if (kind === 'step.record' || kind === 'artefact.record') return 'document_recorded';
88
+ // Every document recording of run-steps.recordRunStep (run-command-service.js).
89
+ if (['step.record', 'artefact.record', 'verified_change.record', 'ur_draft.replace'].includes(kind)) return 'document_recorded';
87
90
  return null;
88
91
  }
89
92
 
@@ -21,16 +21,18 @@ export function deriveFigureState(entry) {
21
21
  if (!entry) return { mood: 'bereit', reason: { key: 'idle' } };
22
22
  if (entry.valid === false) return { mood: 'blockiert', reason: { key: 'unreadable', reason: entry.unreadable_reason ?? 'run_state_invalid' } };
23
23
  if (entry.evaluation_error) return { mood: 'blockiert', reason: { key: 'unreadable', reason: entry.evaluation_error } };
24
- if (entry.lifecycle === 'completed') return { mood: 'fertig', reason: { key: 'done' } };
24
+ // A completed run whose evaluation is blocked is not done: the block is shown like any other.
25
+ const blocked = entry.evaluated === true && (entry.status === 'blocked' || Boolean(entry.blocking_reason && entry.blocking_reason !== 'none'));
26
+ // A completed run without confirmed completion is still shown, but claims nothing: its own reason.
27
+ if (entry.lifecycle === 'completed' && !blocked) return entry.completion_confirmed === true
28
+ ? { mood: 'fertig', reason: { key: 'done' } } : { mood: 'bereit', reason: { key: 'completionUnconfirmed' } };
25
29
  if (ENDED_LIFECYCLES.includes(entry.lifecycle)) return { mood: 'bereit', reason: { key: 'ended' } };
26
30
  if (!entry.evaluated) return { mood: 'bereit', reason: { key: 'idle' } };
27
31
  const gate = entry.current_gate;
28
- if (entry.status === 'blocked' || (entry.blocking_reason && entry.blocking_reason !== 'none'))
29
- return { mood: 'blockiert', reason: { key: 'blocked', gate, decision: entry.blocking_reason } };
32
+ if (blocked) return { mood: 'blockiert', reason: { key: 'blocked', gate, decision: entry.blocking_reason } };
30
33
  const approval = pendingApproval(entry.missing_approval);
31
34
  if (approval) return { mood: 'wartet', reason: { key: 'waitingApproval', gate: approval } };
32
35
  if (entry.user_action_required === 'yes') return { mood: 'wartet', reason: { key: 'waitingDecision', gate } };
33
- if (entry.status === 'pass') return { mood: 'fertig', reason: { key: 'done' } };
34
36
  if (STRUCTURED_MODES.includes(entry.mode) && BUILD_GATES.includes(gate)) return { mood: 'baut', reason: { key: 'building', gate } };
35
37
  return { mood: 'laeuft', reason: { key: 'working', gate } };
36
38
  }
@@ -160,6 +160,12 @@ export function createCompanionJournal({ dataRoot = process.env[COMPANION_DATA_R
160
160
  return Object.freeze({
161
161
  enabled,
162
162
  path: enabled ? journalPath(dataRoot) : null,
163
+ // Whether `observe` writes an event for this result, without writing: a facade asks this for a
164
+ // result its generation produced, and so already journaled.
165
+ observes(toolName, input, result) {
166
+ if (!enabled) return false;
167
+ try { return Boolean(eventKind(toolName, input, result)); } catch { return false; }
168
+ },
163
169
  observe(toolName, input, result) {
164
170
  if (!enabled) return false;
165
171
  try {
@@ -4,8 +4,12 @@
4
4
  // view sees exactly what the status card shows. Nothing here writes or authorizes.
5
5
  import { existsSync, realpathSync } from 'node:fs';
6
6
  import { join } from 'node:path';
7
- import { readFileSync } from '../control-read/fs.js';
7
+ import { readFileSync, statSync } from '../control-read/fs.js';
8
8
  import { captureControlScope } from '../control-read/snapshot.js';
9
+ import { containedRegularFile, hasSymlinkComponent, isSafeControlRelativePath } from '../control-state/contained-file.js';
10
+ import { parseControlState } from '../control-state/run-state-parser.js';
11
+ import { runIdentity } from '../control-state/run-title.js';
12
+ import { localizedDecisionFields } from '../control-evaluation/decision-content-readiness.js';
9
13
  import { evaluateGateCheck } from '../control-evaluation/gate-check.js';
10
14
  import { readRunInventory } from '../control-state/run-inventory-reader.js';
11
15
  import { runFolderEntries } from '../control-state/run-state-reader.js';
@@ -15,7 +19,7 @@ import { runRelativePath } from '../control-state/run-identity.js';
15
19
  import { artefactFileDigest } from '../control-state/run-seal-compute.js';
16
20
  import { boundedCode } from '../control-inspect/run-classification.js';
17
21
  import { localizeBlockingCondition, localizeOperationalValue } from '../interaction-presentation.js';
18
- import { interactionLocales } from '../resources/context.js';
22
+ import { interactionLocales, resolveArtifactPresentationLanguages } from '../resources/context.js';
19
23
  import { deriveFigureState } from './figure-state.js';
20
24
 
21
25
  export const RUN_VIEW_SCHEMA = 'run-view/1';
@@ -43,13 +47,50 @@ export function projectDiagnosis(entries) {
43
47
  return { unreadable, counts: { unreadable: unreadable.length, readable: entries.length - unreadable.length, by_reason } };
44
48
  }
45
49
 
50
+ const NAMING_TEXT_LIMIT = 500;
51
+ const UR_READ_LIMIT = 131072;
52
+ const bounded = value => (typeof value === 'string' && value.length > NAMING_TEXT_LIMIT ? `${value.slice(0, NAMING_TEXT_LIMIT - 1)}…` : value);
53
+
54
+ // How a run names itself to a person, read inside the run's own snapshot: the stable title of the
55
+ // shared Run title rule (registered UR heading, then the authored Objective; a bare Run ID is no
56
+ // title) and the goal in the display language. Where display and artefact language differ, the goal
57
+ // is the one the UR's localized approval summary states. An unreadable UR leaves both to the Run document.
58
+ export function readRunNaming(root, runId, language = 'en') {
59
+ let content;
60
+ try { content = readFileSync(join(root, runRelativePath(runId, '', 'RUN_STATE.md')), 'utf8'); } catch { return null; }
61
+ let urHeading = '', localizedGoal = null;
62
+ try {
63
+ const path = String(parseControlState(content).artefacts.get('UR')?.path ?? '').replace(/^`|`$/gu, '').trim();
64
+ if (isSafeControlRelativePath(path) && path.startsWith('.agdf/control/') && !hasSymlinkComponent(root, path)) {
65
+ const file = containedRegularFile(root, path);
66
+ if (file.status === 'valid' && statSync(file.path).size <= UR_READ_LIMIT) {
67
+ const text = readFileSync(file.path, 'utf8');
68
+ urHeading = (text.match(/^#\s+(.+)$/mu)?.[1] ?? '').replace(/^UR:\s*/iu, '').trim();
69
+ const sourceLanguage = resolveArtifactPresentationLanguages(root, language).artifact_language;
70
+ localizedGoal = localizedDecisionFields('UR', text, { locale: String(language).split('-')[0], sourceLanguage, path }).fields.goal ?? null;
71
+ }
72
+ }
73
+ } catch (error) { if (error?.code === 'source_changed') throw error; }
74
+ const identity = runIdentity({ runId, runContent: content, urHeading });
75
+ return { run_title: identity.title_source === 'run_id' ? null : bounded(identity.title),
76
+ run_title_source: identity.title_source, goal: bounded(localizedGoal ?? identity.objective) };
77
+ }
78
+
46
79
  // One run entry: inventory facts plus, when evaluated, the status card fields of its report.
47
- export function projectRunEntry(inventoryEntry, report, { registry = interactionLocales, language = 'en' } = {}) {
80
+ // `naming` is readRunNaming's result for an evaluated run; without it the inventory names the run.
81
+ export function projectRunEntry(inventoryEntry, report, { registry = interactionLocales, language = 'en', naming = null } = {}) {
82
+ const authored = inventoryEntry.title_source === 'objective' ? inventoryEntry.title ?? null : null;
48
83
  const base = {
49
84
  run_id: inventoryEntry.run_id,
50
85
  title: inventoryEntry.title ?? null,
51
- // The authored objective only; a template objective or a run-id title is no goal (CR finding C2).
52
- objective: inventoryEntry.title_source === 'objective' ? inventoryEntry.title ?? null : null,
86
+ // The stable human title, the same at every gate; null while only the Run ID could name the run.
87
+ run_title: naming ? naming.run_title : authored,
88
+ run_title_source: naming?.run_title_source ?? (authored ? 'objective' : 'run_id'),
89
+ // The goal in the display language where the UR states one, otherwise the authored objective.
90
+ goal: naming ? naming.goal : inventoryEntry.objective ?? null,
91
+ // The authored objective only, from the shared Run title rule; a template objective or a
92
+ // bare Run ID is no goal (CR finding C2).
93
+ objective: inventoryEntry.objective ?? null,
53
94
  lifecycle: inventoryEntry.lifecycle ?? null,
54
95
  valid: inventoryEntry.valid !== false,
55
96
  revision: inventoryEntry.revision ?? null,
@@ -66,6 +107,7 @@ export function projectRunEntry(inventoryEntry, report, { registry = interaction
66
107
  ...base,
67
108
  title: card.humanPresentation?.runTitle ?? base.title,
68
109
  evaluated: true,
110
+ completion_confirmed: base.lifecycle === 'completed' && report.completion?.fulfilled === true,
69
111
  status: card.status,
70
112
  current_gate: card.current_gate,
71
113
  path: card.breadcrumb.map(({ gate, status }) => ({ gate, status })),
@@ -122,9 +164,10 @@ export function readRunDrift(root, runId) {
122
164
  return drift;
123
165
  }
124
166
 
125
- // Active and unreadable runs are evaluated; terminal runs are summarized unless requested by id,
126
- // so a large history does not cost one evaluation per run.
127
- const needsEvaluation = (entry, requested) => requested.has(entry.run_id) || !entry.valid || entry.lifecycle === 'active';
167
+ // Active runs are evaluated; terminal and unreadable runs are summarized unless requested by id, so
168
+ // neither a large history nor many unreadable runs cost one evaluation per run. An unreadable run's
169
+ // reason and figure come from the inventory findings.
170
+ const needsEvaluation = (entry, requested) => requested.has(entry.run_id) || (entry.valid && entry.lifecycle === 'active');
128
171
 
129
172
  // A write that lands during a capture invalidates it (source_changed); the read is repeated a bounded
130
173
  // number of times before the change is reported (CR delta review RV-1).
@@ -152,12 +195,12 @@ export function readRunView(root, { language = 'en', runIds = [] } = {}) {
152
195
  try {
153
196
  return captured(readRoot, base => {
154
197
  const report = evaluateGateCheck(base, { runId, ignoreRunIdEnv: true, presentationLanguage: language });
155
- return { report, drift: report?.status_card ? readRunDrift(base, runId) : null };
198
+ return { report, drift: report?.status_card ? readRunDrift(base, runId) : null, naming: readRunNaming(base, runId, language) };
156
199
  });
157
- } catch (error) { return { report: { evaluation_error: boundedCode(error) }, drift: null }; }
200
+ } catch (error) { return { report: { evaluation_error: boundedCode(error) }, drift: null, naming: null }; }
158
201
  };
159
202
  const entryOf = (inventoryEntry, evaluated) => {
160
- const entry = projectRunEntry(inventoryEntry, evaluated?.report ?? null, { language });
203
+ const entry = projectRunEntry(inventoryEntry, evaluated?.report ?? null, { language, naming: evaluated?.naming ?? null });
161
204
  return entry.evaluated && entry.valid !== false && evaluated.drift ? { ...entry, drift: evaluated.drift } : entry;
162
205
  };
163
206
  let inventory = null, store;
@@ -2,8 +2,11 @@
2
2
  // run inventory and other `node:`-free readers share the same rule.
3
3
  const prompts = new Set(['describe the trustworthy outcome.', 'what outcome should become possible?',
4
4
  'which tp tasks are fully done, partially done or not done?', 'what remains unverified?', 'which risks remain and what is their impact?']);
5
+ // A bare generated Run ID (`run-<operation uuid>`) names the Run but states no outcome.
6
+ const generatedRunId = /^run-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u;
5
7
  export function isDecisionContentPlaceholder(value) {
6
8
  if (typeof value !== 'string') return true;
7
9
  const text = value.trim().replace(/^`([^`]+)`$/u, '$1').replace(/\s+/gu, ' ').toLowerCase();
8
- return !text || prompts.has(text) || /^(?:todo|tbd|<[^>]+>|(?:draft|done|ready|blocked|pass|revise|block)(?:\s*\|\s*(?:draft|done|ready|blocked|pass|revise|block))+)$/.test(text);
10
+ return !text || prompts.has(text) || generatedRunId.test(text)
11
+ || /^(?:todo|tbd|<[^>]+>|(?:draft|done|ready|blocked|pass|revise|block)(?:\s*\|\s*(?:draft|done|ready|blocked|pass|revise|block))+)$/.test(text);
9
12
  }
@@ -159,7 +159,7 @@ export function isReadyUserGateApproval({ status, currentGate, missingApproval }
159
159
 
160
160
  function buildBreadcrumbPath(modeSliceDecision, currentGate, runState) {
161
161
  const gates = BREADCRUMB_PATH_TEMPLATES[modeSliceDecision] ?? ["UR"];
162
- return gates.map((gate) => {
162
+ return gates.map((gate, index) => {
163
163
  if (BREADCRUMB_STANDARD_GATES.includes(gate)) {
164
164
  if (gateApprovalStatus(runState, gate) === "approved") return { gate, status: "fulfilled" };
165
165
  if (gate === currentGate) return { gate, status: "current" };
@@ -170,6 +170,10 @@ function buildBreadcrumbPath(modeSliceDecision, currentGate, runState) {
170
170
  if (gate === currentGate) return { gate, status: "current" };
171
171
  if ((gate === "Verified Change" || gate === "Compact Delivery") && currentGate === "OR")
172
172
  return { gate, status: "fulfilled" };
173
+ // A path has one current entry: while an earlier gate is still the current one (a stored route
174
+ // with an unapproved UR), the route entry after it is open, not current by its route.
175
+ if (gates.slice(0, index).some((earlier) => earlier === currentGate && gateApprovalStatus(runState, earlier) !== "approved"))
176
+ return { gate, status: "open" };
173
177
  if (gate === "Verified Change" && modeSliceDecision === "verified_change")
174
178
  return { gate, status: "current" };
175
179
  if (gate === "Compact Delivery" && modeSliceDecision === "quick_task")
@@ -8,14 +8,16 @@ export function hasConsistentWorkCounts(inventory) {
8
8
  && inventory.entries.every(row => row && COCKPIT_WORK_SECTIONS.includes(row.section));
9
9
  }
10
10
  export function projectCockpitList(inventory, { section = 'active', query = '' } = {}) {
11
- if (!COCKPIT_WORK_SECTIONS.includes(section)) throw Error('dto_invalid');
11
+ if (section !== 'runs' && !COCKPIT_WORK_SECTIONS.includes(section)) throw Error('dto_invalid');
12
12
  const normalized = query.trim().toLowerCase();
13
13
  if (!inventory) return { section, query: normalized, coverage: 'unavailable', diagnostics: [], sectionCount: null, matchCount: 0, rows: [] };
14
14
  if (!hasConsistentWorkCounts(inventory)) throw Error('dto_invalid');
15
- const diagnostics = inventory.diagnostics.filter(row => !row.section || row.section === section);
16
- if (diagnostics.some(row => row.code === 'work_section_unavailable')) return { section, query: normalized, coverage: 'unavailable', diagnostics, sectionCount: null, matchCount: null, rows: [] };
17
- const selected = inventory.entries.map((entry, index) => ({ entry, index })).filter(row => row.entry.section === section);
18
- const rows = selected.filter(({ entry }) => !normalized || [entry.title, entry.key, entry.stored_status]
15
+ const sections = section === 'runs' ? ['active', 'completed'] : [section];
16
+ const diagnostics = inventory.diagnostics.filter(row => !row.section || sections.includes(row.section));
17
+ const unavailable = sections.filter(s => diagnostics.some(row => row.code === 'work_section_unavailable' && (!row.section || row.section === s)));
18
+ if (unavailable.length === sections.length) return { section, query: normalized, coverage: 'unavailable', diagnostics, sectionCount: null, matchCount: null, rows: [] };
19
+ const selected = inventory.entries.map((entry, index) => ({ entry, index })).filter(row => sections.includes(row.entry.section) && !unavailable.includes(row.entry.section));
20
+ const rows = selected.filter(({ entry }) => !normalized || [entry.title, entry.key, entry.stored_status, entry.objective ?? '']
19
21
  .some(value => value.toLowerCase().includes(normalized))).map(({ entry, index }) => ({ entry, index,
20
22
  identity: JSON.stringify([entry.identity, entry.source.digest]), selectable: entry.selectable, displayTitle: entry.title.replace(/^\[(framework[-_]maintenance|external[-_]delivery)\]\s*/i, '') || entry.title,
21
23
  provenance: { path: entry.source.path, digest: entry.source.digest, originalTitle: entry.title } }));
@@ -25,10 +27,10 @@ export function projectCockpitList(inventory, { section = 'active', query = '' }
25
27
 
26
28
  // Presentation-only grouping/search over the shared inventory's already ordered entries.
27
29
  export function projectCockpitRuns(inventory, { section = 'active', query = '' } = {}) {
28
- if (!['active', 'completed'].includes(section)) throw Error('dto_invalid');
30
+ if (!['active', 'completed', 'runs'].includes(section)) throw Error('dto_invalid');
29
31
  const normalized = query.trim().toLowerCase();
30
- const entries = inventory.runs.filter(run => section === 'active'
31
- ? !run.valid || run.lifecycle === 'active' : run.valid && run.lifecycle !== 'active');
32
+ const entries = inventory.runs.filter(run => section === 'runs' || (section === 'active'
33
+ ? !run.valid || run.lifecycle === 'active' : run.valid && run.lifecycle !== 'active'));
32
34
  const rows = entries.filter(run => !normalized || [run.title, run.run_id, run.attention.label]
33
35
  .some(value => value.toLowerCase().includes(normalized)));
34
36
  return { rows, sectionCount: entries.length, matchCount: rows.length,
@@ -11,11 +11,11 @@ import { COCKPIT_LIMITS, parseCockpitArguments, parsePluginCockpitArguments } fr
11
11
  // One connection owns bounded independent read views. Retiring workers still occupy slots.
12
12
  export function createCockpitSessionService(root, {
13
13
  budget, poolFactory = viewRoot => new ReadWorkerPool(viewRoot, { limits: { total: COCKPIT_LIMITS.capture },
14
- maxOldGenerationSizeMb: COCKPIT_LIMITS.workerOldGeneration, budget }), now = Date.now, pluginMode = false,
14
+ maxOldGenerationSizeMb: COCKPIT_LIMITS.workerOldGeneration, budget }), now = Date.now, pluginMode = false, integrationSurface,
15
15
  } = {}) {
16
16
  const target = pluginMode ? null : { target_id: resolveControlCommandTarget(root).target_id, display_path: root };
17
17
  const sessions = new Map();
18
- let closed = false, renderGeneration = 0, publication = null;
18
+ let closed = false, renderGeneration = 0, publication = null, resourceIdentity = null;
19
19
  const failure = (code, viewTarget = target) => ({ schema_version: '2', target: viewTarget, snapshot_id: null, observed_as_of: null,
20
20
  source_digest: null, state: 'blocked', code, retryable: code !== 'session_expired', data: null, authorizes: false });
21
21
  function losePublication(session) {
@@ -59,6 +59,12 @@ export function createCockpitSessionService(root, {
59
59
  function touch(session) { session.touched = now(); schedule(session); }
60
60
  return Object.freeze({
61
61
  failure,
62
+ bindResourceIdentity({ digest, version } = {}) {
63
+ if (resourceIdentity || sessions.size || !/^[a-f0-9]{64}$/u.test(digest ?? '')
64
+ || typeof version !== 'string' || !/^[0-9A-Za-z.+-]{1,80}$/u.test(version)) throw Error('resource_identity_invalid');
65
+ resourceIdentity = Object.freeze({ schema_version: '1', version, resource_digest: `sha256:${digest}`,
66
+ resource_scope: 'server_validated', host_display: 'unconfirmed', candidate_match: 'unverified' });
67
+ },
62
68
  inspectOwnership() {
63
69
  return { views: sessions.size, active: [...sessions.values()].filter(row => !row.retired).length,
64
70
  uncertain: publication?.phase === 'uncertain' ? 1 : 0 };
@@ -99,6 +105,8 @@ export function createCockpitSessionService(root, {
99
105
  sessions.set(session.id, session); schedule(session);
100
106
  return { schema_version: '1', authorizes: false, target: viewTarget,
101
107
  _meta: { agdf_cockpit: { session_id: session.id, target: viewTarget, render_generation: ++renderGeneration,
108
+ ...(['codex', 'claude'].includes(integrationSurface) ? { integration_surface: integrationSurface } : {}),
109
+ ...(resourceIdentity ? { provenance: resourceIdentity } : {}),
102
110
  ...(input.run_id ? { initial_run_id: input.run_id } : {}),
103
111
  idle_timeout_ms: COCKPIT_LIMITS.idle, lifetime_ms: COCKPIT_LIMITS.lifetime } } };
104
112
  },
@@ -154,7 +162,7 @@ export function createCockpitSessionService(root, {
154
162
  touch(session); return result;
155
163
  }
156
164
  if (['planned', 'archive'].includes(input.operation) && !session.rows.has(input.row_id)) return fail('resource_denied');
157
- if (['document', 'prepare_context'].includes(input.operation)
165
+ if (['document', 'preview', 'prepare_context'].includes(input.operation)
158
166
  && session.documents.get(input.resource_id) !== `${input.snapshot_id}:${input.run_id}`) return fail('resource_denied');
159
167
  if (['context', 'artifact_readiness'].includes(input.operation) && input.run_id !== session.run_id) return fail('resource_denied');
160
168
  if (input.operation === 'artifact_readiness' && (!session.draft?.available || session.draft.gate !== input.gate
@@ -7,13 +7,15 @@ import { COCKPIT_WORK_SECTIONS as sections } from './cockpit-list.js';
7
7
  // discriminator changes policy: order and per-row source identity come from Core,
8
8
  // never a Markdown table, stored rank, or adapter-specific lifecycle database.
9
9
  export function projectCockpitWork(root) {
10
- const inventory = readWorkInventory(root, { language: 'de' });
10
+ const inventory = readWorkInventory(root, { language: 'de', includePresentation: true, cockpitAssessment: true });
11
11
  if (inventory.state === 'unavailable') return { state: 'error', code: 'work_inventory_unavailable', data: null };
12
12
  const entries = inventory.entries.map(row => {
13
13
  const planned = row.kind === 'planned';
14
14
  const section = planned ? sections[1] : row.lifecycle === 'active' || row.lifecycle === null ? sections[0] : sections[2];
15
15
  const key = planned ? row.plan_id : row.run_id;
16
- return { kind: row.kind, identity: row.identity, section, key, original_key: key, title: row.title,
16
+ return { kind: row.kind, identity: row.identity, section, key, original_key: key, title: planned ? row.title : row.presentation?.name || key,
17
+ ...(!planned ? { title_source: row.presentation?.name ? 'ur' : 'run_id', objective: row.objective ?? null,
18
+ last_activity: row.last_activity, seal: row.presentation?.seal ?? 'unconfirmed', lifecycle: row.lifecycle, attention_code: row.attention.code } : {}),
17
19
  stored_status: planned ? ({ ready: 'Bereit zur Übergabe', draft: 'UR in Klärung', recovery_required: 'Wiederherstellung erforderlich', unconfirmed: 'Zuordnung ungeklärt', unavailable: 'Quelle nicht verfügbar' })[row.coverage]
18
20
  : row.attention.label, scope: planned ? 'UR-Planung' : 'Run', priority: row.priority ?? null,
19
21
  stored_next_step: planned ? row.actions.join(' · ') : row.next_action ?? '', source_links: row.source.path,
@@ -1,5 +1,5 @@
1
1
  import { readCockpitArchive, readArchiveDocument } from './cockpit-archive.js';
2
- import { runDocumentPath } from '../control-state/run-identity.js';
2
+ import { runDocumentPath, runEvidencePath } from '../control-state/run-identity.js';
3
3
  import { existsSync } from '../control-read/fs.js';
4
4
  import { RUN_ID_PATTERN } from '../control-state/run-identity.js';
5
5
  import { join, posix, sep } from 'node:path';
@@ -11,8 +11,10 @@ import { readRunState, readArtefactHeading } from '../control-evaluation/run-sta
11
11
  import { evaluateGateCheck } from '../control-evaluation/gate-check.js';
12
12
  import { isSafeControlRelativePath } from '../control-state/contained-file.js';
13
13
  import { resolveControlCommandTarget } from '../control-state/approval-command-contract.js';
14
- import { localePack, resolveHumanRunTitle } from '../interaction-presentation.js';
15
- import { interactionLocales } from '../resources/context.js';
14
+ import { localePack } from '../interaction-presentation.js';
15
+ import { runIdentity } from '../control-state/run-title.js';
16
+ import { interactionLocales, resolveArtifactPresentationLanguages } from '../resources/context.js';
17
+ import { projectCockpitSummary } from '../control-state/run-presentation-render.js';
16
18
  import { projectCockpitContext, composeCockpitPacket } from './cockpit-context.js';
17
19
  import { projectCockpitWork } from './cockpit-work.js';
18
20
  import { readControlLayout } from '../control-state/control-layout.js';
@@ -23,6 +25,7 @@ import { ARTIFACT_READINESS_GATES, projectArtifactReadiness, describeArtifactRea
23
25
  import { artefactFileDigest } from '../control-state/run-seal-compute.js';
24
26
  import { inspectApprovedArtefact } from '../control-state/artefact-binding-proof.js';
25
27
  import { runClosure } from '../control-state/run-operations.js';
28
+ import { projectRunEntry } from '../companion/run-view.js';
26
29
  // Shared with the companion run view, in a module every runtime profile carries.
27
30
  import { boundedCode, projectCockpitAssessment } from './run-classification.js';
28
31
  export { boundedCode, projectCockpitAssessment };
@@ -34,7 +37,7 @@ const DOCUMENT_GATES = new Set(['UR', 'PRD', 'SD', 'TP', 'QA', 'UAT']);
34
37
  const DESCRIPTION_LIMIT = 256 * 1024;
35
38
  const rawDigest = bytes => createHash('sha256').update(bytes).digest('hex');
36
39
  const assertDescriptionBound = value => { if (Buffer.byteLength(JSON.stringify(value)) > DESCRIPTION_LIMIT) fail('resource_limit'); };
37
- const objective = content => content?.match(/(?:^|\n)## Objective\s*\n([\s\S]*?)(?=\n## |$)/)?.[1]?.trim() ?? null;
40
+ const storedObjective = content => content?.match(/(?:^|\n)## Objective\s*\n([\s\S]*?)(?=\n## |$)/)?.[1]?.trim() ?? null;
38
41
  // History of an ended Run, read only from its sealed closure receipt; null for every other Run.
39
42
  export function projectRunClosure(content) {
40
43
  const closure = runClosure(content);
@@ -80,9 +83,16 @@ export function createCockpitReader(root, options = {}) {
80
83
  function manifest(runId, state) {
81
84
  const result = [];
82
85
  const paths = new Set();
83
- // The canonical state itself and each explicit Artefacts row are registrations.
84
- // The canonical state path is platform-native; registrations use POSIX separators.
86
+ // Keep Run State and artefact registrations ahead of standalone evidence reports.
85
87
  const rows = [{ type: 'Run State', path: state.path?.split(sep).join(posix.sep) }, ...[...state.artefacts].map(([type, row]) => ({ type, ...row }))];
88
+ const evidenceRoot = runEvidencePath(runId, '') + '/';
89
+ for (const row of state.evidence_applicability.current) {
90
+ const path = row.report?.path;
91
+ if (row.available !== true || !path?.startsWith(evidenceRoot) || !isSafeControlRelativePath(path)
92
+ || !SUPPORTED.has(path.split('.').at(-1)?.toLowerCase())) continue;
93
+ const { bytes } = view.readOptionalFileSync(join(root, path), READ_LIMITS.preview);
94
+ if (bytes && !bytes.includes(0)) rows.push({ type: 'Evidence', path });
95
+ }
86
96
  for (const row of rows) {
87
97
  const path = String(row.path ?? '').replace(/^`|`$/g, '').trim();
88
98
  if (!path || path === 'none' || paths.has(path)) continue;
@@ -117,17 +127,23 @@ export function createCockpitReader(root, options = {}) {
117
127
  const resources = manifest(run.run_id, state);
118
128
  const outside = resources.filter(r => r.status === 'blocked' && isSafeControlRelativePath(r.registered_reference)).map(r => r.registered_reference);
119
129
  const report = outside.length ? null : evaluateGateCheck(root, { runId: run.run_id, ignoreRunIdEnv: true });
130
+ const assessed = report ? evaluation(state, report, run.meta.lifecycle) : outOfScopeEvaluation(state, outside);
131
+ const figure = report?.status_card && assessed.control_assessment.state !== 'unconfirmed'
132
+ ? projectRunEntry({ run_id: run.run_id, lifecycle: run.meta.lifecycle }, report).figure
133
+ : undefined;
120
134
  const work_summary = outside.length ? null : runWorkSummary(root, run.content, state.path);
121
135
  const draft_check = draftDescriptor(run, state, report);
122
136
  const document_states = documentStates(run, state, report, resources, draft_check);
137
+ const urTitle = readArtefactHeading(root, state.artefacts.get('UR')).replace(/^UR:\s*/i, '').trim();
138
+ const identity = runIdentity({ runId: run.run_id, runContent: run.content, urHeading: urTitle });
123
139
  return { run_id: run.run_id, revision_id: run.meta.revision_id, lifecycle: run.meta.lifecycle,
124
140
  closure: projectRunClosure(run.content), work_summary,
141
+ ...(figure && { figure }),
125
142
  // The cockpit identifies the undertaking; the gate card can still name its current artefact.
126
- objective: objective(run.content), title: resolveHumanRunTitle({
127
- urHeading: readArtefactHeading(root, state.artefacts.get('UR')).replace(/^UR:\s*/i, ''),
128
- runContent: run.content, runId: run.run_id,
129
- }),
130
- evaluation: report ? evaluation(state, report, run.meta.lifecycle) : outOfScopeEvaluation(state, outside), persisted: { current_gate: state.current_gate, next_allowed_action: state.next_allowed_action,
143
+ // The detail shows the stored Objective as written, placeholders included (owner decision);
144
+ // the title follows the shared Run title rule.
145
+ objective: storedObjective(run.content), title_source: identity.title_source, title: identity.title,
146
+ evaluation: assessed, persisted: { current_gate: state.current_gate, next_allowed_action: state.next_allowed_action,
131
147
  decision: report?.status ?? 'unavailable', artefacts: [...state.artefacts].map(([type, value]) => ({ type, ...value })) },
132
148
  context_graph: { refs: state.context_graph.refs }, resources,
133
149
  draft_check, document_states, evidence_applicability: Object.fromEntries(['current', 'superseded', 'applicability_unknown'].map(group => [group,
@@ -202,7 +218,8 @@ export function createCockpitReader(root, options = {}) {
202
218
  view.revalidate();
203
219
  } catch (error) { checkedDraft = null; invalidate(); inspected = null; graph = null; throw error; }
204
220
  }
205
- function readDocument(resourceId) {
221
+ // Only the gate preview shows the authored summary; full document reads stay content-only.
222
+ function readDocument(resourceId, { withSummary = false } = {}) {
206
223
  const resource = documents.get(resourceId);
207
224
  if (!resource) fail('resource_denied');
208
225
  if (resource.status === 'blocked') return envelope({ resource }, 'blocked', 'resource_denied');
@@ -227,7 +244,12 @@ export function createCockpitReader(root, options = {}) {
227
244
  if (registered) links[href] = registered.resource_id;
228
245
  }
229
246
  const document_state = details.get(resource.run_id)?.data?.document_states?.find(row => row.resource_id === resourceId);
230
- return envelope({ resource, format, content, content_digest: rawDigest(bytes), links, ...(document_state ? { document_state } : {}) });
247
+ const summary = withSummary && format === 'markdown' && document_state && ['UR', 'PRD', 'SD', 'TP'].includes(resource.type)
248
+ ? projectCockpitSummary(resource.type, content, resolveArtifactPresentationLanguages(root, 'de'), { runId: resource.run_id,
249
+ revisionId: document_state.revision_id, artefactPath: resource.path }) : null;
250
+ const cockpit_summary = summary && { ...summary, run_id: resource.run_id, resource_id: resource.resource_id,
251
+ revision_id: document_state.revision_id, content_digest: rawDigest(bytes) };
252
+ return envelope({ resource, format, content, content_digest: rawDigest(bytes), links, ...(document_state ? { document_state } : {}), ...(cockpit_summary ? { cockpit_summary } : {}) });
231
253
  }
232
254
  function discardScope(retainCheck = false) {
233
255
  if (!retainCheck) checkedDraft = null;
@@ -355,6 +377,14 @@ export function createCockpitReader(root, options = {}) {
355
377
  return selected;
356
378
  });
357
379
  },
380
+ preview(resourceId, id, runId) {
381
+ const resource = documents.get(resourceId);
382
+ if (!resource || resource.run_id !== runId) fail('resource_denied');
383
+ assertSnapshot(id);
384
+ const result = readDocument(resourceId, { withSummary: true });
385
+ assertSnapshot(id);
386
+ return result;
387
+ },
358
388
  document(resourceId, id, runId) {
359
389
  // Check selectors before revalidation or replacing the legitimate view.
360
390
  const previous = documents.get(resourceId);
@@ -0,0 +1,25 @@
1
+ // Presentation of one canonical control assessment. This module never evaluates or authorizes work.
2
+ export function projectWorkSituation({ assessment, gate, blockingReason, missingApproval, closureOutcome, persistedGate, persistedNext, nextAllowedAction } = {}) {
3
+ if (persistedGate !== undefined && (persistedGate !== gate || !(assessment === 'ended' && closureOutcome) && persistedNext !== nextAllowedAction)) assessment = 'unconfirmed';
4
+ let kind = 'unconfirmed';
5
+ if (assessment === 'open') kind = gate === 'CD+Tests' ? 'in_progress' : 'open';
6
+ else if (assessment === 'blocked') {
7
+ kind = blockingReason === 'presentation_version_decided' ? 'revision_required'
8
+ : blockingReason === 'none' && missingApproval === `Approval: ${gate}` ? 'approval_required'
9
+ : gate === 'UR' && blockingReason === 'AGDF_UR_REQUIREMENTS_INCOMPLETE' ? 'clarification_required' : 'blocked';
10
+ } else if (assessment === 'completed') kind = 'completed';
11
+ else if (assessment === 'ended' && closureOutcome) kind = 'ended';
12
+ const values = {
13
+ revision_required: ['clarification_required', 'Revision required', 'Überarbeitung nötig'],
14
+ approval_required: ['decision_required', 'Approval required', 'Freigabe erforderlich'],
15
+ clarification_required: ['clarification_required', 'Clarification required', 'Klärung erforderlich'],
16
+ in_progress: ['in_progress', 'In implementation', 'In Umsetzung'],
17
+ open: ['in_progress', 'Work may continue', 'Weiterarbeit offen'],
18
+ blocked: ['blocked', 'Blocked', 'Blockiert'],
19
+ completed: ['none', 'Completed', 'Vorhaben abgeschlossen'],
20
+ ended: ['none', 'Ended', 'Vorhaben beendet'],
21
+ unconfirmed: ['unclear', 'Current conditions unconfirmed', 'Aktuelle Voraussetzungen nicht bestätigt'],
22
+ };
23
+ const [code, en, de] = values[kind];
24
+ return { kind, code, en, de };
25
+ }
@@ -18,6 +18,7 @@ parentPort.on('message', ({ id, operation, selector, snapshot, input }) => {
18
18
  : operation === 'planned' ? reader.planned(selector, snapshot)
19
19
  : operation === 'artifact_readiness' ? reader.artifactReadiness(input.run_id, snapshot, input.gate, input.expected_revision_id)
20
20
  : operation === 'document' ? reader.document(selector, snapshot, input?.run_id)
21
+ : operation === 'preview' ? reader.preview(selector, snapshot, input?.run_id)
21
22
  : operation === 'freshness' ? reader.freshness(snapshot)
22
23
  : operation === 'context' ? reader.context(selector, snapshot)
23
24
  : operation === 'prepare_context' ? reader.prepareContext(input)
@@ -12,6 +12,7 @@ import { runSealState } from './run-seal.js';
12
12
  import { containedRegularFile, hasSymlinkComponent } from './contained-file.js';
13
13
  import { COOPERATIVE_ASSURANCE } from './approval-command-contract.js';
14
14
  import { withControlMutation } from './control-layout.js';
15
+ import { isDecisionContentPlaceholder } from '../control-evaluation/decision-content-placeholder.js';
15
16
 
16
17
  import { creationRunId } from './run-identity.js';
17
18
  export { creationRunId };
@@ -50,7 +51,11 @@ export function createRunCommand(root, input, { requestDigest, packageVersion, c
50
51
  created_at: new Date().toISOString(), producer: { contract: 'run-command/1', package_version: packageVersion },
51
52
  assurance: COOPERATIVE_ASSURANCE, effect: { previous_revision_id: null,
52
53
  resulting_revision_id: parsed.meta.revision_id, revision: 1, record: { title: input.command.payload.title } } });
53
- return appendRunOperation(content.replace('Describe the trustworthy outcome.', () => input.command.payload.title), receipt);
54
+ // Only a semantic title orients the Objective. A placeholder title such as the bare Run ID
55
+ // keeps the template prompt, so recording the UR projects its Goal there.
56
+ const title = input.command.payload.title;
57
+ const oriented = isDecisionContentPlaceholder(title) ? content : content.replace('Describe the trustworthy outcome.', () => title);
58
+ return appendRunOperation(oriented, receipt);
54
59
  },
55
60
  beforePublish: () => checkpoint('creation_before_publish'),
56
61
  });
@@ -1,16 +1,30 @@
1
1
  import { readControlLayout } from './control-layout.js';
2
- import { prepareRunContentState } from '../control-evaluation/run-state.js';
2
+ import { prepareRunContentState, readArtefactHeading } from '../control-evaluation/run-state.js';
3
3
  import { transitionDecisionForRunState } from '../control-evaluation/gate-policy.js';
4
+ import { evaluateGateCheck } from '../control-evaluation/gate-check.js';
5
+ import { projectCockpitAssessment } from '../control-inspect/run-classification.js';
6
+ import { projectWorkSituation } from '../control-inspect/work-situation.js';
7
+ import { runClosure } from './run-operations.js';
4
8
  import { existsSync } from "../control-read/fs.js";
5
9
  import { join } from "node:path";
6
10
  import { discoverRuns } from "./run-state-reader.js";
7
11
  import { buildRunInventory } from "./run-inventory.js";
12
+ import { parseControlState } from './run-state-parser.js';
13
+ import { runSealState, listedArtefactPaths } from './run-seal.js';
14
+ import { isSafeControlRelativePath } from './contained-file.js';
15
+
16
+ const READ_BOUNDARY_CODES = ['source_changed', 'resource_limit', 'resource_denied', 'timeout'];
17
+ const inControlScope = path => isSafeControlRelativePath(path) && path.startsWith('.agdf/control/');
18
+ // The detail reader's boundary: a run registering sources outside .agdf/control cannot be proven here.
19
+ const registersOnlyControlSources = content => listedArtefactPaths(content).every(inControlScope);
8
20
 
9
21
  // Feeds the pure inventory from the run discovery of the current read view: each run's parsed text
10
22
  // and, when the full parser rejected it, its findings. No run state is read a second time. `observe: false`
11
23
  // skips the per-run gate observation, which reads registered documents, for a caller that evaluates the
12
24
  // runs itself (the companion run view, which reads each run in its own captured snapshot).
13
- export function readRunInventory(root, { language = "en", observe = true } = {}) {
25
+ // `cockpitAssessment` evaluates active runs only, like the companion run view: terminal runs stay
26
+ // summarized history, so a long history costs no gate evaluation per run and per refresh.
27
+ export function readRunInventory(root, { language = "en", observe = true, includePresentation = false, cockpitAssessment = false } = {}) {
14
28
  if (!existsSync(join(root, '.agdf/control'))) return buildRunInventory(null, { language });
15
29
  readControlLayout(root);
16
30
  if (!existsSync(join(root, '.agdf/control/runs'))) return buildRunInventory([], { language,
@@ -20,12 +34,45 @@ export function readRunInventory(root, { language = "en", observe = true } = {})
20
34
  const store = discovered.filter((run) => run.run_id === "<repository>");
21
35
  const sources = discovered.filter(run => run.run_id !== '<repository>').map(run => {
22
36
  const source = { run_id: run.run_id, ...(typeof run.content === 'string' ? { text: run.content } : {}), findings: run.valid ? [] : run.findings };
37
+ if (includePresentation && typeof run.content === 'string') {
38
+ let name = '', seal = 'unconfirmed';
39
+ try {
40
+ const ur = parseControlState(run.content).artefacts.get('UR');
41
+ const path = String(ur?.path ?? '').replace(/^`|`$/g, '').trim();
42
+ if (inControlScope(path)) name = readArtefactHeading(root, ur).replace(/^UR:\s*/i, '').trim();
43
+ } catch { /* An unreadable title must not hide the inventory. */ }
44
+ try {
45
+ if (registersOnlyControlSources(run.content)) {
46
+ const result = runSealState(root, run.content).status;
47
+ seal = result === 'valid' ? 'valid' : result === 'unsealed' ? 'unsealed'
48
+ : ['invalid', 'content_changed', 'approvals_changed'].includes(result) ? 'invalid' : 'unconfirmed';
49
+ }
50
+ } catch { /* Unavailable evidence cannot prove a seal state. */ }
51
+ source.presentation = { name: name || null, seal };
52
+ }
23
53
  if (run.valid && observe) {
24
54
  try {
25
55
  const state = prepareRunContentState(root, run.content, run.path), decision = transitionDecisionForRunState(state);
26
56
  source.observation = { current_gate: decision.current_gate, decision: decision.status, missing: decision.missing_approval,
27
57
  next_action: decision.next_allowed_action, presentation_decision: Boolean(decision.presentation_decision?.decided) };
28
- } catch { source.findings = [{ code: 'AGDF_RUN_OBSERVATION_UNAVAILABLE' }]; }
58
+ if (cockpitAssessment && run.meta?.lifecycle === 'active') {
59
+ let report = null;
60
+ if (registersOnlyControlSources(run.content)) { try { report = evaluateGateCheck(root, { runId: run.run_id, ignoreRunIdEnv: true }); } catch (error) {
61
+ if (READ_BOUNDARY_CODES.includes(error.code)) throw error;
62
+ // Preserve the Run but do not claim a verified assessment.
63
+ } }
64
+ const assessed = report ? projectCockpitAssessment(report, run.meta?.lifecycle) : { state: 'unconfirmed' };
65
+ source.observation.work_situation = projectWorkSituation({ assessment: assessed.state,
66
+ gate: report?.current_gate, blockingReason: report?.blocking_reason, missingApproval: report?.missing_approval,
67
+ persistedGate: report ? state.current_gate : undefined, persistedNext: state.next_allowed_action, nextAllowedAction: report?.next_allowed_action,
68
+ closureOutcome: runClosure(run.content)?.outcome });
69
+ if (report) Object.assign(source.observation, { current_gate: report.current_gate, decision: report.status,
70
+ missing: report.missing_approval, next_action: report.next_allowed_action });
71
+ }
72
+ } catch (error) {
73
+ if (cockpitAssessment && READ_BOUNDARY_CODES.includes(error.code)) throw error;
74
+ source.findings = [{ code: 'AGDF_RUN_OBSERVATION_UNAVAILABLE' }];
75
+ }
29
76
  }
30
77
  return source;
31
78
  });