@aiwg/cli 2026.9.21 → 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 (210) hide show
  1. package/agentic/code/providers/capability-matrix.yaml +2 -1
  2. package/dist/src/artifacts/cli.js +23 -18
  3. package/dist/src/artifacts/discovery-shadow-evaluator.js +177 -0
  4. package/dist/src/artifacts/discovery-shadow.js +195 -0
  5. package/dist/src/artifacts/fortemi-core-query-adapter.js +6 -2
  6. package/dist/src/artifacts/index-builder.js +47 -9
  7. package/dist/src/artifacts/query-engine.js +108 -5
  8. package/dist/src/artifacts/types.js +1 -0
  9. package/dist/src/cli/handlers/decision.js +88 -0
  10. package/dist/src/cli/handlers/gates.js +154 -0
  11. package/dist/src/cli/handlers/index.js +5 -1
  12. package/dist/src/cli/handlers/sessions.js +10 -7
  13. package/dist/src/cli/handlers/utilities.js +20 -0
  14. package/dist/src/config/aiwg-config.js +15 -2
  15. package/dist/src/decision/acceptance.js +25 -0
  16. package/dist/src/decision/adapters/jev.js +24 -2
  17. package/dist/src/decision/calibration/registry.js +9 -0
  18. package/dist/src/decision/compose.js +74 -15
  19. package/dist/src/decision/context-live-promotion.js +344 -0
  20. package/dist/src/decision/context-live-qualification.js +591 -0
  21. package/dist/src/decision/context-pruning.js +847 -0
  22. package/dist/src/decision/driver.js +430 -0
  23. package/dist/src/decision/egress-live-qualification.js +874 -0
  24. package/dist/src/decision/ensemble/contract.js +64 -1
  25. package/dist/src/decision/ensemble/index.js +1 -0
  26. package/dist/src/decision/ensemble/report.js +8 -42
  27. package/dist/src/decision/ensemble/runtime.js +571 -0
  28. package/dist/src/decision/ensemble-study/artifacts.js +85 -0
  29. package/dist/src/decision/ensemble-study/calibration.js +443 -0
  30. package/dist/src/decision/ensemble-study/corpus.js +229 -0
  31. package/dist/src/decision/ensemble-study/multifact-artifacts.js +23 -0
  32. package/dist/src/decision/ensemble-study/multifact-study.js +596 -0
  33. package/dist/src/decision/ensemble-study/multifact.js +241 -0
  34. package/dist/src/decision/ensemble-study/protocol.js +77 -0
  35. package/dist/src/decision/ensemble-study/score.js +462 -0
  36. package/dist/src/decision/ensemble-study/staged.js +87 -0
  37. package/dist/src/decision/ensemble-study/statistics.js +84 -0
  38. package/dist/src/decision/evaluate.js +227 -14
  39. package/dist/src/decision/export.js +4 -0
  40. package/dist/src/decision/feature-export.js +1074 -0
  41. package/dist/src/decision/graph-index.js +16 -0
  42. package/dist/src/decision/graph-live-qualification.js +1041 -0
  43. package/dist/src/decision/graph-live-workload.js +245 -0
  44. package/dist/src/decision/graph-skill-bridge.js +21 -3
  45. package/dist/src/decision/heldout/calibration.js +140 -0
  46. package/dist/src/decision/heldout/collector.js +430 -0
  47. package/dist/src/decision/heldout/contract.js +309 -0
  48. package/dist/src/decision/heldout/d29-generator-ids.js +12 -0
  49. package/dist/src/decision/heldout/d29-passage-baseline-v2.js +93 -0
  50. package/dist/src/decision/heldout/d29-passage-baseline-v3.js +100 -0
  51. package/dist/src/decision/heldout/d29-passage-baseline.js +66 -0
  52. package/dist/src/decision/heldout/d29-pools-v8.js +575 -0
  53. package/dist/src/decision/heldout/d29-pools.js +288 -0
  54. package/dist/src/decision/heldout/d29-v6.js +169 -0
  55. package/dist/src/decision/heldout/d29-v7.js +202 -0
  56. package/dist/src/decision/heldout/d29-v8.js +586 -0
  57. package/dist/src/decision/heldout/generator-registry.js +67 -0
  58. package/dist/src/decision/heldout/generators.js +696 -0
  59. package/dist/src/decision/heldout/index.js +8 -0
  60. package/dist/src/decision/heldout/journal.js +346 -0
  61. package/dist/src/decision/heldout/study-generators.js +64 -0
  62. package/dist/src/decision/heldout/types.js +2 -0
  63. package/dist/src/decision/host-policy.js +61 -0
  64. package/dist/src/decision/index.js +11 -0
  65. package/dist/src/decision/interop-profile.v1.json +112 -0
  66. package/dist/src/decision/interop.js +653 -0
  67. package/dist/src/decision/issue-triage/index.js +3 -0
  68. package/dist/src/decision/issue-triage/runtime.js +1069 -0
  69. package/dist/src/decision/issue-triage/types.js +2 -0
  70. package/dist/src/decision/patterns/catalog.js +28 -8
  71. package/dist/src/decision/patterns/playground.js +28 -11
  72. package/dist/src/decision/patterns/specs.js +19 -10
  73. package/dist/src/decision/preprocessed-evidence.js +724 -0
  74. package/dist/src/decision/qualification/capture.js +8 -2
  75. package/dist/src/decision/qualification/gate-evidence.js +19 -4
  76. package/dist/src/decision/qualification/load.js +8 -1
  77. package/dist/src/decision/qualification/quality.js +57 -49
  78. package/dist/src/decision/qualification/release.js +76 -3
  79. package/dist/src/decision/qualification/runner.js +5 -2
  80. package/dist/src/decision/routing/contract.js +175 -0
  81. package/dist/src/decision/routing/index.js +5 -0
  82. package/dist/src/decision/routing/report.js +216 -0
  83. package/dist/src/decision/routing/runtime.js +685 -0
  84. package/dist/src/decision/routing/types.js +2 -0
  85. package/dist/src/decision/sdlc-screening.js +1157 -0
  86. package/dist/src/decision/sensitivity/comparative.js +154 -0
  87. package/dist/src/decision/sensitivity/contract.js +218 -0
  88. package/dist/src/decision/sensitivity/index.js +5 -0
  89. package/dist/src/decision/sensitivity/runtime.js +912 -0
  90. package/dist/src/decision/sensitivity/types.js +2 -0
  91. package/dist/src/decision/validate.js +26 -0
  92. package/dist/src/extensions/commands/definitions.js +38 -0
  93. package/dist/src/extensions/manifest.js +44 -0
  94. package/dist/src/gates/discovery.js +315 -0
  95. package/dist/src/gates/driver.js +388 -0
  96. package/dist/src/gates/evaluate.js +719 -0
  97. package/dist/src/gates/floors.js +255 -0
  98. package/dist/src/gates/index.js +17 -0
  99. package/dist/src/gates/providers/evidence.js +38 -0
  100. package/dist/src/gates/providers/index.js +21 -0
  101. package/dist/src/gates/providers/isolated-child-source.js +446 -0
  102. package/dist/src/gates/providers/loader.js +1069 -0
  103. package/dist/src/gates/providers/paired.js +52 -0
  104. package/dist/src/gates/providers/proportion.js +60 -0
  105. package/dist/src/gates/providers/registry.js +40 -0
  106. package/dist/src/gates/providers/scalar.js +41 -0
  107. package/dist/src/gates/providers/screening.js +53 -0
  108. package/dist/src/gates/providers/types.js +25 -0
  109. package/dist/src/gates/registry.js +1003 -0
  110. package/dist/src/gates/report.js +102 -0
  111. package/dist/src/gates/schema.js +56 -0
  112. package/dist/src/gates/stats/binomial.js +26 -0
  113. package/dist/src/gates/stats/bootstrap.js +62 -0
  114. package/dist/src/gates/stats/clopper-pearson.js +95 -0
  115. package/dist/src/gates/stats/digest.js +26 -0
  116. package/dist/src/gates/stats/error.js +5 -0
  117. package/dist/src/gates/stats/index.js +9 -0
  118. package/dist/src/gates/stats/normal.js +35 -0
  119. package/dist/src/gates/stats/paired.js +120 -0
  120. package/dist/src/gates/stats/splits.js +30 -0
  121. package/dist/src/gates/types.js +8 -0
  122. package/dist/src/governance/redaction.js +14 -4
  123. package/dist/src/mcp/muse-native-config.mjs +123 -0
  124. package/dist/src/mcp/tools/decision.mjs +188 -0
  125. package/dist/src/mcp/tools/subsystems.mjs +2 -0
  126. package/dist/src/providers/capability-matrix.yaml +2 -1
  127. package/dist/src/providers/provider-definitions.js +2 -1
  128. package/dist/src/sessions/adapters/muse.js +336 -39
  129. package/dist/src/sessions/batch-import.js +7 -0
  130. package/dist/src/sessions/workspace-discovery.js +13 -2
  131. package/package.json +1 -1
  132. package/schemas/decision/ConformalFrozenOpenData.v2.schema.json +371 -0
  133. package/schemas/decision/ConformalOpenDataReport.v2.schema.json +421 -0
  134. package/schemas/decision/ConformalPreregistration.v2.schema.json +529 -0
  135. package/schemas/decision/ContextPruningCandidate.v1.schema.json +47 -0
  136. package/schemas/decision/ContextPruningEvaluationReport.v1.schema.json +758 -0
  137. package/schemas/decision/ContextPruningPreregistration.v1.schema.json +175 -0
  138. package/schemas/decision/ContextPruningReceipt.v1.schema.json +72 -0
  139. package/schemas/decision/D17Multifact.v1.schema.json +1851 -0
  140. package/schemas/decision/D17StudyAnalysis.v1.schema.json +42 -0
  141. package/schemas/decision/D17StudyAnalysis.v2.schema.json +51 -0
  142. package/schemas/decision/D17StudyCalibration.v1.schema.json +819 -0
  143. package/schemas/decision/D17StudyDryRun.v1.schema.json +169 -0
  144. package/schemas/decision/D17StudyDryRun.v2.schema.json +239 -0
  145. package/schemas/decision/D17StudyGold.v1.schema.json +108 -0
  146. package/schemas/decision/D17StudyGuide.v1.schema.json +63 -0
  147. package/schemas/decision/D17StudyNativeTemplates.v1.schema.json +619 -0
  148. package/schemas/decision/D17StudyProtocol.v1.schema.json +241 -0
  149. package/schemas/decision/D17StudyReport.v1.schema.json +902 -0
  150. package/schemas/decision/D17StudyReport.v2.schema.json +1319 -0
  151. package/schemas/decision/D17StudyReview.v1.schema.json +175 -0
  152. package/schemas/decision/D17StudySplits.v1.schema.json +217 -0
  153. package/schemas/decision/D29Study.v1.schema.json +1903 -0
  154. package/schemas/decision/D29Study.v2.schema.json +1344 -0
  155. package/schemas/decision/D29Study.v3.schema.json +609 -0
  156. package/schemas/decision/D29Study.v4.schema.json +1133 -0
  157. package/schemas/decision/D29Study.v6.schema.json +1205 -0
  158. package/schemas/decision/D29Study.v7.schema.json +1471 -0
  159. package/schemas/decision/D29Study.v8.schema.json +671 -0
  160. package/schemas/decision/D29Study.v9.schema.json +778 -0
  161. package/schemas/decision/DecisionComparativeReplayAuditSample.v1.schema.json +137 -0
  162. package/schemas/decision/DecisionComparativeReplayCorpus.v1.schema.json +258 -0
  163. package/schemas/decision/DecisionComparativeReplayFreeze.v1.schema.json +74 -0
  164. package/schemas/decision/DecisionComparativeReplayGold.v1.schema.json +50 -0
  165. package/schemas/decision/DecisionComparativeReplayGoldAsset.v1.schema.json +37 -0
  166. package/schemas/decision/DecisionComparativeReplayIntegrity.v1.schema.json +81 -0
  167. package/schemas/decision/DecisionComparativeReplayMembers.v1.schema.json +52 -0
  168. package/schemas/decision/DecisionComparativeReplayPreregistration.v1.schema.json +113 -0
  169. package/schemas/decision/DecisionComparativeReplayReport.v1.schema.json +407 -0
  170. package/schemas/decision/DecisionComparativeReplayReviewPackets.v1.schema.json +260 -0
  171. package/schemas/decision/DecisionComparativeReplaySplit.v1.schema.json +84 -0
  172. package/schemas/decision/DecisionFeatureObservation.v1.schema.json +564 -0
  173. package/schemas/decision/DecisionFeatureSet.v1.schema.json +155 -0
  174. package/schemas/decision/DecisionInteropMapping.v1.schema.json +85 -0
  175. package/schemas/decision/DecisionIssueTriageEvaluationManifest.v1.schema.json +174 -0
  176. package/schemas/decision/DecisionIssueTriageEvaluationReport.v1.schema.json +280 -0
  177. package/schemas/decision/DecisionIssueTriagePilotPack.v1.schema.json +114 -0
  178. package/schemas/decision/DecisionRouteReceipt.v1.schema.json +155 -0
  179. package/schemas/decision/DecisionRoutingPolicy.v1.schema.json +132 -0
  180. package/schemas/decision/DecisionRoutingShadowReport.v1.schema.json +170 -0
  181. package/schemas/decision/DecisionRoutingTask.v1.schema.json +49 -0
  182. package/schemas/decision/DecisionRuleset.schema.json +8 -2
  183. package/schemas/decision/DecisionRuleset.v1alpha2.schema.json +8 -2
  184. package/schemas/decision/DecisionSensitivityPlan.v1.schema.json +191 -0
  185. package/schemas/decision/DecisionSensitivityReport.v1.schema.json +163 -0
  186. package/schemas/decision/HeldoutApproval.v1.schema.json +312 -0
  187. package/schemas/decision/HeldoutAttempt.v1.schema.json +211 -0
  188. package/schemas/decision/HeldoutBaseline.v1.schema.json +90 -0
  189. package/schemas/decision/HeldoutCalibrationPhase.v1.schema.json +87 -0
  190. package/schemas/decision/HeldoutCorpus.v1.schema.json +167 -0
  191. package/schemas/decision/HeldoutEvent.v1.schema.json +41 -0
  192. package/schemas/decision/HeldoutFrozen.v1.schema.json +142 -0
  193. package/schemas/decision/HeldoutPreregistration.v1.schema.json +222 -0
  194. package/schemas/decision/HeldoutSpendEvent.v1.schema.json +122 -0
  195. package/schemas/decision/HeldoutSpendHead.v1.schema.json +25 -0
  196. package/schemas/decision/HeldoutSpendHead.v2.schema.json +46 -0
  197. package/schemas/decision/HeldoutSummary.v1.schema.json +101 -0
  198. package/schemas/decision/OpaDecisionLogExport.v1.schema.json +65 -0
  199. package/schemas/decision/PreprocessedEvidence.v1.schema.json +306 -0
  200. package/schemas/decision/RulesetResult.v1alpha2.schema.json +175 -0
  201. package/schemas/decision/SdlcEvidenceScreening.v1.schema.json +499 -0
  202. package/schemas/decision/SdlcScreeningPreregistration.v1.schema.json +132 -0
  203. package/schemas/decision/SdlcScreeningPreregistration.v2.schema.json +138 -0
  204. package/schemas/decision/SdlcScreeningRelease.v1.schema.json +654 -0
  205. package/schemas/decision/SdlcScreeningRelease.v2.schema.json +654 -0
  206. package/tools/agents/deploy-agents.mjs +1 -1
  207. package/tools/agents/providers/base.mjs +26 -7
  208. package/tools/agents/providers/factory.mjs +10 -2
  209. package/tools/agents/providers/muse-hooks.mjs +1 -29
  210. package/tools/agents/providers/muse.mjs +6 -3
@@ -0,0 +1,309 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import Ajv2020 from 'ajv/dist/2020.js';
5
+ import addFormats from 'ajv-formats';
6
+ import { admitEntry, DEFAULT_ENTRY_LIMITS } from '../entry.js';
7
+ import { sha256 } from '../compile-cache/identity.js';
8
+ import { artifactPin, validateDefinition, validateDecisionDocument } from '../validate.js';
9
+ import { contextLiveBinding, contextLiveRuleset, contextLiveTarget } from '../context-live-qualification.js';
10
+ import { JEV_ENDPOINT, compileJevQuestion } from '../adapters/jev.js';
11
+ import { projectDecisionState, partitionProjectedState } from '../projection.js';
12
+ import { dagLiveReservationMicros } from '../graph-live-qualification.js';
13
+ import { redactStructured, redactText } from '../../governance/redaction.js';
14
+ import { heldoutCorpusSeed } from './generators.js';
15
+ import { studyHeldoutGeneratorDigest as registeredHeldoutGeneratorDigest, reproducibleStudyHeldoutRow as reproducibleRegisteredHeldoutRow, D17_MULTIFACT_GENERATOR_ID, d17MultifactPublicSeed, usesD17MultifactGenerator } from './study-generators.js';
16
+ import { D29_PAID_GENERATOR_IDS } from './d29-generator-ids.js';
17
+ export { sha256 as heldoutDigest };
18
+ export const HELDOUT_CAP_USD = Object.freeze({ D17: 8, D29: 6 });
19
+ export const HELDOUT_PORTFOLIO_CAP_USD = 48;
20
+ export const HELDOUT_ENV_GATE = 'AIWG_DECISION_HELDOUT_LIVE';
21
+ const D29_PUBLIC_DEMO_SEEDS = new Set(['d29-study-v1', 'd29-study-v2', 'd29-study-v3', 'd29-study-v4', 'd29-study-v5', 'd29-study-v6', 'd29-study-v7', 'd29-study-v8']);
22
+ // Canonical corpus digests, including the public version before the wording correction.
23
+ // Slim corpus headers stay refused alongside the full corpora they pin: both are committed demo bytes.
24
+ const D29_PUBLIC_DEMO_CORPORA = new Set([
25
+ 'sha256:6bb4c5990c2d039841074bfb099e8c1ad5c6c40b5bd334817cb897bc320fd744',
26
+ 'sha256:4a0833e7a82d3809cb3b9448af1fbe5eae0ebeb1ee4d8055ae67b9c24f05baca',
27
+ 'sha256:505283a46217e158b39ba93f61e17c28c39c582cad857f7090f38e8844a6794a',
28
+ 'sha256:35ecc8936b7a251d1c34cf630e9b09ed82eff0d05f96c901c01edfa0f9849934',
29
+ 'sha256:372cb180f129938280751bf3db76e4f9bc142ef85e68f38370b88aeec5602963',
30
+ 'sha256:5d09130d1327033dc00e7edb6e936fc3388036c3177aaef3ba20ae1a6ae38604',
31
+ 'sha256:d5b722d175903aefe1b98c3db0c5310df35c0807eb8b7682ebd852f566e28ebc',
32
+ 'sha256:53dea2a1008af107c1634c4d35c663fc18a9412a82a662141a50b0c8bbbdaf64',
33
+ 'sha256:018912caaae0102593be0196fa86f0721edded8b2bed625b5a32b4df260b8a6e',
34
+ 'sha256:6f5834c418810e5d89ce37185b13063ce0c8922220becbb43ae7253678d2da23',
35
+ 'sha256:76186887d4753a8fd011b6abf438f467d19e3b11b15881522fc59d6f0c7cc7d6',
36
+ 'sha256:65454a6a3c1dfd85b3c7e953d836309328a48442ec9ae931b9a907c8d91ff855',
37
+ // v8 per-generator pins: the same public v6/v7 corpora after the generator digest change.
38
+ 'sha256:4635395bac1c4efc036729879a831d8fd827b8b00d5b3e704b614135123c6549',
39
+ 'sha256:c4a0f5d2a4000091cb435e75a3caca48ae0f4478b03f24a619132ce8bec260de',
40
+ // Generator d29-synthetic/v8 public development corpus (seed d29-study-v8); refused preemptively.
41
+ 'sha256:ecf148eaea5e7da9e65e481698e12b7d3ecbc431ded55e61cb2fb97a5f285dcf',
42
+ ]);
43
+ const limits = { ...DEFAULT_ENTRY_LIMITS, serializedBytes: 32_000_000, properties: 1_000_000,
44
+ arrayLength: 20000, entries: 2_000_000, memoryBytes: 256_000_000 };
45
+ const validators = new Map();
46
+ export class HeldoutError extends Error {
47
+ category;
48
+ detail;
49
+ /** `detail` names where (for example a corpus row index), never row content. */
50
+ constructor(category, detail) {
51
+ super(`Held-out collector refused (${category})${detail ? ` at ${detail}` : ''}`);
52
+ this.category = category;
53
+ this.detail = detail;
54
+ }
55
+ }
56
+ export function checkHeldoutSchema(kind, value) {
57
+ admitEntry(value, limits);
58
+ let validate = validators.get(kind);
59
+ if (!validate) {
60
+ const here = dirname(fileURLToPath(import.meta.url));
61
+ const name = `Heldout${kind}.${kind === 'SpendHead' ? 'v2' : 'v1'}.schema.json`;
62
+ const root = [resolve(here, '../../../schemas/decision'), resolve(here, '../../../../schemas/decision')]
63
+ .find(path => existsSync(resolve(path, name)));
64
+ if (!root)
65
+ throw new HeldoutError('schema-unavailable');
66
+ const ajv = new Ajv2020({ strict: true });
67
+ addFormats(ajv);
68
+ for (const dependency of ['Corpus', 'Preregistration', 'Approval', 'Attempt']) {
69
+ if (kind === 'Frozen' || kind === 'Event')
70
+ ajv.addSchema(JSON.parse(readFileSync(resolve(root, `Heldout${dependency}.v1.schema.json`), 'utf8')));
71
+ }
72
+ validate = ajv.compile(JSON.parse(readFileSync(resolve(root, name), 'utf8')));
73
+ validators.set(kind, validate);
74
+ }
75
+ if (!validate(value))
76
+ throw new HeldoutError('schema');
77
+ }
78
+ export function validateHeldoutInputs(corpus, plan) {
79
+ checkHeldoutSchema('Corpus', corpus);
80
+ checkHeldoutSchema('Preregistration', plan);
81
+ // Every row pins its own generator family: the corpus digest must equal the
82
+ // single family digest all rows resolve to, so no row rides on another study's pin.
83
+ // Null means unknown, so an unregistered generator or mixed families fail closed.
84
+ const familyDigests = new Map();
85
+ const digestOf = (generatorId) => {
86
+ const cached = familyDigests.get(generatorId);
87
+ if (cached !== undefined)
88
+ return cached;
89
+ try {
90
+ const digest = registeredHeldoutGeneratorDigest(generatorId);
91
+ familyDigests.set(generatorId, digest);
92
+ return digest;
93
+ }
94
+ catch {
95
+ throw new HeldoutError('corpus-provenance');
96
+ }
97
+ };
98
+ let familyDigest = null;
99
+ for (const row of corpus.rows) {
100
+ const digest = digestOf(row.provenance.generatorId);
101
+ if (familyDigest === null)
102
+ familyDigest = digest;
103
+ else if (familyDigest !== digest)
104
+ throw new HeldoutError('corpus-provenance');
105
+ }
106
+ if (familyDigest === null || corpus.provenance.generatorDigest !== familyDigest
107
+ || corpus.provenance.seed !== heldoutCorpusSeed(corpus.rows)) {
108
+ throw new HeldoutError('corpus-provenance');
109
+ }
110
+ const ids = new Set(), families = new Map(), payloads = new Set();
111
+ const definitions = new Set(corpus.definitions.map(d => d.metadata.id));
112
+ corpus.definitions.forEach(validateDefinition);
113
+ if (definitions.size !== corpus.definitions.length || plan.study !== corpus.study || plan.corpusDigest !== sha256(corpus)
114
+ || plan.outputAndHiddenTokenAllowance >= plan.perRequestTokenBound)
115
+ throw new HeldoutError('input-pins');
116
+ for (const row of corpus.rows) {
117
+ if (!reproducibleRegisteredHeldoutRow(row))
118
+ throw new HeldoutError('generator-output');
119
+ if (ids.has(row.id) || families.has(row.familyId) && families.get(row.familyId) !== row.split
120
+ || payloads.has(sha256(row.input)) || new Set(row.requests.map(r => r.id)).size !== row.requests.length
121
+ || row.requests.some(r => !definitions.has(r.definitionId)) || !row.requests.length && row.localOutcome === null) {
122
+ throw new HeldoutError('membership');
123
+ }
124
+ ids.add(row.id);
125
+ families.set(row.familyId, row.split);
126
+ payloads.add(sha256(row.input));
127
+ }
128
+ // A recognizable secret is not synthetic text. Opaque private data still needs operator review.
129
+ if (redactStructured(corpus).sensitivity !== 'none')
130
+ throw new HeldoutError('credential-material');
131
+ }
132
+ export function heldoutExecution(corpus, plan, approval, request) {
133
+ const definition = corpus.definitions.find(d => d.metadata.id === request.definitionId);
134
+ const purpose = 'heldout-study', origin = new URL(JEV_ENDPOINT).origin;
135
+ const projection = { version: 'heldout-v1', provider: 'jev', model: approval.model, region: approval.region, origin,
136
+ purpose, allowIncompleteContext: false, maxSensitivity: 'public',
137
+ fields: [{ pointer: '/payload', output: 'payload', source: 'synthetic-corpus', subject: 'heldout-subject', trust: 'untrusted',
138
+ sensitivity: 'public', purpose, retentionClass: 'qualification', accessScopes: ['heldout-reviewer'], exportPolicy: 'denied',
139
+ deletionPolicy: 'erase', backupPolicy: 'not-persisted', allowedProviders: ['jev'], allowedModels: [approval.model],
140
+ allowedOrigins: [origin], allowedRegions: [approval.region] }] };
141
+ const ruleset = contextLiveRuleset(definition.metadata.id, ['q0'], [definition]);
142
+ const target = contextLiveTarget({ model: approval.model, secretServiceReference: approval.credentialRef }, '1.0.0', plan.requestTimeoutMs);
143
+ return { definition, projection, ruleset, binding: contextLiveBinding(ruleset, ['q0'], target, plan.requestTimeoutMs) };
144
+ }
145
+ export function heldoutExecutionDigest(corpus, plan, approval) {
146
+ return sha256(corpus.definitions.map(definition => {
147
+ const item = heldoutExecution(corpus, plan, approval, { id: 'pin', arm: 'pin', definitionId: definition.metadata.id });
148
+ return { definition: artifactPin(item.definition), ruleset: artifactPin(item.ruleset), binding: artifactPin(item.binding), projection: sha256(item.projection),
149
+ adapter: { id: 'jev', version: '1.0.0' } };
150
+ }));
151
+ }
152
+ export function validateHeldoutBundle(bundle, trustedApprovalDigest) {
153
+ admitEntry(bundle, limits);
154
+ if (Object.keys(bundle).sort().join(',') !== 'approval,corpus,preregistration')
155
+ throw new HeldoutError('bundle-fields');
156
+ const { corpus, preregistration: plan, approval: a } = bundle;
157
+ if (corpus?.study === 'D29') {
158
+ if (D29_PUBLIC_DEMO_CORPORA.has(sha256(corpus)))
159
+ throw new HeldoutError('public-demo-corpus');
160
+ if (D29_PUBLIC_DEMO_SEEDS.has(corpus.provenance?.seed))
161
+ throw new HeldoutError('public-demo-seed');
162
+ // A D29 bundle is paid-eligible only when every row comes from the current
163
+ // paid-eligible D29 generator. Older D29 generators (v1–v7) reproduce public
164
+ // development corpora only, and no other generator (lamp, D17) may be
165
+ // relabelled as D29.
166
+ const rows = Array.isArray(corpus.rows) ? corpus.rows : [];
167
+ if (!rows.length || rows.some(row => !D29_PAID_GENERATOR_IDS.includes(row?.provenance?.generatorId))) {
168
+ throw new HeldoutError('paid-generator');
169
+ }
170
+ }
171
+ if (usesD17MultifactGenerator(corpus?.rows)) {
172
+ // D17-MF (#2850): only its own generator, only D17, only a private seed, only the registered calibrator (artifact mode).
173
+ if (corpus.study !== 'D17' || corpus.rows.some(row => row?.provenance?.generatorId !== D17_MULTIFACT_GENERATOR_ID))
174
+ throw new HeldoutError('paid-generator');
175
+ if (d17MultifactPublicSeed(corpus.provenance?.seed))
176
+ throw new HeldoutError('public-demo-seed');
177
+ if (a?.calibration?.mode !== 'artifact')
178
+ throw new HeldoutError('calibration-scope');
179
+ }
180
+ validateHeldoutInputs(corpus, plan);
181
+ if (a?.priceBound?.outputUsdPerMTok !== 0)
182
+ throw new HeldoutError('free-output-required');
183
+ checkHeldoutSchema('Approval', a);
184
+ if (redactText(JSON.stringify([plan, a])).sensitivity !== 'none')
185
+ throw new HeldoutError('credential-material');
186
+ if (!plan.calibration.allowedModes.includes(a.calibration.mode)
187
+ || a.calibration.mode === 'uncalibrated-diagnostic' && plan.calibration.scope !== 'uncalibrated-diagnostic')
188
+ throw new HeldoutError('calibration-scope');
189
+ if (!heldoutRowsInScope(bundle).length)
190
+ throw new HeldoutError('calibration-phase-empty');
191
+ if (sha256(a) !== trustedApprovalDigest || a.study !== corpus.study || a.corpusDigest !== sha256(corpus)
192
+ || a.preregistrationDigest !== sha256(plan) || a.executionDigest !== heldoutExecutionDigest(corpus, plan, a)
193
+ || a.budget.usd > HELDOUT_CAP_USD[a.study] || a.priorStudySpendUsd >= HELDOUT_CAP_USD[a.study]
194
+ || a.priorPortfolioSpendUsd >= HELDOUT_PORTFOLIO_CAP_USD || a.region.toLowerCase() === 'unknown')
195
+ throw new HeldoutError('approval-pins');
196
+ }
197
+ /** Input-only pricing: Jev has no remote generation limit, so paid output is inadmissible. */
198
+ export function heldoutReservationMicros(a, inputTokenBound) {
199
+ if (a.priceBound.outputUsdPerMTok !== 0)
200
+ throw new HeldoutError('free-output-required');
201
+ if (!Number.isSafeInteger(inputTokenBound) || inputTokenBound < 1)
202
+ throw new HeldoutError('input-bound');
203
+ return dagLiveReservationMicros({ ...a.priceBound, perRequestUsd: undefined }, inputTokenBound)
204
+ + Math.ceil(a.priceBound.perRequestUsd * 1_000_000);
205
+ }
206
+ export function heldoutReservationTokens(plan, inputTokenBound) {
207
+ return inputTokenBound + plan.outputAndHiddenTokenAllowance;
208
+ }
209
+ export function heldoutRowsInScope(bundle) {
210
+ const { calibration } = bundle.approval;
211
+ if (calibration.mode !== 'staged')
212
+ return bundle.corpus.rows;
213
+ const plan = bundle.preregistration.calibration;
214
+ if (plan.scope !== 'calibrated' || !plan.calibrationPhaseSplits?.length)
215
+ throw new HeldoutError('calibration-scope');
216
+ return bundle.corpus.rows.filter(row => calibration.phase === 'test' ? row.split === 'test'
217
+ : plan.calibrationPhaseSplits.includes(row.split));
218
+ }
219
+ export async function heldoutRequest(corpus, plan, approval, row, request) {
220
+ if (!heldoutRowsInScope({ corpus, preregistration: plan, approval }).some(item => item.id === row.id && sha256(item) === sha256(row))) {
221
+ throw new HeldoutError('calibration-phase-row');
222
+ }
223
+ return heldoutRequestSize(corpus, plan, approval, row, request);
224
+ }
225
+ /**
226
+ * The request a row would send and its preregistered size bound, for any row
227
+ * of the corpus whatever the current phase. Only sizing and planning call it
228
+ * directly; dispatch goes through heldoutRequest, which also checks the phase.
229
+ */
230
+ export async function heldoutRequestSize(corpus, plan, approval, row, request) {
231
+ const execution = heldoutExecution(corpus, plan, approval, request);
232
+ const projected = await projectDecisionState(row.input, execution.projection);
233
+ const wire = { state: partitionProjectedState(projected.state, projected.evidence), model: approval.model,
234
+ questions: { q0: JSON.parse(compileJevQuestion(execution.definition).question) } };
235
+ // Provider-added input is covered by a preregistered allowance, not by a remote token limit.
236
+ const requestBytes = Buffer.byteLength(JSON.stringify(wire), 'utf8');
237
+ const inputTokenBound = requestBytes + (plan.providerOverheadTokens ?? 512);
238
+ if (inputTokenBound + plan.outputAndHiddenTokenAllowance > plan.perRequestTokenBound)
239
+ throw new HeldoutError('payload-bound');
240
+ if (redactStructured(projected.state).sensitivity !== 'none')
241
+ throw new HeldoutError('credential-material');
242
+ return { execution, requestDigest: sha256(wire), requestBytes, estimatedTokens: inputTokenBound };
243
+ }
244
+ /**
245
+ * Sizes every request of every corpus row, in every split, against the
246
+ * preregistered per-request bound and refuses (`payload-bound`, naming the row
247
+ * index) before any provider call. A staged run therefore learns at its
248
+ * calibration phase, before any spend, that a later phase could not dispatch.
249
+ */
250
+ export async function assertHeldoutRequestBounds(corpus, plan, approval) {
251
+ for (const [index, row] of corpus.rows.entries())
252
+ for (const request of row.requests) {
253
+ try {
254
+ await heldoutRequestSize(corpus, plan, approval, row, request);
255
+ }
256
+ catch (error) {
257
+ if (error instanceof HeldoutError && error.category === 'payload-bound')
258
+ throw new HeldoutError('payload-bound', `row ${index}`);
259
+ throw error;
260
+ }
261
+ }
262
+ }
263
+ export async function planHeldoutCollection(bundle, digest) {
264
+ validateHeldoutBundle(bundle, digest);
265
+ await assertHeldoutRequestBounds(bundle.corpus, bundle.preregistration, bundle.approval);
266
+ let maximumRequestEstimateTokens = 0, tokens = 0, usdMicros = 0;
267
+ const rows = heldoutRowsInScope(bundle);
268
+ for (const row of rows)
269
+ for (const request of row.requests) {
270
+ const planned = await heldoutRequest(bundle.corpus, bundle.preregistration, bundle.approval, row, request);
271
+ maximumRequestEstimateTokens = Math.max(maximumRequestEstimateTokens, planned.estimatedTokens);
272
+ const reservation = heldoutReservationMicros(bundle.approval, planned.estimatedTokens);
273
+ if (reservation > Math.floor(bundle.approval.budget.usd * 1_000_000))
274
+ throw new HeldoutError('approval-call-budget');
275
+ const attempts = 1 + bundle.preregistration.providerFailurePolicy.maxRetries;
276
+ tokens += attempts * heldoutReservationTokens(bundle.preregistration, planned.estimatedTokens);
277
+ usdMicros += attempts * reservation;
278
+ }
279
+ const attempts = rows.reduce((n, r) => n + r.requests.length, 0) * (1 + bundle.preregistration.providerFailurePolicy.maxRetries);
280
+ const a = bundle.approval;
281
+ return { calibration: structuredClone(a.calibration), rowsInScope: rows.map(row => row.id), providerCalls: 0, maximumAttempts: attempts, maximumRequestEstimateTokens, reservedTokens: tokens, reservedUsdMicros: usdMicros,
282
+ fitsBeforeStop: attempts <= Math.floor(a.budget.calls * 0.8) && tokens <= Math.floor(a.budget.tokens * 0.8)
283
+ && usdMicros <= Math.floor(Math.min(a.budget.usd, HELDOUT_CAP_USD[a.study] - a.priorStudySpendUsd, HELDOUT_PORTFOLIO_CAP_USD - a.priorPortfolioSpendUsd) * 800_000) };
284
+ }
285
+ /** An intentionally unapproved, unpriced form. Unknown attestations stay null, never fixture approvals. */
286
+ export function heldoutApprovalTemplate(corpus, plan) {
287
+ validateHeldoutInputs(corpus, plan);
288
+ const attempts = corpus.rows.reduce((n, row) => n + row.requests.length, 0) * (1 + plan.providerFailurePolicy.maxRetries);
289
+ return { schemaVersion: 'decision-heldout-approval/v1', approved: false, study: corpus.study, runId: null,
290
+ reviewer: null, approvalReference: null, sourceCommit: null, exactHeadCi: null, stagingHost: 'titan', stagingWorkspace: null,
291
+ model: 'jev-1.13.0', servedModel: 'jev-1.13.0', region: null, credentialRef: null, credentialResolverDigest: null,
292
+ corpusDigest: sha256(corpus), preregistrationDigest: sha256(plan), executionDigest: null, calibration: plan.calibration.scope === 'uncalibrated-diagnostic'
293
+ ? { mode: 'uncalibrated-diagnostic' } : null,
294
+ providerTermsReference: null, priceBound: { inputUsdPerMTok: null, outputUsdPerMTok: null, perRequestUsd: null,
295
+ evidenceReferences: [], approvalReference: null }, budget: { calls: Math.max(1, Math.ceil(attempts / 0.8)),
296
+ tokens: Math.max(1, Math.ceil(attempts * (plan.perRequestTokenBound + plan.outputAndHiddenTokenAllowance) / 0.8)), usd: HELDOUT_CAP_USD[corpus.study] },
297
+ priorStudySpendUsd: null, priorPortfolioSpendUsd: null };
298
+ }
299
+ export function validateHeldoutAttempt(attempt) {
300
+ checkHeldoutSchema('Attempt', attempt);
301
+ if (attempt.result?.receipt) {
302
+ validateDecisionDocument(attempt.result.receipt);
303
+ if (attempt.result.receiptDigest !== sha256(attempt.result.receipt))
304
+ throw new HeldoutError('receipt-digest');
305
+ }
306
+ else if (attempt.result?.receiptDigest)
307
+ throw new HeldoutError('receipt-missing');
308
+ }
309
+ //# sourceMappingURL=contract.js.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * D29 generator identities. A leaf module with no imports, so every module in
3
+ * the generator / contract / registry import cycle can read these constants
4
+ * during evaluation regardless of which module is imported first.
5
+ */
6
+ export const D29_V8_GENERATOR_ID = 'd29-synthetic/v8';
7
+ export const D29_V8_SEED = 'd29-study-v8';
8
+ /** Latest D29 generator: fresh private seeds always prepare with it. */
9
+ export const D29_LATEST_GENERATOR_ID = D29_V8_GENERATOR_ID;
10
+ /** Generators whose corpora may be approved for paid collection; older ids are public-replay-only. */
11
+ export const D29_PAID_GENERATOR_IDS = Object.freeze([D29_V8_GENERATOR_ID]);
12
+ //# sourceMappingURL=d29-generator-ids.js.map
@@ -0,0 +1,93 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { sha256 } from '../compile-cache/identity.js';
3
+ import { d29PassageBaseline } from './d29-passage-baseline.js';
4
+ const identifier = '([A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)';
5
+ const attribute = '(port|protocol|region|owner team|major version)';
6
+ const values = '([A-Za-z0-9-]+(?: and [A-Za-z0-9-]+)*)';
7
+ const singleValued = new Set(['region', 'owner team', 'major version']);
8
+ const forms = [
9
+ [new RegExp(`^Module ${identifier} (?:uses|has|runs|listens on|now uses) ${attribute} ${values}$`), 1, 2, 3, false, false],
10
+ [new RegExp(`^[Tt]he ${attribute} recorded for Module ${identifier} is ${values}$`), 2, 1, 3, false, false],
11
+ [new RegExp(`^Port ${values} is the port Module ${identifier} uses$`), 2, 0, 1, false, false],
12
+ [new RegExp(`^Module ${identifier} is configured for, and currently uses, ${attribute} ${values}$`), 1, 2, 3, false, false],
13
+ [new RegExp(`^Module ${identifier} (?:does not use|no longer uses|does not have|no longer has|does not run|no longer runs) ${attribute} ${values}$`), 1, 2, 3, false, true],
14
+ [new RegExp(`^Module ${identifier} uses a single ${attribute}, ${values}, with no alternatives$`), 1, 2, 3, true, false],
15
+ [new RegExp(`^For Module ${identifier}, the ${attribute} is restricted to ${values}$`), 1, 2, 3, true, false],
16
+ [new RegExp(`^Module ${identifier} uses exactly one ${attribute}: ${values}$`), 1, 2, 3, true, false],
17
+ [new RegExp(`^Module ${identifier} only uses ${attribute} ${values}$`), 1, 2, 3, true, false],
18
+ [new RegExp(`^Module ${identifier} uses ${attribute} ${values} and no other (?:port|protocol|region|owner team|major version)$`), 1, 2, 3, true, false],
19
+ ];
20
+ function clauses(text) {
21
+ return text.split(/[.!?](?=\s|$|[)\]]|-->)|;|\r?\n|,?\s+(?:while|whereas|and)\s+(?=Module )/)
22
+ .map(clause => clause.replace(/^(?:-->|<!--|[\s>()\[\]])+/, '').replace(/(?:-->|[\s()\[\]])+$/, ''))
23
+ .filter(Boolean);
24
+ }
25
+ function statement(input) {
26
+ let text = input;
27
+ const tentative = /^(?:An unconfirmed report suggests that|The draft proposes that|It is possible that) /;
28
+ const scope = / (?:in staging|during the pilot|in the test environment)$/;
29
+ const history = /, according to the inventory (?:before the 2025 migration|until the previous release|as of the retired pilot)$/;
30
+ const qualified = tentative.test(text) || scope.test(text) || history.test(text)
31
+ || / (?:might use|reportedly uses|was planned to use) /.test(text);
32
+ text = text.replace(tentative, '').replace(scope, '').replace(history, '')
33
+ .replace(/ (?:might use|reportedly uses|was planned to use) /, ' uses ');
34
+ const noAlternatives = /, with no alternatives$/.test(text);
35
+ if (noAlternatives && !/ uses a single /.test(text))
36
+ text = text.replace(/, with no alternatives$/, '');
37
+ text = text.replace(new RegExp(`^(Module ${identifier}) (.*)$`), (_, prefix, _module, body) => {
38
+ const normalized = body.replace(/^now /, '').replace(/^is now /, 'is ')
39
+ .replace(/^is (?:not|no longer) owned by team /, 'does not use owner team ')
40
+ .replace(/^is owned by team /, 'uses owner team ')
41
+ .replace(/^is (?:not|no longer) deployed in region /, 'does not use region ')
42
+ .replace(/^is deployed in region /, 'uses region ')
43
+ .replace(/^(?:does not listen|no longer listens) on port /, 'does not use port ')
44
+ .replace(/^(?:does not communicate|no longer communicates) over protocol /, 'does not use protocol ')
45
+ .replace(/^listens only on port /, 'only uses port ')
46
+ .replace(/^communicates only over protocol /, 'only uses protocol ')
47
+ .replace(/^listens on port /, 'uses port ')
48
+ .replace(/^communicates over protocol /, 'uses protocol ');
49
+ return `${prefix} ${normalized}`;
50
+ });
51
+ for (const [pattern, moduleIndex, attributeIndex, valuesIndex, exclusive, negated] of forms) {
52
+ const match = pattern.exec(text);
53
+ if (match)
54
+ return { module: match[moduleIndex], attribute: attributeIndex ? match[attributeIndex] : 'port',
55
+ values: match[valuesIndex].split(' and '), exclusive: exclusive || noAlternatives, negated, qualified };
56
+ }
57
+ return null;
58
+ }
59
+ function statements(text) {
60
+ const result = [];
61
+ let previous = null;
62
+ for (const clause of clauses(text)) {
63
+ const continued = /^(?:it |now )/.test(clause) && previous
64
+ ? `Module ${previous} ${clause.replace(/^it /, '')}` : clause;
65
+ const fact = statement(continued);
66
+ if (fact) {
67
+ result.push(fact);
68
+ previous = fact.module;
69
+ }
70
+ else
71
+ previous = null;
72
+ }
73
+ return result;
74
+ }
75
+ /** Visible-text comparator with explicit missing-fact handling; screening-control attempts remain unclassified. */
76
+ export function d29PassageBaselineV2(payload, hardPass) {
77
+ if (!hardPass || payload.kind !== 'citation')
78
+ return d29PassageBaseline(payload, hardPass);
79
+ const claimParts = clauses(payload.claim ?? ''), claim = claimParts.length === 1 ? statement(claimParts[0]) : null;
80
+ if (!claim || claim.negated || claim.qualified || claim.values.length !== 1)
81
+ return { route: 'REVIEW', support: 'unclear' };
82
+ const facts = statements(payload.source ?? '').filter(fact => fact.module === claim.module && fact.attribute === claim.attribute);
83
+ const current = facts.filter(fact => !fact.qualified);
84
+ const contradicts = current.some(fact => fact.negated ? fact.values.includes(claim.values[0])
85
+ : !fact.values.includes(claim.values[0]) && (fact.exclusive || singleValued.has(claim.attribute)));
86
+ const supports = current.some(fact => !fact.negated && fact.values.includes(claim.values[0]));
87
+ const support = contradicts ? 'contradicts' : supports ? 'supports' : facts.length ? 'unclear' : 'does-not-support';
88
+ return { route: support === 'supports' ? 'ADVISORY_READY' : 'REVIEW', support };
89
+ }
90
+ export function d29PassageBaselineV2Digest() {
91
+ return sha256(readFileSync(new URL(import.meta.url), 'utf8'));
92
+ }
93
+ //# sourceMappingURL=d29-passage-baseline-v2.js.map
@@ -0,0 +1,100 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { sha256 } from '../compile-cache/identity.js';
3
+ import { D29_TRAIN } from './d29-pools.js';
4
+ /** The v3 comparator knows only TRAIN-pool wording: every verb, qualifier
5
+ * and criterion pattern below is derived from the imported TRAIN pools, so
6
+ * TEST-pool phrasing (novel verbs, periphrases, coreference) is
7
+ * parser-unseen by construction. Screening-control attempts stay unclassified. */
8
+ const identifier = '([A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)';
9
+ const values = '([A-Za-z0-9-]+(?: and [A-Za-z0-9-]+)*)';
10
+ const singleValued = new Set(['region', 'owner team', 'major version']);
11
+ const escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
12
+ const patterns = [];
13
+ for (const [name, verbs] of Object.entries(D29_TRAIN.verbs)) {
14
+ const attribute = name.replaceAll('-', ' ');
15
+ patterns.push({ pattern: new RegExp(`^Module ${identifier} ${escape(verbs.direct)} ${values}$`), attribute, exclusive: false, negated: false });
16
+ patterns.push({ pattern: new RegExp(`^Module ${identifier} ${escape(verbs.negated)} ${values}$`), attribute, exclusive: false, negated: true });
17
+ patterns.push({ pattern: new RegExp(`^Module ${identifier} ${escape(verbs.movedOut)} ${values}$`), attribute, exclusive: false, negated: true });
18
+ }
19
+ const scopedTails = D29_TRAIN.scoped.map(escape).join('|');
20
+ const tentativeHeads = D29_TRAIN.tentative.map(escape).join('|');
21
+ const temporalTails = D29_TRAIN.temporal.map(escape).join('|');
22
+ const restricted = (attribute) => new RegExp(`^For Module ${identifier}, the ${escape(attribute)} is restricted to ${values}$`);
23
+ function clauses(text) {
24
+ return text.split(/[.!?](?=\s|$|[)\]]|-->)|;|\r?\n|,?\s+(?:while|whereas|and)\s+(?=Module )/)
25
+ .map(clause => clause.replace(/^(?:-->|<!--|[\s>()\[\]])+/, '').replace(/(?:-->|[\s()\[\]])+$/, ''))
26
+ .filter(Boolean);
27
+ }
28
+ function statement(input) {
29
+ let text = input;
30
+ const tentative = new RegExp(`^(?:${tentativeHeads}) `);
31
+ const scope = new RegExp(` (?:${scopedTails})$`);
32
+ const history = new RegExp(`, according to the inventory (?:${temporalTails})$`);
33
+ const qualified = tentative.test(text) || scope.test(text) || history.test(text);
34
+ text = text.replace(tentative, '').replace(scope, '').replace(history, '');
35
+ const noAlternatives = /, with no alternatives$/.test(text);
36
+ if (noAlternatives)
37
+ text = text.replace(/, with no alternatives$/, '');
38
+ for (const attribute of Object.keys(D29_TRAIN.verbs).map(name => name.replaceAll('-', ' '))) {
39
+ const match = restricted(attribute).exec(text);
40
+ if (match)
41
+ return { module: match[1], attribute, values: match[2].split(' and '), exclusive: true, negated: false, qualified };
42
+ }
43
+ for (const { pattern, attribute, exclusive, negated } of patterns) {
44
+ const match = pattern.exec(text);
45
+ if (match)
46
+ return { module: match[1], attribute, values: match[2].split(' and '), exclusive: exclusive || noAlternatives, negated, qualified };
47
+ }
48
+ const paraphraseMatch = new RegExp(`^The (port|protocol|region|owner team|major version) ${escape(D29_TRAIN.paraphraseVerb)} for Module ${identifier} is ${values}$`).exec(text);
49
+ if (paraphraseMatch)
50
+ return { module: paraphraseMatch[2], attribute: paraphraseMatch[1], values: paraphraseMatch[3].split(' and '), exclusive: noAlternatives, negated: false, qualified };
51
+ return null;
52
+ }
53
+ function statements(text) {
54
+ const result = [];
55
+ let previous = null;
56
+ for (const clause of clauses(text)) {
57
+ // TRAIN wording has no pronominal coreference; only `now ...` continuations.
58
+ const continued = /^now /.test(clause) && previous ? `Module ${previous} ${clause.slice(4)}` : clause;
59
+ const fact = statement(continued);
60
+ if (fact) {
61
+ result.push(fact);
62
+ previous = fact.module;
63
+ }
64
+ else
65
+ previous = null;
66
+ }
67
+ return result;
68
+ }
69
+ function trainReadyForms(module, name) {
70
+ return new Set(['exact', 'verified', 'checklist'].map(mode => D29_TRAIN.criterion[mode](module, name, '2')));
71
+ }
72
+ /** Train-wording comparator; screening-control attempts remain unclassified. */
73
+ export function d29PassageBaselineV3(payload, hardPass) {
74
+ if (!hardPass)
75
+ return { route: 'REVIEW', support: null };
76
+ if (payload.kind === 'phase-criterion') {
77
+ const match = new RegExp(`^Module ${identifier} requires independently verified (rollback coverage|security review sign-off|migration test coverage) for all components of the current release\\.$`).exec(payload.criterion ?? '');
78
+ if (!match)
79
+ return { route: 'REVIEW', support: null };
80
+ const [, module, name] = match;
81
+ const ready = clauses(payload.evidence ?? '').some(sentence => trainReadyForms(module, name).has(sentence));
82
+ return { route: ready ? 'ADVISORY_READY' : 'REVIEW', support: null };
83
+ }
84
+ if (payload.kind !== 'citation')
85
+ return { route: 'REVIEW', support: 'unclear' };
86
+ const claimParts = clauses(payload.claim ?? ''), claim = claimParts.length === 1 ? statement(claimParts[0]) : null;
87
+ if (!claim || claim.negated || claim.qualified || claim.values.length !== 1)
88
+ return { route: 'REVIEW', support: 'unclear' };
89
+ const facts = statements(payload.source ?? '').filter(fact => fact.module === claim.module && fact.attribute === claim.attribute);
90
+ const current = facts.filter(fact => !fact.qualified);
91
+ const contradicts = current.some(fact => fact.negated ? fact.values.includes(claim.values[0])
92
+ : !fact.values.includes(claim.values[0]) && (fact.exclusive || singleValued.has(claim.attribute)));
93
+ const supports = current.some(fact => !fact.negated && fact.values.includes(claim.values[0]));
94
+ const support = contradicts ? 'contradicts' : supports ? 'supports' : facts.length ? 'unclear' : 'does-not-support';
95
+ return { route: support === 'supports' ? 'ADVISORY_READY' : 'REVIEW', support };
96
+ }
97
+ export function d29PassageBaselineV3Digest() {
98
+ return sha256(readFileSync(new URL(import.meta.url), 'utf8'));
99
+ }
100
+ //# sourceMappingURL=d29-passage-baseline-v3.js.map
@@ -0,0 +1,66 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { sha256 } from '../compile-cache/identity.js';
3
+ const identifier = '([A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)';
4
+ const attribute = '(port|protocol|region|owner team|major version)';
5
+ const values = '([A-Za-z0-9-]+(?: and [A-Za-z0-9-]+)*)';
6
+ const singleValued = new Set(['region', 'owner team', 'major version']);
7
+ function sentences(text) {
8
+ return text.split(/[.!?](?=\s|$|[)\]]|-->)|;|\r?\n/)
9
+ .map(sentence => sentence.replace(/^(?:-->|<!--|[\s>()\[\]])+/, '').replace(/(?:-->|[\s()\[\]])+$/, ''))
10
+ .filter(Boolean);
11
+ }
12
+ function statement(text) {
13
+ const forms = [
14
+ [new RegExp(`^Module ${identifier} (?:uses|has|runs|listens on|now uses) ${attribute} ${values}$`), 1, 2, 3, false, false],
15
+ [new RegExp(`^The ${attribute} recorded for Module ${identifier} is ${values}$`), 2, 1, 3, false, false],
16
+ [new RegExp(`^Port ${values} is the port Module ${identifier} uses$`), 2, 0, 1, false, false],
17
+ [new RegExp(`^Module ${identifier} is configured for, and currently uses, ${attribute} ${values}$`), 1, 2, 3, false, false],
18
+ [new RegExp(`^Module ${identifier} (?:does not use|no longer uses|does not have|no longer has|does not run|no longer runs) ${attribute} ${values}$`), 1, 2, 3, false, true],
19
+ [new RegExp(`^Module ${identifier} uses a single ${attribute}, ${values}, with no alternatives$`), 1, 2, 3, true, false],
20
+ [new RegExp(`^For Module ${identifier}, the ${attribute} is restricted to ${values}$`), 1, 2, 3, true, false],
21
+ [new RegExp(`^Module ${identifier} uses exactly one ${attribute}: ${values}$`), 1, 2, 3, true, false],
22
+ [new RegExp(`^Module ${identifier} only uses ${attribute} ${values}$`), 1, 2, 3, true, false],
23
+ [new RegExp(`^Module ${identifier} uses ${attribute} ${values} and no other (?:port|protocol|region|owner team|major version)$`), 1, 2, 3, true, false],
24
+ ];
25
+ for (const [pattern, moduleIndex, attributeIndex, valuesIndex, exclusive, negated] of forms) {
26
+ const match = pattern.exec(text);
27
+ if (match)
28
+ return { module: match[moduleIndex], attribute: attributeIndex ? match[attributeIndex] : 'port',
29
+ values: match[valuesIndex].split(' and '), exclusive, negated };
30
+ }
31
+ return null;
32
+ }
33
+ function verifiedCriterion(criterion, evidence) {
34
+ const match = new RegExp(`^Module ${identifier} requires independently verified (rollback coverage|security review sign-off|migration test coverage) for all components of the current release\\.$`).exec(criterion);
35
+ if (!match)
36
+ return false;
37
+ const [, module, name] = match;
38
+ const accepted = new Set([
39
+ `Module ${module} has ${name} independently verified for all components of the current release`,
40
+ `An independent assessor verified ${name} for every component in the current release of Module ${module}`,
41
+ `The current release checklist for Module ${module} records independent verification of ${name} for all components`,
42
+ ]);
43
+ return sentences(evidence).some(sentence => accepted.has(sentence));
44
+ }
45
+ /** Deliberately naive visible-text comparator: context and screening-control attempts are not classified. */
46
+ export function d29PassageBaseline(payload, hardPass) {
47
+ if (!hardPass)
48
+ return { route: 'REVIEW', support: null };
49
+ if (payload.kind === 'citation') {
50
+ const claimParts = sentences(payload.claim ?? ''), claim = claimParts.length === 1 ? statement(claimParts[0]) : null;
51
+ if (!claim || claim.negated || claim.values.length !== 1)
52
+ return { route: 'REVIEW', support: 'unclear' };
53
+ const facts = sentences(payload.source ?? '').map(statement).filter((fact) => fact !== null && fact.module === claim.module && fact.attribute === claim.attribute);
54
+ const contradicts = facts.some(fact => fact.negated ? fact.values.includes(claim.values[0])
55
+ : !fact.values.includes(claim.values[0]) && (fact.exclusive || singleValued.has(claim.attribute)));
56
+ const supports = facts.some(fact => !fact.negated && fact.values.includes(claim.values[0]));
57
+ const support = contradicts ? 'contradicts' : supports ? 'supports' : 'unclear';
58
+ return { route: support === 'supports' ? 'ADVISORY_READY' : 'REVIEW', support };
59
+ }
60
+ return { route: payload.kind === 'phase-criterion' && verifiedCriterion(payload.criterion ?? '', payload.evidence ?? '')
61
+ ? 'ADVISORY_READY' : 'REVIEW', support: null };
62
+ }
63
+ export function d29PassageBaselineDigest() {
64
+ return sha256(readFileSync(new URL(import.meta.url), 'utf8'));
65
+ }
66
+ //# sourceMappingURL=d29-passage-baseline.js.map