@aiwg/cli 2026.9.24 → 2026.10.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 (207) hide show
  1. package/agentic/code/providers/capability-matrix.yaml +2 -1
  2. package/dist/src/artifacts/discovery-shadow-evaluator.js +177 -0
  3. package/dist/src/artifacts/discovery-shadow.js +195 -0
  4. package/dist/src/artifacts/index-builder.js +47 -9
  5. package/dist/src/artifacts/query-engine.js +94 -1
  6. package/dist/src/artifacts/types.js +1 -0
  7. package/dist/src/cli/handlers/decision.js +88 -0
  8. package/dist/src/cli/handlers/gates.js +154 -0
  9. package/dist/src/cli/handlers/index.js +5 -1
  10. package/dist/src/cli/handlers/sessions.js +10 -7
  11. package/dist/src/cli/handlers/utilities.js +20 -0
  12. package/dist/src/config/aiwg-config.js +15 -2
  13. package/dist/src/decision/acceptance.js +25 -0
  14. package/dist/src/decision/adapters/jev.js +24 -2
  15. package/dist/src/decision/calibration/registry.js +9 -0
  16. package/dist/src/decision/compose.js +74 -15
  17. package/dist/src/decision/context-live-promotion.js +344 -0
  18. package/dist/src/decision/context-live-qualification.js +591 -0
  19. package/dist/src/decision/context-pruning.js +847 -0
  20. package/dist/src/decision/driver.js +430 -0
  21. package/dist/src/decision/egress-live-qualification.js +874 -0
  22. package/dist/src/decision/ensemble/contract.js +64 -1
  23. package/dist/src/decision/ensemble/index.js +1 -0
  24. package/dist/src/decision/ensemble/report.js +8 -42
  25. package/dist/src/decision/ensemble/runtime.js +571 -0
  26. package/dist/src/decision/ensemble-study/artifacts.js +85 -0
  27. package/dist/src/decision/ensemble-study/calibration.js +443 -0
  28. package/dist/src/decision/ensemble-study/corpus.js +229 -0
  29. package/dist/src/decision/ensemble-study/multifact-artifacts.js +23 -0
  30. package/dist/src/decision/ensemble-study/multifact-study.js +596 -0
  31. package/dist/src/decision/ensemble-study/multifact.js +241 -0
  32. package/dist/src/decision/ensemble-study/protocol.js +77 -0
  33. package/dist/src/decision/ensemble-study/score.js +462 -0
  34. package/dist/src/decision/ensemble-study/staged.js +87 -0
  35. package/dist/src/decision/ensemble-study/statistics.js +84 -0
  36. package/dist/src/decision/evaluate.js +227 -14
  37. package/dist/src/decision/export.js +4 -0
  38. package/dist/src/decision/feature-export.js +1074 -0
  39. package/dist/src/decision/graph-index.js +16 -0
  40. package/dist/src/decision/graph-live-qualification.js +1041 -0
  41. package/dist/src/decision/graph-live-workload.js +245 -0
  42. package/dist/src/decision/graph-skill-bridge.js +21 -3
  43. package/dist/src/decision/heldout/calibration.js +140 -0
  44. package/dist/src/decision/heldout/collector.js +430 -0
  45. package/dist/src/decision/heldout/contract.js +309 -0
  46. package/dist/src/decision/heldout/d29-generator-ids.js +12 -0
  47. package/dist/src/decision/heldout/d29-passage-baseline-v2.js +93 -0
  48. package/dist/src/decision/heldout/d29-passage-baseline-v3.js +100 -0
  49. package/dist/src/decision/heldout/d29-passage-baseline.js +66 -0
  50. package/dist/src/decision/heldout/d29-pools-v8.js +575 -0
  51. package/dist/src/decision/heldout/d29-pools.js +288 -0
  52. package/dist/src/decision/heldout/d29-v6.js +169 -0
  53. package/dist/src/decision/heldout/d29-v7.js +202 -0
  54. package/dist/src/decision/heldout/d29-v8.js +586 -0
  55. package/dist/src/decision/heldout/generator-registry.js +67 -0
  56. package/dist/src/decision/heldout/generators.js +696 -0
  57. package/dist/src/decision/heldout/index.js +8 -0
  58. package/dist/src/decision/heldout/journal.js +346 -0
  59. package/dist/src/decision/heldout/study-generators.js +64 -0
  60. package/dist/src/decision/heldout/types.js +2 -0
  61. package/dist/src/decision/host-policy.js +61 -0
  62. package/dist/src/decision/index.js +11 -0
  63. package/dist/src/decision/interop-profile.v1.json +112 -0
  64. package/dist/src/decision/interop.js +653 -0
  65. package/dist/src/decision/issue-triage/index.js +3 -0
  66. package/dist/src/decision/issue-triage/runtime.js +1069 -0
  67. package/dist/src/decision/issue-triage/types.js +2 -0
  68. package/dist/src/decision/patterns/catalog.js +28 -8
  69. package/dist/src/decision/patterns/playground.js +28 -11
  70. package/dist/src/decision/patterns/specs.js +19 -10
  71. package/dist/src/decision/preprocessed-evidence.js +724 -0
  72. package/dist/src/decision/qualification/capture.js +8 -2
  73. package/dist/src/decision/qualification/gate-evidence.js +19 -4
  74. package/dist/src/decision/qualification/quality.js +57 -49
  75. package/dist/src/decision/qualification/release.js +76 -3
  76. package/dist/src/decision/qualification/runner.js +5 -2
  77. package/dist/src/decision/routing/contract.js +175 -0
  78. package/dist/src/decision/routing/index.js +5 -0
  79. package/dist/src/decision/routing/report.js +216 -0
  80. package/dist/src/decision/routing/runtime.js +685 -0
  81. package/dist/src/decision/routing/types.js +2 -0
  82. package/dist/src/decision/sdlc-screening.js +1157 -0
  83. package/dist/src/decision/sensitivity/comparative.js +154 -0
  84. package/dist/src/decision/sensitivity/contract.js +218 -0
  85. package/dist/src/decision/sensitivity/index.js +5 -0
  86. package/dist/src/decision/sensitivity/runtime.js +912 -0
  87. package/dist/src/decision/sensitivity/types.js +2 -0
  88. package/dist/src/decision/validate.js +26 -0
  89. package/dist/src/extensions/commands/definitions.js +38 -0
  90. package/dist/src/extensions/manifest.js +44 -0
  91. package/dist/src/gates/discovery.js +315 -0
  92. package/dist/src/gates/driver.js +388 -0
  93. package/dist/src/gates/evaluate.js +719 -0
  94. package/dist/src/gates/floors.js +255 -0
  95. package/dist/src/gates/index.js +17 -0
  96. package/dist/src/gates/providers/evidence.js +38 -0
  97. package/dist/src/gates/providers/index.js +21 -0
  98. package/dist/src/gates/providers/isolated-child-source.js +446 -0
  99. package/dist/src/gates/providers/loader.js +1069 -0
  100. package/dist/src/gates/providers/paired.js +52 -0
  101. package/dist/src/gates/providers/proportion.js +60 -0
  102. package/dist/src/gates/providers/registry.js +40 -0
  103. package/dist/src/gates/providers/scalar.js +41 -0
  104. package/dist/src/gates/providers/screening.js +53 -0
  105. package/dist/src/gates/providers/types.js +25 -0
  106. package/dist/src/gates/registry.js +1003 -0
  107. package/dist/src/gates/report.js +102 -0
  108. package/dist/src/gates/schema.js +56 -0
  109. package/dist/src/gates/stats/binomial.js +26 -0
  110. package/dist/src/gates/stats/bootstrap.js +62 -0
  111. package/dist/src/gates/stats/clopper-pearson.js +95 -0
  112. package/dist/src/gates/stats/digest.js +26 -0
  113. package/dist/src/gates/stats/error.js +5 -0
  114. package/dist/src/gates/stats/index.js +9 -0
  115. package/dist/src/gates/stats/normal.js +35 -0
  116. package/dist/src/gates/stats/paired.js +120 -0
  117. package/dist/src/gates/stats/splits.js +30 -0
  118. package/dist/src/gates/types.js +8 -0
  119. package/dist/src/governance/redaction.js +14 -4
  120. package/dist/src/mcp/muse-native-config.mjs +123 -0
  121. package/dist/src/mcp/tools/decision.mjs +188 -0
  122. package/dist/src/mcp/tools/subsystems.mjs +2 -0
  123. package/dist/src/providers/capability-matrix.yaml +2 -1
  124. package/dist/src/providers/provider-definitions.js +2 -1
  125. package/dist/src/sessions/adapters/muse.js +336 -39
  126. package/dist/src/sessions/batch-import.js +7 -0
  127. package/dist/src/sessions/workspace-discovery.js +13 -2
  128. package/package.json +1 -1
  129. package/schemas/decision/ConformalFrozenOpenData.v2.schema.json +371 -0
  130. package/schemas/decision/ConformalOpenDataReport.v2.schema.json +421 -0
  131. package/schemas/decision/ConformalPreregistration.v2.schema.json +529 -0
  132. package/schemas/decision/ContextPruningCandidate.v1.schema.json +47 -0
  133. package/schemas/decision/ContextPruningEvaluationReport.v1.schema.json +758 -0
  134. package/schemas/decision/ContextPruningPreregistration.v1.schema.json +175 -0
  135. package/schemas/decision/ContextPruningReceipt.v1.schema.json +72 -0
  136. package/schemas/decision/D17Multifact.v1.schema.json +1851 -0
  137. package/schemas/decision/D17StudyAnalysis.v1.schema.json +42 -0
  138. package/schemas/decision/D17StudyAnalysis.v2.schema.json +51 -0
  139. package/schemas/decision/D17StudyCalibration.v1.schema.json +819 -0
  140. package/schemas/decision/D17StudyDryRun.v1.schema.json +169 -0
  141. package/schemas/decision/D17StudyDryRun.v2.schema.json +239 -0
  142. package/schemas/decision/D17StudyGold.v1.schema.json +108 -0
  143. package/schemas/decision/D17StudyGuide.v1.schema.json +63 -0
  144. package/schemas/decision/D17StudyNativeTemplates.v1.schema.json +619 -0
  145. package/schemas/decision/D17StudyProtocol.v1.schema.json +241 -0
  146. package/schemas/decision/D17StudyReport.v1.schema.json +902 -0
  147. package/schemas/decision/D17StudyReport.v2.schema.json +1319 -0
  148. package/schemas/decision/D17StudyReview.v1.schema.json +175 -0
  149. package/schemas/decision/D17StudySplits.v1.schema.json +217 -0
  150. package/schemas/decision/D29Study.v1.schema.json +1903 -0
  151. package/schemas/decision/D29Study.v2.schema.json +1344 -0
  152. package/schemas/decision/D29Study.v3.schema.json +609 -0
  153. package/schemas/decision/D29Study.v4.schema.json +1133 -0
  154. package/schemas/decision/D29Study.v6.schema.json +1205 -0
  155. package/schemas/decision/D29Study.v7.schema.json +1471 -0
  156. package/schemas/decision/D29Study.v8.schema.json +671 -0
  157. package/schemas/decision/D29Study.v9.schema.json +778 -0
  158. package/schemas/decision/DecisionComparativeReplayAuditSample.v1.schema.json +137 -0
  159. package/schemas/decision/DecisionComparativeReplayCorpus.v1.schema.json +258 -0
  160. package/schemas/decision/DecisionComparativeReplayFreeze.v1.schema.json +74 -0
  161. package/schemas/decision/DecisionComparativeReplayGold.v1.schema.json +50 -0
  162. package/schemas/decision/DecisionComparativeReplayGoldAsset.v1.schema.json +37 -0
  163. package/schemas/decision/DecisionComparativeReplayIntegrity.v1.schema.json +81 -0
  164. package/schemas/decision/DecisionComparativeReplayMembers.v1.schema.json +52 -0
  165. package/schemas/decision/DecisionComparativeReplayPreregistration.v1.schema.json +113 -0
  166. package/schemas/decision/DecisionComparativeReplayReport.v1.schema.json +407 -0
  167. package/schemas/decision/DecisionComparativeReplayReviewPackets.v1.schema.json +260 -0
  168. package/schemas/decision/DecisionComparativeReplaySplit.v1.schema.json +84 -0
  169. package/schemas/decision/DecisionFeatureObservation.v1.schema.json +564 -0
  170. package/schemas/decision/DecisionFeatureSet.v1.schema.json +155 -0
  171. package/schemas/decision/DecisionInteropMapping.v1.schema.json +85 -0
  172. package/schemas/decision/DecisionIssueTriageEvaluationManifest.v1.schema.json +174 -0
  173. package/schemas/decision/DecisionIssueTriageEvaluationReport.v1.schema.json +280 -0
  174. package/schemas/decision/DecisionIssueTriagePilotPack.v1.schema.json +114 -0
  175. package/schemas/decision/DecisionRouteReceipt.v1.schema.json +155 -0
  176. package/schemas/decision/DecisionRoutingPolicy.v1.schema.json +132 -0
  177. package/schemas/decision/DecisionRoutingShadowReport.v1.schema.json +170 -0
  178. package/schemas/decision/DecisionRoutingTask.v1.schema.json +49 -0
  179. package/schemas/decision/DecisionRuleset.schema.json +8 -2
  180. package/schemas/decision/DecisionRuleset.v1alpha2.schema.json +8 -2
  181. package/schemas/decision/DecisionSensitivityPlan.v1.schema.json +191 -0
  182. package/schemas/decision/DecisionSensitivityReport.v1.schema.json +163 -0
  183. package/schemas/decision/HeldoutApproval.v1.schema.json +312 -0
  184. package/schemas/decision/HeldoutAttempt.v1.schema.json +211 -0
  185. package/schemas/decision/HeldoutBaseline.v1.schema.json +90 -0
  186. package/schemas/decision/HeldoutCalibrationPhase.v1.schema.json +87 -0
  187. package/schemas/decision/HeldoutCorpus.v1.schema.json +167 -0
  188. package/schemas/decision/HeldoutEvent.v1.schema.json +41 -0
  189. package/schemas/decision/HeldoutFrozen.v1.schema.json +142 -0
  190. package/schemas/decision/HeldoutPreregistration.v1.schema.json +222 -0
  191. package/schemas/decision/HeldoutSpendEvent.v1.schema.json +122 -0
  192. package/schemas/decision/HeldoutSpendHead.v1.schema.json +25 -0
  193. package/schemas/decision/HeldoutSpendHead.v2.schema.json +46 -0
  194. package/schemas/decision/HeldoutSummary.v1.schema.json +101 -0
  195. package/schemas/decision/OpaDecisionLogExport.v1.schema.json +65 -0
  196. package/schemas/decision/PreprocessedEvidence.v1.schema.json +306 -0
  197. package/schemas/decision/RulesetResult.v1alpha2.schema.json +175 -0
  198. package/schemas/decision/SdlcEvidenceScreening.v1.schema.json +499 -0
  199. package/schemas/decision/SdlcScreeningPreregistration.v1.schema.json +132 -0
  200. package/schemas/decision/SdlcScreeningPreregistration.v2.schema.json +138 -0
  201. package/schemas/decision/SdlcScreeningRelease.v1.schema.json +654 -0
  202. package/schemas/decision/SdlcScreeningRelease.v2.schema.json +654 -0
  203. package/tools/agents/deploy-agents.mjs +1 -1
  204. package/tools/agents/providers/base.mjs +26 -7
  205. package/tools/agents/providers/factory.mjs +10 -2
  206. package/tools/agents/providers/muse-hooks.mjs +1 -29
  207. package/tools/agents/providers/muse.mjs +6 -3
@@ -0,0 +1,724 @@
1
+ import { canonicalJson, sha256 } from '../security/artifact-trust.js';
2
+ import { admitEntry } from './entry.js';
3
+ import { artifactDigest, preprocessedEvidenceSchemaErrors, preprocessingLineageSchemaErrors, resolveJsonPointer, } from './validate.js';
4
+ export const PREPROCESSED_EVIDENCE_KIND = 'PreprocessedEvidence';
5
+ /**
6
+ * Destination origin for an adapter that declares `egress: { mode: 'none' }` and runs with no
7
+ * D10 projection policy. Manifests authorize local derived-text use by listing
8
+ * `{ provider: <adapter id>, origin: PREPROCESSING_LOCAL_ORIGIN }`.
9
+ */
10
+ export const PREPROCESSING_LOCAL_ORIGIN = 'local://no-egress';
11
+ const EXTRACTION_KINDS = ['ocr', 'asr', 'caption', 'image-description'];
12
+ const STEP_KINDS = [...EXTRACTION_KINDS, 'normalization', 'redaction', 'translation', 'truncation', 'summary', 'concatenation'];
13
+ const TRANSFORMATION_KINDS = ['normalization', 'redaction', 'translation', 'truncation', 'human-correction', 'summary', 'concatenation'];
14
+ /** Quality flags that block automatic use. `human-corrected` alone does not. */
15
+ const REVIEW_FLAGS = ['low-quality', 'truncated', 'incomplete', 'stale', 'policy-limited'];
16
+ export class PreprocessedEvidenceError extends Error {
17
+ reason;
18
+ constructor(reason, message) {
19
+ super(message);
20
+ this.reason = reason;
21
+ this.name = 'PreprocessedEvidenceError';
22
+ }
23
+ }
24
+ export function preprocessedEvidencePin(manifest) {
25
+ return { id: manifest.metadata.id, version: manifest.metadata.version, digest: artifactDigest(manifest) };
26
+ }
27
+ export function preprocessedEvidenceContentDigest(value) {
28
+ return `sha256:${sha256(typeof value === 'string' ? Buffer.from(value, 'utf8') : value)}`;
29
+ }
30
+ /** True when a lineage carries no references and no traces: it is treated exactly as absent. */
31
+ export function isEmptyPreprocessingLineage(lineage) {
32
+ if (lineage === undefined)
33
+ return true;
34
+ const record = lineage;
35
+ return isRecord(record) && Array.isArray(record.references) && record.references.length === 0
36
+ && Array.isArray(record.traces) && record.traces.length === 0;
37
+ }
38
+ /**
39
+ * A host-supplied lineage the evaluator can gate: it matches the closed result schema, has at
40
+ * least one reference, and carries no pre-filled evaluator verdict.
41
+ */
42
+ export function isWellFormedPreprocessingLineage(lineage) {
43
+ return preprocessingLineageSchemaErrors(lineage) === null
44
+ && lineage.dispatchGate === undefined
45
+ && lineage.references.length > 0;
46
+ }
47
+ export function checkPreprocessedEvidenceReference(reference, manifest) {
48
+ assertPreprocessedEvidence(manifest);
49
+ assertChain(manifest);
50
+ const current = referenceFor(manifest, preprocessedEvidencePin(manifest), selectedSegments(manifest), reference.destination);
51
+ const reasons = [];
52
+ if (canonicalJson(reference.evidence) !== canonicalJson(current.evidence))
53
+ reasons.push('manifest-pin');
54
+ if (reference.sourceAssetId !== current.sourceAssetId || reference.sourceDigest !== current.sourceDigest)
55
+ reasons.push('source');
56
+ if (reference.outputId !== current.outputId || reference.outputVersion !== current.outputVersion
57
+ || reference.outputDigest !== current.outputDigest)
58
+ reasons.push('output');
59
+ if (canonicalJson(reference.selectedSegments) !== canonicalJson(current.selectedSegments))
60
+ reasons.push('segments');
61
+ if (canonicalJson(reference.quality) !== canonicalJson(current.quality))
62
+ reasons.push('quality');
63
+ if (canonicalJson(reference.policy) !== canonicalJson(current.policy))
64
+ reasons.push('policy');
65
+ return { status: reasons.length ? 'stale' : 'current', reasons };
66
+ }
67
+ export function resolvePreprocessedEvidence(manifests, options) {
68
+ if (!manifests.length) {
69
+ return { status: 'ready', reasons: [], state: { text: '', lineage: [] },
70
+ receiptEvidence: { schemaVersion: 'decision-preprocessing-lineage/v1', status: 'ready', references: [], traces: [] } };
71
+ }
72
+ const resolved = manifests.map(manifest => resolveOne(manifest, options));
73
+ const reasons = [...new Set(resolved.flatMap(item => item.reasons))];
74
+ const status = reasons.length ? 'review' : 'ready';
75
+ const text = resolved.map(item => item.text).join(MANIFEST_TEXT_SEPARATOR);
76
+ return {
77
+ status,
78
+ reasons,
79
+ state: {
80
+ text,
81
+ lineage: resolved.map(item => item.reference),
82
+ },
83
+ receiptEvidence: {
84
+ schemaVersion: 'decision-preprocessing-lineage/v1',
85
+ status,
86
+ references: resolved.map(item => item.reference),
87
+ traces: resolved.map(item => item.trace),
88
+ },
89
+ };
90
+ }
91
+ function resolveOne(manifest, options) {
92
+ assertPreprocessedEvidence(manifest);
93
+ const pin = preprocessedEvidencePin(manifest);
94
+ const reasons = [];
95
+ const locatorClasses = new Set(options.allowedLocatorClasses ?? ['opaque-id', 'artifact-ref', 'local-redacted']);
96
+ assertLocatorAuthorized(manifest.spec.source.locator, locatorClasses, 'source');
97
+ if (manifest.spec.output.reference)
98
+ assertLocatorAuthorized(manifest.spec.output.reference, locatorClasses, 'output');
99
+ if (options.sourceBytes && preprocessedEvidenceContentDigest(options.sourceBytes) !== manifest.spec.source.contentDigest) {
100
+ throw new PreprocessedEvidenceError('digest-mismatch', 'source content digest mismatch');
101
+ }
102
+ assertChain(manifest);
103
+ if (options.requireRawEgress
104
+ && !egressAllowed(manifest.spec.policy.rawEgress, options.destination)) {
105
+ reasons.push('raw-egress-denied');
106
+ }
107
+ if (!egressAllowed(manifest.spec.policy.derivedEgress, options.destination)) {
108
+ reasons.push('derived-egress-denied');
109
+ }
110
+ const minQuality = options.minQualityScore ?? 0;
111
+ if (manifest.spec.quality.score < minQuality)
112
+ reasons.push('low-quality');
113
+ reasons.push(...qualityFlagReasons(manifest.spec.quality.flags));
114
+ if (manifest.spec.policy.trust !== 'verified')
115
+ reasons.push('untrusted');
116
+ if (options.maxAgeMs !== undefined) {
117
+ const acquired = Date.parse(manifest.spec.source.acquiredAt);
118
+ const now = options.now?.() ?? Date.now();
119
+ if (!Number.isFinite(acquired) || now - acquired > options.maxAgeMs)
120
+ reasons.push('stale');
121
+ }
122
+ const selected = selectedSegments(manifest);
123
+ if (selected.some(item => item.reference.outputVersion !== manifest.spec.output.version
124
+ || item.reference.outputDigest !== manifest.spec.output.contentDigest)) {
125
+ reasons.push('stale');
126
+ }
127
+ const lifecycle = options.lifecycle ? preprocessingLifecycleReasons(manifest, options.lifecycle) : [];
128
+ reasons.push(...lifecycle);
129
+ const uniqueReasons = [...new Set(reasons)];
130
+ const reference = referenceFor(manifest, pin, selected, options.destination);
131
+ const releasable = reference.policy.derivedEgressAllowed && !lifecycle.includes('lifecycle-unavailable');
132
+ return {
133
+ text: releasable ? verifiedSelectedText(manifest) : '',
134
+ reference,
135
+ trace: {
136
+ evidence: pin,
137
+ stepIds: manifest.spec.preprocessing.map(step => step.id),
138
+ stepCount: manifest.spec.preprocessing.length,
139
+ noOpStepIds: manifest.spec.preprocessing.filter(step => step.noOp === true).map(step => step.id),
140
+ sourceDigest: manifest.spec.source.contentDigest,
141
+ outputDigest: manifest.spec.output.contentDigest,
142
+ selectedSegmentCount: selected.length,
143
+ durationMs: manifest.spec.preprocessing.reduce((sum, step) => sum + Math.max(0, Date.parse(step.endedAt) - Date.parse(step.startedAt)), 0),
144
+ qualityFlags: [...manifest.spec.quality.flags],
145
+ policyOutcome: uniqueReasons.length ? 'review' : 'allowed',
146
+ reasons: uniqueReasons,
147
+ },
148
+ reasons: uniqueReasons,
149
+ };
150
+ }
151
+ const MANIFEST_TEXT_SEPARATOR = '\n\n';
152
+ /** Selected segment text cut from the digest-verified output by byte range, in source order. */
153
+ function verifiedSelectedText(manifest) {
154
+ const output = Buffer.from(manifest.spec.output.value, 'utf8');
155
+ return selectedSegments(manifest).map(item => output.subarray(item.segment.outputRange.start, item.segment.outputRange.end)
156
+ .toString('utf8')).join('\n');
157
+ }
158
+ function qualityFlagReasons(flags) {
159
+ return REVIEW_FLAGS.filter(flag => flags.includes(flag));
160
+ }
161
+ /**
162
+ * D10 lifecycle records for one manifest. Hosts register these as `links()` of the owning subject,
163
+ * so cascading erasure tombstones the source, every transformation, the output and the manifest.
164
+ * Identical sources or transformations in other manifests map to the same opaque IDs, so one
165
+ * tombstone invalidates every dependent manifest and reference.
166
+ */
167
+ export function preprocessingLifecycleReferences(manifest) {
168
+ assertPreprocessedEvidence(manifest);
169
+ const opaque = (role, value) => ({
170
+ surface: 'preprocessing-lineage', opaqueId: `preprocessing:${role}:${digestJson(value).slice('sha256:'.length)}`,
171
+ });
172
+ const pin = preprocessedEvidencePin(manifest);
173
+ return [
174
+ { role: 'manifest', reference: opaque('manifest', pin) },
175
+ { role: 'source', reference: opaque('source', { assetId: manifest.spec.source.assetId, digest: manifest.spec.source.contentDigest }) },
176
+ ...manifest.spec.preprocessing.map(step => ({ role: 'transformation', reference: opaque('transformation', {
177
+ kind: step.kind, tool: step.tool, model: step.model ?? null, configurationDigest: step.configurationDigest,
178
+ inputDigest: step.inputDigest, outputDigest: step.outputDigest,
179
+ }) })),
180
+ ...manifest.spec.transformations.map(event => ({ role: 'transformation', reference: opaque('transformation', {
181
+ kind: event.kind, inputDigest: event.inputDigest, outputDigest: event.outputDigest, previousOutput: event.previousOutput ?? null,
182
+ }) })),
183
+ { role: 'output', reference: opaque('output', { id: manifest.spec.output.id, version: manifest.spec.output.version,
184
+ digest: manifest.spec.output.contentDigest }) },
185
+ ];
186
+ }
187
+ /** Lifecycle reasons that stop automatic use of a manifest's derived text. */
188
+ export function preprocessingLifecycleReasons(manifest, state) {
189
+ const references = preprocessingLifecycleReferences(manifest).map(item => item.reference);
190
+ const reasons = [];
191
+ if (!isRecord(state) || !Array.isArray(state.tombstones) || !Array.isArray(state.holds) || !nonEmpty(state.subject)
192
+ || !Number.isSafeInteger(state.now)) {
193
+ return ['lifecycle-unavailable'];
194
+ }
195
+ if (state.tombstones.some(tombstone => references.some(reference => tombstone?.reference?.surface === reference.surface && tombstone.reference.opaqueId === reference.opaqueId))) {
196
+ reasons.push('lifecycle-unavailable');
197
+ }
198
+ if (state.holds.some(hold => hold?.subject === state.subject && hold.expiresAt > state.now
199
+ && Array.isArray(hold.scope) && hold.scope.includes('preprocessing-lineage'))) {
200
+ reasons.push('legal-hold');
201
+ }
202
+ return reasons;
203
+ }
204
+ /**
205
+ * Pre-dispatch gate for a stored lineage. Runs before credential resolution and transport.
206
+ * `destinations` are the D10 provider/origin of every target the evaluator could dispatch to
207
+ * (`null` when a destination cannot be bound); `input` is the decision input the evaluator
208
+ * projects and dispatches; `dispatchPointers` are the ruleset evaluation input pointers.
209
+ * Stored trace status, reasons and digests are never trusted to allow dispatch.
210
+ */
211
+ export function gatePreprocessedEvidenceDispatch(lineage, verification, destinations, input, dispatchPointers) {
212
+ // The host expects lineage (it supplied verification) but none arrived: never fall back to text-native.
213
+ if (isEmptyPreprocessingLineage(lineage))
214
+ return { outcome: 'review', reasons: ['lineage-missing'] };
215
+ if (!isWellFormedPreprocessingLineage(lineage))
216
+ return { outcome: 'refused', reasons: ['malformed-lineage'] };
217
+ const refused = new Set();
218
+ for (const destination of destinations) {
219
+ if (!destination) {
220
+ refused.add('destination-unbound');
221
+ continue;
222
+ }
223
+ for (const reference of lineage.references) {
224
+ if (!sameDestination(reference.destination, destination))
225
+ refused.add('destination-mismatch');
226
+ else if (!reference.policy.derivedEgressAllowed)
227
+ refused.add('derived-egress-denied');
228
+ }
229
+ }
230
+ if (refused.size)
231
+ return { outcome: 'refused', reasons: [...refused] };
232
+ // Stored evidence can only add review reasons.
233
+ const review = new Set();
234
+ for (const trace of lineage.traces) {
235
+ trace.reasons.forEach(reason => review.add(reason));
236
+ qualityFlagReasons(trace.qualityFlags).forEach(reason => review.add(reason));
237
+ }
238
+ for (const reference of lineage.references) {
239
+ qualityFlagReasons(reference.quality.flags).forEach(reason => review.add(reason));
240
+ if (reference.policy.trust !== 'verified')
241
+ review.add('untrusted');
242
+ }
243
+ if (lineage.status !== 'ready' || lineage.traces.some(trace => trace.policyOutcome !== 'allowed')) {
244
+ if (!review.size)
245
+ review.add('incomplete');
246
+ }
247
+ if (lineage.traces.length !== lineage.references.length || lineage.references.some((reference, index) => canonicalJson(lineage.traces[index].evidence) !== canonicalJson(reference.evidence)))
248
+ review.add('incomplete');
249
+ if (!isRecord(verification) || !Array.isArray(verification.manifests)) {
250
+ review.add('unverified');
251
+ return { outcome: 'review', reasons: [...review] };
252
+ }
253
+ const thresholds = validThresholds(verification);
254
+ if (!thresholds)
255
+ review.add('unverified');
256
+ const lifecycle = validLifecycle(verification.lifecycle) ? verification.lifecycle : null;
257
+ if (!lifecycle)
258
+ review.add('lifecycle-unavailable');
259
+ // Re-derive every reason from the current, host-stored manifests.
260
+ const current = new Map();
261
+ let allCurrent = true;
262
+ for (const reference of lineage.references) {
263
+ const matches = verification.manifests.filter(manifest => manifest?.metadata?.id === reference.evidence.id);
264
+ if (matches.length !== 1) {
265
+ review.add('unavailable');
266
+ allCurrent = false;
267
+ continue;
268
+ }
269
+ try {
270
+ if (checkPreprocessedEvidenceReference(reference, matches[0]).status !== 'current') {
271
+ review.add('stale');
272
+ allCurrent = false;
273
+ continue;
274
+ }
275
+ resolveOne(matches[0], { destination: reference.destination,
276
+ ...(thresholds ?? {}), ...(lifecycle ? { lifecycle } : {}) }).reasons.forEach(reason => review.add(reason));
277
+ current.set(reference.evidence.id, matches[0]);
278
+ }
279
+ catch {
280
+ review.add('stale');
281
+ allCurrent = false;
282
+ }
283
+ }
284
+ // The dispatched text must be exactly the verified text at host-bound pointers.
285
+ if (allCurrent) {
286
+ const binding = inputBindingReasons(verification, current, input, dispatchPointers);
287
+ if (binding === null)
288
+ review.add('unverified');
289
+ else if (binding.length)
290
+ return { outcome: 'refused', reasons: binding };
291
+ }
292
+ return review.size ? { outcome: 'review', reasons: [...review] } : { outcome: 'allowed', reasons: [] };
293
+ }
294
+ function validThresholds(verification) {
295
+ const { minQualityScore, maxAgeMs, now } = verification;
296
+ if (typeof minQualityScore !== 'number' || !Number.isFinite(minQualityScore) || minQualityScore < 0 || minQualityScore > 1
297
+ || typeof maxAgeMs !== 'number' || !Number.isFinite(maxAgeMs) || maxAgeMs < 0
298
+ || (now !== undefined && typeof now !== 'function'))
299
+ return null;
300
+ const clock = now ?? Date.now;
301
+ const at = clock();
302
+ if (!Number.isFinite(at))
303
+ return null;
304
+ return { minQualityScore, maxAgeMs, now: () => at };
305
+ }
306
+ function validLifecycle(value) {
307
+ return isRecord(value) && nonEmpty(value.subject) && Number.isSafeInteger(value.now)
308
+ && Array.isArray(value.tombstones) && Array.isArray(value.holds);
309
+ }
310
+ /**
311
+ * Binding reasons (empty when every check passes), or null when the host bindings are malformed.
312
+ * Every lineage reference must be bound; every bound pointer must be inside a dispatched evaluation
313
+ * input and hold exactly the recomputed verified text; every other string field must be declared
314
+ * non-lineage by the host. The root pointer is malformed in both lists: a root non-lineage
315
+ * declaration would cover every text-bearing position, so it is rejected instead.
316
+ */
317
+ function inputBindingReasons(verification, current, input, dispatchPointers) {
318
+ const bindings = verification.inputBindings;
319
+ const declared = verification.nonLineagePointers ?? [];
320
+ if (!Array.isArray(bindings) || !Array.isArray(declared) || !declared.every(isJsonPointer) || declared.includes('')
321
+ || bindings.some(binding => !isRecord(binding) || !isJsonPointer(binding.pointer) || binding.pointer === ''
322
+ || !Array.isArray(binding.manifestIds) || !binding.manifestIds.length
323
+ || new Set(binding.manifestIds).size !== binding.manifestIds.length
324
+ || binding.manifestIds.some(id => typeof id !== 'string'))
325
+ || new Set(bindings.map(binding => binding.pointer)).size !== bindings.length)
326
+ return null;
327
+ const typed = bindings;
328
+ const reasons = new Set();
329
+ const boundIds = new Set(typed.flatMap(binding => binding.manifestIds));
330
+ if ([...current.keys()].some(id => !boundIds.has(id)) || [...boundIds].some(id => !current.has(id)))
331
+ reasons.add('input-unbound');
332
+ for (const binding of typed) {
333
+ const dispatched = dispatchPointers.some(pointer => pointer === '' || binding.pointer === pointer
334
+ || binding.pointer.startsWith(`${pointer}/`));
335
+ if (!dispatched) {
336
+ reasons.add('input-unbound');
337
+ continue;
338
+ }
339
+ if (binding.manifestIds.some(id => !current.has(id)))
340
+ continue;
341
+ const expected = binding.manifestIds.map(id => verifiedSelectedText(current.get(id))).join(MANIFEST_TEXT_SEPARATOR);
342
+ const actual = resolveJsonPointer(input, binding.pointer);
343
+ if (!actual.found || actual.value !== expected)
344
+ reasons.add('input-mismatch');
345
+ }
346
+ // A bound pointer covers exactly its string; a non-lineage declaration covers its whole subtree.
347
+ const bound = new Set(typed.map(binding => binding.pointer));
348
+ const covered = (pointer) => bound.has(pointer)
349
+ || declared.some(prefix => pointer === prefix || pointer.startsWith(`${prefix}/`));
350
+ if (textLeafPointers(input).some(pointer => !covered(pointer)))
351
+ reasons.add('input-undeclared');
352
+ return [...reasons];
353
+ }
354
+ function isJsonPointer(value) {
355
+ return typeof value === 'string' && (value === '' || value.startsWith('/'));
356
+ }
357
+ /**
358
+ * JSON pointers of every text-bearing position in the input: each string value and each object key.
359
+ * A key is reported at its member's pointer, so it is text that must be bound or declared too.
360
+ */
361
+ function textLeafPointers(value, pointer = '') {
362
+ if (typeof value === 'string')
363
+ return [pointer];
364
+ if (Array.isArray(value))
365
+ return value.flatMap((item, index) => textLeafPointers(item, `${pointer}/${index}`));
366
+ if (isRecord(value)) {
367
+ return Object.entries(value).flatMap(([key, item]) => {
368
+ const member = `${pointer}/${key.replace(/~/g, '~0').replace(/\//g, '~1')}`;
369
+ return [member, ...textLeafPointers(item, member)];
370
+ });
371
+ }
372
+ return [];
373
+ }
374
+ export function assertPreprocessedEvidence(value) {
375
+ try {
376
+ admitEntry(value);
377
+ }
378
+ catch (error) {
379
+ throw invalid('manifest is not admissible', error);
380
+ }
381
+ if (!isRecord(value) || value.apiVersion !== 'decision.aiwg.io/v1alpha2' || value.kind !== PREPROCESSED_EVIDENCE_KIND) {
382
+ throw new PreprocessedEvidenceError('invalid-manifest', 'unsupported preprocessing manifest kind/version');
383
+ }
384
+ rejectUnknown(value, ['apiVersion', 'kind', 'metadata', 'spec'], 'manifest');
385
+ if (!isRecord(value.metadata) || !isRecord(value.spec))
386
+ throw new PreprocessedEvidenceError('invalid-manifest', 'manifest metadata/spec are required');
387
+ rejectUnknown(value.metadata, ['id', 'version', 'description'], 'metadata');
388
+ rejectUnknown(value.spec, ['source', 'preprocessing', 'output', 'segments', 'selectedSegments', 'quality', 'transformations', 'policy'], 'spec');
389
+ if (![value.metadata.id, value.metadata.version, value.metadata.description].every(nonEmpty)) {
390
+ throw new PreprocessedEvidenceError('invalid-manifest', 'metadata identity is incomplete');
391
+ }
392
+ const spec = value.spec;
393
+ if (!isRecord(spec.source) || !isRecord(spec.output) || !isRecord(spec.quality) || !isRecord(spec.policy)
394
+ || !Array.isArray(spec.preprocessing) || !Array.isArray(spec.segments) || !Array.isArray(spec.selectedSegments)
395
+ || !Array.isArray(spec.transformations)) {
396
+ throw new PreprocessedEvidenceError('invalid-manifest', 'manifest sections are malformed');
397
+ }
398
+ rejectUnknown(spec.source, ['assetId', 'contentDigest', 'mediaType', 'sizeBytes', 'acquiredAt', 'locator', 'owner', 'license', 'consent', 'classification'], 'source');
399
+ if (![spec.source.assetId, spec.source.contentDigest, spec.source.mediaType, spec.source.acquiredAt, spec.source.classification].every(nonEmpty)
400
+ || !Number.isSafeInteger(spec.source.sizeBytes) || spec.source.sizeBytes < 0 || !validDigest(spec.source.contentDigest)
401
+ || !isRecord(spec.source.locator) || !['opaque-id', 'artifact-ref', 'local-redacted', 'remote-url'].includes(String(spec.source.locator.class))
402
+ || !nonEmpty(spec.source.locator.value)) {
403
+ throw new PreprocessedEvidenceError('invalid-manifest', 'source identity is malformed');
404
+ }
405
+ rejectUnknown(spec.output, ['id', 'version', 'contentDigest', 'language', 'value', 'reference'], 'output');
406
+ if (![spec.output.id, spec.output.contentDigest, spec.output.language].every(nonEmpty) || typeof spec.output.value !== 'string'
407
+ || !Number.isSafeInteger(spec.output.version) || spec.output.version < 1 || !validDigest(spec.output.contentDigest)) {
408
+ throw new PreprocessedEvidenceError('invalid-manifest', 'output identity is malformed');
409
+ }
410
+ if (preprocessedEvidenceContentDigest(spec.output.value) !== spec.output.contentDigest) {
411
+ throw new PreprocessedEvidenceError('digest-mismatch', 'output content digest mismatch');
412
+ }
413
+ validateSteps(spec.preprocessing);
414
+ validateMediaCompatibility(spec);
415
+ validateSegments(spec);
416
+ validateQuality(spec.quality);
417
+ validateTransformations(spec);
418
+ validatePolicy(spec.policy);
419
+ // The closed schema is the final gate: the runtime never accepts what the schema rejects.
420
+ const schemaErrors = preprocessedEvidenceSchemaErrors(value);
421
+ if (schemaErrors !== null)
422
+ throw new PreprocessedEvidenceError('invalid-manifest', `manifest violates the closed schema: ${schemaErrors}`);
423
+ }
424
+ function assertLocatorAuthorized(locator, classes, label) {
425
+ if (!classes.has(locator.class) || locator.class === 'remote-url') {
426
+ throw new PreprocessedEvidenceError('policy-denied', `${label} locator class is not authorized`);
427
+ }
428
+ }
429
+ function assertChain(manifest) {
430
+ const seenStepIds = new Set();
431
+ const seenDigests = new Set([manifest.spec.source.contentDigest]);
432
+ let previous = manifest.spec.source.contentDigest;
433
+ manifest.spec.preprocessing.forEach((step, index) => {
434
+ if (seenStepIds.has(step.id))
435
+ throw new PreprocessedEvidenceError('broken-chain', 'preprocessing chain contains duplicate step IDs');
436
+ seenStepIds.add(step.id);
437
+ if (step.ordinal !== index + 1 || step.inputDigest !== previous) {
438
+ throw new PreprocessedEvidenceError('broken-chain', 'preprocessing chain continuity is broken');
439
+ }
440
+ const identity = step.outputDigest === step.inputDigest;
441
+ if (identity !== (step.noOp === true)) {
442
+ throw new PreprocessedEvidenceError('broken-chain', identity
443
+ ? 'identity step must be declared as an explicit no-op' : 'declared no-op step changes content');
444
+ }
445
+ if (!identity && seenDigests.has(step.outputDigest)) {
446
+ throw new PreprocessedEvidenceError('broken-chain', 'preprocessing chain contains a digest cycle');
447
+ }
448
+ seenDigests.add(step.outputDigest);
449
+ previous = step.outputDigest;
450
+ });
451
+ // Human corrections are append-only: each takes the current output as input and bumps the version.
452
+ let correctedFrom = null;
453
+ for (const event of manifest.spec.transformations.filter(item => item.kind === 'human-correction')) {
454
+ const previousOutput = event.previousOutput;
455
+ const at = Date.parse(event.at);
456
+ if (event.inputDigest !== previous || previousOutput.digest !== previous || previousOutput.id !== manifest.spec.output.id
457
+ || event.outputDigest === previous || seenDigests.has(event.outputDigest)
458
+ || (correctedFrom && (previousOutput.version <= correctedFrom.version || at < correctedFrom.at))) {
459
+ throw new PreprocessedEvidenceError('broken-chain', 'human correction must append a newer linked output version');
460
+ }
461
+ correctedFrom = { version: previousOutput.version, at };
462
+ seenDigests.add(event.outputDigest);
463
+ previous = event.outputDigest;
464
+ }
465
+ if (correctedFrom && manifest.spec.output.version <= correctedFrom.version) {
466
+ throw new PreprocessedEvidenceError('invalid-manifest', 'human correction must create a newer linked output version');
467
+ }
468
+ if (previous !== manifest.spec.output.contentDigest) {
469
+ throw new PreprocessedEvidenceError('digest-mismatch', 'final preprocessing output does not match output identity');
470
+ }
471
+ for (const event of manifest.spec.transformations) {
472
+ if (!seenDigests.has(event.inputDigest) || !seenDigests.has(event.outputDigest)) {
473
+ throw new PreprocessedEvidenceError('broken-chain', 'transformation event is not linked to the preprocessing chain');
474
+ }
475
+ }
476
+ }
477
+ function selectedSegments(manifest) {
478
+ const segments = new Map(manifest.spec.segments.map(segment => [segment.id, segment]));
479
+ return manifest.spec.selectedSegments.map(reference => {
480
+ const segment = segments.get(reference.segmentId);
481
+ if (!segment)
482
+ throw new PreprocessedEvidenceError('broken-chain', 'selected segment is missing');
483
+ return { segment, reference };
484
+ }).sort((left, right) => left.segment.ordinal - right.segment.ordinal);
485
+ }
486
+ function referenceFor(manifest, pin, selected, destination) {
487
+ const preprocessingDigest = digestJson({
488
+ steps: manifest.spec.preprocessing.map(step => ({
489
+ id: step.id, ordinal: step.ordinal, kind: step.kind, tool: step.tool, model: step.model ?? null,
490
+ configurationDigest: step.configurationDigest, noOp: step.noOp === true,
491
+ })),
492
+ transformations: manifest.spec.transformations.map(event => ({
493
+ id: event.id, kind: event.kind, inputDigest: event.inputDigest, outputDigest: event.outputDigest,
494
+ reviewer: event.reviewer ?? null, rationale: event.rationale ?? null, previousOutput: event.previousOutput ?? null,
495
+ })),
496
+ });
497
+ return {
498
+ evidence: pin,
499
+ sourceAssetId: manifest.spec.source.assetId,
500
+ sourceDigest: manifest.spec.source.contentDigest,
501
+ outputId: manifest.spec.output.id,
502
+ outputVersion: manifest.spec.output.version,
503
+ outputDigest: manifest.spec.output.contentDigest,
504
+ destination: { provider: destination.provider, origin: destination.origin },
505
+ selectedSegments: selected.map(item => ({
506
+ id: item.segment.id,
507
+ ordinal: item.segment.ordinal,
508
+ locatorDigest: digestJson({ source: item.segment.sourceLocator, output: item.segment.outputRange }),
509
+ textDigest: item.segment.textDigest,
510
+ preprocessingDigest,
511
+ })),
512
+ quality: {
513
+ source: manifest.spec.quality.source,
514
+ profileDigest: preprocessedEvidenceContentDigest(manifest.spec.quality.profile),
515
+ labelDigest: preprocessedEvidenceContentDigest(manifest.spec.quality.label),
516
+ score: manifest.spec.quality.score,
517
+ flags: [...manifest.spec.quality.flags],
518
+ },
519
+ policy: {
520
+ trust: manifest.spec.policy.trust,
521
+ sensitivity: manifest.spec.policy.sensitivity,
522
+ retentionDigest: preprocessedEvidenceContentDigest(manifest.spec.policy.retention),
523
+ residencyDigest: preprocessedEvidenceContentDigest(manifest.spec.policy.residency),
524
+ rawEgressAllowed: egressAllowed(manifest.spec.policy.rawEgress, destination),
525
+ derivedEgressAllowed: egressAllowed(manifest.spec.policy.derivedEgress, destination),
526
+ },
527
+ };
528
+ }
529
+ function validateSteps(steps) {
530
+ if (!steps.length)
531
+ throw new PreprocessedEvidenceError('invalid-manifest', 'preprocessing chain is required');
532
+ for (const step of steps) {
533
+ if (!isRecord(step))
534
+ throw new PreprocessedEvidenceError('invalid-manifest', 'preprocessing step is malformed');
535
+ rejectUnknown(step, ['id', 'ordinal', 'kind', 'tool', 'model', 'configurationDigest', 'runtime', 'startedAt', 'endedAt', 'inputDigest', 'outputDigest', 'noOp', 'notesDigest'], 'preprocessing step');
536
+ if (!nonEmpty(step.id) || !Number.isSafeInteger(step.ordinal) || step.ordinal < 1
537
+ || !STEP_KINDS.includes(step.kind)
538
+ || !isRecord(step.tool) || !nonEmpty(step.tool.id) || !nonEmpty(step.tool.version)
539
+ || !validDigest(step.configurationDigest) || !validDigest(step.inputDigest) || !validDigest(step.outputDigest)
540
+ || !['recorded-fixture', 'offline', 'live-observed'].includes(step.runtime)
541
+ || !Number.isFinite(Date.parse(step.startedAt)) || !Number.isFinite(Date.parse(step.endedAt))) {
542
+ throw new PreprocessedEvidenceError('invalid-manifest', 'preprocessing step identity is malformed');
543
+ }
544
+ if (step.noOp !== undefined && (step.noOp !== true || EXTRACTION_KINDS.includes(step.kind))) {
545
+ throw new PreprocessedEvidenceError('invalid-manifest', 'only a non-extraction step can be declared no-op');
546
+ }
547
+ }
548
+ }
549
+ function validateSegments(spec) {
550
+ if (!spec.segments.length || !spec.selectedSegments.length)
551
+ throw new PreprocessedEvidenceError('invalid-manifest', 'segments are required');
552
+ const ids = new Set();
553
+ const ordinals = new Set();
554
+ const output = Buffer.from(spec.output.value, 'utf8');
555
+ for (const segment of spec.segments) {
556
+ if (!isRecord(segment))
557
+ throw new PreprocessedEvidenceError('invalid-manifest', 'segment is malformed');
558
+ rejectUnknown(segment, ['id', 'ordinal', 'sourceLocator', 'outputRange', 'text', 'textDigest'], 'segment');
559
+ if (!nonEmpty(segment.id) || !Number.isSafeInteger(segment.ordinal) || segment.ordinal < 1
560
+ || typeof segment.text !== 'string' || !validDigest(segment.textDigest) || !isRecord(segment.sourceLocator)) {
561
+ throw new PreprocessedEvidenceError('invalid-manifest', 'segment locator is malformed');
562
+ }
563
+ if (ids.has(segment.id) || ordinals.has(segment.ordinal)) {
564
+ throw new PreprocessedEvidenceError('invalid-manifest', 'segments contain duplicate IDs or ordinals');
565
+ }
566
+ ids.add(segment.id);
567
+ ordinals.add(segment.ordinal);
568
+ if (preprocessedEvidenceContentDigest(segment.text) !== segment.textDigest) {
569
+ throw new PreprocessedEvidenceError('digest-mismatch', 'segment text digest mismatch');
570
+ }
571
+ const range = segment.outputRange;
572
+ if (!isRecord(range) || !Number.isSafeInteger(range.start) || !Number.isSafeInteger(range.end)
573
+ || range.start < 0 || range.end < range.start || range.end > output.length) {
574
+ throw new PreprocessedEvidenceError('invalid-manifest', 'segment output range is outside the verified output');
575
+ }
576
+ if (!output.subarray(range.start, range.end).equals(Buffer.from(segment.text, 'utf8'))) {
577
+ throw new PreprocessedEvidenceError('digest-mismatch', 'segment text is not the verified output slice');
578
+ }
579
+ const locator = segment.sourceLocator;
580
+ const hasPage = Number.isSafeInteger(locator.page);
581
+ const hasTime = Number.isSafeInteger(locator.startMs) && Number.isSafeInteger(locator.endMs)
582
+ && typeof locator.endMs === 'number' && typeof locator.startMs === 'number' && locator.endMs >= locator.startMs;
583
+ const hasFrame = Number.isSafeInteger(locator.frame);
584
+ const hasWholeImage = locator.wholeImage === true;
585
+ const hasBox = isRecord(locator.bbox) && typeof locator.bbox.x === 'number' && typeof locator.bbox.y === 'number'
586
+ && typeof locator.bbox.width === 'number' && typeof locator.bbox.height === 'number'
587
+ && ['pixel', 'ratio'].includes(String(locator.bbox.unit));
588
+ if (!hasPage && !hasTime && !hasFrame && !hasBox && !hasWholeImage) {
589
+ throw new PreprocessedEvidenceError('invalid-manifest', 'segment requires a page, time, frame, box or whole-image locator');
590
+ }
591
+ validateLocatorForMedia(spec.source.mediaType, locator, primaryExtractionKind(spec.preprocessing));
592
+ }
593
+ // Source order is preserved: ordinal order is also output order.
594
+ const ordered = [...spec.segments].sort((left, right) => left.ordinal - right.ordinal);
595
+ if (ordered.some((segment, index) => index > 0 && segment.outputRange.start < ordered[index - 1].outputRange.end)) {
596
+ throw new PreprocessedEvidenceError('invalid-manifest', 'segment output ranges must follow ordinal order without overlap');
597
+ }
598
+ const selectedIds = new Set();
599
+ for (const reference of spec.selectedSegments) {
600
+ if (!isRecord(reference))
601
+ throw new PreprocessedEvidenceError('invalid-manifest', 'selected segment is malformed');
602
+ if (selectedIds.has(String(reference.segmentId))) {
603
+ throw new PreprocessedEvidenceError('invalid-manifest', 'selected segments contain duplicate segment IDs');
604
+ }
605
+ selectedIds.add(String(reference.segmentId));
606
+ rejectUnknown(reference, ['segmentId', 'outputVersion', 'outputDigest'], 'selected segment');
607
+ if (!nonEmpty(reference.segmentId) || !Number.isSafeInteger(reference.outputVersion)
608
+ || reference.outputVersion < 1 || !validDigest(reference.outputDigest)) {
609
+ throw new PreprocessedEvidenceError('invalid-manifest', 'selected segment identity is malformed');
610
+ }
611
+ }
612
+ }
613
+ function validateQuality(quality) {
614
+ rejectUnknown(quality, ['source', 'profile', 'label', 'score', 'flags'], 'quality');
615
+ if (!['preprocessor', 'human-reviewer'].includes(quality.source) || !nonEmpty(quality.profile)
616
+ || !nonEmpty(quality.label) || typeof quality.score !== 'number' || quality.score < 0 || quality.score > 1
617
+ || !Array.isArray(quality.flags) || quality.flags.some(flag => !['low-quality', 'truncated', 'incomplete', 'stale', 'policy-limited', 'human-corrected'].includes(flag))) {
618
+ throw new PreprocessedEvidenceError('invalid-manifest', 'quality evidence is malformed');
619
+ }
620
+ }
621
+ function validateTransformations(spec) {
622
+ for (const event of spec.transformations) {
623
+ if (!isRecord(event))
624
+ throw new PreprocessedEvidenceError('invalid-manifest', 'transformation event is malformed');
625
+ rejectUnknown(event, ['id', 'kind', 'inputDigest', 'outputDigest', 'at', 'reviewer', 'rationale', 'previousOutput'], 'transformation event');
626
+ if (!nonEmpty(event.id) || !TRANSFORMATION_KINDS.includes(event.kind)
627
+ || !validDigest(event.inputDigest) || !validDigest(event.outputDigest) || !Number.isFinite(Date.parse(event.at))) {
628
+ throw new PreprocessedEvidenceError('invalid-manifest', 'transformation event is malformed');
629
+ }
630
+ if (event.kind === 'human-correction' && (!nonEmpty(event.reviewer) || !nonEmpty(event.rationale) || !isRecord(event.previousOutput)
631
+ || !nonEmpty(event.previousOutput.id) || !Number.isSafeInteger(event.previousOutput.version)
632
+ || event.previousOutput.version < 1 || !validDigest(event.previousOutput.digest))) {
633
+ throw new PreprocessedEvidenceError('invalid-manifest', 'human correction requires reviewer, rationale and previous output link');
634
+ }
635
+ }
636
+ }
637
+ function validatePolicy(policy) {
638
+ rejectUnknown(policy, ['trust', 'sensitivity', 'retention', 'residency', 'rawEgress', 'derivedEgress'], 'policy');
639
+ if (!['verified', 'untrusted'].includes(policy.trust) || !['public', 'internal', 'confidential', 'restricted'].includes(policy.sensitivity)
640
+ || !nonEmpty(policy.retention) || !nonEmpty(policy.residency) || !egress(policy.rawEgress) || !egress(policy.derivedEgress)) {
641
+ throw new PreprocessedEvidenceError('invalid-manifest', 'lineage policy is malformed');
642
+ }
643
+ }
644
+ function egress(value) {
645
+ return isRecord(value) && typeof value.allowed === 'boolean' && Array.isArray(value.destinations)
646
+ && value.destinations.every(destination => isRecord(destination) && nonEmpty(destination.provider) && nonEmpty(destination.origin));
647
+ }
648
+ function egressAllowed(value, destination) {
649
+ return value.allowed && value.destinations.some(item => sameDestination(item, destination));
650
+ }
651
+ function sameDestination(left, right) {
652
+ return left.provider === right.provider && normalizeOrigin(left.origin) === normalizeOrigin(right.origin);
653
+ }
654
+ function validateMediaCompatibility(spec) {
655
+ const kind = primaryExtractionKind(spec.preprocessing);
656
+ if (!kind)
657
+ throw new PreprocessedEvidenceError('invalid-manifest', 'preprocessing chain requires an extraction step');
658
+ if (kind === 'ocr' && !(spec.source.mediaType.startsWith('image/') || spec.source.mediaType === 'application/pdf')) {
659
+ throw new PreprocessedEvidenceError('invalid-manifest', 'OCR source media type must be image or PDF');
660
+ }
661
+ if (kind === 'asr' && !(spec.source.mediaType.startsWith('audio/') || spec.source.mediaType.startsWith('video/'))) {
662
+ throw new PreprocessedEvidenceError('invalid-manifest', 'transcription source media type must be audio or video');
663
+ }
664
+ if (kind === 'caption' && !spec.source.mediaType.startsWith('video/')) {
665
+ throw new PreprocessedEvidenceError('invalid-manifest', 'caption source media type must be video');
666
+ }
667
+ if (kind === 'image-description' && !spec.source.mediaType.startsWith('image/')) {
668
+ throw new PreprocessedEvidenceError('invalid-manifest', 'image description source media type must be image');
669
+ }
670
+ }
671
+ function primaryExtractionKind(steps) {
672
+ return steps.find((step) => EXTRACTION_KINDS.includes(step.kind))?.kind ?? null;
673
+ }
674
+ function validateLocatorForMedia(mediaType, locator, kind) {
675
+ const hasPage = Number.isSafeInteger(locator.page);
676
+ const hasTime = Number.isSafeInteger(locator.startMs) && Number.isSafeInteger(locator.endMs)
677
+ && typeof locator.endMs === 'number' && typeof locator.startMs === 'number' && locator.endMs >= locator.startMs;
678
+ const hasFrame = Number.isSafeInteger(locator.frame);
679
+ const hasBox = isRecord(locator.bbox);
680
+ const hasWholeImage = locator.wholeImage === true;
681
+ if (mediaType.startsWith('audio/') && (!hasTime || hasPage || hasFrame || hasBox || hasWholeImage)) {
682
+ throw new PreprocessedEvidenceError('invalid-manifest', 'audio segments require a time range locator');
683
+ }
684
+ if (mediaType.startsWith('video/') && !hasTime && !hasFrame) {
685
+ throw new PreprocessedEvidenceError('invalid-manifest', 'video segments require time or frame locators');
686
+ }
687
+ if (kind === 'ocr' && (mediaType === 'application/pdf' || mediaType.startsWith('image/')) && !hasPage) {
688
+ throw new PreprocessedEvidenceError('invalid-manifest', 'scanned document OCR segments require a page locator');
689
+ }
690
+ if (kind === 'image-description' && mediaType.startsWith('image/') && !hasBox && !hasWholeImage) {
691
+ throw new PreprocessedEvidenceError('invalid-manifest', 'image description segments require a box or whole-image locator');
692
+ }
693
+ }
694
+ /** HTTP(S) origins compare by URL origin; any other scheme (for example `local://`) compares exactly. */
695
+ function normalizeOrigin(value) {
696
+ try {
697
+ const url = new URL(value);
698
+ return url.protocol === 'https:' || url.protocol === 'http:' ? url.origin : value;
699
+ }
700
+ catch {
701
+ return value;
702
+ }
703
+ }
704
+ function invalid(message, _error) {
705
+ return new PreprocessedEvidenceError('invalid-manifest', message);
706
+ }
707
+ function isRecord(value) {
708
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
709
+ }
710
+ function nonEmpty(value) {
711
+ return typeof value === 'string' && value.trim().length > 0;
712
+ }
713
+ function validDigest(value) {
714
+ return typeof value === 'string' && /^sha256:[0-9a-f]{64}$/.test(value);
715
+ }
716
+ function rejectUnknown(value, allowed, label) {
717
+ const unknown = Object.keys(value).filter(key => !allowed.includes(key));
718
+ if (unknown.length)
719
+ throw new PreprocessedEvidenceError('invalid-manifest', `${label} contains unsupported fields`);
720
+ }
721
+ function digestJson(value) {
722
+ return `sha256:${sha256(Buffer.from(canonicalJson(value), 'utf8'))}`;
723
+ }
724
+ //# sourceMappingURL=preprocessed-evidence.js.map