@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,245 @@
1
+ /**
2
+ * D12 live qualification workload (#2686): seeded, labelled, synthetic-only tasks for the
3
+ * three graph patterns, plus the per-task decision definitions both arms ask Jev.
4
+ * Generation is pure and deterministic; the frozen copy is digest-pinned in
5
+ * docs/decision/evidence/dag-live-v1/. No customer or personal data is used.
6
+ */
7
+ import { createHash } from 'node:crypto';
8
+ import { canonicalJson } from '../security/artifact-trust.js';
9
+ import { artifactPin, validateDefinition } from './validate.js';
10
+ export const DAG_LIVE_PATTERNS = ['shortlist-rerank', 'taxonomy-beam', 'extractor-verifier-fallback'];
11
+ export const dagLiveDigest = (value) => `sha256:${createHash('sha256').update(canonicalJson(value)).digest('hex')}`;
12
+ // Same deterministic generator as the offline graph benchmark and property suites.
13
+ function random(seed) {
14
+ let state = seed >>> 0;
15
+ return () => (state = (Math.imul(state, 1_664_525) + 1_013_904_223) >>> 0) / 2 ** 32;
16
+ }
17
+ const pick = (next, items) => items[Math.floor(next() * items.length)];
18
+ function shuffle(next, items) {
19
+ const copy = [...items];
20
+ for (let i = copy.length - 1; i > 0; i--) {
21
+ const j = Math.floor(next() * (i + 1));
22
+ [copy[i], copy[j]] = [copy[j], copy[i]];
23
+ }
24
+ return copy;
25
+ }
26
+ /** Fictional office-supply catalog. Each item has two paraphrased needs. */
27
+ const CATALOG = {
28
+ writing: { name: 'Writing instruments', items: [
29
+ { id: 'ballpoint-pen', name: 'Ballpoint pen', cues: ['a cheap pen with quick-drying ink for signing forms', 'something with a rolling ball tip for everyday writing'] },
30
+ { id: 'pencil', name: 'Graphite pencil', cues: ['something whose marks I can erase later', 'a wooden graphite stick for sketching'] },
31
+ { id: 'highlighter', name: 'Highlighter', cues: ['something to mark key sentences in bright translucent yellow', 'a fluorescent marker for emphasising printed text'] },
32
+ { id: 'fountain-pen', name: 'Fountain pen', cues: ['a refillable nib pen for calligraphy practice', 'a pen that draws bottled ink through a nib'] },
33
+ { id: 'permanent-marker', name: 'Permanent marker', cues: ['something to label plastic boxes that will not wash off', 'a thick waterproof marker for cardboard'] },
34
+ ] },
35
+ paper: { name: 'Paper products', items: [
36
+ { id: 'sticky-notes', name: 'Sticky notes', cues: ['small adhesive squares for reminders on a monitor', 'repositionable notes I can move around a whiteboard'] },
37
+ { id: 'legal-pad', name: 'Legal pad', cues: ['a yellow ruled pad for meeting notes', 'a tear-off pad with long ruled sheets'] },
38
+ { id: 'printer-paper', name: 'Printer paper', cues: ['a ream of plain sheets for the office printer', 'blank letter-size sheets for the copier'] },
39
+ { id: 'index-cards', name: 'Index cards', cues: ['small stiff cards for flash cards', 'cards sized for a recipe box'] },
40
+ { id: 'notebook', name: 'Spiral notebook', cues: ['a bound book for daily class notes', 'a wire-bound book of lined pages'] },
41
+ ] },
42
+ desk: { name: 'Desk tools', items: [
43
+ { id: 'stapler', name: 'Stapler', cues: ['something to fasten a stack of pages with metal', 'a device that joins sheets permanently at the corner'] },
44
+ { id: 'tape-dispenser', name: 'Tape dispenser', cues: ['a weighted holder for clear adhesive tape', 'something to tear off tape strips one-handed'] },
45
+ { id: 'scissors', name: 'Scissors', cues: ['a tool with two blades to cut paper shapes', 'something to trim ribbon and string'] },
46
+ { id: 'paper-clips', name: 'Paper clips', cues: ['small wire fasteners that hold sheets without puncturing them', 'reusable bent-wire clips for temporary bundles'] },
47
+ { id: 'hole-punch', name: 'Hole punch', cues: ['a tool that makes holes for a ring binder', 'something to perforate pages before filing them'] },
48
+ ] },
49
+ storage: { name: 'Filing and storage', items: [
50
+ { id: 'ring-binder', name: 'Ring binder', cues: ['a cover with metal rings to hold punched pages', 'a folder that opens rings to insert sheets'] },
51
+ { id: 'file-folder', name: 'File folder', cues: ['a manila sleeve with a tab for a filing cabinet', 'a folded card holder labelled on its tab'] },
52
+ { id: 'archive-box', name: 'Archive box', cues: ['a lidded cardboard box for storing old records', 'a sturdy box to move years of paperwork to the basement'] },
53
+ { id: 'desk-organizer', name: 'Desk organizer', cues: ['a tray with compartments to tidy loose supplies', 'a caddy that keeps pens and clips sorted on the desk'] },
54
+ { id: 'label-maker', name: 'Label maker', cues: ['a handheld device that prints adhesive name strips', 'something to print neat labels for shelves'] },
55
+ ] },
56
+ };
57
+ const REQUEST_PREFIX = ['I am looking for', 'Please find me', 'Our team needs', 'Can you suggest', 'We want to order'];
58
+ function shortlistTask(index, seed) {
59
+ const next = random(seed + index * 7_919);
60
+ const [first, second, ...others] = shuffle(next, Object.keys(CATALOG));
61
+ const sections = [first, second].map((category, s) => {
62
+ const all = CATALOG[category].items;
63
+ const dropped = Math.floor(next() * all.length);
64
+ return { category, dropped: all[dropped], items: all.filter((_, i) => i !== dropped),
65
+ view: { id: `section-${s + 1}`, name: CATALOG[category].name } };
66
+ });
67
+ const none = next() < 0.2;
68
+ let target;
69
+ let label;
70
+ let slice;
71
+ if (none) {
72
+ // Near misses: an item dropped from a shown section, or one from a hidden category.
73
+ target = next() < 0.5 ? pick(next, sections).dropped : pick(next, CATALOG[pick(next, others)].items);
74
+ label = 'none';
75
+ slice = 'no-match';
76
+ }
77
+ else {
78
+ const section = pick(next, sections);
79
+ target = pick(next, section.items);
80
+ label = target.id;
81
+ slice = 'match';
82
+ }
83
+ const request = `${pick(next, REQUEST_PREFIX)} ${target.cues[next() < 0.5 ? 0 : 1]}.`;
84
+ return { id: `sr-${String(index + 1).padStart(3, '0')}`, pattern: 'shortlist-rerank', slice, label,
85
+ subject: { request, sections: sections.map(s => ({ ...s.view, items: s.items.map(item => ({ id: item.id, name: item.name })) })) } };
86
+ }
87
+ /** Fixed two-level support taxonomy: two branches of five leaves each. */
88
+ export const DAG_LIVE_TAXONOMY = {
89
+ 'branch-a': { name: 'Billing and account', leaves: [
90
+ { id: 'refund-request', name: 'Refund request', cues: ['wants their money back for an order they cancelled', 'asks to reverse a charge for an unused subscription month', 'requests reimbursement after returning a damaged item'] },
91
+ { id: 'invoice-copy', name: 'Invoice copy', cues: ['needs a PDF copy of last month\'s invoice for accounting', 'asks for the billing statement to be re-sent with a VAT number', 'wants an itemised receipt for an expense report'] },
92
+ { id: 'payment-failure', name: 'Payment failure', cues: ['reports that their card was declined at checkout', 'says the automatic renewal payment did not go through', 'sees a payment error when paying the latest bill'] },
93
+ { id: 'plan-upgrade', name: 'Plan upgrade', cues: ['wants to move from the basic plan to the team plan', 'asks how to add more seats to the subscription', 'would like the premium tier with more storage'] },
94
+ { id: 'account-closure', name: 'Account closure', cues: ['wants to permanently delete their account', 'asks to cancel everything and close the profile', 'requests that the organisation account be shut down'] },
95
+ ] },
96
+ 'branch-b': { name: 'Technical support', leaves: [
97
+ { id: 'login-problem', name: 'Login problem', cues: ['cannot sign in even after resetting the password', 'is locked out after too many sign-in attempts', 'never receives the two-factor code when logging in'] },
98
+ { id: 'data-export', name: 'Data export', cues: ['wants to download all project data as CSV', 'asks how to export their records to a spreadsheet', 'needs a full backup file of the workspace'] },
99
+ { id: 'performance-slowness', name: 'Performance slowness', cues: ['says the dashboard takes a minute to load', 'reports that searches have become very slow', 'notices pages freezing when opening large reports'] },
100
+ { id: 'integration-error', name: 'Integration error', cues: ['sees webhook deliveries failing with errors', 'reports that the calendar sync stopped working', 'gets an API authentication error from their connector'] },
101
+ { id: 'mobile-app-crash', name: 'Mobile app crash', cues: ['says the phone app closes immediately on launch', 'reports the tablet app crashing when uploading a photo', 'finds the mobile app quits when opening notifications'] },
102
+ ] },
103
+ };
104
+ const PRODUCTS = ['Planner Pro', 'TaskBoard', 'Ledgerly', 'NoteNest', 'ShipTrack'];
105
+ const OPENERS = ['Hello team,', 'Hi support,', 'Good morning,', 'Hey there,'];
106
+ // Neutral context sentences that mention another topic without being the request.
107
+ const ASIDES = ['Earlier this year I also updated my billing address, which went fine.', 'The mobile app has been working well otherwise.',
108
+ 'We exported a report last week without trouble.', 'My colleague upgraded their own plan and had no issues.'];
109
+ function taxonomyTask(index, seed) {
110
+ const next = random(seed + 104_729 + index * 7_919);
111
+ const branch = pick(next, ['branch-a', 'branch-b']);
112
+ const leaf = pick(next, DAG_LIVE_TAXONOMY[branch].leaves);
113
+ const aside = next() < 0.3;
114
+ const reference = `${String.fromCharCode(65 + Math.floor(next() * 26))}-${10_000 + Math.floor(next() * 90_000)}`;
115
+ const text = `${pick(next, OPENERS)} the customer on ticket ${reference} using ${pick(next, PRODUCTS)} ${pick(next, leaf.cues)}.`
116
+ + (aside ? ` ${pick(next, ASIDES)}` : '');
117
+ return { id: `tb-${String(index + 1).padStart(3, '0')}`, pattern: 'taxonomy-beam', slice: aside ? `${branch}-aside` : branch,
118
+ label: leaf.id, subject: { ticket: text } };
119
+ }
120
+ export const DAG_LIVE_STATUSES = [
121
+ { id: 'delivered', name: 'Delivered', cue: 'was handed to the recipient and signed for' },
122
+ { id: 'in-transit', name: 'In transit', cue: 'left the regional hub and is on the way' },
123
+ { id: 'delayed', name: 'Delayed', cue: 'is held up by a missed connection and will arrive later than planned' },
124
+ { id: 'returned', name: 'Returned to sender', cue: 'was refused at the door and is going back to the sender' },
125
+ { id: 'lost', name: 'Lost', cue: 'cannot be located after a warehouse search and has been declared lost' },
126
+ { id: 'awaiting-pickup', name: 'Awaiting pickup', cue: 'is waiting at the parcel locker for the recipient to collect it' },
127
+ ];
128
+ function extractorTask(index, seed) {
129
+ const next = random(seed + 1_299_709 + index * 7_919);
130
+ const status = pick(next, DAG_LIVE_STATUSES);
131
+ const parcel = `P-${1_000 + Math.floor(next() * 9_000)}`;
132
+ const kind = next();
133
+ const other = pick(next, DAG_LIVE_STATUSES.filter(item => item.id !== status.id));
134
+ let log;
135
+ let slice;
136
+ if (kind < 0.55) {
137
+ log = [`Day 1: Parcel ${parcel} ${status.cue}.`];
138
+ slice = 'direct';
139
+ }
140
+ else if (kind < 0.8) {
141
+ // An earlier status is superseded by the latest update.
142
+ log = [`Day 1: Parcel ${parcel} ${other.cue}.`, `Day 3: Update: parcel ${parcel} ${status.cue}.`];
143
+ slice = 'superseded';
144
+ }
145
+ else {
146
+ // A note about a different parcel must not be read as this parcel's status.
147
+ const neighbour = `P-${1_000 + Math.floor(next() * 9_000)}`;
148
+ log = [`Day 2: Parcel ${parcel} ${status.cue}.`, `Note: a different parcel, ${neighbour === parcel ? `${parcel}-B` : neighbour}, ${other.cue}.`];
149
+ slice = 'distractor';
150
+ }
151
+ return { id: `ev-${String(index + 1).padStart(3, '0')}`, pattern: 'extractor-verifier-fallback', slice, label: status.id,
152
+ subject: { parcel, log } };
153
+ }
154
+ export const DAG_LIVE_SEED = 0x2686;
155
+ export const DAG_LIVE_TASKS_PER_PATTERN = 100;
156
+ /** Pure generator for the preregistered workload. Same seed, same bytes. */
157
+ export function generateDagLiveWorkload(seed = DAG_LIVE_SEED, tasksPerPattern = DAG_LIVE_TASKS_PER_PATTERN) {
158
+ if (!Number.isSafeInteger(seed) || seed < 0 || seed > 0xffffffff || !Number.isSafeInteger(tasksPerPattern) || tasksPerPattern < 2 || tasksPerPattern > 999) {
159
+ throw new Error('invalid DAG live workload parameters');
160
+ }
161
+ const tasks = Array.from({ length: tasksPerPattern }, (_, i) => [shortlistTask(i, seed), taxonomyTask(i, seed), extractorTask(i, seed)]).flat()
162
+ .sort((a, b) => DAG_LIVE_PATTERNS.indexOf(a.pattern) - DAG_LIVE_PATTERNS.indexOf(b.pattern) || a.id.localeCompare(b.id));
163
+ return { schemaVersion: 'dag-live-workload/v1', issue: '#2686', syntheticOnly: true, generator: 'lcg-1664525-1013904223/v1',
164
+ seed, tasksPerPattern, tasks };
165
+ }
166
+ const inputSchema = { type: 'object', properties: { subject: {}, evidence: {} }, required: ['subject', 'evidence'], additionalProperties: false };
167
+ function definition(id, question, answer) {
168
+ const value = { apiVersion: 'decision.aiwg.io/v1alpha1', kind: 'DecisionDefinition',
169
+ metadata: { id, version: '1.0.0', description: 'D12 live qualification synthetic decision (#2686)' },
170
+ spec: { purpose: 'Synthetic D12 paired qualification only; no action is authorized.', inputSchema, question, answer,
171
+ requiredCapabilities: [answer.kind] } };
172
+ validateDefinition(value);
173
+ return value;
174
+ }
175
+ const choice = (options) => ({ kind: 'choice', options: options.map(([id, description]) => ({ id, description })) });
176
+ const POSITIONS = ['first', 'second', 'third', 'fourth', 'fifth'];
177
+ /**
178
+ * Definitions asked by each arm for one task, keyed by node ID. `flat` is the single-call
179
+ * baseline; the other keys are graph nodes. Host-local nodes (taxonomy, select) ask nothing.
180
+ */
181
+ export function dagLiveDefinitions(task) {
182
+ if (task.pattern === 'shortlist-rerank') {
183
+ const sections = task.subject.sections;
184
+ return {
185
+ flat: definition('dag-live-sr-flat', 'Choose the catalog item in subject.sections that best satisfies subject.request. Choose none if no listed item satisfies it.', choice([...sections.flatMap(section => section.items.map(item => [item.id, item.name])),
186
+ ['none', 'No listed item satisfies the request.']])),
187
+ shortlist: definition('dag-live-sr-shortlist', 'Which section of subject.sections contains an item that satisfies subject.request? Choose none if no listed item satisfies it.', choice([...sections.map(section => [section.id, `${section.name}: ${section.items.map(item => item.name).join(', ')}.`]),
188
+ ['none', 'No listed item satisfies the request.']])),
189
+ rerank: definition('dag-live-sr-rerank', 'evidence.candidates.items lists shortlisted items in order. Choose the position of the item that best satisfies subject.request.', choice(POSITIONS.slice(0, 4).map((word, i) => [`position-${i + 1}`, `The ${word} item in evidence.candidates.items.`]))),
190
+ };
191
+ }
192
+ if (task.pattern === 'taxonomy-beam') {
193
+ const leaves = Object.values(DAG_LIVE_TAXONOMY).flatMap(branch => branch.leaves);
194
+ const out = {
195
+ flat: definition('dag-live-tb-flat', 'Classify the support request in subject.ticket into exactly one category.', choice(leaves.map(leaf => [leaf.id, leaf.name]))),
196
+ };
197
+ for (const [id, branch] of Object.entries(DAG_LIVE_TAXONOMY)) {
198
+ out[id] = definition(`dag-live-tb-${id}`, `How well does the support request in subject.ticket fit the ${branch.name} area (${branch.leaves.map(leaf => leaf.name).join(', ')})?`, { kind: 'ordinal-score', levels: ['Does not fit at all.', 'Unlikely to fit.', 'Could fit.', 'Likely fits.', 'Clearly fits.'] });
199
+ out[`detail-${id}`] = definition(`dag-live-tb-detail-${id}`, `Classify the ${branch.name} support request in subject.ticket into exactly one category.`, choice(branch.leaves.map(leaf => [leaf.id, leaf.name])));
200
+ }
201
+ return out;
202
+ }
203
+ const statuses = choice(DAG_LIVE_STATUSES.map(status => [status.id, status.name]));
204
+ return {
205
+ flat: definition('dag-live-ev-flat', 'What is the current status of parcel subject.parcel according to subject.log?', statuses),
206
+ extractor: definition('dag-live-ev-extractor', 'What is the current status of parcel subject.parcel according to subject.log?', statuses),
207
+ verifier: definition('dag-live-ev-verifier', 'Does subject.log support that the current status of parcel subject.parcel is evidence.evidence.status? Later updates override earlier ones, and notes about other parcels do not count.', choice([['supported', 'The log supports that status as current for this parcel.'], ['not-supported', 'The log does not support that status as current for this parcel.']])),
208
+ fallback: definition('dag-live-ev-fallback', 'A previous extraction was judged unsupported. Read subject.log carefully: later updates override earlier ones and notes about other parcels do not count. What is the current status of parcel subject.parcel?', statuses),
209
+ };
210
+ }
211
+ /** Preregistered analysis, economics and stop rules. Changing any value changes the digest. */
212
+ export const DAG_LIVE_ANALYSIS = {
213
+ qualityMetric: 'exact-match-accuracy', pairing: 'task-id', armOrder: 'alternating-by-task-index',
214
+ nonInferiority: { method: 'newcombe-10', levelBps: 9000, marginBps: -1000 },
215
+ economics: { maxCallRatio: 3, maxP95LatencyRatio: 4, maxExtraTokensPerTaskUpper: 8000,
216
+ bootstrap: { levelBps: 9000, seed: 0x2686, resamples: 20_000 } },
217
+ /** Call ratios (economics and the stop rule) count first attempts of usable pairs; retries are charged and reported. */
218
+ stopRules: { maxPatternCallRatio: 3, callRatioBasis: 'first-attempt-calls-of-usable-pairs', budgetStopFraction: 0.8, speculativeAction: 'stop-run' },
219
+ /**
220
+ * Jev returns occasional non-success outcomes (about 2-3% invalid output in the #2613 study). A retryable
221
+ * outcome gets at most one retry, reserved and charged like any call; a second failure makes the TASK a
222
+ * measurement failure in both arms, excluded from the paired table. More than the tolerated fraction of a
223
+ * pattern's tasks makes that pattern insufficient evidence (and it is abandoned), not a run stop.
224
+ * Identity, accounting, budget and credential anomalies still stop the run at once.
225
+ */
226
+ providerFailurePolicy: {
227
+ retriesPerCall: 1, retryCharged: true,
228
+ retryableOutcomes: ['provider-invalid-output', 'provider-timeout', 'provider-network-transient', 'provider-rate-limited',
229
+ 'provider-overloaded', 'provider-service-error', 'unknown-usage'],
230
+ measurementFailure: 'task-excluded-from-both-arms', maxMeasurementFailureFractionPerPattern: 0.05,
231
+ beyondTolerance: 'pattern-insufficient-evidence',
232
+ immediateStops: ['served-model-mismatch', 'usage-exceeded-reservation', 'missing-request-id', 'budget', 'credential-or-authorization',
233
+ 'data-boundary-denied', 'invalid-request', 'adapter-exception', 'any-other-provider-outcome'],
234
+ },
235
+ perCallTokenBound: 4000, concurrency: 1,
236
+ };
237
+ export function dagLivePreregistration(workload, promotionOwner = 'roctinam') {
238
+ const definitions = workload.tasks.map(task => ({ task: task.id, pins: Object.fromEntries(Object.entries(dagLiveDefinitions(task))
239
+ .map(([node, value]) => [node, artifactPin(value).digest])) }));
240
+ return { schemaVersion: 'dag-live-preregistration/v1', issue: '#2686', workloadDigest: dagLiveDigest(workload),
241
+ definitionsDigest: dagLiveDigest(definitions), tasksPerPattern: workload.tasksPerPattern,
242
+ arms: { baseline: 'flat-single-call-flowgraph/v1', candidate: 'decision-graph-template/v1' },
243
+ analysis: DAG_LIVE_ANALYSIS, promotionOwner, automaticPromotion: false };
244
+ }
245
+ //# sourceMappingURL=graph-live-workload.js.map
@@ -4,6 +4,7 @@ import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
4
4
  import { resolveRoutableCapability } from '../artifacts/capability-resolver.js';
5
5
  import { parseDecisionJson } from './entry.js';
6
6
  import { DecisionGraphError } from './graph.js';
7
+ import { assertDecisionHostPolicyRefs, resolveDecisionHostPolicies } from './host-policy.js';
7
8
  import { assertDecisionFlowPins, assertUnknownCostBound, decisionFlowNode, decisionFlowResponse } from './graph-decision-bridge.js';
8
9
  /** Resolve the packaged `decision-evaluate` skill: its stable catalog ID becomes the
9
10
  * Flow node ref, and its declared script entrypoint is the only program invoked.
@@ -26,8 +27,14 @@ export async function resolveDecisionEvaluateSkill(frameworkRoot) {
26
27
  * Only PATH, the opt-in flag and host-listed variables reach the child process.
27
28
  */
28
29
  export function runDecisionEvaluateSkill(run) {
30
+ if (run.hostPolicies && Object.keys(run.hostPolicies).length) {
31
+ return Promise.reject(new DecisionGraphError('in-process host policy registry requires a custom decision-evaluate runner'));
32
+ }
29
33
  return new Promise(done => {
30
- execFile(process.execPath, [run.scriptPath, '--request', run.requestPath], {
34
+ const args = [run.scriptPath, '--request', run.requestPath];
35
+ if (run.hostPolicyModulePath)
36
+ args.push('--host-policy-module', run.hostPolicyModulePath);
37
+ execFile(process.execPath, args, {
31
38
  cwd: dirname(run.requestPath), timeout: run.timeoutMs, maxBuffer: 16 * 1024 * 1024,
32
39
  env: { PATH: process.env.PATH ?? '', ...run.env, AIWG_DECISION_ENABLED: '1' },
33
40
  }, (error, stdout) => done({ code: error ? (typeof error.code === 'number' ? error.code : -1) : 0, stdout }));
@@ -46,8 +53,18 @@ export function decisionEvaluateSkillFlowInvoker(graph, host) {
46
53
  return async (flow) => {
47
54
  const { node, input } = decisionFlowNode(graph, flow);
48
55
  const base = host.request(node.id);
56
+ if (base.hostPolicies !== undefined) {
57
+ if (host.hostPolicies)
58
+ resolveDecisionHostPolicies(base.hostPolicies, host.hostPolicies);
59
+ else
60
+ assertDecisionHostPolicyRefs(base.hostPolicies);
61
+ }
62
+ if (base.hostPolicyModulePath !== undefined && !isAbsolute(base.hostPolicyModulePath)) {
63
+ throw new DecisionGraphError('host policy module path must be absolute');
64
+ }
49
65
  const paths = [base.rulesetPath, base.bindingPath, ...base.definitionPaths,
50
66
  ...(base.inputPath === undefined ? [] : [base.inputPath]), ...Object.values(base.adapterModules ?? {}),
67
+ ...(base.projectionPolicyPath === undefined ? [] : [base.projectionPolicyPath]),
51
68
  ...(base.receiptDirectory === undefined ? [] : [base.receiptDirectory])];
52
69
  if (!base.definitionPaths.length || paths.some(path => typeof path !== 'string' || !isAbsolute(path)) ||
53
70
  (node.id === graph.entry && base.inputPath === undefined))
@@ -63,9 +80,10 @@ export function decisionEvaluateSkillFlowInvoker(graph, host) {
63
80
  await writeFile(inputPath, JSON.stringify(input), { mode: 0o600, flag: 'wx' });
64
81
  }
65
82
  const requestPath = join(directory, 'request.json');
66
- await writeFile(requestPath, JSON.stringify({ ...base, inputPath, runId: flow.runId, invocationId: flow.invocationKey }), { mode: 0o600, flag: 'wx' });
83
+ const { hostPolicyModulePath, ...serializableBase } = base;
84
+ await writeFile(requestPath, JSON.stringify({ ...serializableBase, inputPath, runId: flow.runId, invocationId: flow.invocationKey }), { mode: 0o600, flag: 'wx' });
67
85
  const outcome = await (host.run ?? runDecisionEvaluateSkill)({ scriptPath: host.skill.scriptPath, requestPath,
68
- env: { ...(host.env ?? {}) }, timeoutMs: host.timeoutMs ?? 60_000 });
86
+ env: { ...(host.env ?? {}) }, timeoutMs: host.timeoutMs ?? 60_000, hostPolicyModulePath, hostPolicies: host.hostPolicies });
69
87
  // Exit 1 still carries an error/cancelled RulesetResult; anything else is a failure.
70
88
  if (outcome.code !== 0 && outcome.code !== 1)
71
89
  throw new DecisionGraphError('decision-evaluate skill failed');
@@ -0,0 +1,140 @@
1
+ import { basename, join, resolve } from 'node:path';
2
+ import { checkHeldoutSchema, heldoutDigest, HeldoutError, heldoutRowsInScope, validateHeldoutBundle } from './contract.js';
3
+ import { heldoutEvidenceDigest, readHeldoutFile, readHeldoutJournal, validateHeldoutJournal, writeHeldoutFile } from './journal.js';
4
+ export async function readHeldoutFrozen(run, trustedApprovalDigest) {
5
+ const frozen = await readHeldoutFile(join(run, 'frozen.json'));
6
+ checkHeldoutSchema('Frozen', frozen);
7
+ if (frozen.digest !== heldoutDigest(frozen.bundle))
8
+ throw new HeldoutError('frozen-inputs');
9
+ validateHeldoutBundle(frozen.bundle, trustedApprovalDigest ?? heldoutDigest(frozen.bundle.approval));
10
+ if (basename(run) !== frozen.bundle.approval.runId || frozen.approval.runId !== frozen.bundle.approval.runId
11
+ || frozen.approval.study !== frozen.bundle.approval.study)
12
+ throw new HeldoutError('frozen-run');
13
+ return frozen;
14
+ }
15
+ function calibrationApproval(bundle) {
16
+ return bundle.approval.calibration.mode === 'staged' && bundle.approval.calibration.phase === 'calibration';
17
+ }
18
+ function sameStudy(bundle, other) {
19
+ return bundle.approval.study === other.approval.study && bundle.approval.corpusDigest === other.approval.corpusDigest
20
+ && bundle.approval.preregistrationDigest === other.approval.preregistrationDigest;
21
+ }
22
+ function sameBudget(bundle, other) {
23
+ return heldoutDigest(bundle.approval.budget) === heldoutDigest(other.approval.budget)
24
+ && bundle.approval.priorStudySpendUsd === other.approval.priorStudySpendUsd
25
+ && bundle.approval.priorPortfolioSpendUsd === other.approval.priorPortfolioSpendUsd;
26
+ }
27
+ function terminal(rows, attempts) {
28
+ return rows.every(row => row.requests.length === 0 && row.localOutcome !== null
29
+ || attempts.some(a => a.rowId === row.id && a.result?.disposition === 'measurement-failure')
30
+ || row.requests.every(request => attempts.some(a => a.rowId === row.id && a.requestId === request.id && a.result?.disposition === 'success')));
31
+ }
32
+ /** Reconstruct the seal from frozen approvals and full journals, including reservations and retries. */
33
+ async function phaseRecord(run, sealedAt, captured) {
34
+ const frozen = await readHeldoutFrozen(run), { bundle } = frozen;
35
+ if (!calibrationApproval(bundle))
36
+ throw new HeldoutError('calibration-phase-approval');
37
+ const lineage = [], journals = [], attempts = [];
38
+ const seen = new Set();
39
+ const runs = [...frozen.priorRuns.map(prior => ({ runId: prior.runId, expected: prior.evidenceDigest })),
40
+ { runId: bundle.approval.runId, expected: null }];
41
+ for (const item of runs) {
42
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/.test(item.runId) || seen.has(item.runId))
43
+ throw new HeldoutError('calibration-phase-lineage');
44
+ seen.add(item.runId);
45
+ const path = resolve(run, '..', item.runId), prior = await readHeldoutFrozen(path);
46
+ if (!sameStudy(bundle, prior.bundle) || !sameBudget(bundle, prior.bundle) || !calibrationApproval(prior.bundle)) {
47
+ throw new HeldoutError('calibration-phase-lineage');
48
+ }
49
+ if (captured && prior.source === 'injected-transport')
50
+ captured.source = 'injected-transport';
51
+ const events = await readHeldoutJournal(path);
52
+ await validateHeldoutJournal(prior.bundle, events);
53
+ const evidenceDigest = await heldoutEvidenceDigest(path, events);
54
+ if (item.expected !== null && item.expected !== evidenceDigest)
55
+ throw new HeldoutError('calibration-phase-lineage');
56
+ lineage.push({ runId: item.runId, evidenceDigest });
57
+ journals.push({ runId: item.runId, events });
58
+ attempts.push(...events.map(event => event.attempt).filter(attempt => attempt.result));
59
+ const settled = new Set(events.filter(event => event.attempt.result).map(event => heldoutDigest({ ...event.attempt, result: null })));
60
+ if (events.some(event => !event.attempt.result && !settled.has(heldoutDigest(event.attempt))))
61
+ return null;
62
+ }
63
+ const rows = heldoutRowsInScope(bundle);
64
+ if (attempts.some(attempt => attempt.result?.disposition === 'stop') || !terminal(rows, attempts))
65
+ return null;
66
+ captured?.attempts.push(...attempts);
67
+ return { schemaVersion: 'decision-heldout-calibration-phase/v1', study: bundle.approval.study, runId: bundle.approval.runId,
68
+ corpusDigest: bundle.approval.corpusDigest, preregistrationDigest: bundle.approval.preregistrationDigest,
69
+ approvalDigest: heldoutDigest(bundle.approval), sealedAt, rowIds: rows.map(row => row.id), lineage, lineageDigest: heldoutDigest(journals) };
70
+ }
71
+ /** Caller holds the dispatch lock. Publication is exclusive and fsynced; incomplete phases have no seal. */
72
+ export async function sealHeldoutCalibrationPhase(run, sealedAt) {
73
+ const record = await phaseRecord(run, sealedAt);
74
+ if (!record)
75
+ return null;
76
+ checkHeldoutSchema('CalibrationPhase', record);
77
+ await writeHeldoutFile(join(run, 'calibration-phase.json'), record);
78
+ return heldoutDigest(record);
79
+ }
80
+ /** Offline fitting reads only a reconstructed, independently pinned terminal phase. */
81
+ export async function readHeldoutCalibrationPhase(run, trustedApprovalDigest, trustedCalibrationPhaseRecordDigest) {
82
+ const frozen = await readHeldoutFrozen(run, trustedApprovalDigest);
83
+ const record = await readHeldoutFile(join(run, 'calibration-phase.json'));
84
+ checkHeldoutSchema('CalibrationPhase', record);
85
+ if (heldoutDigest(record) !== trustedCalibrationPhaseRecordDigest || record.approvalDigest !== trustedApprovalDigest) {
86
+ throw new HeldoutError('calibration-phase-seal');
87
+ }
88
+ const captured = { attempts: [], source: frozen.source };
89
+ const reconstructed = await phaseRecord(run, record.sealedAt, captured);
90
+ if (!reconstructed || heldoutDigest(reconstructed) !== heldoutDigest(record))
91
+ throw new HeldoutError('calibration-phase-lineage');
92
+ return { bundle: frozen.bundle, source: captured.source, record, attempts: captured.attempts };
93
+ }
94
+ /** Test access is bound to a separately approved artifact and the terminal calibration lineage. */
95
+ export async function validateHeldoutPhaseAccess(bundle, runsRoot, priorRuns, accessAt) {
96
+ const calibration = bundle.approval.calibration;
97
+ if (calibration.mode !== 'staged')
98
+ return;
99
+ try {
100
+ const candidates = [];
101
+ for (const prior of priorRuns.filter(prior => prior.study === bundle.approval.study && prior.corpusDigest === bundle.approval.corpusDigest)) {
102
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/.test(prior.runId))
103
+ throw new HeldoutError('calibration-phase-lineage');
104
+ const run = join(runsRoot, prior.runId), frozen = await readHeldoutFrozen(run);
105
+ if (!sameStudy(bundle, frozen.bundle) || !sameBudget(bundle, frozen.bundle)
106
+ || frozen.bundle.approval.calibration.mode !== 'staged')
107
+ throw new HeldoutError('calibration-phase-lineage');
108
+ if (await heldoutEvidenceDigest(run, await readHeldoutJournal(run)) !== prior.evidenceDigest)
109
+ throw new HeldoutError('calibration-phase-lineage');
110
+ candidates.push({ run, frozen });
111
+ }
112
+ if (calibration.phase === 'calibration') {
113
+ if (candidates.some(item => !calibrationApproval(item.frozen.bundle)))
114
+ throw new HeldoutError('calibration-phase-order');
115
+ return;
116
+ }
117
+ const owner = candidates.find(item => heldoutDigest(item.frozen.bundle.approval) === calibration.priorApprovalDigest);
118
+ if (!owner || !calibrationApproval(owner.frozen.bundle))
119
+ throw new HeldoutError('calibration-phase-approval');
120
+ const record = await readHeldoutFile(join(owner.run, 'calibration-phase.json'));
121
+ checkHeldoutSchema('CalibrationPhase', record);
122
+ if (heldoutDigest(record) !== calibration.calibrationPhaseRecordDigest || record.approvalDigest !== calibration.priorApprovalDigest
123
+ || !accessAt || !Number.isFinite(Date.parse(accessAt)) || Date.parse(accessAt) <= Date.parse(record.sealedAt))
124
+ throw new HeldoutError('calibration-phase-seal');
125
+ const reconstructed = await phaseRecord(owner.run, record.sealedAt);
126
+ if (!reconstructed || heldoutDigest(reconstructed) !== heldoutDigest(record))
127
+ throw new HeldoutError('calibration-phase-lineage');
128
+ for (const { frozen } of candidates) {
129
+ const prior = frozen.bundle.approval.calibration;
130
+ if (prior.mode === 'staged' && prior.phase === 'test' && heldoutDigest(prior) !== heldoutDigest(calibration))
131
+ throw new HeldoutError('calibration-phase-binding');
132
+ if (calibrationApproval(frozen.bundle) && !record.lineage.some(item => item.runId === frozen.bundle.approval.runId))
133
+ throw new HeldoutError('calibration-phase-lineage');
134
+ }
135
+ }
136
+ catch {
137
+ throw new HeldoutError('calibration-phase-access');
138
+ }
139
+ }
140
+ //# sourceMappingURL=calibration.js.map