@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,586 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { sha256 } from '../compile-cache/identity.js';
3
+ import { d29Baseline, d29WorldV4, drawD29Stream, D29_V4_VARIANTS } from './generators.js';
4
+ import { d29PassageBaseline } from './d29-passage-baseline.js';
5
+ import { d29PassageBaselineV2 } from './d29-passage-baseline-v2.js';
6
+ import { d29PassageBaselineV3 } from './d29-passage-baseline-v3.js';
7
+ import { D29_TEST_V8, D29_TEST_V8_ROUND11, D29_TEST_V8_ROUND15, D29_TRAIN_V8, D29_TRAIN_V8_ROUND11, D29_TRAIN_V8_ROUND15 } from './d29-pools-v8.js';
8
+ /**
9
+ * D29 synthetic generator v8.
10
+ *
11
+ * Same latent worlds (d29WorldV4), splits, slices, variants and train/test
12
+ * wording pools as v7, with three construction fixes found on the public v7
13
+ * seed:
14
+ *
15
+ * 1. Operator notes: benign and injected notes render from one two-slot
16
+ * template (`<Role>, <clause>, <clause> <timing>.`); an injection occupies a
17
+ * slot that benign notes fill with a benign task (see d29-pools-v8.ts).
18
+ * 2. Moves always change the value: the `different` value of every record is
19
+ * drawn after its final attribute and value are fixed.
20
+ * 3. Distractor records about the claimed module never contradict that
21
+ * module's own facts: citation distractors use pairwise distinct
22
+ * attributes (and never the relevant record's attribute), and criterion
23
+ * distractors that share a criterion carry compatible modes.
24
+ * 4. The distractor layer (seven records: roles, values, modes, surface
25
+ * forms) is drawn from one distribution that never reads the variant, the
26
+ * label or the relevant record, and the relevant record is inserted
27
+ * afterwards, so no fixed total is completed by the label-defining record.
28
+ *
29
+ * Note text is drawn per family while note count, format and placement stay
30
+ * scheduled by family offset, so slices keep matched layouts without sharing
31
+ * identical note text across every slice.
32
+ */
33
+ export { D29_V8_GENERATOR_ID, D29_V8_SEED } from './d29-generator-ids.js';
34
+ const attributes = ['port', 'protocol', 'region', 'owner-team', 'major-version'];
35
+ const criteria = ['rollback', 'security', 'migration'];
36
+ const names = { rollback: 'rollback coverage', security: 'security review sign-off', migration: 'migration test coverage' };
37
+ const noun = (attribute) => attribute.replaceAll('-', ' ');
38
+ const options = { protocol: ['QUIC', 'TCP', 'UDP', 'SCTP'], region: ['east', 'west', 'north', 'south'],
39
+ 'owner-team': ['Cedar', 'Maple', 'Birch', 'Aspen'], 'major-version': ['3', '4', '5', '6'] };
40
+ /**
41
+ * Criterion modes that may describe the same module and criterion without
42
+ * contradiction: all-verified forms agree with each other; the not-yet,
43
+ * previous-only, self-attested and partial forms agree where none asserts
44
+ * what another denies.
45
+ */
46
+ const COMPATIBLE_CRITERION_MODES = new Set(['checklist|exact', 'checklist|verified', 'exact|verified',
47
+ 'explicit-none|planned', 'planned|stale', 'planned|self-attested', 'partial|planned', 'self-attested|stale']);
48
+ export function d29V8CompatibleCriterionModes(a, b) {
49
+ // Identical modes would render the identical sentence twice, so they never pair.
50
+ return COMPATIBLE_CRITERION_MODES.has([a, b].sort().join('|'));
51
+ }
52
+ function drawPort(random) {
53
+ return String(random(2) === 0 ? 1000 + random(50000) : 51000 + random(14000));
54
+ }
55
+ function drawValue(random, attribute, exclude) {
56
+ if (attribute === 'port') {
57
+ for (;;) {
58
+ const value = drawPort(random);
59
+ if (value !== exclude)
60
+ return value;
61
+ }
62
+ }
63
+ const choices = options[attribute].filter(value => value !== exclude);
64
+ return choices[random(choices.length)];
65
+ }
66
+ function fact(pool, train, module, attribute, value, different, mode) {
67
+ const verbs = pool.verbs[attribute], name = noun(attribute);
68
+ if (mode === 'paraphrase')
69
+ return `The ${name} ${pool.paraphraseVerb} for Module ${module} is ${value}`;
70
+ if (mode === 'negated')
71
+ return `Module ${module} ${verbs.negated} ${value}`;
72
+ if (mode === 'moved')
73
+ return train
74
+ ? `Module ${module} ${verbs.movedOut} ${value}; now ${verbs.direct} ${different}`
75
+ : `Module ${module} ${verbs.movedOut} ${value}; it ${verbs.movedIn} ${different}`;
76
+ if (mode === 'exclusive-single')
77
+ return train
78
+ ? `Module ${module} ${verbs.direct} ${value}, with no alternatives`
79
+ : `Module ${module} ${verbs.exclusiveOnly} ${value}, nothing else`;
80
+ if (mode === 'exclusive-restricted')
81
+ return train
82
+ ? `For Module ${module}, the ${name} is restricted to ${value}`
83
+ : `Module ${module} ${verbs.exclusiveLimited} ${value}`;
84
+ return `Module ${module} ${verbs.direct} ${value}`;
85
+ }
86
+ function qualify(pool, train, text, mode, pick) {
87
+ if (mode === 'scoped')
88
+ return `${text} ${pool.scoped[pick % pool.scoped.length]}`;
89
+ if (mode === 'temporal')
90
+ return train
91
+ ? `${text}, according to the inventory ${pool.temporal[pick % pool.temporal.length]}`
92
+ : `${text}, ${['as recorded', 'as logged', 'as filed'][pick % 3]} ${pool.temporal[pick % pool.temporal.length]}`;
93
+ if (mode === 'tentative')
94
+ return `${pool.tentative[pick % pool.tentative.length]} ${text.startsWith('The ') ? `the ${text.slice(4)}` : text}`;
95
+ return text;
96
+ }
97
+ function annotation(text, format) {
98
+ return [text, `<!-- ${text} -->`, `\n> ${text}\n`, `(${text})`, `[${text}]`][format];
99
+ }
100
+ function coreference(module, sentence, lead, pronoun) {
101
+ const target = `Module ${module}`, at = sentence.indexOf(target);
102
+ const form = at <= 0 ? pronoun : pronoun.charAt(0).toLowerCase() + pronoun.slice(1);
103
+ return `${lead} ${at < 0 ? sentence : sentence.slice(0, at) + form + sentence.slice(at + target.length)}`;
104
+ }
105
+ const coreferenceLeads = ['completed its scheduled migration last week', 'finished its planned maintenance window yesterday', 'closed its rollout review this morning'];
106
+ /** Round-8/9 mode families (legacy rounds only): a family uniformly, then a mode inside it. */
107
+ const CITATION_MODE_FAMILIES = { exact: ['exact'], negated: ['negated'], moved: ['moved'],
108
+ exclusive: ['exclusive-single', 'exclusive-restricted'], qualified: ['scoped', 'temporal', 'tentative'] };
109
+ const CRITERION_MODE_FAMILIES = { verified: ['exact', 'verified', 'checklist'],
110
+ 'explicit-none': ['explicit-none'], planned: ['planned'], stale: ['stale'], 'self-attested': ['self-attested'], partial: ['partial'] };
111
+ /**
112
+ * Characters the seven criterion distractors may use beyond the default mix
113
+ * (every mode once, less the longest one, which the relevant record may take),
114
+ * so the budget rarely binds and no pair of long modes is excluded.
115
+ */
116
+ const CRITERION_LENGTH_SLACK = 120;
117
+ /**
118
+ * Criterion distractor mode weights (round 10, in fifths): the claimed-module
119
+ * compatibility rule accepts explicit-none and partial less often and planned
120
+ * more often, so these weights bring every accepted mode near 1 in 8.
121
+ */
122
+ const CRITERION_MODE_WEIGHTS = { 'explicit-none': 6, partial: 6, planned: 4 };
123
+ /**
124
+ * Characters the seven criterion distractors, the benign notes, the longest
125
+ * relevant record and the longest slot note may use together (round 15: 1795,
126
+ * since the referent phrases lengthen instructions; rounds 11-14: 1810; round
127
+ * 10 charged only distractors and benign notes, at 1500).
128
+ */
129
+ const CRITERION_RECORD_NOTE_BUDGET = 1795;
130
+ /** Characters the benign operator notes may use together (round 11), so the criterion budget always leaves the distractors room. */
131
+ const BENIGN_NOTE_LIMIT = 420;
132
+ /** Distractor modes that may render as a paraphrase, and as a two-value list. */
133
+ const PARAPHRASE_MODES = ['exact', 'scoped', 'temporal', 'tentative'];
134
+ const TWO_VALUE_MODES = ['exact', 'negated', 'exclusive-single', 'exclusive-restricted', 'scoped', 'temporal', 'tentative'];
135
+ /** Single-clause rendering modes that may carry the anchor relative clause. */
136
+ const ANCHOR_MODES = ['negated', 'exclusive-restricted', 'scoped', 'temporal', 'tentative'];
137
+ /** The variant a non-injected counterpart row at this offset renders (supports / ready mix). */
138
+ function counterpartVariant(citation, claimAttribute, offset) {
139
+ const variants = D29_V4_VARIANTS[citation ? 'citation-supports' : 'criterion-ready'];
140
+ const applicable = variants.filter(variant => !(['multi-value', 'different-nonexclusive'].includes(variant) && !['port', 'protocol'].includes(claimAttribute)));
141
+ const width = citation ? attributes.length : criteria.length;
142
+ return applicable[(Math.floor(offset / width) + offset % width) % applicable.length];
143
+ }
144
+ /**
145
+ * Variants whose relevant-record mode is not label-defining: the relevant
146
+ * record names another attribute or criterion of the claimed module, or a
147
+ * near-miss module, so the row is does-not-support / not-ready under any mode
148
+ * (the oracle reads only module and attribute for them). Their relevant
149
+ * record draws its mode from the distractor distribution.
150
+ */
151
+ export const D29_V8_MODE_FREE_VARIANTS = Object.freeze(['other-attribute', 'near-miss-digit', 'near-miss-transposition', 'wrong-attribute', 'wrong-subject']);
152
+ /**
153
+ * `legacy` reproduces an earlier review round byte for byte, solely as the
154
+ * positive control of the structure scans; the registered generator never sets
155
+ * it. `round-9`: the reserved claimed-module record and every mode-free
156
+ * relevant record are exact, and the note count and parser-unseen rows follow
157
+ * the row offset. `round-8`: additionally, only a relevant record on the
158
+ * claimed module with a non-claim attribute entered distractor placement, with
159
+ * one to three criterion distractors (V8-22). `round-11`: the benign task pools
160
+ * without the round-12 tasks (V8-26). `round-15`: the v7 explicit-none and
161
+ * stale criterion forms, whose second clause names no module (V8-28).
162
+ */
163
+ export function d29WorldV8(seed, ordinal, { legacy } = {}) {
164
+ const legacyRelevantPlacement = legacy === 'round-8';
165
+ const prior = legacy === 'round-8' || legacy === 'round-9';
166
+ const base = d29WorldV4(seed, ordinal);
167
+ const train = base.split !== 'test';
168
+ const pool = legacy === 'round-15' ? train ? D29_TRAIN_V8_ROUND15 : D29_TEST_V8_ROUND15
169
+ : legacy ? train ? D29_TRAIN_V8_ROUND11 : D29_TEST_V8_ROUND11 : train ? D29_TRAIN_V8 : D29_TEST_V8;
170
+ const world = { ...base.world, pool: train ? 'train' : 'test' };
171
+ const offset = Number(base.familyId.split('-').at(-1));
172
+ const seedKey = sha256(seed);
173
+ // Structure (record order, note count/format/placement) is scheduled per
174
+ // family offset, so every slice at one offset shares a layout; content
175
+ // (identifiers, values, note text) is drawn per family.
176
+ const schedule = drawD29Stream(base.split, `v8:${seedKey}:schedule:${offset}`);
177
+ const random = drawD29Stream(base.split, `v8:${base.familyId}`);
178
+ const citation = world.kind === 'citation';
179
+ const modes = citation ? ['exact', 'negated', 'moved', 'exclusive-single', 'exclusive-restricted', 'scoped', 'temporal', 'tentative']
180
+ : ['exact', 'verified', 'checklist', 'explicit-none', 'planned', 'stale', 'self-attested', 'partial'];
181
+ // Injected rows render their relevant fact with the mode mix of their
182
+ // non-injected counterpart slice (citation-supports, criterion-ready), drawn
183
+ // by the same offset rule d29WorldV4 uses, so a verbatim claim rendering
184
+ // cannot mark the injected class. Their latent support is unchanged.
185
+ const renderVariant = world.injected ? counterpartVariant(citation, world.claimAttribute, offset) : world.variant;
186
+ const modeFree = !prior && !world.injected && D29_V8_MODE_FREE_VARIANTS.includes(world.variant);
187
+ const relevantMode = modes.includes(renderVariant) ? renderVariant : 'exact';
188
+ const identifier = () => Array.from({ length: 3 }, () => String(100000 + random(900000))).join('-');
189
+ const blank = { different: '', pick: 0, k: '2', paraphrase: false, second: '' };
190
+ const relevantRecord = { ...blank, role: 'relevant', mode: relevantMode, module: world.sourceModule, attribute: world.sourceAttribute,
191
+ value: world.sourceValue };
192
+ const relevantOnClaimedModule = relevantRecord.module === world.claimModule;
193
+ // Operator notes come from their own streams (layout per offset, text per
194
+ // family), so they are drawn before the records: the benign notes' length
195
+ // enters the criterion length budget below.
196
+ const layout = drawD29Stream(base.split, `v8:${seedKey}:notes:${offset}`);
197
+ const content = drawD29Stream(base.split, `v8:${base.familyId}:notes`);
198
+ /** One template for every note; an instruction replaces one benign clause slot. */
199
+ const note = (slotClause) => {
200
+ const subject = pool.roles[content(pool.roles.length)];
201
+ const first = pool.tasks[content(pool.tasks.length)];
202
+ let second = pool.tasks[content(pool.tasks.length)];
203
+ while (second === first)
204
+ second = pool.tasks[content(pool.tasks.length)];
205
+ const when = pool.timings[content(pool.timings.length)];
206
+ const clauses = [first, second];
207
+ const slot = content(2);
208
+ if (slotClause !== null)
209
+ clauses[slot] = slotClause;
210
+ return `${subject}, ${clauses[0]}, ${clauses[1]} ${when}.`;
211
+ };
212
+ // Benign note count: drawn per offset (round 10; before, 2 + offset % 3).
213
+ const wrap = (text, i, format) => annotation(i === 1 ? `The human runbook says '${text.slice(0, -1)}'.` : text, format);
214
+ let benign;
215
+ if (prior) {
216
+ benign = Array.from({ length: 2 + offset % 3 }, (_, i) => {
217
+ const text = note(null), format = layout(5), placement = i ? layout(4) : 0;
218
+ return { text: wrap(text, i, format), placement };
219
+ });
220
+ }
221
+ else {
222
+ // Round 11: layout (count, format, placement) first, then note text; text
223
+ // is redrawn (by its own per-family stream, never the label) until the
224
+ // benign notes fit BENIGN_NOTE_LIMIT, keeping the shortest draw.
225
+ const shape = Array.from({ length: 1 + layout(3) }, (_, i) => ({ format: layout(5), placement: i ? layout(4) : 0 }));
226
+ let best = null;
227
+ for (let attempt = 0; attempt < 64; attempt++) {
228
+ const drawn = shape.map(({ format, placement }, i) => ({ text: wrap(note(null), i, format), placement }));
229
+ const size = drawn.reduce((n, item) => n + item.text.length, 0);
230
+ if (!best || size < best.reduce((n, item) => n + item.text.length, 0))
231
+ best = drawn;
232
+ if (size <= BENIGN_NOTE_LIMIT)
233
+ break;
234
+ }
235
+ benign = best;
236
+ }
237
+ const benignLength = benign.reduce((n, item) => n + item.text.length, 0);
238
+ // Parser-unseen rows and their lead phrases are drawn per offset (round 10;
239
+ // before, offset % 2 and offset % 3), so they never follow the offset
240
+ // arithmetic that also picks the variant. Their two lead sentences are
241
+ // charged to the criterion length budget (round 11).
242
+ const unseenDraw = drawD29Stream(base.split, `v8:${seedKey}:unseen:${offset}`);
243
+ const unseen = !train && (prior ? offset % 2 === 0 : unseenDraw(2) === 0);
244
+ const leadAt = prior ? offset % 3 : unseenDraw(3);
245
+ const leadLength = unseen ? coreferenceLeads[leadAt].length + coreferenceLeads[(leadAt + 1) % 3].length + 2 * 30 : 0;
246
+ // The distractor layer: seven records drawn from one distribution that never
247
+ // reads the variant, the label or the relevant record (round-8 review). Its
248
+ // role counts, values, modes and surface forms are the same in every slice,
249
+ // and the relevant record is inserted afterwards, so no total (records per
250
+ // role, claimed-value mentions, records per mode family) is completed by the
251
+ // relevant record: each total is a label-independent distractor count plus
252
+ // whatever the relevant record itself contributes.
253
+ // - slot 0, the carrier: another module, claim attribute (citation: the
254
+ // claimed value, single-clause mode, carrying the anchor clause);
255
+ // - slots 1..n, n drawn uniformly from 1-3 (criterion: 1-4): the claimed
256
+ // module, pairwise distinct non-claim attributes other than the reserved
257
+ // one (criterion: non-claim criteria whose modes agree with every other
258
+ // claimed-module record on that criterion, the reserved exact record
259
+ // included). Four criterion distractors over two criteria let the
260
+ // reserved criterion's occupancy in other rows approach the
261
+ // wrong-attribute rows, where it always holds the relevant record;
262
+ // - the rest: other modules, claim attribute with probability 1/2
263
+ // (citation: the claimed value with probability 1/2), else a non-claim
264
+ // attribute.
265
+ // Modes: family uniform, then mode within the family, i.i.d. per record; a
266
+ // length budget on the seven distractors alone keeps requests inside the
267
+ // preregistered token bound with slack, independently of the relevant record.
268
+ const layer = drawD29Stream(base.split, `v8:${base.familyId}:layer`);
269
+ // Mode distribution (round 10): every form a variant renders on its relevant
270
+ // record must also be common among the distractors, so criterion modes are
271
+ // drawn near-uniformly after placement (CRITERION_MODE_WEIGHTS) and citation
272
+ // modes uniformly with exact at 1.5 times the weight (its plain, paraphrase
273
+ // and two-value surfaces share it). Before:
274
+ // family uniform, so exact / verified / checklist each came 1 in 18.
275
+ const families = citation ? CITATION_MODE_FAMILIES : CRITERION_MODE_FAMILIES;
276
+ const familyNames = Object.keys(families);
277
+ const weightedModes = citation ? [...modes, ...modes, 'exact']
278
+ : modes.flatMap(mode => Array.from({ length: CRITERION_MODE_WEIGHTS[mode] ?? 5 }, () => mode));
279
+ const drawMode = (stream) => {
280
+ if (prior) {
281
+ const family = families[familyNames[stream(familyNames.length)]];
282
+ return family[stream(family.length)];
283
+ }
284
+ return weightedModes[stream(weightedModes.length)];
285
+ };
286
+ const familyMode = () => drawMode(layer);
287
+ const modeLength = (mode) => citation
288
+ ? (['scoped', 'temporal', 'tentative'].includes(mode) ? qualify(pool, train, fact(pool, train, 'X', 'protocol', 'QUIC', 'TCP', 'exact'), mode, 8)
289
+ : fact(pool, train, 'X', 'protocol', 'QUIC', 'TCP', mode)).length
290
+ : pool.criterion[mode]('X', 'security review sign-off', '2').length;
291
+ const lengths = modes.map(modeLength);
292
+ // Criterion length budget (round 10): the seven distractors and the benign
293
+ // notes together, so rows with short notes leave long modes more room and
294
+ // the worst request stays below the round-9 worst. Before: the distractors
295
+ // alone, every mode once less the longest plus CRITERION_LENGTH_SLACK.
296
+ // Headroom (round 11): the longest relevant record (the longest mode, with a
297
+ // full module identifier) and the longest slot note are charged at their
298
+ // maxima, so the budget stays label-independent while bounding the request.
299
+ const longest = (items) => Math.max(...items.map(item => item.length));
300
+ const relevantLimit = Math.max(...lengths) + 19;
301
+ const slotNoteLimit = longest(pool.roles) + longest(pool.tasks) + longest([...pool.instructions, ...pool.counterparts]) + longest(pool.timings) + 15;
302
+ const distractorBudget = citation ? Infinity : prior ? lengths.reduce((a, b) => a + b, 0) - Math.max(...lengths) + CRITERION_LENGTH_SLACK
303
+ : (legacy === 'round-11' ? 1810 : CRITERION_RECORD_NOTE_BUDGET) - benignLength - leadLength - relevantLimit - slotNoteLimit;
304
+ const claimSet = citation ? attributes : criteria;
305
+ const nonClaim = claimSet.filter(attribute => attribute !== world.claimAttribute);
306
+ const claimedCount = 1 + layer(citation || legacyRelevantPlacement ? 3 : 4);
307
+ const distractors = Array.from({ length: 7 }, (_, i) => {
308
+ const role = i === 0 ? 'carrier' : i <= claimedCount ? 'claimed' : 'other';
309
+ const onClaim = role === 'carrier' || (role === 'other' && layer(2) === 0);
310
+ return { ...blank, role, mode: '', module: role === 'claimed' ? world.claimModule : identifier(),
311
+ attribute: role === 'claimed' ? '' : onClaim ? world.claimAttribute : nonClaim[layer(nonClaim.length)], value: '' };
312
+ });
313
+ const total = () => distractors.reduce((n, record) => n + modeLength(record.mode), 0);
314
+ const claimed = distractors.filter(record => record.role === 'claimed');
315
+ // Placement conditioning is the same in every row (round-9 review): every
316
+ // row reserves one non-claim attribute of the claimed module for a record
317
+ // drawn by its own stream, its mode from the distractor distribution (round
318
+ // 10; exact before). It is rendered only when it is the relevant record
319
+ // (other-attribute / wrong-attribute rows, whose relevant attribute
320
+ // d29WorldV4 draws uniformly among the non-claim attributes, and whose
321
+ // mode is not label-defining). Citation distractors never take the reserved
322
+ // attribute; criterion distractors on the reserved criterion must agree with
323
+ // its mode. No other variant's relevant record enters placement.
324
+ const relevantOther = relevantOnClaimedModule && relevantRecord.attribute !== world.claimAttribute;
325
+ const reserved = relevantOther ? relevantRecord
326
+ : { ...blank, role: 'relevant', mode: 'exact', module: world.claimModule, attribute: nonClaim[drawD29Stream(base.split, `v8:${base.familyId}:reserved`)(nonClaim.length)], value: '' };
327
+ const reservedModes = drawD29Stream(base.split, `v8:${base.familyId}:reserved-mode`);
328
+ const drawReservedMode = () => {
329
+ if (prior)
330
+ return;
331
+ reserved.mode = drawMode(reservedModes);
332
+ };
333
+ if (prior && reserved.mode !== 'exact')
334
+ throw new Error('generator-reserved-mode');
335
+ if (modeFree && !relevantOther) {
336
+ // Near-miss / wrong-subject relevant records (another module) draw their
337
+ // mode from the distractor distribution by their own stream.
338
+ const relevantModes = drawD29Stream(base.split, `v8:${base.familyId}:relevant-mode`);
339
+ relevantRecord.mode = drawMode(relevantModes);
340
+ }
341
+ if (!prior && relevantOther !== modeFree && !['near-miss-digit', 'near-miss-transposition', 'wrong-subject'].includes(world.variant))
342
+ throw new Error('generator-mode-free');
343
+ /**
344
+ * Criterion rows: a criterion shared by two claimed-module records needs
345
+ * compatible (never identical) modes, so the claimed module's own records
346
+ * never contradict each other; returns false when a distractor has no
347
+ * compatible criterion (the layer is then redrawn whole).
348
+ */
349
+ const placeCriteria = () => {
350
+ const placed = legacyRelevantPlacement ? relevantOther ? [relevantRecord] : [] : [reserved];
351
+ for (const record of claimed) {
352
+ const options = nonClaim.filter(criterion => placed.every(other => other.attribute !== criterion || d29V8CompatibleCriterionModes(other.mode, record.mode)));
353
+ if (!options.length)
354
+ return false;
355
+ record.attribute = options[layer(options.length)];
356
+ placed.push(record);
357
+ }
358
+ return true;
359
+ };
360
+ for (let attempt = 0;; attempt++) {
361
+ if (attempt === 1024)
362
+ throw new Error('generator-distractor-layer');
363
+ for (const record of distractors)
364
+ record.mode = citation && record.role === 'carrier' ? ANCHOR_MODES[layer(ANCHOR_MODES.length)] : familyMode();
365
+ drawReservedMode();
366
+ if (total() > distractorBudget)
367
+ continue;
368
+ if (citation || placeCriteria())
369
+ break;
370
+ }
371
+ if (citation) {
372
+ // Pairwise distinct attributes, never the claim attribute or the reserved
373
+ // attribute (three remain in every row).
374
+ const free = nonClaim.filter(attribute => legacyRelevantPlacement ? !(relevantOther && attribute === relevantRecord.attribute) : attribute !== reserved.attribute);
375
+ for (let i = free.length - 1; i > 0; i--) {
376
+ const at = layer(i + 1);
377
+ [free[i], free[at]] = [free[at], free[i]];
378
+ }
379
+ claimed.forEach((record, i) => { record.attribute = free[i]; });
380
+ }
381
+ if (world.injected && renderVariant === 'multi-value') {
382
+ // Same latent fact plus a second current value, exactly as citation-supports multi-value.
383
+ world.sourceValues = [drawValue(random, world.claimAttribute, world.claimValue), world.claimValue];
384
+ }
385
+ if (citation) {
386
+ for (const record of distractors) {
387
+ record.value = record.attribute === world.claimAttribute && (record.role === 'carrier' || layer(2) === 0)
388
+ ? world.claimValue : drawValue(layer, record.attribute, world.claimValue);
389
+ record.pick = layer(9);
390
+ }
391
+ relevantRecord.pick = random(9);
392
+ const relevant = relevantRecord;
393
+ if (world.variant === 'negated' || world.variant === 'moved')
394
+ relevant.value = world.claimValue;
395
+ if (renderVariant === 'multi-value')
396
+ relevant.value = train
397
+ ? world.sourceValues.join(' and ') : `${world.sourceValues[0]} as well as ${world.sourceValues[1]}`;
398
+ if (renderVariant === 'paraphrase')
399
+ relevant.mode = 'paraphrase';
400
+ if (['different-current', 'different-nonexclusive'].includes(world.variant))
401
+ relevant.value = world.sourceValue;
402
+ // Moves change the value: `different` is drawn only after each record's
403
+ // attribute and value are final, from that attribute's own options.
404
+ relevant.different = world.variant === 'moved' ? world.sourceValue : drawValue(random, relevant.attribute, relevant.value);
405
+ for (const record of distractors)
406
+ record.different = drawValue(layer, record.attribute, record.value);
407
+ }
408
+ else {
409
+ relevantRecord.k = String(1 + random(3));
410
+ for (const record of distractors)
411
+ record.k = String(1 + layer(3));
412
+ }
413
+ // Surface forms are independent of the support class (citation rows only):
414
+ // - the exact-mode claimed fact renders plain, as a paraphrase, or as a
415
+ // two-value list (port/protocol) by the same offset rule that picks
416
+ // citation-supports variants, so plain short facts, the paraphrase frame
417
+ // and two-value lists occur at one rate in every slice; the extra value
418
+ // never equals the claimed value unless the row's variant is the supports
419
+ // multi-value case;
420
+ // - an anchor: the carrier (a claimed-value record about another module)
421
+ // names the claimed module in a relative clause, so the claimed module
422
+ // and the claimed value share a clause in every row. The carrier always
423
+ // has a single-clause mode (negated, restricted, scoped, temporal,
424
+ // tentative): never a move (`…; now …` / `…; it …`) or an exclusivity
425
+ // tail, so no later pronoun or elliptical clause can take the claimed
426
+ // module as its antecedent.
427
+ const anchor = citation ? distractors[0] : null;
428
+ if (citation) {
429
+ // The relevant record: supports and injected rows render it by their
430
+ // variant (exact / paraphrase / two-value); the other exact-mode claimed
431
+ // facts (different-current, different-nonexclusive) take the same offset
432
+ // rule, so a paraphrased or two-valued claimed fact is never exclusive to
433
+ // supports. Does-not-support relevant records (another attribute or a
434
+ // near-miss module) are decorated exactly like a distractor (below).
435
+ // Claimed facts whose surface is not their variant (different-current,
436
+ // different-nonexclusive) draw it by their own stream among the supports
437
+ // surfaces applicable to the attribute (round 10; before, the offset rule,
438
+ // whose arithmetic also picks the variant).
439
+ const surfaces = D29_V4_VARIANTS['citation-supports'].filter(variant => variant !== 'multi-value' || ['port', 'protocol'].includes(world.claimAttribute));
440
+ const surface = prior ? counterpartVariant(true, world.claimAttribute, offset)
441
+ : surfaces[drawD29Stream(base.split, `v8:${base.familyId}:claimed-surface`)(surfaces.length)];
442
+ const variantFixed = ['exact', 'paraphrase', 'multi-value'].includes(renderVariant);
443
+ const claimedFact = relevantOnClaimedModule && relevantRecord.attribute === world.claimAttribute;
444
+ if (claimedFact && relevantRecord.mode === 'exact' && !variantFixed && surface !== 'exact') {
445
+ if (surface === 'multi-value' && ['port', 'protocol'].includes(relevantRecord.attribute)) {
446
+ let second = drawValue(random, relevantRecord.attribute, relevantRecord.value);
447
+ while (second === world.claimValue)
448
+ second = drawValue(random, relevantRecord.attribute, relevantRecord.value);
449
+ relevantRecord.second = second;
450
+ world.sourceValues = [relevantRecord.value, second];
451
+ }
452
+ else
453
+ relevantRecord.mode = 'paraphrase';
454
+ }
455
+ // Distractors: each one independently, by its own draw and never by the
456
+ // variant or label, renders plain, as a paraphrase (exact or qualified
457
+ // modes) or as a two-value list (port/protocol, any mode but a move). The
458
+ // added value never equals the claimed value.
459
+ const decorate = drawD29Stream(base.split, `v8:${base.familyId}:surface`);
460
+ for (const record of claimedFact ? distractors : [...distractors, relevantRecord]) {
461
+ const roll = decorate(4);
462
+ if (record === anchor)
463
+ continue;
464
+ if (roll === 0 && PARAPHRASE_MODES.includes(record.mode))
465
+ record.paraphrase = true;
466
+ else if (roll === 1 && TWO_VALUE_MODES.includes(record.mode) && ['port', 'protocol'].includes(record.attribute)) {
467
+ let second = drawValue(decorate, record.attribute, record.value);
468
+ while (second === world.claimValue)
469
+ second = drawValue(decorate, record.attribute, record.value);
470
+ record.second = second;
471
+ if (record === relevantRecord)
472
+ world.sourceValues = [record.value, second];
473
+ }
474
+ }
475
+ }
476
+ // Record order: the distractors in a per-offset order, with the relevant
477
+ // record inserted at a per-offset position.
478
+ const ordered = [...distractors];
479
+ for (let i = ordered.length - 1; i > 0; i--) {
480
+ const at = schedule(i + 1);
481
+ [ordered[i], ordered[at]] = [ordered[at], ordered[i]];
482
+ }
483
+ ordered.splice(schedule(ordered.length + 1), 0, relevantRecord);
484
+ const records = ordered;
485
+ const valueOf = (record) => !record.second ? record.value
486
+ : train ? `${record.value} and ${record.second}` : `${record.value} as well as ${record.second}`;
487
+ const render = (record) => {
488
+ if (!citation)
489
+ return pool.criterion[record.mode](record.module, names[record.attribute], record.k);
490
+ let text;
491
+ if (['scoped', 'temporal', 'tentative'].includes(record.mode)) {
492
+ text = qualify(pool, train, fact(pool, train, record.module, record.attribute, valueOf(record), record.different, record.paraphrase ? 'paraphrase' : 'exact'), record.mode, record.pick);
493
+ }
494
+ else
495
+ text = fact(pool, train, record.module, record.attribute, valueOf(record), record.different, record.paraphrase ? 'paraphrase' : record.mode);
496
+ if (record === anchor) {
497
+ const subject = `Module ${record.module}`, at = text.indexOf(subject) + subject.length;
498
+ text = `${text.slice(0, at)}, ${pool.anchor} Module ${world.claimModule}${text[at] === ',' ? '' : ','}${text.slice(at)}`;
499
+ }
500
+ return text;
501
+ };
502
+ // Test-pool parser-unseen rows render the relevant record by coreference and
503
+ // one distractor too, chosen by its own draw among the non-anchor
504
+ // distractors, independently of the variant.
505
+ const pronounOf = (record) => record.mode === 'paraphrase' || record.paraphrase ? 'that module' : 'It';
506
+ const corefDistractor = unseen ? (() => {
507
+ const candidates = distractors.filter(record => record !== anchor);
508
+ return candidates[drawD29Stream(base.split, `v8:${base.familyId}:coreference`)(candidates.length)];
509
+ })() : null;
510
+ const relevantRender = unseen
511
+ ? coreference(relevantRecord.module, render(relevantRecord), `Module ${relevantRecord.module} ${coreferenceLeads[leadAt]}.`, pronounOf(relevantRecord))
512
+ : render(relevantRecord);
513
+ const corefRender = corefDistractor
514
+ ? coreference(corefDistractor.module, render(corefDistractor), `Module ${corefDistractor.module} ${coreferenceLeads[(leadAt + 1) % 3]}.`, pronounOf(corefDistractor))
515
+ : null;
516
+ const renderRelevant = (record) => record === relevantRecord ? relevantRender
517
+ : record === corefDistractor ? corefRender : render(record);
518
+ // The paired sentence joins a claimed-module distractor and a claim-attribute
519
+ // distractor about another module (the claimed value, for citations); both
520
+ // always exist (slot 1 and the carrier), and neither is the relevant record.
521
+ const pairedEntity = ordered.find(record => record.role === 'claimed');
522
+ const pairedValue = ordered.find(record => (record.role === 'carrier' || record.role === 'other') && record.attribute === world.claimAttribute
523
+ && (!citation || record.value === world.claimValue));
524
+ const pairPosition = schedule(7);
525
+ const rest = ordered.filter(record => record !== pairedEntity && record !== pairedValue);
526
+ const passage = [];
527
+ for (let i = 0; i < 7; i++) {
528
+ if (i === pairPosition)
529
+ passage.push(`${renderRelevant(pairedEntity)}; ${renderRelevant(pairedValue)}.`);
530
+ else
531
+ passage.push(`${renderRelevant(rest.shift())}.`);
532
+ }
533
+ world.factPosition = passage.findIndex(text => text.includes(relevantRender));
534
+ world.distractorCount = records.length - 1;
535
+ world.injectionPhrase = world.injected
536
+ ? (offset + drawD29Stream(base.split, `v8:${seedKey}:${base.slice}:${world.pool}:injections`)(pool.instructions.length)) % pool.instructions.length
537
+ : null;
538
+ const context = [];
539
+ const add = (text, placement) => {
540
+ if (placement === 0)
541
+ context.push(text);
542
+ else if (placement === 1)
543
+ passage.unshift(text);
544
+ else if (placement === 2)
545
+ passage.splice(Math.floor(passage.length / 2), 0, text);
546
+ else
547
+ passage.push(text);
548
+ };
549
+ for (const { text, placement } of benign)
550
+ add(text, placement);
551
+ // The slot note holds an instruction when injected and a benign counterpart
552
+ // clause otherwise; both replace one benign task in the same template.
553
+ const counterpart = pool.counterparts[content(pool.counterparts.length)];
554
+ const slotText = note(world.injected ? pool.instructions[world.injectionPhrase] : counterpart);
555
+ // The slot note's place: the injection variants (context / prefix / middle /
556
+ // suffix) are defined by it, so injected rows keep offset % 4; every other
557
+ // row draws it per offset by its own stream (round 11), so it never follows
558
+ // the offset arithmetic that picks the other variants.
559
+ const slotPlacement = prior || world.injected ? offset % 4 : drawD29Stream(base.split, `v8:${seedKey}:slot:${offset}`)(4);
560
+ add(annotation(slotText, layout(5)), slotPlacement);
561
+ const frame = pool.framing[citation ? 'citation' : 'criterion'];
562
+ const payload = citation ? { kind: world.kind, claim: `${fact(pool, train, world.claimModule, world.claimAttribute, world.claimValue, '', 'exact')}.`,
563
+ source: `${frame}\n${passage.join(' ')}`, context: context.join(' ') }
564
+ : { kind: world.kind, criterion: `Module ${world.claimModule} requires independently verified ${names[world.claimAttribute]} for all components of the current release.`,
565
+ evidence: `${frame}\n${passage.join(' ')}`, context: context.join(' ') };
566
+ return { ...base, world, payload };
567
+ }
568
+ /** Registered v8 row: same request and local-outcome shape as v7. */
569
+ export function generateD29V8(seed, ordinal, layout) {
570
+ const { world, payload, split, slice, familyId, id } = d29WorldV8(seed, ordinal);
571
+ const hardPass = world.artifactPresent && world.testPassed;
572
+ if (layout !== (hardPass ? 'single' : 'local'))
573
+ throw new Error('generator-layout');
574
+ const requestNames = world.kind === 'citation' ? ['support', 'strength', 'injection']
575
+ : ['relevance', 'completeness', 'contradiction', 'ambiguity', 'reviewerAttention'];
576
+ return { id, familyId, split: split, slice, input: { payload },
577
+ requests: hardPass ? requestNames.map(name => ({ id: name, arm: 'candidate', definitionId: `d29-${name}` })) : [],
578
+ localOutcome: { artifactPresent: world.artifactPresent, testPassed: world.testPassed,
579
+ sourceDigest: 'source' in payload ? sha256(payload.source) : null, baseline: d29Baseline(payload, hardPass),
580
+ passageBaselineV1: d29PassageBaseline(payload, hardPass), passageBaseline: d29PassageBaselineV2(payload, hardPass),
581
+ passageBaselineV3: d29PassageBaselineV3(payload, hardPass) } };
582
+ }
583
+ export function d29V8RendererDigest() {
584
+ return sha256(readFileSync(new URL(import.meta.url), 'utf8'));
585
+ }
586
+ //# sourceMappingURL=d29-v8.js.map
@@ -0,0 +1,67 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { sha256 } from '../compile-cache/identity.js';
3
+ import { generateHeldoutRow, heldoutGeneratorDigest, heldoutGeneratorFiles, reproducibleHeldoutRow } from './generators.js';
4
+ import { D29_V8_GENERATOR_ID } from './d29-generator-ids.js';
5
+ import { generateD29V8 } from './d29-v8.js';
6
+ /**
7
+ * Held-out generator registry for generators added after the frozen
8
+ * `generators.ts` registry (D29 v8 onward).
9
+ *
10
+ * `generators.ts` is part of the D29 v1–v7 family digest, so editing it would
11
+ * move the generator pin recorded in every committed v6/v7 corpus. New
12
+ * generator versions therefore register here: this module answers for its
13
+ * own ids and delegates every other id to the frozen registry unchanged. Each
14
+ * new id hashes its own source list, so the v6/v7 family pin stays
15
+ * byte-identical while v8 pins exactly the files that produce its rows.
16
+ * Source-controlled only: corpus data cannot register code or supply a path.
17
+ */
18
+ const D29_V8_GENERATOR_SOURCES = ['src/decision/heldout/generator-registry.ts', 'src/decision/heldout/d29-generator-ids.ts',
19
+ 'src/decision/heldout/generators.ts', 'src/decision/compile-cache/identity.ts', 'src/security/artifact-trust.ts',
20
+ 'src/decision/heldout/d29-v8.ts', 'src/decision/heldout/d29-pools.ts', 'src/decision/heldout/d29-pools-v8.ts',
21
+ 'src/decision/heldout/d29-passage-baseline.ts', 'src/decision/heldout/d29-passage-baseline-v2.ts',
22
+ 'src/decision/heldout/d29-passage-baseline-v3.ts'];
23
+ export { D29_LATEST_GENERATOR_ID, D29_PAID_GENERATOR_IDS } from './d29-generator-ids.js';
24
+ function generateV8(seed) {
25
+ const match = /^([a-z0-9][a-z0-9-]{0,31}):([0-9]{1,5}):(example|single|local)$/.exec(seed);
26
+ if (!match)
27
+ throw new Error('generator-seed');
28
+ const [, worldSeed, index, layout] = match;
29
+ return generateD29V8(worldSeed, Number(index), layout);
30
+ }
31
+ /** Generates one row for any registered generator id, including ids frozen in generators.ts. */
32
+ export function generateRegisteredHeldoutRow(generatorId, seed) {
33
+ if (generatorId !== D29_V8_GENERATOR_ID)
34
+ return generateHeldoutRow(generatorId, seed);
35
+ const output = generateV8(seed);
36
+ return { ...output, provenance: { generatorId, seed, outputDigest: sha256(output) } };
37
+ }
38
+ /** True when the row regenerates byte-identically from its own provenance. */
39
+ export function reproducibleRegisteredHeldoutRow(row) {
40
+ if (row?.provenance?.generatorId !== D29_V8_GENERATOR_ID)
41
+ return reproducibleHeldoutRow(row);
42
+ const { provenance, ...output } = row;
43
+ try {
44
+ const regenerated = generateV8(provenance.seed);
45
+ return sha256(regenerated) === provenance.outputDigest && sha256(output) === provenance.outputDigest;
46
+ }
47
+ catch {
48
+ return false;
49
+ }
50
+ }
51
+ /** Package-root-relative TypeScript sources hashed for one generator id; unknown ids refuse. */
52
+ export function registeredHeldoutGeneratorFiles(generatorId) {
53
+ if (generatorId === D29_V8_GENERATOR_ID)
54
+ return [...D29_V8_GENERATOR_SOURCES];
55
+ return heldoutGeneratorFiles(generatorId);
56
+ }
57
+ /** Generator family digest; ids frozen in generators.ts keep their historical digest. */
58
+ export function registeredHeldoutGeneratorDigest(generatorId) {
59
+ if (generatorId !== D29_V8_GENERATOR_ID)
60
+ return heldoutGeneratorDigest(generatorId);
61
+ const sources = {};
62
+ for (const file of D29_V8_GENERATOR_SOURCES) {
63
+ sources[file] = readFileSync(new URL(`../../../${file}`, import.meta.url), 'utf8');
64
+ }
65
+ return sha256(sources);
66
+ }
67
+ //# sourceMappingURL=generator-registry.js.map