@aiwg/cli 2026.10.11 → 2026.10.12

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 (132) hide show
  1. package/agentic/code/providers/model-catalog.v1.json +62 -12
  2. package/dist/src/artifacts/binding-target-identity.js +51 -0
  3. package/dist/src/artifacts/checksum-manifest.js +12 -0
  4. package/dist/src/artifacts/index-builder.js +11 -13
  5. package/dist/src/artifacts/query-engine.js +5 -11
  6. package/dist/src/cli/handlers/doctor-help.js +32 -0
  7. package/dist/src/cli/handlers/gates.js +19 -18
  8. package/dist/src/cli/handlers/models-help.js +7 -1
  9. package/dist/src/cli/handlers/models.js +3 -3
  10. package/dist/src/cli/handlers/script-runner.js +8 -5
  11. package/dist/src/cli/handlers/sessions-intake.js +104 -0
  12. package/dist/src/cli/handlers/sessions-tail.js +172 -0
  13. package/dist/src/cli/handlers/sessions.js +90 -17
  14. package/dist/src/cli/handlers/skill-lint.js +10 -3
  15. package/dist/src/cli/handlers/steward-format.js +100 -0
  16. package/dist/src/cli/handlers/steward-models.js +167 -0
  17. package/dist/src/cli/handlers/steward.js +14 -248
  18. package/dist/src/cli/handlers/use-bundle-counts.js +61 -0
  19. package/dist/src/cli/handlers/use.js +6 -24
  20. package/dist/src/cli/handlers/utilities.js +1 -24
  21. package/dist/src/config/aiwg-config.js +7 -0
  22. package/dist/src/config/authorization-cli.js +89 -0
  23. package/dist/src/config/cli-help.js +1 -0
  24. package/dist/src/config/cli.js +10 -1
  25. package/dist/src/decision/context-pruning.js +3 -2
  26. package/dist/src/decision/ensemble/contract.js +15 -4
  27. package/dist/src/decision/ensemble-study/artifacts.js +5 -0
  28. package/dist/src/decision/ensemble-study/score.js +4 -3
  29. package/dist/src/decision/feature-export.js +3 -9
  30. package/dist/src/decision/heldout/collector.js +2 -2
  31. package/dist/src/decision/issue-triage/runtime.js +3 -9
  32. package/dist/src/decision/qualification/gates.js +2 -1
  33. package/dist/src/decision/qualification/release.js +2 -3
  34. package/dist/src/decision/routing/report.js +2 -0
  35. package/dist/src/decision/sdlc-screening.js +3 -5
  36. package/dist/src/decision/sensitivity/comparative.js +5 -3
  37. package/dist/src/gates/driver.js +6 -77
  38. package/dist/src/gates/evaluate.js +4 -11
  39. package/dist/src/gates/floors.js +4 -4
  40. package/dist/src/gates/index.js +3 -3
  41. package/dist/src/gates/lattice.js +26 -0
  42. package/dist/src/gates/registry.js +2 -1
  43. package/dist/src/gates/report.js +19 -7
  44. package/dist/src/issues/audit-cli.js +120 -0
  45. package/dist/src/issues/audit-contract.js +64 -0
  46. package/dist/src/issues/audit-local.js +82 -0
  47. package/dist/src/issues/audit.js +220 -0
  48. package/dist/src/lint/rule-activation.js +15 -1
  49. package/dist/src/mission-protocol/canonical-codecs.js +270 -0
  50. package/dist/src/mission-protocol/codecs.js +22 -550
  51. package/dist/src/mission-protocol/health-trace-codecs.js +281 -0
  52. package/dist/src/mission-protocol/health-trace-types.js +43 -0
  53. package/dist/src/mission-protocol/types.js +2 -42
  54. package/dist/src/mission-protocol/worker-contract-json.js +10 -0
  55. package/dist/src/mission-protocol/worker-review-gate.js +100 -0
  56. package/dist/src/mission-protocol/worker-review-types.js +2 -0
  57. package/dist/src/models/catalog-policy.js +280 -0
  58. package/dist/src/models/index.js +1 -1
  59. package/dist/src/models/model-catalog.v1.json +62 -12
  60. package/dist/src/models/policy-resources.js +28 -0
  61. package/dist/src/models/provider-policy.js +10 -546
  62. package/dist/src/models/resolver.js +26 -8
  63. package/dist/src/models/router.js +15 -2
  64. package/dist/src/models/tiers.js +13 -0
  65. package/dist/src/models/types.js +0 -6
  66. package/dist/src/models/worker-ceilings.js +59 -0
  67. package/dist/src/models/worker-invocation-policy.js +243 -0
  68. package/dist/src/models/worker-plan.js +16 -20
  69. package/dist/src/models/worker-review-lineage.js +55 -0
  70. package/dist/src/models/worker-selection-policy.js +1 -1
  71. package/dist/src/models/wrapper-route.js +6 -3
  72. package/dist/src/policy/authorization.js +1 -0
  73. package/dist/src/policy/hitl-config.js +63 -0
  74. package/dist/src/policy/hitl-receipt.js +57 -0
  75. package/dist/src/policy/hitl-types.js +4 -0
  76. package/dist/src/policy/hitl-validation.js +39 -0
  77. package/dist/src/policy/hitl.js +117 -0
  78. package/dist/src/rules/activation.d.mts +1 -1
  79. package/dist/src/rules/activation.mjs +0 -2
  80. package/dist/src/rules/gate-coverage.js +51 -0
  81. package/dist/src/rules/gate-references.js +16 -0
  82. package/dist/src/rules/rule-frontmatter.schema.json +7 -1
  83. package/dist/src/serve/worker-command-request.js +21 -4
  84. package/dist/src/serve/worker-invocation-preparation.js +3 -1
  85. package/dist/src/serve/worker-invocation.js +21 -10
  86. package/dist/src/sessions/adapters/deepseek-harness.js +1 -0
  87. package/dist/src/sessions/adapters/grok-build-native.js +269 -0
  88. package/dist/src/sessions/adapters/grok-build.js +57 -15
  89. package/dist/src/sessions/adapters/hermes-state-db.js +252 -0
  90. package/dist/src/sessions/adapters/hermes.js +42 -3
  91. package/dist/src/sessions/adapters/warp.js +1 -1
  92. package/dist/src/sessions/batch-import.js +10 -0
  93. package/dist/src/sessions/contracts.js +1 -1
  94. package/dist/src/sessions/discovery-capability.js +38 -0
  95. package/dist/src/sessions/importer.js +8 -251
  96. package/dist/src/sessions/index.js +4 -0
  97. package/dist/src/sessions/intake-dir.js +71 -0
  98. package/dist/src/sessions/normalization.js +251 -0
  99. package/dist/src/sessions/tail.js +91 -0
  100. package/dist/src/sessions/workspace-discovery.js +83 -7
  101. package/dist/src/skills/agent-skill-sidecar.js +28 -0
  102. package/dist/src/skills/agent-skills.js +4 -23
  103. package/dist/src/skills/claude-surface-projection.js +25 -0
  104. package/dist/src/skills/deployer.js +8 -288
  105. package/dist/src/skills/deployment-policy.js +114 -0
  106. package/dist/src/skills/deployment-projection.js +152 -0
  107. package/dist/src/skills/deployment-types.js +20 -0
  108. package/dist/src/skills/exporter.js +8 -31
  109. package/dist/src/skills/provider-extensions.js +41 -0
  110. package/dist/src/skills/run.js +6 -0
  111. package/dist/src/skills/types.js +0 -8
  112. package/dist/src/skills/validator-resources.js +68 -0
  113. package/dist/src/skills/validator.js +6 -55
  114. package/dist/src/storage/cli-migrate-options.js +125 -0
  115. package/dist/src/storage/cli-migrate.js +111 -0
  116. package/dist/src/storage/cli.js +4 -279
  117. package/dist/src/storage/migration-adapter.js +79 -0
  118. package/dist/src/storage/migration-copy.js +108 -0
  119. package/dist/src/storage/migration-protocol.js +14 -197
  120. package/dist/src/storage/migration-records.js +186 -0
  121. package/dist/src/storage/migration-types.js +12 -0
  122. package/dist/src/tracker/forge-http.js +2 -1
  123. package/dist/src/tracker/issue-pages.js +110 -0
  124. package/package.json +1 -1
  125. package/schemas/mission-v1.schema.json +335 -0
  126. package/schemas/models/model-catalog.v1.schema.json +19 -1
  127. package/schemas/models/model-config-compatibility.v1.schema.json +3 -2
  128. package/schemas/models/model-policy.v1.schema.json +1 -1
  129. package/tools/agents/providers/provider-paths-staging.mjs +35 -0
  130. package/tools/agents/providers/rule-binding-targets.mjs +146 -0
  131. package/tools/agents/providers/rule-delivery.mjs +7 -126
  132. package/vscode-extension/schemas/aiwg.config.v1.json +177 -0
@@ -1,3 +1,6 @@
1
+ // Mission worker payload validation, negotiation and immutable snapshots.
2
+ import { stableJson } from './worker-contract-json.js';
3
+ import { prepareWorkerAcceptance, workerReviewEvidenceErrors, assertWorkerReviewSuccessor } from './worker-review-gate.js';
1
4
  import { workerSemanticErrors, contractValidationPointer } from './worker-contract-diagnostics.js';
2
5
  import { createHash } from 'node:crypto';
3
6
  import { readFileSync } from 'node:fs';
@@ -5,273 +8,9 @@ import { dirname, join, resolve } from 'node:path';
5
8
  import { fileURLToPath } from 'node:url';
6
9
  import Ajv2020 from 'ajv/dist/2020.js';
7
10
  import addFormats from 'ajv-formats';
8
- import { HEALTH_ACTIVITY_SUMMARY_MAX_UTF8_BYTES, HEALTH_KNOWN_REASON_CODES, MISSION_API_VERSION, WORKER_BASELINE_FEATURES, WORKER_CONTRACT_REVISION, WORKER_DEFINITIONS, WORKER_DEFINITION_PRODUCERS, WORKER_KNOWN_FEATURES, WorkerContractError, } from './types.js';
9
- const TERMINAL = new Set(['completed', 'failed', 'incomplete', 'cancelled']);
10
- function object(value, label = 'Mission input') {
11
- if (!value || typeof value !== 'object' || Array.isArray(value))
12
- throw new Error(`${label} must be an object.`);
13
- return value;
14
- }
15
- function string(value) {
16
- return typeof value === 'string' && value.length ? value : undefined;
17
- }
18
- function requiredId(record) {
19
- const metadata = record.metadata && typeof record.metadata === 'object' ? record.metadata : {};
20
- const id = string(record.missionId) ?? string(record.mission_id) ?? string(record.id)
21
- ?? string(record.taskId) ?? string(record.task_id) ?? string(record.responseId) ?? string(metadata.id);
22
- if (!id)
23
- throw new Error('Mission source is missing a stable identifier.');
24
- return id;
25
- }
26
- export function normalizeMissionState(nativeState) {
27
- const state = String(nativeState ?? 'unknown').toLowerCase().replace(/_/g, '-');
28
- if (['done', 'completed', 'succeeded', 'success'].includes(state))
29
- return 'completed';
30
- if (['aborted', 'cancelled', 'canceled'].includes(state))
31
- return 'cancelled';
32
- if (['failed', 'error', 'rejected', 'timed-out'].includes(state))
33
- return 'failed';
34
- if (['incomplete', 'budget-exhausted', 'max-iterations', 'max-tokens'].includes(state))
35
- return 'incomplete';
36
- if (['running', 'in-progress', 'working', 'started', 'assigned', 'admitted', 'starting'].includes(state))
37
- return 'running';
38
- if (['pending', 'submitted', 'queued', 'runnable', 'scheduled'].includes(state))
39
- return 'pending';
40
- if (['blocked', 'blocked-hitl', 'input-required', 'auth-required', 'paused', 'suspended'].includes(state))
41
- return 'blocked';
42
- if (['operator-review', 'operator-review-required', 'manual-review'].includes(state))
43
- return 'operator-review';
44
- if (['unknown', 'disconnected', 'detached', 'unreachable'].includes(state))
45
- return 'unknown';
46
- return 'unknown';
47
- }
48
- function knownKeysFor(source) {
49
- const common = ['id', 'missionId', 'mission_id', 'taskId', 'task_id', 'responseId', 'title', 'goal', 'objective', 'prompt', 'completion', 'completionCriterion', 'completion_criterion', 'status', 'state', 'nativeState', 'createdAt', 'created_at', 'updatedAt', 'updated_at', 'metadata', 'spec', 'provenance', 'extensions', 'artifacts', 'output', 'partialOutput', 'error', 'checkpoint', 'cycles', 'activityLog', 'totalCost', 'runtimesUsed', 'apiVersion', 'api_version', 'schemaVersion', 'schema_version', 'kind', 'session_id', 'previous_response_id'];
50
- if (source === 'uhp-2026-08-11')
51
- common.push('object', 'model', 'incomplete_details', 'store', 'usage');
52
- return new Set(common);
53
- }
54
- function extensions(record, source) {
55
- const explicit = record.extensions && typeof record.extensions === 'object' ? structuredClone(record.extensions) : {};
56
- if (record.metadata && typeof record.metadata === 'object')
57
- explicit.metadata = structuredClone(record.metadata);
58
- if (record.status && typeof record.status === 'object')
59
- explicit.nativeStatus = structuredClone(record.status);
60
- for (const [key, value] of Object.entries(record))
61
- if (!knownKeysFor(source).has(key))
62
- explicit[key] = structuredClone(value);
63
- return explicit;
64
- }
65
- function artifacts(record) {
66
- const values = Array.isArray(record.artifacts) ? record.artifacts : [];
67
- return values.flatMap((item, index) => {
68
- if (!item || typeof item !== 'object')
69
- return [];
70
- const artifact = item;
71
- const id = string(artifact.id) ?? string(artifact.fileId) ?? string(artifact.file_id) ?? string(artifact.uri) ?? `artifact-${index}`;
72
- return [{
73
- id,
74
- kind: string(artifact.kind) ?? 'other',
75
- ...(string(artifact.uri) ? { uri: string(artifact.uri) } : {}),
76
- ...(string(artifact.sha256) ? { sha256: string(artifact.sha256) } : {}),
77
- ...(string(artifact.mediaType) ?? string(artifact.media_type) ? { mediaType: string(artifact.mediaType) ?? string(artifact.media_type) } : {}),
78
- extensions: structuredClone(artifact),
79
- }];
80
- });
81
- }
82
- function uhpOutputArtifacts(record) {
83
- const found = new Map();
84
- function visit(value) {
85
- if (Array.isArray(value)) {
86
- value.forEach(visit);
87
- return;
88
- }
89
- if (!value || typeof value !== 'object')
90
- return;
91
- const item = value;
92
- const id = string(item.file_id);
93
- if (id)
94
- found.set(id, { id, kind: 'uhp-file', ...(string(item.media_type) ? { mediaType: string(item.media_type) } : {}), extensions: { 'uhp.file': structuredClone(item) } });
95
- Object.values(item).forEach(visit);
96
- }
97
- visit(record.output);
98
- return [...found.values()];
99
- }
100
- function nativeStateFor(source, record, statusRecord) {
101
- const explicit = string(statusRecord.state) ?? string(record.nativeState) ?? string(record.status)
102
- ?? string(record.state) ?? string(record.observed_state) ?? string(record.observedState) ?? string(record.nodeState);
103
- if (explicit)
104
- return explicit;
105
- if (source === 'mission-ledger' && record.checkpoint && typeof record.checkpoint === 'object') {
106
- const checkpoint = record.checkpoint;
107
- if (Array.isArray(checkpoint.pending) && checkpoint.pending.length)
108
- return 'running';
109
- if (Array.isArray(checkpoint.failed) && checkpoint.failed.length)
110
- return 'failed';
111
- if (Array.isArray(checkpoint.completed) && checkpoint.completed.length)
112
- return 'completed';
113
- }
114
- return 'unknown';
115
- }
116
- function sourceVersion(source, record) {
117
- if (source === 'canonical')
118
- return string(record.apiVersion) ?? 'unknown';
119
- if (source === 'uhp-2026-08-11')
120
- return '2026-08-11';
121
- if (source === 'executor-v1')
122
- return 'executor.aiwg.io/v1';
123
- if (source === 'fleet-workload-v1')
124
- return 'fleet-workload/v1';
125
- if (source === 'graph-flow-v1')
126
- return 'graph.flow.aiwg.io/v1';
127
- if (source === 'activity-v1')
128
- return 'activity.aiwg.io/v1';
129
- return string(record.apiVersion) ?? string(record.api_version) ?? string(record.schemaVersion) ?? string(record.schema_version) ?? 'unversioned';
130
- }
131
- function assertSupported(version) {
132
- const supported = new Set(['unversioned', MISSION_API_VERSION, '2026-08-11', 'executor.aiwg.io/v1', 'fleet-workload/v1', 'graph.flow.aiwg.io/v1', 'activity.aiwg.io/v1']);
133
- if (supported.has(version))
134
- return;
135
- const major = version.match(/(?:^|\/)v(\d+)(?:$|[.-])/)?.[1];
136
- if (major && major !== '1')
137
- throw new Error(`Unsupported Mission source major version '${version}'; supported major is v1.`);
138
- throw new Error(`Unsupported Mission source version '${version}'.`);
139
- }
140
- export function validateCanonicalMission(value) {
141
- const record = object(value, 'Canonical Mission');
142
- assertMissionWorkerHandoff(record, 'canonical');
143
- if (record.apiVersion !== MISSION_API_VERSION)
144
- throw new Error(`Unsupported canonical Mission version '${String(record.apiVersion)}'.`);
145
- if (record.kind !== 'Mission')
146
- throw new Error("Canonical Mission kind must be 'Mission'.");
147
- const metadata = object(record.metadata, 'Mission metadata');
148
- const spec = object(record.spec, 'Mission spec');
149
- const status = object(record.status, 'Mission status');
150
- const provenance = object(record.provenance, 'Mission provenance');
151
- if (!string(metadata.id) || !string(spec.objective) || !string(provenance.sourceContract) || !string(provenance.sourceVersion))
152
- throw new Error('Canonical Mission is missing required identity, objective, or provenance.');
153
- const state = normalizeMissionState(status.state);
154
- if (status.state !== state)
155
- throw new Error(`Canonical Mission state '${String(status.state)}' is not normalized.`);
156
- if (status.terminal !== TERMINAL.has(state))
157
- throw new Error(`Canonical Mission terminal flag contradicts state '${state}'.`);
158
- if (!Array.isArray(status.artifacts))
159
- throw new Error('Canonical Mission artifacts must be an array.');
160
- return structuredClone(value);
161
- }
162
- export function decodeMission(input, source, workerConsumer) {
163
- const record = object(input);
164
- assertMissionWorkerHandoff(record, source, workerConsumer);
165
- if (source === 'canonical') {
166
- const value = validateCanonicalMission(record);
167
- return { value, sourceVersion: MISSION_API_VERSION, warnings: [], preservedExtensions: structuredClone(value.extensions ?? {}), lossReport: [] };
168
- }
169
- const version = sourceVersion(source, record);
170
- assertSupported(version);
171
- const statusRecord = record.status && typeof record.status === 'object' ? record.status : {};
172
- const nativeState = nativeStateFor(source, record, statusRecord);
173
- const state = normalizeMissionState(nativeState);
174
- const objective = string(record.goal) ?? string(record.objective) ?? string(record.title) ?? string(record.prompt) ?? '(legacy mission objective unavailable)';
175
- const completionCriterion = string(record.completionCriterion) ?? string(record.completion_criterion) ?? string(record.completion);
176
- const preserved = extensions(record, source);
177
- const warnings = [];
178
- const lossReport = [];
179
- if (objective.startsWith('(legacy'))
180
- warnings.push('Source did not contain a mission objective; placeholder retained.');
181
- if (state === 'unknown' && nativeState !== 'unknown')
182
- warnings.push(`Unknown native state '${nativeState}' preserved beside normalized unknown state.`);
183
- const sourceArtifacts = artifacts(record);
184
- if (source === 'uhp-2026-08-11') {
185
- const ids = new Set(sourceArtifacts.map(artifact => artifact.id));
186
- for (const artifact of uhpOutputArtifacts(record))
187
- if (!ids.has(artifact.id))
188
- sourceArtifacts.push(artifact);
189
- }
190
- const budgets = record.budgets && typeof record.budgets === 'object' ? record.budgets : undefined;
191
- const lineage = Array.isArray(record.lineage) ? record.lineage.flatMap(item => {
192
- if (!item || typeof item !== 'object')
193
- return [];
194
- const entry = item;
195
- return string(entry.relation) && string(entry.id) ? [{ relation: string(entry.relation), id: string(entry.id) }] : [];
196
- }) : undefined;
197
- const value = {
198
- apiVersion: MISSION_API_VERSION,
199
- kind: 'Mission',
200
- metadata: {
201
- id: requiredId(record),
202
- ...(string(record.createdAt) ?? string(record.created_at) ? { createdAt: string(record.createdAt) ?? string(record.created_at) } : {}),
203
- ...(string(record.updatedAt) ?? string(record.updated_at) ? { updatedAt: string(record.updatedAt) ?? string(record.updated_at) } : {}),
204
- ...(string(record.previous_response_id) ? { previousId: string(record.previous_response_id) } : {}),
205
- ...(lineage?.length ? { lineage } : {}),
206
- },
207
- spec: { objective, ...(completionCriterion ? { completionCriterion } : {}), ...(budgets ? { budgets: structuredClone(budgets) } : {}) },
208
- status: {
209
- state,
210
- terminal: TERMINAL.has(state),
211
- nativeState,
212
- artifacts: sourceArtifacts,
213
- ...(record.output !== undefined || record.partialOutput !== undefined || record.partial_output !== undefined ? { partialOutput: structuredClone(record.partialOutput ?? record.partial_output ?? record.output) } : {}),
214
- },
215
- provenance: { sourceContract: source, sourceVersion: version, ...(source === 'a2a' || source.startsWith('uhp') ? { transport: source.split('-')[0] } : {}), sourceId: requiredId(record) },
216
- ...(Object.keys(preserved).length ? { extensions: { [`aiwg.source.${source}`]: preserved } } : {}),
217
- };
218
- return { value, sourceVersion: version, warnings, preservedExtensions: preserved, lossReport };
219
- }
220
- function projectionLosses(value, target) {
221
- const losses = [];
222
- if (value.status.verification?.length)
223
- losses.push({ path: '/status/verification', reason: `${target} has no native verification ledger`, severity: 'warning' });
224
- if (value.metadata.lineage?.length && !['mission-ledger', 'fleet-workload-v1', 'canonical'].includes(target))
225
- losses.push({ path: '/metadata/lineage', reason: `${target} cannot represent full lineage`, severity: 'required' });
226
- if (value.spec.budgets && !['fleet-workload-v1', 'canonical'].includes(target))
227
- losses.push({ path: '/spec/budgets', reason: `${target} cannot represent every canonical budget`, severity: 'warning' });
228
- return losses;
229
- }
230
- export function encodeMission(value, target, options = {}) {
231
- assertMissionWorkerHandoff(value, target, options.workerConsumer);
232
- const mission = validateCanonicalMission(value);
233
- if (target === 'canonical')
234
- return { value: mission, targetVersion: MISSION_API_VERSION, warnings: [], lossReport: [] };
235
- const lossReport = projectionLosses(mission, target);
236
- const required = lossReport.filter(loss => loss.severity === 'required');
237
- if (required.length && !options.allowLoss)
238
- throw new Error(`Projection to ${target} would silently lose required semantics: ${required.map(loss => loss.path).join(', ')}.`);
239
- const base = {
240
- missionId: mission.metadata.id,
241
- goal: mission.spec.objective,
242
- completionCriterion: mission.spec.completionCriterion,
243
- status: mission.status.nativeState ?? mission.status.state,
244
- artifacts: structuredClone(mission.status.artifacts),
245
- partialOutput: structuredClone(mission.status.partialOutput),
246
- lineage: structuredClone(mission.metadata.lineage ?? []),
247
- extensions: {
248
- 'aiwg.mission.canonical': { apiVersion: mission.apiVersion, state: mission.status.state, lossReport },
249
- ...(mission.extensions ?? {}),
250
- },
251
- };
252
- let projected = base;
253
- if (target === 'mission-plan')
254
- projected = { missionId: base.missionId, goal: base.goal, completionCriterion: base.completionCriterion ?? '', cycles: [], extensions: base.extensions };
255
- else if (target === 'mission-ledger')
256
- projected = { ...base, activityLog: [], cycles: [], totalCost: 0, checkpoint: { completed: [], pending: [], failed: [] }, runtimesUsed: [] };
257
- else if (target === 'uhp-2026-08-11')
258
- projected = { id: mission.provenance.sourceId ?? mission.metadata.id, object: 'response', created_at: 0, status: base.status, model: '', output: mission.status.partialOutput ?? [], metadata: base.extensions };
259
- else if (target === 'a2a')
260
- projected = { id: mission.provenance.sourceId ?? mission.metadata.id, status: { state: base.status }, artifacts: base.artifacts, partialOutput: base.partialOutput, metadata: base.extensions };
261
- else if (target === 'graph-flow-v1')
262
- projected = { schemaVersion: 'graph.flow.aiwg.io/v1', runId: mission.metadata.id, nodeState: mission.status.state, metadata: base.extensions };
263
- else if (target === 'cockpit')
264
- projected = { id: mission.metadata.id, session_id: mission.metadata.id, source: mission.provenance.transport ?? mission.provenance.sourceContract, title: mission.spec.objective, completion: mission.spec.completionCriterion, status: mission.status.state, artifacts: base.artifacts, partialOutput: base.partialOutput, extensions: base.extensions };
265
- else if (target === 'activity-v1')
266
- projected = { event: 'mission.projected', missionId: mission.metadata.id, objective: mission.spec.objective, state: mission.status.state, metadata: base.extensions };
267
- else if (target === 'executor-v1')
268
- projected = { mission_id: mission.metadata.id, objective: mission.spec.objective, completion_criterion: mission.spec.completionCriterion, state: base.status, artifacts: base.artifacts, partial_output: base.partialOutput, metadata: base.extensions };
269
- else if (target === 'fleet-workload-v1')
270
- projected = { mission_id: mission.metadata.id, objective: mission.spec.objective, completion_criterion: mission.spec.completionCriterion, observed_state: base.status, budgets: mission.spec.budgets, artifacts: base.artifacts, partial_output: base.partialOutput, lineage: base.lineage, metadata: base.extensions };
271
- else if (target === 'mission-control-session')
272
- projected = { id: mission.metadata.id, objective: mission.spec.objective, status: mission.status.state, completion: mission.spec.completionCriterion, artifacts: base.artifacts, partialOutput: base.partialOutput, extensions: base.extensions };
273
- return { value: projected, targetVersion: target, warnings: lossReport.map(loss => `${loss.path}: ${loss.reason}`), lossReport };
274
- }
11
+ import { WORKER_BASELINE_FEATURES, WORKER_CONTRACT_REVISION, WORKER_DEFINITIONS, WORKER_DEFINITION_PRODUCERS, WORKER_KNOWN_FEATURES, WorkerContractError, } from './types.js';
12
+ export * from './canonical-codecs.js';
13
+ export * from './health-trace-codecs.js';
275
14
  let cachedWorkerValidators;
276
15
  function missionPackageRoot(start) {
277
16
  let current = resolve(start);
@@ -296,7 +35,7 @@ function loadWorkerValidators() {
296
35
  const schema = JSON.parse(readFileSync(schemaPath, 'utf8'));
297
36
  if (typeof schema.$id !== 'string')
298
37
  throw new WorkerContractError('OG_CONTRACT_INVALID', 'Worker contract owner has no schema $id.');
299
- const ajv = addFormats(new Ajv2020({ strict: false, allErrors: true }));
38
+ const ajv = addFormats(new Ajv2020({ strict: true, strictRequired: false, allErrors: true }));
300
39
  ajv.addSchema(schema);
301
40
  const validators = new Map();
302
41
  for (const definition of WORKER_DEFINITIONS)
@@ -305,14 +44,6 @@ function loadWorkerValidators() {
305
44
  }
306
45
  return cachedWorkerValidators;
307
46
  }
308
- function stableJson(value) {
309
- if (Array.isArray(value))
310
- return `[${value.map(stableJson).join(',')}]`;
311
- if (value && typeof value === 'object') {
312
- return `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${stableJson(value[key])}`).join(',')}}`;
313
- }
314
- return JSON.stringify(value) ?? 'null';
315
- }
316
47
  function workerBundleDigestFor(schema) {
317
48
  const defs = schema.$defs;
318
49
  const workerDefs = Object.fromEntries(WORKER_DEFINITIONS.filter(definition => definition in defs).map(definition => [definition, defs[definition]]));
@@ -345,6 +76,10 @@ export function validateWorkerPayload(definition, value) {
345
76
  if (health.quietSeconds >= health.stallSeconds)
346
77
  errors.push('Health quietSeconds must be less than stallSeconds');
347
78
  };
79
+ if (definition === 'workerVerification' || definition === 'workerDirectVerification')
80
+ errors.push(...workerReviewEvidenceErrors(value));
81
+ if (definition === 'workerResult' || definition === 'workerDirectResult')
82
+ errors.push(...workerReviewEvidenceErrors(value.verification));
348
83
  if (definition === 'workerEffectiveHealth')
349
84
  checkHealth(value);
350
85
  if (definition === 'workerResolvedInvocation') {
@@ -410,7 +145,14 @@ export function assembleWorkerResult(input, previous) {
410
145
  const brief = decodeWorkerProducedPayload(briefDefinition, 'conductor', input.brief);
411
146
  const execution = decodeWorkerProducedPayload('workerExecution', 'adapter', input.execution);
412
147
  const worker = input.worker === null ? null : decodeWorkerProducedPayload('workerClaims', 'worker', input.worker);
413
- const verification = decodeWorkerProducedPayload(direct ? 'workerDirectVerification' : 'workerVerification', 'verifier', input.verification);
148
+ // Shape-check before relational checks; telemetry is derived only by trusted assembly.
149
+ const verificationInput = structuredClone(input.verification);
150
+ if (verificationInput.status === 'accepted' && verificationInput.reviewRounds?.length) {
151
+ verificationInput.roundsToClean ??= verificationInput.reviewRounds.length;
152
+ verificationInput.headSha ??= input.headSha;
153
+ }
154
+ const shapedVerification = decodeWorkerProducedPayload(direct ? 'workerDirectVerification' : 'workerVerification', 'verifier', verificationInput);
155
+ const verification = prepareWorkerAcceptance(brief, execution, shapedVerification, input);
414
156
  if (stableJson(execution.requested) !== stableJson(brief.selected)) {
415
157
  throw new WorkerContractError('OG_CONTRACT_INVALID', 'Adapter requested identity differs from the bound brief.');
416
158
  }
@@ -604,6 +346,9 @@ export function encodeWorkerPayloadSnapshot(definition, value, previous) {
604
346
  if (prior[identity] !== next[identity])
605
347
  throw new WorkerContractError('OG_CONTRACT_INVALID', `Worker successor changes ${identity}.`);
606
348
  }
349
+ if (definition === 'workerResult' || definition === 'workerDirectResult') {
350
+ assertWorkerReviewSuccessor(prior.verification, next.verification);
351
+ }
607
352
  if (definition === 'workerDispatch') {
608
353
  const link = next.previousRecord;
609
354
  if (link?.digest !== previous.artifact.digest || link?.ref !== previous.artifact.ref) {
@@ -615,277 +360,4 @@ export function encodeWorkerPayloadSnapshot(definition, value, previous) {
615
360
  const digest = `sha256:${createHash('sha256').update(bytes, 'utf8').digest('hex')}`;
616
361
  return Object.freeze({ definition, bytes, artifact: Object.freeze({ ref: `artifact:${digest}`, digest }), previousRecord: previous ? Object.freeze({ ...previous.artifact }) : null });
617
362
  }
618
- // S1 health-to-lease and Mission reconciliation mapping (B-OG-54 / #2916,
619
- // 05 §8, ADR-OG-10/13). Pure functions over validated shapes: no scheduler,
620
- // store, Mission runtime or managed import. S2 owners (B-OG-12/16) wire these
621
- // verdicts into actual renew/fence/stop/settle/release runtime.
622
- const HEALTH_TERMINAL_LIKE = new Set(['completed', 'failed', 'cancelled', 'crashed', 'failed-to-start']);
623
- /**
624
- * Classify supervision evidence into a distinct shared-trace kind. Quiet,
625
- * stalled and uncertain map distinctly. Heartbeat/lease-only observation
626
- * yields activity-only, never productive work. A crashed or failed-to-start
627
- * worker without confirmed termination is terminal-unconfirmed: fail-stop is
628
- * claimed but a live descendant may survive, so it is never stop proof
629
- * (ADR-OG-10). Precondition: schema-valid workerHealthSummary.
630
- */
631
- export function classifyHealthTrace(health) {
632
- if (health.termination === 'confirmed' && HEALTH_TERMINAL_LIKE.has(health.state))
633
- return 'terminal-proof';
634
- if (HEALTH_TERMINAL_LIKE.has(health.state))
635
- return 'terminal-unconfirmed';
636
- // Confirmed termination with a non-terminal state is contradictory owner
637
- // evidence (finish() never produces it): downgrade, never productive.
638
- if (health.termination === 'confirmed')
639
- return 'uncertain';
640
- if (health.termination === 'uncertain')
641
- return 'uncertain';
642
- switch (health.state) {
643
- case 'launching':
644
- case 'started':
645
- return 'setup';
646
- case 'active':
647
- return health.lastProgressAt === null ? 'activity-only' : 'productive';
648
- case 'quiet':
649
- return 'quiet';
650
- case 'stalled':
651
- case 'hung':
652
- return 'stalled';
653
- default:
654
- return 'uncertain';
655
- }
656
- }
657
- const TRACE_TRUNCATION_MARKER = '…[truncated]';
658
- /** Strip control/format characters and cap at maxUtf8Bytes (marker included). */
659
- export function boundTraceText(text, maxUtf8Bytes) {
660
- const clean = String(text).replace(/[\u0000-\u001F\u007F-\u009F\u2028\u2029]/g, '');
661
- if (maxUtf8Bytes <= 0)
662
- return '';
663
- if (Buffer.byteLength(clean, 'utf8') <= maxUtf8Bytes)
664
- return clean;
665
- const markerBytes = Buffer.byteLength(TRACE_TRUNCATION_MARKER, 'utf8');
666
- if (maxUtf8Bytes < markerBytes) {
667
- // The budget cannot hold even the marker: return the longest
668
- // character-boundary prefix of the marker that fits, so the output
669
- // never exceeds the budget.
670
- let bytes = 0;
671
- let end = 0;
672
- for (const char of TRACE_TRUNCATION_MARKER) {
673
- const size = Buffer.byteLength(char, 'utf8');
674
- if (bytes + size > maxUtf8Bytes)
675
- break;
676
- bytes += size;
677
- end += char.length;
678
- }
679
- return TRACE_TRUNCATION_MARKER.slice(0, end);
680
- }
681
- let bytes = 0;
682
- let end = 0;
683
- for (const char of clean) {
684
- const size = Buffer.byteLength(char, 'utf8');
685
- if (bytes + size > maxUtf8Bytes - markerBytes)
686
- break;
687
- bytes += size;
688
- end += char.length;
689
- }
690
- return clean.slice(0, end) + TRACE_TRUNCATION_MARKER;
691
- }
692
- /**
693
- * Allowlisted reason label for the shared trace. Unlisted codes (including
694
- * any free text carrying secret material) project as UNCLASSIFIED#digest:
695
- * stable and bounded, never verbatim.
696
- */
697
- export function healthReasonLabel(reasonCode) {
698
- if (HEALTH_KNOWN_REASON_CODES.includes(reasonCode))
699
- return reasonCode;
700
- const digest = createHash('sha256').update(reasonCode, 'utf8').digest('hex').slice(0, 12);
701
- return `UNCLASSIFIED#${digest}`;
702
- }
703
- /**
704
- * Digest-only lifecycle reference for the effect ledger. Only enum, counter,
705
- * digest and timestamp fields enter the digest; no payload content crosses.
706
- */
707
- export function healthLifecycleDigest(health) {
708
- assertValidWorkerPayload('workerHealthSummary', health);
709
- const lifecycle = {
710
- version: health.version,
711
- state: health.state,
712
- coverage: health.coverage,
713
- handshake: health.handshake,
714
- termination: health.termination,
715
- confidence: health.confidence,
716
- observerGeneration: health.observerGeneration,
717
- transitionCount: health.transitionCount,
718
- restartCount: health.restartCount,
719
- lastObservedAt: health.lastObservedAt,
720
- lastActivityAt: health.lastActivityAt,
721
- lastProgressAt: health.lastProgressAt,
722
- };
723
- const digest = `sha256:${createHash('sha256').update(stableJson(lifecycle), 'utf8').digest('hex')}`;
724
- return { ref: `artifact:${digest}`, digest };
725
- }
726
- /**
727
- * Closed observed-state vocabulary for the previous-state channel. Free
728
- * text (including leaked bearer material) never interpolates into the
729
- * shared trace.
730
- */
731
- const HEALTH_OBSERVED_STATES = new Set([
732
- 'launching', 'started', 'active', 'quiet', 'stalled', 'hung',
733
- 'crashed', 'failed-to-start', 'completed', 'failed', 'cancelled',
734
- ]);
735
- /**
736
- * Parse an ISO timestamp into epoch milliseconds for chronological
737
- * comparison. Comparison must never fall back to string ordering: distinct
738
- * representations of the same instant (e.g. '.000Z' vs 'Z') must compare
739
- * equal, not greater/lesser. Unparseable values reject as having no
740
- * timestamp evidence (null), never throw.
741
- */
742
- function parseProgressTimestampMs(value) {
743
- if (value === null)
744
- return null;
745
- const ms = Date.parse(value);
746
- return Number.isFinite(ms) ? ms : null;
747
- }
748
- /**
749
- * Fresh-progress gate: credit requires genuinely new progress evidence.
750
- * The health shape carries no progress sequence/key, so freshness is a
751
- * NONDECREASING credit watermark: `previousWatermark` is not merely "the
752
- * last observation" but the highest-ever CREDITED lastProgressAt, compared
753
- * by parsed epoch milliseconds (never raw strings). Out-of-order or
754
- * replayed snapshots (older than, or equal to, the watermark) earn no
755
- * credit and never move the watermark backward; heartbeats (which advance
756
- * activity/observed timestamps while the watermark is frozen) never
757
- * re-credit historical progress. Unparseable timestamps earn no credit.
758
- * Callers thread the watermark forward with `advanceProgressWatermark`.
759
- */
760
- function isFreshProgressEvidence(health, previousWatermark) {
761
- const currentMs = parseProgressTimestampMs(health.lastProgressAt);
762
- if (currentMs === null)
763
- return false;
764
- if (previousWatermark === null || previousWatermark.lastProgressAt === null)
765
- return true;
766
- const watermarkMs = parseProgressTimestampMs(previousWatermark.lastProgressAt);
767
- if (watermarkMs === null)
768
- return false;
769
- return currentMs > watermarkMs;
770
- }
771
- /**
772
- * Pure watermark reducer (no store): callers thread the returned value into
773
- * the next observation's `previousHealth`/watermark argument instead of
774
- * re-passing the raw last observation. The watermark advances ONLY when the
775
- * current observation was actually credited; a non-credited observation
776
- * (replay, heartbeat, or an out-of-order/older snapshot) leaves the
777
- * watermark exactly where it was. This is what makes real A -> B -> A -> B
778
- * delivery sequences credit true,true,false,false instead of re-crediting
779
- * B the second time it arrives: a naive caller that re-threads "the last
780
- * observation" (rather than this reducer's output) regresses to that bug.
781
- */
782
- export function advanceProgressWatermark(previousWatermark, credited, health) {
783
- if (credited)
784
- return { lastProgressAt: health.lastProgressAt };
785
- return previousWatermark ?? { lastProgressAt: null };
786
- }
787
- /**
788
- * Project one health observation into the shared trace. The activity summary
789
- * is built only from enum/counter/generation fields plus the allowlisted
790
- * reason label; completionCredit is always false because Mission/Flow
791
- * completion gates stay authoritative. previousState must be a closed
792
- * observed state (or null); anything else is rejected, never interpolated.
793
- * progressCredit requires fresh progress evidence relative to
794
- * previousHealth, which callers must maintain as the nondecreasing credit
795
- * watermark (see advanceProgressWatermark) rather than the raw last
796
- * observation (null for a first observation / no watermark yet).
797
- */
798
- export function projectHealthTrace(health, previousState = null, previousHealth = null) {
799
- assertValidWorkerPayload('workerHealthSummary', health);
800
- if (previousState !== null && !HEALTH_OBSERVED_STATES.has(previousState)) {
801
- throw new WorkerContractError('OG_CONTRACT_INVALID', 'Invalid previous health state.');
802
- }
803
- const kind = classifyHealthTrace(health);
804
- const summary = `health.${health.state}:${health.termination}`
805
- + ` gen=${health.observerGeneration ?? 'none'} transitions=${health.transitionCount}`
806
- + ` prev=${previousState ?? 'none'} reason=${healthReasonLabel(health.reasonCode)}`;
807
- return {
808
- kind,
809
- progressCredit: kind === 'productive' && isFreshProgressEvidence(health, previousHealth),
810
- completionCredit: false,
811
- activitySummary: boundTraceText(summary, HEALTH_ACTIVITY_SUMMARY_MAX_UTF8_BYTES),
812
- effectRef: healthLifecycleDigest(health),
813
- };
814
- }
815
- /**
816
- * Derive stop proof from owner-observed supervision evidence (ADR-OG-10).
817
- * Only a terminal state with owner-confirmed termination is stop proof;
818
- * terminal-unconfirmed and uncertain observations stay uncertain, and the
819
- * absence of evidence is none. There is no free-form proof parameter:
820
- * callers pass the health summary (or null) and the proof is derived.
821
- * A late final after cancel-disconnect therefore never releases while
822
- * termination is uncertain (ADR-OG-13: late results stay unaccepted
823
- * evidence).
824
- */
825
- export function stopProofFromHealth(health) {
826
- if (health === null)
827
- return 'none';
828
- assertValidWorkerPayload('workerHealthSummary', health);
829
- if (health.termination === 'confirmed') {
830
- return HEALTH_TERMINAL_LIKE.has(health.state) ? 'confirmed' : 'uncertain';
831
- }
832
- if (HEALTH_TERMINAL_LIKE.has(health.state) || health.termination === 'uncertain')
833
- return 'uncertain';
834
- return 'none';
835
- }
836
- /**
837
- * The generation the stop-proof evidence actually belongs to, bridging a
838
- * missing observerGeneration to 0 (the bootstrap generation) for fencing —
839
- * mirroring the same bridge decideLeaseMapping already applies to the
840
- * caller-supplied leaseGeneration/observedGeneration fields. This is bound
841
- * to the health summary's OWN observerGeneration field, which the caller
842
- * cannot set independently of the evidence: a caller cannot claim generation
843
- * 3 while attaching generation-2 evidence and have it treated as current.
844
- */
845
- function stopProofGeneration(health) {
846
- if (health === null)
847
- return null;
848
- return health.observerGeneration ?? 0;
849
- }
850
- /**
851
- * Pure renewal/reconcile/release verdict over a mocked scheduler boundary.
852
- * Fencing binds every action to the current generation: a stale observation
853
- * denies renew, cancel, settle and accept alike. Expiry or cancel-disconnect
854
- * without derived confirmed stop proof retains the uncertain reservation
855
- * and releases nothing. Confirmed stop proof settles exactly once — and only
856
- * when that proof's OWN generation (not merely the caller-asserted
857
- * observedGeneration) matches the current leaseGeneration; a confirmed
858
- * proof carried over from a stale generation never releases.
859
- */
860
- export function decideLeaseMapping(input) {
861
- let record;
862
- try {
863
- record = object(input, 'Lease mapping input');
864
- }
865
- catch {
866
- throw new WorkerContractError('OG_CONTRACT_INVALID', 'Invalid lease mapping input.');
867
- }
868
- const leaseStates = ['admitted', 'expired', 'cancelled-disconnected'];
869
- if (typeof record.leaseState !== 'string' || !leaseStates.includes(record.leaseState)
870
- || !Number.isSafeInteger(record.leaseGeneration) || record.leaseGeneration < 0
871
- || !Number.isSafeInteger(record.observedGeneration) || record.observedGeneration < 0
872
- || typeof record.settled !== 'boolean'
873
- || !('health' in record)
874
- || (record.health !== null && !validateWorkerPayload('workerHealthSummary', record.health).valid)) {
875
- throw new WorkerContractError('OG_CONTRACT_INVALID', 'Invalid lease mapping input.');
876
- }
877
- const mapping = input;
878
- if (mapping.observedGeneration !== mapping.leaseGeneration)
879
- return 'deny-stale';
880
- if (mapping.settled)
881
- return 'deny-already-settled';
882
- const stopProof = stopProofFromHealth(mapping.health);
883
- const proofIsCurrent = stopProofGeneration(mapping.health) === mapping.leaseGeneration;
884
- if (stopProof === 'confirmed' && proofIsCurrent)
885
- return 'release-once';
886
- if (stopProof === 'uncertain' || (stopProof === 'confirmed' && !proofIsCurrent) || mapping.leaseState !== 'admitted') {
887
- return 'retain-uncertain';
888
- }
889
- return 'renew';
890
- }
891
363
  //# sourceMappingURL=codecs.js.map