@evomap/evolver-core 2.0.0-beta.8 → 2.0.0-beta.9

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.
@@ -0,0 +1,3074 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { redactString, scanForLeaks } from '../hub/sanitize.js';
3
+ const KEY_VALUE_SECRET_RE = /\b(?:api[_-]?key|token|secret)\s*[:=]\s*(?:"[^"\r\n]*"|'[^'\r\n]*'|[^\s,;]+)/gi;
4
+ const OPENAI_SECRET_RE = /\bsk-[A-Za-z0-9_-]+\b/gi;
5
+ const EMAIL_RE = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
6
+ const PHONE_RE = /\b1[3-9]\d{9}\b/g;
7
+ const IMPORTANT_TERMS = new Set([
8
+ 'ai', 'ask', 'ci', 'log', 'run', 'test', 'tests', 'live', 'selection', 'secret', 'secrets', 'token', 'tokens',
9
+ ]);
10
+ function sha256(text) {
11
+ return `sha256:${createHash('sha256').update(text).digest('hex')}`;
12
+ }
13
+ function normalize(text) {
14
+ return text
15
+ .toLowerCase()
16
+ .replace(/[\u201C\u201D]/g, '"')
17
+ .replace(/[\u2018\u2019]/g, "'")
18
+ .replace(/[`*_#>()[\].,;:!?]/g, ' ')
19
+ .replace(/\s+/g, ' ')
20
+ .trim();
21
+ }
22
+ export function redactConstraintText(text) {
23
+ return redactString(text
24
+ .replace(OPENAI_SECRET_RE, '[REDACTED_SECRET]')
25
+ .replace(KEY_VALUE_SECRET_RE, '[REDACTED_SECRET]')
26
+ .replace(EMAIL_RE, '[REDACTED_EMAIL]')
27
+ .replace(PHONE_RE, '[REDACTED_PHONE]'));
28
+ }
29
+ const CLAUSE_MARKER_RE = /\b(?:(?:(?:must|should|shall|do)\s+not|(?:mustn|shouldn|shalln|shan|don)(?:['\u2018\u2019])t)\s+only|(?:require(?:d|s)?|need(?:s)?)\s+to\s+not\s+only|must\s+(?:not(?!\s+only\b)|never)|must-not|should\s+(?:not(?!\s+only\b)|never)|shall\s+(?:not(?!\s+only\b)|never)|mustn(?:['\u2018\u2019])t(?!\s+only\b)|shouldn(?:['\u2018\u2019])t(?!\s+only\b)|shalln(?:['\u2018\u2019])t(?!\s+only\b)|shan(?:['\u2018\u2019])t(?!\s+only\b)|do\s+not(?!\s+only\b)|don(?:['\u2018\u2019])t(?!\s+only\b)|(?:require(?:d|s)?|need(?:s)?)\s+to\s+not(?!\s+only\b)|never|must|should|shall|require(?:d|s)?|need(?:s)?\s+to)\b/gi;
30
+ const NEGATIVE_CONSTRAINT_MARKER_RE = /^(?:must\s+(?:not(?!\s+only\b)|never)|must-not|should\s+(?:not(?!\s+only\b)|never)|shall\s+(?:not(?!\s+only\b)|never)|mustn(?:['\u2018\u2019])t(?!\s+only\b)|shouldn(?:['\u2018\u2019])t(?!\s+only\b)|shalln(?:['\u2018\u2019])t(?!\s+only\b)|shan(?:['\u2018\u2019])t(?!\s+only\b)|do\s+not(?!\s+only\b)|don(?:['\u2018\u2019])t(?!\s+only\b)|(?:require(?:d|s)?|need(?:s)?)\s+to\s+not(?!\s+only\b)|never)$/i;
31
+ const REDACTION_MARKER_RE = /\[REDACTED(?:_[A-Z]+)?\]/gi;
32
+ const EMAIL_VALUE_RE = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i;
33
+ const NEGATED_ACTION_RE = /(?:^not\s+(?!only\b)|\b(?:(?:am|are|did|do|does|had|has|have|is|need|needs|ought|was|were)\s+not(?!\s+only\b)|(?:aren't|couldn't|didn't|doesn't|don't|hadn't|hasn't|haven't|isn't|mightn't|needn't|oughtn't|wasn't|weren't|won't|wouldn't)(?!\s+only\b)|never|must\s+not(?!\s+only\b)|mustn't(?!\s+only\b)|should\s+not(?!\s+only\b)|shouldn't(?!\s+only\b)|shall\s+not(?!\s+only\b)|shalln't(?!\s+only\b)|shan't(?!\s+only\b)|cannot(?!\s+only\b)|can't(?!\s+only\b)|can\s+not(?!\s+only\b)|(?:will|would|can|could|may|might)\s+not(?!\s+only\b))\b)/i;
34
+ const WITHOUT_ACTION_RE = /\bwithout\b/i;
35
+ const POTENTIAL_CONDITIONAL_SCOPE_RE = /(?:^|,)\s*(?:(?:eventually|finally|initially|later|subsequently)\s+)*(?:assuming|if|once|provided|supposing|unless|when|while)\b|\b(?:and|or|then)\s+(?:(?:eventually|finally|initially|later|subsequently)\s+)*(?:assuming|if|once|provided|supposing|unless|when|while)\b|\bprovided\s+that\b/i;
36
+ const CLOSED_IF_ANYTHING_RE = /(^|,)\s*if\s+anything\s*,/gi;
37
+ const COORDINATED_PROVIDED_RE = /\b(and|or|then)\s+(?:(?:eventually|finally|initially|later|subsequently)\s+)*provided\b/gi;
38
+ const LEADING_PROVIDED_RE = /(^|,)\s*(?:(?:eventually|finally|initially|later|subsequently)\s+)*provided\b/gi;
39
+ const PROVIDED_BY_ADJUNCT_RE = /\bprovided\s+by\b/gi;
40
+ const LEADING_DISCOURSE_TEMPORAL_RE = /^\s*(?:eventually|finally|initially|later|subsequently)\s*,\s*(?:once|when|while)\b[^,;.!?\r\n]*,\s*/i;
41
+ const COORDINATED_TEMPORAL_RE = /\b(and|or|then)\s+(?:(?:eventually|finally|initially|later|subsequently)\s+)*(?:once|when|while)\b/gi;
42
+ const NEGATIVE_SUBJECT_MARKER_RE = /\b(?:neither|no|nobody|none|nothing|zero)\b/i;
43
+ const COMMA_DELIMITED_WITHOUT_RE = /\bwithout\b[^,;.!?\r\n]*,/gi;
44
+ const COMMA_DELIMITED_TEMPORAL_RE = /,\s*(?:once|when|while)\b[^,;.!?\r\n]*,/gi;
45
+ const AFFIRMATIVE_ACTION_LEMMAS = [
46
+ 'access', 'allow', 'call', 'display', 'emit', 'enable', 'expose', 'export', 'feed', 'include', 'leak',
47
+ 'log', 'output', 'print', 'provide', 'publish', 'reveal', 'run', 'send', 'share', 'show', 'store',
48
+ 'transmit', 'upload', 'use', 'write',
49
+ ];
50
+ const INDEPENDENT_AFFIRMATIVE_AUXILIARIES = new Set([
51
+ 'am', 'are', 'be', 'been', 'being', 'can', 'could', 'did', 'do', 'does', 'had', 'has', 'have', 'is',
52
+ 'may', 'might', 'must', 'shall', 'should', 'was', 'were', 'will', 'would',
53
+ ]);
54
+ const CREDENTIAL_LEAK_TYPES = new Set([
55
+ 'api_key',
56
+ 'azure_client_secret',
57
+ 'azure_instrumentation_key',
58
+ 'azure_key',
59
+ 'basic_auth',
60
+ 'bearer_token',
61
+ 'db_url',
62
+ 'discord_token',
63
+ 'env_value_leak',
64
+ 'github_token',
65
+ 'jwt',
66
+ 'npm_token',
67
+ 'password',
68
+ 'private_key',
69
+ 'proxy_token',
70
+ 'secret',
71
+ 'slack_token',
72
+ ]);
73
+ function splitConstraintCandidates(text) {
74
+ const spans = [];
75
+ const boundary = /\r?\n|(?<=[.!?])\s+/g;
76
+ let start = 0;
77
+ const append = (end) => {
78
+ const raw = text.slice(start, end);
79
+ const leadingWhitespace = raw.match(/^\s*/)?.[0].length ?? 0;
80
+ let textStart = start + leadingWhitespace;
81
+ const listMarker = text.slice(textStart, end).match(/^(?:[-*]\s+|\d+[.)]\s+|\[[ xX]\]\s*)/);
82
+ if (listMarker)
83
+ textStart += listMarker[0].length;
84
+ const trailingWhitespace = text.slice(textStart, end).match(/\s*$/)?.[0].length ?? 0;
85
+ const textEnd = end - trailingWhitespace;
86
+ const candidate = text.slice(textStart, textEnd);
87
+ if (candidate)
88
+ spans.push({ text: candidate, start, end, textStart });
89
+ };
90
+ for (const match of text.matchAll(boundary)) {
91
+ const boundaryStart = match.index;
92
+ if (!match[0].includes('\n')) {
93
+ const punctuationIndex = boundaryStart - 1;
94
+ const candidatePrefix = text.slice(start, punctuationIndex + 1);
95
+ if (/^\s*\d+[.)]$/.test(candidatePrefix))
96
+ continue;
97
+ }
98
+ append(boundaryStart);
99
+ start = boundaryStart + match[0].length;
100
+ }
101
+ append(text.length);
102
+ return spans;
103
+ }
104
+ function kindForMarker(marker) {
105
+ return NEGATIVE_CONSTRAINT_MARKER_RE.test(marker) ? 'must_not' : 'must';
106
+ }
107
+ function splitConstraintClauses(text) {
108
+ const matches = [...text.matchAll(CLAUSE_MARKER_RE)];
109
+ return matches.flatMap((match, index) => {
110
+ const start = match.index;
111
+ const nextStart = matches[index + 1]?.index ?? text.length;
112
+ const segment = text.slice(start, nextStart);
113
+ const connector = segment.match(/\b(?:and|but|however|although|though)\s*$/i);
114
+ const contentEnd = start + (connector?.index ?? segment.length);
115
+ const trailingWhitespace = text.slice(start, contentEnd).match(/\s*$/)?.[0].length ?? 0;
116
+ const end = contentEnd - trailingWhitespace;
117
+ if (end <= start)
118
+ return [];
119
+ return [{ kind: kindForMarker(match[0]), text: text.slice(start, end), start, end }];
120
+ });
121
+ }
122
+ function safeSource(source) {
123
+ return source === 'plan' || source === 'task' || source === 'trace' ? source : 'trace';
124
+ }
125
+ function safeTraceId(traceId) {
126
+ if (typeof traceId !== 'string' || traceId.length === 0)
127
+ return undefined;
128
+ return `trace:${sha256(traceId).slice('sha256:'.length, 'sha256:'.length + 16)}`;
129
+ }
130
+ function sensitiveClassesForConstraint(text) {
131
+ const normalized = normalize(text);
132
+ const classes = new Set();
133
+ if (/\b(?:secrets?|tokens?|api[_ -]?keys?|credentials?|passwords?|bearer|private[_ -]?keys?)\b/.test(normalized)) {
134
+ classes.add('credential');
135
+ }
136
+ if (/\b(?:email|e-mail|mail)\b/.test(normalized))
137
+ classes.add('email');
138
+ if (/\b(?:paths?|directories?|filesystem|home\s+directory|user\s+profile)\b/.test(normalized)) {
139
+ classes.add('filesystem_path');
140
+ }
141
+ return [...classes];
142
+ }
143
+ const TARGET_STOPWORDS = new Set([
144
+ 'a', 'also', 'although', 'an', 'and', 'are', 'as', 'at', 'be', 'but', 'by', 'for', 'from', 'however',
145
+ 'in', 'into', 'is', 'of', 'on', 'only', 'or', 'redacted', 'that', 'the', 'this', 'though', 'to', 'until',
146
+ 'when', 'with', 'without',
147
+ ]);
148
+ const GENERIC_ACTION_TERMS = new Set(['add', 'call', 'include', 'print', 'use']);
149
+ const AMBIGUOUS_DEFERRED_VERB_TERMS = new Set(['live']);
150
+ function targetTerms(text, kind) {
151
+ const stripped = normalize(text
152
+ .replace(REDACTION_MARKER_RE, ' ')
153
+ .replace(CLAUSE_MARKER_RE, ' '))
154
+ .replace(/\b(?:must\s+not|must-not|mustn't|do\s+not|don't|never|must|required|requires|require|needs\s+to|need\s+to|should)\b/g, ' ')
155
+ .replace(/\s+/g, ' ')
156
+ .trim();
157
+ const seen = new Set();
158
+ const terms = [];
159
+ let expectVerb = true;
160
+ let skippedExpectedVerb = false;
161
+ const contrastive = isContrastiveConstraint(text);
162
+ let group = 0;
163
+ const groupCounts = new Map();
164
+ const groupLimit = kind === 'must_not' ? 6 : 4;
165
+ for (const term of stripped.split(' ')) {
166
+ if (!term)
167
+ continue;
168
+ if (TARGET_STOPWORDS.has(term)) {
169
+ if (contrastive && term === 'also') {
170
+ group += 1;
171
+ expectVerb = true;
172
+ skippedExpectedVerb = false;
173
+ }
174
+ else if (term === 'and' || term === 'or') {
175
+ expectVerb = true;
176
+ skippedExpectedVerb = false;
177
+ }
178
+ continue;
179
+ }
180
+ const verb = expectVerb;
181
+ const key = `${group}:${term}`;
182
+ if (GENERIC_ACTION_TERMS.has(term)
183
+ || (term.length < 4 && !IMPORTANT_TERMS.has(term))
184
+ || seen.has(key)
185
+ || (groupCounts.get(group) ?? 0) >= groupLimit) {
186
+ if (expectVerb)
187
+ skippedExpectedVerb = true;
188
+ continue;
189
+ }
190
+ expectVerb = false;
191
+ seen.add(key);
192
+ groupCounts.set(group, (groupCounts.get(group) ?? 0) + 1);
193
+ terms.push({ text: term, verb, verbAfterSkippedTerm: verb && skippedExpectedVerb, group });
194
+ skippedExpectedVerb = false;
195
+ }
196
+ return terms;
197
+ }
198
+ const IRREGULAR_VERB_FORMS = new Map([
199
+ ['be', ['be', 'am', 'is', 'are', 'was', 'were', 'been', 'being']],
200
+ ['begin', ['begin', 'begins', 'began', 'begun', 'beginning']],
201
+ ['bleed', ['bleed', 'bleeds', 'bled', 'bleeding']],
202
+ ['break', ['break', 'breaks', 'broke', 'broken', 'breaking']],
203
+ ['breed', ['breed', 'breeds', 'bred', 'breeding']],
204
+ ['bring', ['bring', 'brings', 'brought', 'bringing']],
205
+ ['build', ['build', 'builds', 'built', 'building']],
206
+ ['buy', ['buy', 'buys', 'bought', 'buying']],
207
+ ['catch', ['catch', 'catches', 'caught', 'catching']],
208
+ ['choose', ['choose', 'chooses', 'chose', 'chosen', 'choosing']],
209
+ ['do', ['do', 'does', 'did', 'done', 'doing']],
210
+ ['feed', ['feed', 'feeds', 'fed', 'feeding']],
211
+ ['find', ['find', 'finds', 'found', 'finding']],
212
+ ['get', ['get', 'gets', 'got', 'gotten', 'getting']],
213
+ ['give', ['give', 'gives', 'gave', 'given', 'giving']],
214
+ ['go', ['go', 'goes', 'went', 'gone', 'going']],
215
+ ['keep', ['keep', 'keeps', 'kept', 'keeping']],
216
+ ['leave', ['leave', 'leaves', 'left', 'leaving']],
217
+ ['make', ['make', 'makes', 'made', 'making']],
218
+ ['read', ['read', 'reads', 'reading']],
219
+ ['ring', ['ring', 'rings', 'rang', 'rung', 'ringing']],
220
+ ['run', ['run', 'runs', 'ran', 'running']],
221
+ ['send', ['send', 'sends', 'sent', 'sending']],
222
+ ['show', ['show', 'shows', 'showed', 'shown', 'showing']],
223
+ ['sing', ['sing', 'sings', 'sang', 'sung', 'singing']],
224
+ ['speed', ['speed', 'speeds', 'sped', 'speeded', 'speeding']],
225
+ ['take', ['take', 'takes', 'took', 'taken', 'taking']],
226
+ ['teach', ['teach', 'teaches', 'taught', 'teaching']],
227
+ ['tell', ['tell', 'tells', 'told', 'telling']],
228
+ ['think', ['think', 'thinks', 'thought', 'thinking']],
229
+ ['write', ['write', 'writes', 'wrote', 'written', 'writing']],
230
+ ]);
231
+ const IRREGULAR_SIMPLE_PAST_FORMS = new Set(['read', 'was', 'were', ...[...IRREGULAR_VERB_FORMS.entries()]
232
+ .filter(([lemma]) => lemma !== 'be' && lemma !== 'read')
233
+ .map(([, forms]) => forms[2])]
234
+ .filter((form) => form !== undefined && !form.endsWith('ing')));
235
+ const IRREGULAR_PAST_PARTICIPLE_FORMS = new Set([
236
+ 'been', 'read', 'showed', 'sped',
237
+ ...[...IRREGULAR_VERB_FORMS.entries()]
238
+ .filter(([lemma]) => lemma !== 'be' && lemma !== 'read')
239
+ .map(([, forms]) => forms.at(-2))
240
+ .filter((form) => form !== undefined && !form.endsWith('ing')),
241
+ ]);
242
+ const IRREGULAR_PROGRESSIVE_VERB_LEMMAS = new Map([...IRREGULAR_VERB_FORMS].flatMap(([lemma, forms]) => forms.filter((form) => form.endsWith('ing')).map((form) => [form, lemma])));
243
+ const IRREGULAR_NOUN_PLURALS = new Map([
244
+ ['analysis', 'analyses'],
245
+ ['basis', 'bases'],
246
+ ['bus', 'buses'],
247
+ ['crisis', 'crises'],
248
+ ['leaf', 'leaves'],
249
+ ['life', 'lives'],
250
+ ['status', 'statuses'],
251
+ ]);
252
+ const UNAMBIGUOUS_PLURAL_SINGULARS = new Map([
253
+ ['analyses', 'analysis'],
254
+ ['buses', 'bus'],
255
+ ['crises', 'crisis'],
256
+ ['statuses', 'status'],
257
+ ]);
258
+ const DOUBLED_INFLECTION_BASES = new Set([
259
+ 'admit', 'commit', 'control', 'debug', 'defer', 'embed', 'format', 'occur', 'permit', 'prefer', 'refer', 'submit',
260
+ 'shred', 'transmit',
261
+ ]);
262
+ const NON_DOUBLED_SHORT_CVC_BASES = new Set(['edit', 'open']);
263
+ const ED_SUFFIX_BASE_VERBS = new Set(['exceed', 'heed', 'need', 'proceed', 'seed', 'succeed']);
264
+ const EXACT_ONLY_PROGRESSIVE_LEMMAS = new Set(['be', 'do', 'go']);
265
+ const SHORT_INFLECTED_VERB_LEMMAS = new Map([
266
+ ['died', 'die'],
267
+ ['dying', 'die'],
268
+ ['lied', 'lie'],
269
+ ['lying', 'lie'],
270
+ ['skied', 'ski'],
271
+ ['skiing', 'ski'],
272
+ ['taxied', 'taxi'],
273
+ ['tied', 'tie'],
274
+ ['tying', 'tie'],
275
+ ['vied', 'vie'],
276
+ ['vying', 'vie'],
277
+ ]);
278
+ const SILENT_E_VERB_LEMMAS = new Map([
279
+ ['ac', 'ace'],
280
+ ['ag', 'age'],
281
+ ['ap', 'ape'],
282
+ ['creat', 'create'],
283
+ ['delet', 'delete'],
284
+ ['enabl', 'enable'],
285
+ ['expos', 'expose'],
286
+ ['includ', 'include'],
287
+ ['ic', 'ice'],
288
+ ['leav', 'leave'],
289
+ ['liv', 'live'],
290
+ ['mak', 'make'],
291
+ ['mov', 'move'],
292
+ ['ow', 'owe'],
293
+ ['preserv', 'preserve'],
294
+ ['provid', 'provide'],
295
+ ['requir', 'require'],
296
+ ['sav', 'save'],
297
+ ['shar', 'share'],
298
+ ['stor', 'store'],
299
+ ['su', 'sue'],
300
+ ['tak', 'take'],
301
+ ['us', 'use'],
302
+ ['validat', 'validate'],
303
+ ['writ', 'write'],
304
+ ]);
305
+ function addPluralForm(base, forms) {
306
+ const irregular = IRREGULAR_NOUN_PLURALS.get(base);
307
+ if (irregular)
308
+ forms.add(irregular);
309
+ else if (/[^aeiou]y$/u.test(base))
310
+ forms.add(`${base.slice(0, -1)}ies`);
311
+ else if (/(?:s|x|z|ch|sh)$/u.test(base))
312
+ forms.add(`${base}es`);
313
+ else
314
+ forms.add(`${base}s`);
315
+ }
316
+ function addSingularCandidates(term, bases) {
317
+ const irregular = UNAMBIGUOUS_PLURAL_SINGULARS.get(term);
318
+ if (irregular) {
319
+ bases.add(irregular);
320
+ return;
321
+ }
322
+ if (/[^aeiou]ies$/u.test(term) && term.length > 3)
323
+ bases.add(`${term.slice(0, -3)}y`);
324
+ if (term.endsWith('ves') && term.length > 4) {
325
+ const stem = term.slice(0, -3);
326
+ bases.add(`${stem}f`);
327
+ bases.add(`${stem}fe`);
328
+ return;
329
+ }
330
+ if (/(?:sses|xes|zzes|ches|shes)$/u.test(term) && term.length > 4)
331
+ bases.add(term.slice(0, -2));
332
+ else if (term.endsWith('s') && term.length > 3 && !/(?:ss|us|is)$/u.test(term))
333
+ bases.add(term.slice(0, -1));
334
+ }
335
+ function doublesFinalConsonant(base) {
336
+ if (DOUBLED_INFLECTION_BASES.has(base))
337
+ return true;
338
+ if (base.length > 4 || NON_DOUBLED_SHORT_CVC_BASES.has(base))
339
+ return false;
340
+ return /[bcdfghjklmnpqrstvwxyz][aeiou][bcdfghjklmnpqrstvz]$/u.test(base);
341
+ }
342
+ function addRegularVerbForms(base, forms) {
343
+ forms.add(base);
344
+ addPluralForm(base, forms);
345
+ if (base.endsWith('ie')) {
346
+ forms.add(`${base}d`);
347
+ forms.add(`${base.slice(0, -2)}ying`);
348
+ return;
349
+ }
350
+ if (base.endsWith('e')) {
351
+ forms.add(`${base}d`);
352
+ forms.add(`${base.slice(0, -1)}ing`);
353
+ return;
354
+ }
355
+ if (/[^aeiou]y$/u.test(base))
356
+ forms.add(`${base.slice(0, -1)}ied`);
357
+ if (doublesFinalConsonant(base)) {
358
+ const last = base.at(-1);
359
+ forms.add(`${base}${last}ed`);
360
+ forms.add(`${base}${last}ing`);
361
+ }
362
+ else {
363
+ if (!/[^aeiou]y$/u.test(base))
364
+ forms.add(`${base}ed`);
365
+ forms.add(`${base}ing`);
366
+ }
367
+ }
368
+ function nominalForms(term) {
369
+ const forms = new Set([term]);
370
+ const bases = new Set();
371
+ addSingularCandidates(term, bases);
372
+ if (bases.size === 0)
373
+ addPluralForm(term, forms);
374
+ for (const base of bases) {
375
+ forms.add(base);
376
+ addPluralForm(base, forms);
377
+ }
378
+ return forms;
379
+ }
380
+ function verbForms(term, kind) {
381
+ const forms = new Set([term]);
382
+ const lemma = verbLemma(term);
383
+ if (!lemma)
384
+ return forms;
385
+ if (kind === 'must_not'
386
+ && term.endsWith('ing')
387
+ && EXACT_ONLY_PROGRESSIVE_LEMMAS.has(lemma))
388
+ return forms;
389
+ const irregular = IRREGULAR_VERB_FORMS.get(lemma);
390
+ if (irregular) {
391
+ for (const form of irregular)
392
+ forms.add(form);
393
+ }
394
+ else {
395
+ addRegularVerbForms(lemma, forms);
396
+ }
397
+ return forms;
398
+ }
399
+ function targetTermForms(term, kind) {
400
+ const nominal = nominalForms(term.text);
401
+ if (!term.verb || (term.verbAfterSkippedTerm && AMBIGUOUS_DEFERRED_VERB_TERMS.has(term.text))) {
402
+ return nominal;
403
+ }
404
+ const forms = verbForms(term.text, kind);
405
+ if (term.verbAfterSkippedTerm) {
406
+ for (const form of nominal)
407
+ forms.add(form);
408
+ }
409
+ return forms;
410
+ }
411
+ const AFFIRMATIVE_ACTION_FORMS = new Set([
412
+ ...AFFIRMATIVE_ACTION_LEMMAS.flatMap((lemma) => [...verbForms(lemma, 'must_not')]),
413
+ 'outputted',
414
+ 'outputting',
415
+ ]);
416
+ function verbLemma(term) {
417
+ if (IRREGULAR_VERB_FORMS.has(term)
418
+ || DOUBLED_INFLECTION_BASES.has(term)
419
+ || ED_SUFFIX_BASE_VERBS.has(term))
420
+ return term;
421
+ const shortInflectedLemma = SHORT_INFLECTED_VERB_LEMMAS.get(term);
422
+ if (shortInflectedLemma)
423
+ return shortInflectedLemma;
424
+ if (term.endsWith('ied') && term.length > 4) {
425
+ return `${term.slice(0, -3)}y`;
426
+ }
427
+ const suffix = term.endsWith('ing') ? 'ing' : term.endsWith('ed') ? 'ed' : undefined;
428
+ if (!suffix)
429
+ return term;
430
+ const irregularProgressiveLemma = IRREGULAR_PROGRESSIVE_VERB_LEMMAS.get(term);
431
+ if (irregularProgressiveLemma)
432
+ return irregularProgressiveLemma;
433
+ const stem = term.slice(0, -suffix.length);
434
+ const silentELemma = SILENT_E_VERB_LEMMAS.get(stem);
435
+ if (silentELemma)
436
+ return silentELemma;
437
+ if (stem.length <= 2)
438
+ return term;
439
+ const last = stem.at(-1);
440
+ if (last && last === stem.at(-2)) {
441
+ const undoubled = stem.slice(0, -1);
442
+ if (doublesFinalConsonant(undoubled))
443
+ return undoubled;
444
+ }
445
+ return stem;
446
+ }
447
+ function termMatchIndexes(output, term, kind) {
448
+ const outputTerms = output.match(/[a-z0-9_]+/gu) ?? [];
449
+ const forms = targetTermForms(term, kind);
450
+ return outputTerms.flatMap((outputTerm, index) => forms.has(outputTerm) ? [index] : []);
451
+ }
452
+ function termMatches(output, term, kind) {
453
+ return termMatchIndexes(output, term, kind).length > 0;
454
+ }
455
+ function isViolated(kind, terms, matchedTerms, contrastive) {
456
+ if (kind === 'must' && contrastive) {
457
+ const matched = new Set(matchedTerms);
458
+ const groups = new Set(terms.map((term) => term.group));
459
+ return [...groups].some((group) => {
460
+ const groupTerms = terms.filter((term) => term.group === group);
461
+ return groupTerms.filter((term) => matched.has(term.text)).length < Math.min(2, groupTerms.length);
462
+ });
463
+ }
464
+ if (kind === 'must')
465
+ return matchedTerms.length < Math.min(2, terms.length);
466
+ return matchedTerms.length >= Math.min(2, terms.length);
467
+ }
468
+ function isContrastiveConstraint(text) {
469
+ return /(?:\bnot|n't)\s+only\b[\s\S]*\bbut\s+also\b/i.test(normalize(text));
470
+ }
471
+ function severityFor(kind) {
472
+ return kind === 'must_not' ? 'high' : 'medium';
473
+ }
474
+ export function extractConstraints(traces) {
475
+ const seen = new Set();
476
+ const out = [];
477
+ for (const trace of traces) {
478
+ for (const candidate of splitConstraintCandidates(trace.text)) {
479
+ for (const clause of splitConstraintClauses(candidate.text)) {
480
+ const redactedText = redactConstraintText(clause.text);
481
+ const textHash = sha256(normalize(redactedText));
482
+ const source = safeSource(trace.source);
483
+ const traceId = trace.traceId ? safeTraceId(trace.traceId) : undefined;
484
+ const key = `${clause.kind}:${textHash}`;
485
+ if (seen.has(key))
486
+ continue;
487
+ seen.add(key);
488
+ const id = `constraint:${createHash('sha256').update(key).digest('hex').slice(0, 16)}`;
489
+ out.push({
490
+ id,
491
+ kind: clause.kind,
492
+ textHash,
493
+ redactedText,
494
+ source,
495
+ ...(traceId ? { traceId } : {}),
496
+ sensitiveClasses: sensitiveClassesForConstraint(clause.text),
497
+ });
498
+ }
499
+ }
500
+ }
501
+ return out;
502
+ }
503
+ export function buildConstraintAblatedPrompts(prompt, constraints, opts = {}) {
504
+ const candidates = splitConstraintCandidates(prompt);
505
+ const originalPromptHash = sha256(prompt);
506
+ return constraints.flatMap((constraint) => {
507
+ const matches = candidates.flatMap((candidate) => {
508
+ return splitConstraintClauses(candidate.text).flatMap((clause) => {
509
+ if (sha256(normalize(redactConstraintText(clause.text))) !== constraint.textHash)
510
+ return [];
511
+ let start = candidate.textStart + clause.start;
512
+ let end = start + clause.text.length;
513
+ const before = prompt.slice(candidate.start, start);
514
+ const trailingConnector = before.match(/(?:,\s*|\s+)(?:and|but|however|although|though)\s*$/i);
515
+ if (trailingConnector)
516
+ start -= trailingConnector[0].length;
517
+ else if (/^\s*(?:[-*]\s+|\d+[.)]\s+|\[[ xX]\]\s*)$/.test(before))
518
+ start = candidate.start;
519
+ const after = prompt.slice(end, candidate.end);
520
+ const leadingConnector = after.match(/^\s+but\s+also\s+/i)
521
+ ?? after.match(/^\s+(?:and|but|however|although|though)\s+/i);
522
+ if (leadingConnector && !trailingConnector)
523
+ end += leadingConnector[0].length;
524
+ return [{ start, end }];
525
+ });
526
+ });
527
+ if (matches.length !== 1)
528
+ return [];
529
+ const match = matches[0];
530
+ if (!match)
531
+ return [];
532
+ const ablatedPrompt = prompt.slice(0, match.start) + prompt.slice(match.end);
533
+ if (ablatedPrompt === prompt)
534
+ return [];
535
+ return [{
536
+ originalPromptHash,
537
+ ablatedPromptHash: sha256(ablatedPrompt),
538
+ removedConstraintIds: [constraint.id],
539
+ ...(opts.includeRedactedPreview ? { redactedPreview: redactConstraintText(ablatedPrompt) } : {}),
540
+ }];
541
+ });
542
+ }
543
+ function sensitiveClassesForValue(value) {
544
+ const classes = new Set();
545
+ if (EMAIL_VALUE_RE.test(value))
546
+ classes.add('email');
547
+ if (new RegExp(KEY_VALUE_SECRET_RE.source, KEY_VALUE_SECRET_RE.flags).test(value)
548
+ || new RegExp(OPENAI_SECRET_RE.source, OPENAI_SECRET_RE.flags).test(value)) {
549
+ classes.add('credential');
550
+ }
551
+ for (const leak of scanForLeaks(value).leaks) {
552
+ const type = String(leak.type);
553
+ if (type === 'email')
554
+ classes.add('email');
555
+ else if (type === 'local_path')
556
+ classes.add('filesystem_path');
557
+ else if (CREDENTIAL_LEAK_TYPES.has(type))
558
+ classes.add('credential');
559
+ }
560
+ return [...classes];
561
+ }
562
+ function hasIndependentAffirmativeAction(segment, actionForms, inheritsSubject = false) {
563
+ const stream = lexRequiredEvidence(segment);
564
+ for (let actionIndex = inheritsSubject ? 0 : 1; actionIndex < stream.tokens.length; actionIndex += 1) {
565
+ if (!actionForms.has(stream.tokens[actionIndex]?.value ?? ''))
566
+ continue;
567
+ if (stream.conditional[actionIndex] === true)
568
+ continue;
569
+ if (actionIndex === 0)
570
+ return true;
571
+ let prefixStart = 0;
572
+ while (prefixStart < actionIndex) {
573
+ const term = stream.tokens[prefixStart]?.value ?? '';
574
+ if (!isIgnoredRequiredToken(stream, prefixStart)
575
+ && !LEADING_CLAUSE_MODIFIER_TERMS.has(term)
576
+ && !/ly$/u.test(term))
577
+ break;
578
+ prefixStart += 1;
579
+ }
580
+ const first = stream.tokens[prefixStart]?.value ?? '';
581
+ if (inheritsSubject && prefixStart === actionIndex)
582
+ return true;
583
+ if (INDEPENDENT_AFFIRMATIVE_AUXILIARIES.has(first))
584
+ return true;
585
+ if (leadingSubjectPolarity(stream, prefixStart, actionIndex) !== 'positive')
586
+ continue;
587
+ if (SUBJECT_ARTICLE_TERMS.has(first) && prefixStart + 1 >= actionIndex)
588
+ continue;
589
+ return true;
590
+ }
591
+ return false;
592
+ }
593
+ const NEGATED_FINITE_AUXILIARIES = new Set([
594
+ 'am', 'are', 'can', 'could', 'did', 'do', 'does', 'had', 'has', 'have', 'is', 'may', 'might', 'must',
595
+ 'need', 'needs', 'ought', 'shall', 'should', 'was', 'were', 'will', 'would',
596
+ ]);
597
+ const NEGATED_FINITE_CONTRACTIONS = new Set([
598
+ "aren't", "can't", 'cannot', "couldn't", "didn't", "doesn't", "don't", "hadn't", "hasn't", "haven't",
599
+ "isn't", "mightn't", "mustn't", "needn't", "oughtn't", "shan't", "shouldn't", "wasn't", "weren't",
600
+ "won't", "wouldn't",
601
+ ]);
602
+ function hasExplicitSubjectBeforeNegation(segment) {
603
+ const stream = lexRequiredEvidence(segment);
604
+ for (let index = 0; index < stream.tokens.length; index += 1) {
605
+ const term = stream.tokens[index]?.value ?? '';
606
+ const negatedAuxiliary = NEGATED_FINITE_CONTRACTIONS.has(term)
607
+ || (NEGATED_FINITE_AUXILIARIES.has(term) && stream.tokens[index + 1]?.value === 'not');
608
+ if (!negatedAuxiliary)
609
+ continue;
610
+ return index > 0 && leadingSubjectPolarity(stream, 0, index) === 'positive';
611
+ }
612
+ return false;
613
+ }
614
+ function startsWithSubjectElidedNegation(segment) {
615
+ const stream = lexRequiredEvidence(segment);
616
+ const first = stream.tokens.findIndex((_, index) => !isIgnoredRequiredToken(stream, index));
617
+ if (first < 0)
618
+ return false;
619
+ const term = stream.tokens[first]?.value ?? '';
620
+ if (NEGATED_FINITE_CONTRACTIONS.has(term))
621
+ return true;
622
+ return NEGATED_FINITE_AUXILIARIES.has(term)
623
+ && stream.tokens[first + 1]?.value === 'not';
624
+ }
625
+ function startsWithSubjectElidedFiniteAction(segment, actionForms) {
626
+ const stream = lexRequiredEvidence(segment);
627
+ for (let index = 0; index < stream.tokens.length; index += 1) {
628
+ if (isIgnoredRequiredToken(stream, index))
629
+ continue;
630
+ const term = stream.tokens[index]?.value ?? '';
631
+ if (LEADING_CLAUSE_MODIFIER_TERMS.has(term) || /ly$/u.test(term))
632
+ continue;
633
+ return stream.conditional[index] !== true
634
+ && actionForms.has(term)
635
+ && (term.endsWith('ed') || term.endsWith('s') || IRREGULAR_SIMPLE_PAST_FORMS.has(term));
636
+ }
637
+ return false;
638
+ }
639
+ const BASE_AGREEMENT_SUBJECTS = new Set(['i', 'they', 'we', 'you']);
640
+ const THIRD_PERSON_SUBJECTS = new Set(['he', 'it', 'she']);
641
+ const BASE_AGREEMENT_AUXILIARIES = new Set(['am', 'are', "aren't", 'do', "don't", 'have', "haven't"]);
642
+ const THIRD_PERSON_AUXILIARIES = new Set(['does', "doesn't", 'has', "hasn't", 'is', "isn't", 'needs']);
643
+ const NEGATED_AUXILIARY_TERMS = new Set([
644
+ 'am', 'are', 'can', 'cannot', 'could', 'did', 'do', 'does', 'had', 'has', 'have', 'is', 'may', 'might',
645
+ 'must', 'need', 'needs', 'ought', 'shall', 'should', 'was', 'were', 'will', 'would', "aren't", "can't", "couldn't", "didn't",
646
+ "doesn't", "don't", "hadn't", "hasn't", "haven't", "isn't", "mightn't", "mustn't", "shan't",
647
+ "needn't", "oughtn't", "shouldn't", "wasn't", "weren't", "won't", "wouldn't",
648
+ ]);
649
+ function subjectAgreementBeforeNegation(segment) {
650
+ const stream = lexRequiredEvidence(segment);
651
+ let auxiliaryIndex = -1;
652
+ let auxiliary = '';
653
+ for (let index = 0; index < stream.tokens.length; index += 1) {
654
+ const term = stream.tokens[index]?.value ?? '';
655
+ if (NEGATED_AUXILIARY_TERMS.has(term)
656
+ && (term === 'cannot'
657
+ || term.endsWith("n't")
658
+ || ['not', 'never'].includes(stream.tokens[index + 1]?.value ?? ''))) {
659
+ auxiliaryIndex = index;
660
+ auxiliary = term;
661
+ break;
662
+ }
663
+ }
664
+ if (auxiliaryIndex < 0)
665
+ return 'unknown';
666
+ if (BASE_AGREEMENT_AUXILIARIES.has(auxiliary))
667
+ return 'base';
668
+ if (THIRD_PERSON_AUXILIARIES.has(auxiliary))
669
+ return 'third-person';
670
+ let noun = '';
671
+ for (let index = 0; index < auxiliaryIndex; index += 1) {
672
+ if (isIgnoredRequiredToken(stream, index))
673
+ continue;
674
+ const term = stream.tokens[index]?.value ?? '';
675
+ if (BASE_AGREEMENT_SUBJECTS.has(term))
676
+ return 'base';
677
+ if (THIRD_PERSON_SUBJECTS.has(term))
678
+ return 'third-person';
679
+ if (SUBJECT_ARTICLE_TERMS.has(term)
680
+ || NON_SUBJECT_PREFIX_TERMS.has(term)
681
+ || LEADING_CLAUSE_MODIFIER_TERMS.has(term)
682
+ || /ly$/u.test(term))
683
+ continue;
684
+ noun = term;
685
+ }
686
+ if (!noun)
687
+ return 'unknown';
688
+ return noun.endsWith('s') && !/(?:is|ss|us)$/u.test(noun) ? 'base' : 'third-person';
689
+ }
690
+ function negatedAuxiliaryScope(segment) {
691
+ if (/\b(?:(?:had|has|have)\s+(?:not|never)|hadn't|hasn't|haven't)\b/i.test(segment))
692
+ return 'perfect';
693
+ if (/\b(?:did\s+(?:not|never)|didn't)\b/i.test(segment))
694
+ return 'past-bare';
695
+ const progressive = /\b(?:(?:am|are|is|was|were)\s+(?:not|never)|aren't|isn't|wasn't|weren't)\b/i.exec(segment);
696
+ if (progressive) {
697
+ const suffix = segment.slice(progressive.index + progressive[0].length);
698
+ const localComplement = suffix.split(/[,;]|\b(?:after|because|before|once|so|when|while|without)\b/i, 1)[0] ?? '';
699
+ if (/\b[a-z]+ing\b/i.test(localComplement))
700
+ return 'progressive';
701
+ }
702
+ return /\b(?:(?:can|could|did|do|does|may|might|must|need|needs|ought|shall|should|will|would)\s+(?:not|never)|cannot|can't|couldn't|didn't|doesn't|don't|mightn't|mustn't|needn't|oughtn't|shan't|shouldn't|won't|wouldn't)\b/i.test(segment)
703
+ ? 'bare'
704
+ : 'other';
705
+ }
706
+ function isThirdPersonActionForm(term, actionForms) {
707
+ if ([...IRREGULAR_VERB_FORMS.values()].some((forms) => forms[1] === term))
708
+ return true;
709
+ const bases = new Set();
710
+ addSingularCandidates(term, bases);
711
+ return [...bases].some((base) => actionForms.has(base));
712
+ }
713
+ function subjectElidedActionMorphology(segment, actionForms) {
714
+ const stream = lexRequiredEvidence(segment);
715
+ for (let index = 0; index < stream.tokens.length; index += 1) {
716
+ if (isIgnoredRequiredToken(stream, index))
717
+ continue;
718
+ const term = stream.tokens[index]?.value ?? '';
719
+ if (LEADING_CLAUSE_MODIFIER_TERMS.has(term) || /ly$/u.test(term))
720
+ continue;
721
+ if (!actionForms.has(term) || stream.conditional[index] === true)
722
+ return undefined;
723
+ if (term.endsWith('ing'))
724
+ return 'progressive';
725
+ if (IRREGULAR_SIMPLE_PAST_FORMS.has(term) && !IRREGULAR_PAST_PARTICIPLE_FORMS.has(term)) {
726
+ return 'simple-past';
727
+ }
728
+ if (term.endsWith('ed')
729
+ || IRREGULAR_SIMPLE_PAST_FORMS.has(term)
730
+ || IRREGULAR_PAST_PARTICIPLE_FORMS.has(term))
731
+ return 'participle';
732
+ return isThirdPersonActionForm(term, actionForms) ? 'third-person' : 'base';
733
+ }
734
+ return undefined;
735
+ }
736
+ function startsWithSubjectElidedGerund(segment) {
737
+ const stream = lexRequiredEvidence(segment);
738
+ for (let index = 0; index < stream.tokens.length; index += 1) {
739
+ if (isIgnoredRequiredToken(stream, index))
740
+ continue;
741
+ const term = stream.tokens[index]?.value ?? '';
742
+ if (LEADING_CLAUSE_MODIFIER_TERMS.has(term) || /ly$/u.test(term))
743
+ continue;
744
+ return term.endsWith('ing') && stream.conditional[index] !== true;
745
+ }
746
+ return false;
747
+ }
748
+ function finiteClauseSubjectPolarity(segment) {
749
+ const stream = lexRequiredEvidence(segment);
750
+ if (!hasFinitePredicatePrefix(stream, 0, stream.tokens.length))
751
+ return 'absent';
752
+ return leadingSubjectPolarity(stream, 0, stream.tokens.length);
753
+ }
754
+ function matchingActionSubjectPolarity(segment, actionForms) {
755
+ const stream = lexRequiredEvidence(segment);
756
+ for (let actionIndex = 0; actionIndex < stream.tokens.length; actionIndex += 1) {
757
+ if (!actionForms.has(stream.tokens[actionIndex]?.value ?? ''))
758
+ continue;
759
+ return leadingSubjectPolarity(stream, 0, actionIndex);
760
+ }
761
+ return 'absent';
762
+ }
763
+ function hasIndependentFiniteAction(segment, actionForms) {
764
+ const stream = lexRequiredEvidence(segment);
765
+ for (let actionIndex = 1; actionIndex < stream.tokens.length; actionIndex += 1) {
766
+ const term = stream.tokens[actionIndex]?.value ?? '';
767
+ if (!actionForms.has(term) || stream.conditional[actionIndex] === true)
768
+ continue;
769
+ const finite = term.endsWith('ed') || term.endsWith('s') || IRREGULAR_SIMPLE_PAST_FORMS.has(term);
770
+ if (finite && leadingSubjectPolarity(stream, 0, actionIndex) === 'positive')
771
+ return true;
772
+ }
773
+ return false;
774
+ }
775
+ function stripCommaDelimitedWithoutAdjuncts(clause, actionForms) {
776
+ return clause.replace(COMMA_DELIMITED_WITHOUT_RE, (match) => {
777
+ const coordinated = match.replace(/^\s*without\b/i, '').replace(/,\s*$/u, '').split(/\band\b/i).slice(1);
778
+ const hasFiniteCoordination = coordinated.some((segment) => (startsWithSubjectElidedFiniteAction(segment, actionForms)
779
+ || hasIndependentFiniteAction(segment, actionForms)));
780
+ return hasFiniteCoordination ? match : ' ';
781
+ });
782
+ }
783
+ function affirmativePrefixBeforeNegation(segment, actionForms, inheritsSubject = true) {
784
+ const match = NEGATED_ACTION_RE.exec(segment);
785
+ if (!match || match.index === 0)
786
+ return undefined;
787
+ const prefix = normalize(segment.slice(0, match.index));
788
+ return prefix && hasIndependentAffirmativeAction(prefix, actionForms, inheritsSubject) ? prefix : undefined;
789
+ }
790
+ function hasIndependentSimplePastPredicate(segment) {
791
+ const stream = lexRequiredEvidence(segment);
792
+ for (let predicateIndex = 1; predicateIndex < stream.tokens.length; predicateIndex += 1) {
793
+ const term = stream.tokens[predicateIndex]?.value ?? '';
794
+ const simplePast = ['did', 'had', 'was', 'were'].includes(term)
795
+ || term.endsWith('ed')
796
+ || IRREGULAR_SIMPLE_PAST_FORMS.has(term);
797
+ if (simplePast && leadingSubjectPolarity(stream, 0, predicateIndex) === 'positive')
798
+ return true;
799
+ }
800
+ return false;
801
+ }
802
+ function hasIndependentSimplePastAction(segment, actionForms, inheritsSubject = false) {
803
+ const stream = lexRequiredEvidence(segment.slice(0, MAX_FACTUAL_TAIL_CHARS));
804
+ actionLoop: for (let index = 0; index < stream.tokens.length; index += 1) {
805
+ const term = stream.tokens[index]?.value ?? '';
806
+ if (!actionForms.has(term) || stream.conditional[index] === true)
807
+ continue;
808
+ for (let prior = index - 1; prior >= 0; prior -= 1) {
809
+ const priorTerm = stream.tokens[prior]?.value ?? '';
810
+ if (PREDICATE_COORDINATOR_TERMS.has(priorTerm))
811
+ break;
812
+ if (MODAL_AUXILIARY_TERMS.has(priorTerm) || SEMI_MODAL_AUXILIARY_TERMS.has(priorTerm)) {
813
+ continue actionLoop;
814
+ }
815
+ }
816
+ const subjectPolarity = leadingSubjectPolarity(stream, 0, index);
817
+ if ((term.endsWith('ed') || IRREGULAR_SIMPLE_PAST_FORMS.has(term))
818
+ && (subjectPolarity === 'positive'
819
+ || (inheritsSubject && subjectPolarity !== 'negative')))
820
+ return true;
821
+ }
822
+ return false;
823
+ }
824
+ function hasPotentialConditionalScope(segment, actionForms, targetActionForms, terms) {
825
+ const providedTargetPrefixKind = (source) => {
826
+ const normalizedSource = normalize(source);
827
+ const objectTerms = terms.filter((term) => !term.verb);
828
+ if (!normalizedSource || objectTerms.length === 0)
829
+ return 'none';
830
+ const stream = lexRequiredEvidence(normalizedSource);
831
+ const requiredObjectMatches = Math.min(2, objectTerms.length);
832
+ const objectForms = objectTerms.map((term) => ({
833
+ forms: targetTermForms(term, 'must_not'),
834
+ term,
835
+ }));
836
+ const matchedObjectTerms = new Set();
837
+ const matchedObjectIndexes = new Set();
838
+ let objectPrefixEnd = -1;
839
+ objectPrefix: for (let index = 0; index < stream.tokens.length; index += 1) {
840
+ const value = stream.tokens[index]?.value ?? '';
841
+ for (const candidate of objectForms) {
842
+ if (matchedObjectTerms.has(candidate.term) || !candidate.forms.has(value))
843
+ continue;
844
+ matchedObjectTerms.add(candidate.term);
845
+ matchedObjectIndexes.add(index);
846
+ if (matchedObjectTerms.size >= requiredObjectMatches) {
847
+ objectPrefixEnd = index;
848
+ break objectPrefix;
849
+ }
850
+ }
851
+ }
852
+ if (objectPrefixEnd < 0)
853
+ return 'none';
854
+ const nonObjectPrefix = stream.tokens
855
+ .slice(0, objectPrefixEnd + 1)
856
+ .filter((_, index) => !matchedObjectIndexes.has(index))
857
+ .map((token) => token.value)
858
+ .join(' ');
859
+ if (finiteClauseSubjectPolarity(nonObjectPrefix) !== 'absent'
860
+ || hasIndependentAffirmativeAction(nonObjectPrefix, actionForms))
861
+ return 'conditional';
862
+ // A leading "provided" is ambiguous; walk bounded tail structures and keep unknown shapes conditional.
863
+ const skipProvidedModifiers = (initialIndex) => {
864
+ let index = initialIndex;
865
+ while (index < stream.tokens.length) {
866
+ const term = stream.tokens[index]?.value ?? '';
867
+ if (!LEADING_CLAUSE_MODIFIER_TERMS.has(term)
868
+ && !PROVIDED_OBJECT_TRAILING_MODIFIER_TERMS.has(term)
869
+ && !/ly$/u.test(term))
870
+ break;
871
+ index += 1;
872
+ }
873
+ return index;
874
+ };
875
+ const consumeRemainingObjectTerms = (initialIndex) => {
876
+ let index = skipProvidedModifiers(initialIndex);
877
+ const remaining = new Set(objectForms.filter((candidate) => !matchedObjectTerms.has(candidate.term)));
878
+ while (index < stream.tokens.length) {
879
+ const candidate = [...remaining].find((entry) => (entry.forms.has(stream.tokens[index]?.value ?? '')));
880
+ if (!candidate)
881
+ break;
882
+ remaining.delete(candidate);
883
+ index = skipProvidedModifiers(index + 1);
884
+ }
885
+ return index;
886
+ };
887
+ const isProvidedPastPredicate = (term) => (term.endsWith('ed')
888
+ || IRREGULAR_SIMPLE_PAST_FORMS.has(term)
889
+ || IRREGULAR_PAST_PARTICIPLE_FORMS.has(term));
890
+ const consumeProvidedNominal = (initialIndex) => {
891
+ let index = skipProvidedModifiers(initialIndex);
892
+ if (SUBJECT_ARTICLE_TERMS.has(stream.tokens[index]?.value ?? ''))
893
+ index += 1;
894
+ while (index + 1 < stream.tokens.length) {
895
+ const term = stream.tokens[index]?.value ?? '';
896
+ if (!isAttributiveTargetModifier(term)
897
+ && !PROVIDED_OBJECT_NOMINAL_MODIFIER_TERMS.has(term))
898
+ break;
899
+ index += 1;
900
+ }
901
+ if (index < stream.tokens.length)
902
+ index += 1;
903
+ return skipProvidedModifiers(index);
904
+ };
905
+ const isProvidedObjectForm = (term) => objectForms.some((candidate) => (candidate.forms.has(term)));
906
+ const isProvidedMatrixPredicate = (term) => (PROVIDED_OBJECT_MATRIX_PREDICATE_TERMS.has(term)
907
+ || isFinitePredicateTerm(term)
908
+ || (actionForms.has(term) && !isProvidedObjectForm(term)));
909
+ const isProvidedNominalHead = (term) => (PROVIDED_OBJECT_NOMINAL_HEAD_TERMS.has(term) || isProvidedObjectForm(term));
910
+ const isProvidedNominalModifier = (term) => (isAttributiveTargetModifier(term)
911
+ || PROVIDED_OBJECT_NOMINAL_MODIFIER_TERMS.has(term)
912
+ || isProvidedObjectForm(term));
913
+ const reachesKnownProvidedNominalHead = (initialIndex, genericModifierBudget) => {
914
+ let index = initialIndex;
915
+ while (index < stream.tokens.length) {
916
+ const term = stream.tokens[index]?.value ?? '';
917
+ if (isProvidedNominalHead(term))
918
+ return true;
919
+ if (!isProvidedNominalModifier(term)) {
920
+ if (genericModifierBudget <= 0)
921
+ return false;
922
+ genericModifierBudget -= 1;
923
+ }
924
+ index = skipProvidedModifiers(index + 1);
925
+ }
926
+ return false;
927
+ };
928
+ const isProvidedMatrixNominalCollocation = (initialIndex) => {
929
+ const modifier = stream.tokens[initialIndex]?.value ?? '';
930
+ let index = skipProvidedModifiers(initialIndex + 1);
931
+ while (index < stream.tokens.length) {
932
+ const term = stream.tokens[index]?.value ?? '';
933
+ if (isProvidedNominalHead(term)) {
934
+ return PROVIDED_OBJECT_MATRIX_NOMINAL_COLLOCATIONS.has(`${modifier}:${term}`);
935
+ }
936
+ if (!isProvidedNominalModifier(term))
937
+ return false;
938
+ index = skipProvidedModifiers(index + 1);
939
+ }
940
+ return false;
941
+ };
942
+ const consumeProvidedNominalPhrase = (initialIndex) => {
943
+ let index = skipProvidedModifiers(initialIndex);
944
+ const hadArticle = SUBJECT_ARTICLE_TERMS.has(stream.tokens[index]?.value ?? '');
945
+ if (hadArticle)
946
+ index += 1;
947
+ let sawPrenominalModifier = false;
948
+ let genericPrenominalModifiers = 0;
949
+ const genericPrenominalLimit = hadArticle ? 2 : 1;
950
+ while (index + 1 < stream.tokens.length) {
951
+ const term = stream.tokens[index]?.value ?? '';
952
+ const nextIndex = skipProvidedModifiers(index + 1);
953
+ const next = stream.tokens[nextIndex]?.value ?? '';
954
+ if (nextIndex >= stream.tokens.length
955
+ || PROVIDED_OBJECT_RELATIVE_TERMS.has(next)
956
+ || CONDITIONAL_MARKER_TERMS.has(next)
957
+ || PROVIDED_OBJECT_FACTUAL_TAIL_PREFIX_TERMS.has(next)
958
+ || SUBJECT_ARTICLE_TERMS.has(next)
959
+ || (isProvidedMatrixPredicate(next)
960
+ && !isProvidedMatrixNominalCollocation(nextIndex)))
961
+ break;
962
+ const genericBeforeKnownHead = genericPrenominalModifiers < genericPrenominalLimit
963
+ && !isProvidedNominalHead(term)
964
+ && reachesKnownProvidedNominalHead(nextIndex, genericPrenominalLimit - genericPrenominalModifiers - 1);
965
+ const modifier = isProvidedNominalModifier(term)
966
+ || genericBeforeKnownHead;
967
+ if (!modifier)
968
+ break;
969
+ if (!isProvidedNominalModifier(term))
970
+ genericPrenominalModifiers += 1;
971
+ sawPrenominalModifier = true;
972
+ index = nextIndex;
973
+ }
974
+ const head = stream.tokens[index]?.value ?? '';
975
+ if (!head
976
+ || PROVIDED_OBJECT_RELATIVE_TERMS.has(head)
977
+ || CONDITIONAL_MARKER_TERMS.has(head)
978
+ || PROVIDED_OBJECT_FACTUAL_TAIL_PREFIX_TERMS.has(head)
979
+ || SUBJECT_ARTICLE_TERMS.has(head)
980
+ || isProvidedMatrixPredicate(head)
981
+ || (sawPrenominalModifier && !isProvidedNominalHead(head)))
982
+ return index;
983
+ index = skipProvidedModifiers(index + 1);
984
+ while (stream.tokens[index]?.value.endsWith('ing')) {
985
+ const complementStart = skipProvidedModifiers(index + 1);
986
+ const complementEnd = consumeProvidedNominalPhrase(complementStart);
987
+ if (complementEnd <= complementStart)
988
+ break;
989
+ index = complementEnd;
990
+ }
991
+ return index;
992
+ };
993
+ const consumeProvidedTerminalNominal = (initialIndex) => {
994
+ const nominalEnd = consumeProvidedNominalPhrase(initialIndex);
995
+ if (nominalEnd >= stream.tokens.length)
996
+ return nominalEnd;
997
+ const rejectedHead = stream.tokens[nominalEnd]?.value ?? '';
998
+ let prefixIndex = skipProvidedModifiers(initialIndex);
999
+ if (SUBJECT_ARTICLE_TERMS.has(stream.tokens[prefixIndex]?.value ?? '')) {
1000
+ prefixIndex = skipProvidedModifiers(prefixIndex + 1);
1001
+ }
1002
+ while (prefixIndex < nominalEnd
1003
+ && !isProvidedNominalHead(stream.tokens[prefixIndex]?.value ?? '')) {
1004
+ prefixIndex = skipProvidedModifiers(prefixIndex + 1);
1005
+ }
1006
+ return isProvidedNominalHead(rejectedHead)
1007
+ && prefixIndex === nominalEnd
1008
+ && skipProvidedModifiers(nominalEnd + 1) >= stream.tokens.length
1009
+ ? stream.tokens.length
1010
+ : nominalEnd;
1011
+ };
1012
+ const classifyProvidedRelativeTail = (initialIndex) => {
1013
+ let index = skipProvidedModifiers(initialIndex);
1014
+ const first = stream.tokens[index]?.value ?? '';
1015
+ if (SUBJECT_ARTICLE_TERMS.has(first)) {
1016
+ index = consumeProvidedNominal(index);
1017
+ }
1018
+ else if (!isProvidedPastPredicate(first)
1019
+ && !PASSIVE_AUXILIARY_TERMS.has(first)
1020
+ && isProvidedPastPredicate(stream.tokens[index + 1]?.value ?? '')) {
1021
+ index = skipProvidedModifiers(index + 1);
1022
+ }
1023
+ while (PASSIVE_AUXILIARY_TERMS.has(stream.tokens[index]?.value ?? '')) {
1024
+ index = skipProvidedModifiers(index + 1);
1025
+ }
1026
+ const predicate = stream.tokens[index]?.value ?? '';
1027
+ if (!isProvidedPastPredicate(predicate))
1028
+ return 'conditional';
1029
+ index = skipProvidedModifiers(index + 1);
1030
+ if (index >= stream.tokens.length)
1031
+ return 'factual';
1032
+ return consumeProvidedTerminalNominal(index) >= stream.tokens.length ? 'factual' : 'conditional';
1033
+ };
1034
+ const classifyProvidedSubordinateTail = (initialIndex) => {
1035
+ let index = skipProvidedModifiers(initialIndex);
1036
+ if (index >= stream.tokens.length)
1037
+ return 'factual';
1038
+ if (isProvidedPastPredicate(stream.tokens[index]?.value ?? '')) {
1039
+ index = skipProvidedModifiers(index + 1);
1040
+ if (index >= stream.tokens.length)
1041
+ return 'factual';
1042
+ }
1043
+ return consumeProvidedTerminalNominal(index) >= stream.tokens.length ? 'factual' : 'conditional';
1044
+ };
1045
+ const classifyProvidedObjectTail = (initialIndex) => {
1046
+ let index = skipProvidedModifiers(initialIndex);
1047
+ if (index >= stream.tokens.length)
1048
+ return 'factual';
1049
+ const term = stream.tokens[index]?.value ?? '';
1050
+ if (PROVIDED_OBJECT_COORDINATOR_TERMS.has(term)) {
1051
+ index = skipProvidedModifiers(index + 1);
1052
+ if (index >= stream.tokens.length)
1053
+ return 'factual';
1054
+ const coordinatedTerm = stream.tokens[index]?.value ?? '';
1055
+ const subjectElidedPastAction = actionForms.has(coordinatedTerm)
1056
+ && isProvidedPastPredicate(coordinatedTerm);
1057
+ if (subjectElidedPastAction) {
1058
+ index = skipProvidedModifiers(index + 1);
1059
+ if (index >= stream.tokens.length)
1060
+ return 'factual';
1061
+ if (COMMA_DELIMITED_PREPOSITION_TERMS.has(stream.tokens[index]?.value ?? '')) {
1062
+ return classifyProvidedObjectTail(index);
1063
+ }
1064
+ }
1065
+ return classifyProvidedObjectTail(consumeProvidedTerminalNominal(index));
1066
+ }
1067
+ if (PROVIDED_OBJECT_RELATIVE_TERMS.has(term))
1068
+ return classifyProvidedRelativeTail(index + 1);
1069
+ if (COMMA_DELIMITED_PREPOSITION_TERMS.has(term)) {
1070
+ return classifyProvidedObjectTail(consumeProvidedTerminalNominal(index + 1));
1071
+ }
1072
+ if (CONDITIONAL_MARKER_TERMS.has(term))
1073
+ return 'conditional';
1074
+ if (PROVIDED_OBJECT_SUBORDINATOR_TERMS.has(term)) {
1075
+ return classifyProvidedSubordinateTail(index + 1);
1076
+ }
1077
+ if (PROVIDED_OBJECT_FACTUAL_TAIL_PREFIX_TERMS.has(term))
1078
+ return 'factual';
1079
+ if (term.endsWith('ing')) {
1080
+ return classifyProvidedObjectTail(consumeProvidedTerminalNominal(index + 1));
1081
+ }
1082
+ return 'conditional';
1083
+ };
1084
+ return classifyProvidedObjectTail(consumeRemainingObjectTerms(objectPrefixEnd + 1));
1085
+ };
1086
+ const hasTargetActionPrefix = (source) => {
1087
+ const stream = lexRequiredEvidence(source);
1088
+ for (let actionIndex = 0; actionIndex < stream.tokens.length; actionIndex += 1) {
1089
+ if (!targetActionForms.has(stream.tokens[actionIndex]?.value ?? ''))
1090
+ continue;
1091
+ if (actionIndex === 0 || leadingSubjectPolarity(stream, 0, actionIndex) === 'positive')
1092
+ return true;
1093
+ }
1094
+ return false;
1095
+ };
1096
+ const hasProvidedMatrixAction = (source) => {
1097
+ // Restrict disambiguation to the immediate consequent, not a later factual clause.
1098
+ const commaIndex = source.indexOf(',');
1099
+ if (commaIndex >= 0) {
1100
+ const prefixKind = providedTargetPrefixKind(source.slice(0, commaIndex));
1101
+ if (prefixKind === 'factual')
1102
+ return false;
1103
+ if (prefixKind === 'conditional')
1104
+ return true;
1105
+ }
1106
+ let matrixScope = commaIndex >= 0
1107
+ ? source.slice(commaIndex + 1)
1108
+ : source.replace(/^\s*that\b/iu, ' ');
1109
+ const subordinateIndex = matrixScope.search(/\b(?:after|assuming|because|before|if|once|supposing|that|unless|when|while)\b/iu);
1110
+ if (subordinateIndex >= 0)
1111
+ matrixScope = matrixScope.slice(0, subordinateIndex);
1112
+ const candidates = matrixScope.split(/\b(?:and|but|or|then)\b/iu);
1113
+ return (commaIndex >= 0 ? candidates : candidates.slice(0, 1))
1114
+ .some((candidate) => hasIndependentAffirmativeAction(candidate, targetActionForms));
1115
+ };
1116
+ const hasPotentialMarker = (source) => {
1117
+ const normalizedSource = source
1118
+ .replace(CLOSED_IF_ANYTHING_RE, '$1 ')
1119
+ .replace(COORDINATED_PROVIDED_RE, (match, connector, offset, whole) => (!targetActionForms.has('provided')
1120
+ || hasTargetActionPrefix(whole.slice(0, offset))
1121
+ || hasProvidedMatrixAction(whole.slice(offset + match.length))
1122
+ ? match
1123
+ : `${connector} `))
1124
+ .replace(LEADING_PROVIDED_RE, (match, boundary, offset, whole) => (!targetActionForms.has('provided')
1125
+ || hasTargetActionPrefix(whole.slice(0, offset))
1126
+ || hasProvidedMatrixAction(whole.slice(offset + match.length))
1127
+ ? match
1128
+ : `${boundary} `));
1129
+ return POTENTIAL_CONDITIONAL_SCOPE_RE.test(normalizedSource)
1130
+ || hasTargetActionBeforePostposedConditionalMarker(normalizedSource, targetActionForms);
1131
+ };
1132
+ const hasFactualProvidedAction = (source) => {
1133
+ const stream = lexRequiredEvidence(source);
1134
+ for (let index = 0; index < stream.tokens.length; index += 1) {
1135
+ const token = stream.tokens[index];
1136
+ if (token?.value !== 'provided' || isAdjectivalProvided(stream.tokens, index))
1137
+ continue;
1138
+ let clauseStart = index;
1139
+ while (clauseStart > 0 && stream.tokens[clauseStart - 1]?.clause === token.clause)
1140
+ clauseStart -= 1;
1141
+ if (leadingSubjectPolarity(stream, clauseStart, index) !== 'positive')
1142
+ continue;
1143
+ const tail = stream.tokens.slice(index + 1)
1144
+ .filter((candidate) => candidate.clause === token.clause)
1145
+ .map((candidate) => candidate.value)
1146
+ .join(' ');
1147
+ if (!hasProvidedMatrixAction(tail))
1148
+ return true;
1149
+ }
1150
+ return false;
1151
+ };
1152
+ if (!hasPotentialMarker(segment))
1153
+ return false;
1154
+ const hadProvidedBy = segment.search(PROVIDED_BY_ADJUNCT_RE) >= 0;
1155
+ let probe = segment.replace(PROVIDED_BY_ADJUNCT_RE, ' ');
1156
+ probe = probe.replace(COORDINATED_TEMPORAL_RE, (match, connector, offset) => {
1157
+ const prefix = probe.slice(0, offset);
1158
+ const suffix = probe.slice(offset + match.length);
1159
+ return hasIndependentSimplePastPredicate(prefix) && hasIndependentSimplePastAction(suffix, actionForms, true)
1160
+ ? `${connector} `
1161
+ : match;
1162
+ });
1163
+ const leadingTemporal = LEADING_DISCOURSE_TEMPORAL_RE.exec(probe);
1164
+ if (leadingTemporal) {
1165
+ const suffix = probe.slice(leadingTemporal[0].length);
1166
+ if (hasIndependentSimplePastAction(suffix, actionForms)
1167
+ || hasFactualProvidedAction(suffix))
1168
+ probe = suffix;
1169
+ }
1170
+ const hasRemainingPotentialMarker = hasPotentialMarker(probe)
1171
+ || hasTargetActionBeforePostposedConditionalMarker(probe, targetActionForms);
1172
+ return hasRemainingPotentialMarker || (hadProvidedBy
1173
+ && !hasFactualProvidedAction(probe)
1174
+ && lexRequiredEvidence(probe).conditional.some(Boolean));
1175
+ }
1176
+ const NON_FACTUAL_MATRIX_AUXILIARIES = new Set([
1177
+ 'can', 'cannot', "can't", 'could', "couldn't", 'may', 'might', "mightn't", 'must', "mustn't",
1178
+ 'need', "needn't", 'ought', "oughtn't", 'shall', "shan't", 'should', "shouldn't", 'will', "won't",
1179
+ 'would', "wouldn't",
1180
+ ]);
1181
+ function hasFactualMatrixPredicate(segment) {
1182
+ const stream = lexRequiredEvidence(segment);
1183
+ if (stream.tokens.some((token, index) => (!isIgnoredRequiredToken(stream, index) && NON_FACTUAL_MATRIX_AUXILIARIES.has(token.value))))
1184
+ return false;
1185
+ return finiteClauseSubjectPolarity(segment) !== 'absent';
1186
+ }
1187
+ const MAX_FACTUAL_TAIL_PROBES = 256;
1188
+ const MAX_FACTUAL_TAIL_CHARS = 4_096;
1189
+ function boundedFactualTail(segment, start) {
1190
+ const window = segment.slice(start, start + MAX_FACTUAL_TAIL_CHARS);
1191
+ const hardBoundary = window.search(/[.!?;\r\n]/u);
1192
+ return normalize(hardBoundary >= 0 ? window.slice(0, hardBoundary) : window);
1193
+ }
1194
+ function factualTailsAfterBoundary(segment) {
1195
+ const tails = [];
1196
+ const seen = new Set();
1197
+ const addTail = (start, temporal, matrixEnd, allowsInheritedGerund = false) => {
1198
+ const candidate = boundedFactualTail(segment, start);
1199
+ if (!candidate || seen.has(candidate))
1200
+ return;
1201
+ seen.add(candidate);
1202
+ if (temporal) {
1203
+ const prefixEnd = matrixEnd ?? start;
1204
+ const matrixPrefix = normalize(segment.slice(Math.max(0, prefixEnd - MAX_FACTUAL_TAIL_CHARS), prefixEnd));
1205
+ const inheritedGerund = allowsInheritedGerund && startsWithSubjectElidedGerund(candidate);
1206
+ if (!hasFactualMatrixPredicate(matrixPrefix)
1207
+ || (!hasIndependentSimplePastPredicate(candidate) && !inheritedGerund))
1208
+ return;
1209
+ }
1210
+ tails.push(candidate);
1211
+ };
1212
+ const boundaryRe = /\b(after|because|before|once|so|when|while)\b/gi;
1213
+ let boundaryProbes = 0;
1214
+ for (const match of segment.matchAll(boundaryRe)) {
1215
+ if (boundaryProbes >= MAX_FACTUAL_TAIL_PROBES)
1216
+ break;
1217
+ boundaryProbes += 1;
1218
+ const marker = match[1]?.toLowerCase() ?? '';
1219
+ const temporal = marker === 'once' || marker === 'when' || marker === 'while';
1220
+ addTail(match.index + match[0].length, temporal, match.index, marker === 'when' || marker === 'while');
1221
+ }
1222
+ const commaRe = /,/g;
1223
+ let commaProbes = 0;
1224
+ for (const match of segment.matchAll(commaRe)) {
1225
+ if (commaProbes >= MAX_FACTUAL_TAIL_PROBES)
1226
+ break;
1227
+ commaProbes += 1;
1228
+ addTail(match.index + 1, false);
1229
+ }
1230
+ return tails;
1231
+ }
1232
+ function affirmativeFragmentFromTail(candidate, actionForms, inheritsSubject) {
1233
+ if (NEGATED_ACTION_RE.test(candidate)) {
1234
+ return affirmativePrefixBeforeNegation(candidate, actionForms, inheritsSubject);
1235
+ }
1236
+ return hasIndependentAffirmativeAction(candidate, actionForms, inheritsSubject) ? candidate : undefined;
1237
+ }
1238
+ function factualTailSubjectPolarity(segment) {
1239
+ const stream = lexRequiredEvidence(segment);
1240
+ for (let predicateIndex = 1; predicateIndex < stream.tokens.length; predicateIndex += 1) {
1241
+ if (stream.conditional[predicateIndex] === true)
1242
+ continue;
1243
+ const term = stream.tokens[predicateIndex]?.value ?? '';
1244
+ const finite = isFinitePredicateTerm(term)
1245
+ || IRREGULAR_SIMPLE_PAST_FORMS.has(term)
1246
+ || term.endsWith('ed')
1247
+ || (KNOWN_EVIDENCE_ACTION_TERMS.has(term) && term.endsWith('s'));
1248
+ if (finite)
1249
+ return leadingSubjectPolarity(stream, 0, predicateIndex);
1250
+ }
1251
+ return 'absent';
1252
+ }
1253
+ function factualTailEvidence(tails, actionForms, inheritsSubject) {
1254
+ let subjectMode = 'none';
1255
+ let subjectAgreement = 'unknown';
1256
+ for (const tail of tails) {
1257
+ const affirmative = affirmativeFragmentFromTail(tail, actionForms, inheritsSubject);
1258
+ if (affirmative)
1259
+ return { affirmative, subjectAgreement: 'unknown', subjectMode: 'affirmative' };
1260
+ if (NEGATED_ACTION_RE.test(tail)) {
1261
+ if (hasExplicitSubjectBeforeNegation(tail)) {
1262
+ const auxiliaryScope = negatedAuxiliaryScope(tail);
1263
+ subjectMode = auxiliaryScope === 'bare'
1264
+ ? 'negated-bare'
1265
+ : auxiliaryScope === 'past-bare'
1266
+ ? 'negated-past-bare'
1267
+ : auxiliaryScope === 'perfect'
1268
+ ? 'negated-perfect'
1269
+ : auxiliaryScope === 'progressive'
1270
+ ? 'negated-progressive'
1271
+ : 'finite';
1272
+ subjectAgreement = subjectAgreementBeforeNegation(tail);
1273
+ }
1274
+ continue;
1275
+ }
1276
+ if (inheritsSubject && startsWithSubjectElidedGerund(tail)) {
1277
+ subjectMode = 'affirmative';
1278
+ subjectAgreement = 'unknown';
1279
+ continue;
1280
+ }
1281
+ const polarity = factualTailSubjectPolarity(tail);
1282
+ if (polarity === 'positive') {
1283
+ subjectMode = 'affirmative';
1284
+ subjectAgreement = 'unknown';
1285
+ }
1286
+ else if (polarity === 'negative' && subjectMode === 'none') {
1287
+ subjectMode = 'negative';
1288
+ subjectAgreement = 'unknown';
1289
+ }
1290
+ }
1291
+ return { subjectAgreement, subjectMode };
1292
+ }
1293
+ function contextualNoAdjunctAllowsImperative(segment) {
1294
+ const commaIndex = segment.indexOf(',');
1295
+ if (commaIndex < 0)
1296
+ return false;
1297
+ const terms = normalize(segment.slice(0, commaIndex)).split(' ').filter(Boolean);
1298
+ return terms[0] === 'no' && (terms[1] === 'later' || terms[1] === 'matter');
1299
+ }
1300
+ function predicateConditionality(segment, actionForms) {
1301
+ const stream = lexRequiredEvidence(segment);
1302
+ let conditional = false;
1303
+ let unconditional = false;
1304
+ for (let index = 0; index < stream.tokens.length; index += 1) {
1305
+ if (isIgnoredRequiredToken(stream, index))
1306
+ continue;
1307
+ const term = stream.tokens[index]?.value ?? '';
1308
+ const predicate = actionForms.has(term)
1309
+ || isFinitePredicateTerm(term)
1310
+ || IRREGULAR_SIMPLE_PAST_FORMS.has(term)
1311
+ || term.endsWith('ed')
1312
+ || (KNOWN_EVIDENCE_ACTION_TERMS.has(term) && /(?:ing|s)$/u.test(term));
1313
+ if (!predicate)
1314
+ continue;
1315
+ if (stream.conditional[index] === true)
1316
+ conditional = true;
1317
+ else
1318
+ unconditional = true;
1319
+ }
1320
+ if (conditional && unconditional)
1321
+ return 'mixed';
1322
+ if (conditional)
1323
+ return 'conditional';
1324
+ if (unconditional)
1325
+ return 'unconditional';
1326
+ return 'none';
1327
+ }
1328
+ function hasMatrixPredicateBeforeConditionalMarker(segment, actionForms) {
1329
+ const stream = lexRequiredEvidence(segment);
1330
+ const markerIndex = stream.tokens.findIndex((token, index) => (!isIgnoredRequiredToken(stream, index)
1331
+ && CONDITIONAL_MARKER_TERMS.has(token.value)
1332
+ && stream.conditional[index] === true));
1333
+ if (markerIndex < 0)
1334
+ return false;
1335
+ for (let index = 0; index < markerIndex; index += 1) {
1336
+ if (isIgnoredRequiredToken(stream, index))
1337
+ continue;
1338
+ const term = stream.tokens[index]?.value ?? '';
1339
+ if (actionForms.has(term)
1340
+ || isFinitePredicateTerm(term)
1341
+ || IRREGULAR_SIMPLE_PAST_FORMS.has(term)
1342
+ || term.endsWith('ed')
1343
+ || (KNOWN_EVIDENCE_ACTION_TERMS.has(term) && /(?:ing|s)$/u.test(term)))
1344
+ return true;
1345
+ }
1346
+ return false;
1347
+ }
1348
+ function hasTargetActionBeforePostposedConditionalMarker(segment, targetActionForms) {
1349
+ const stream = lexRequiredEvidence(segment);
1350
+ let targetActionIndex;
1351
+ for (let index = 0; index < stream.tokens.length; index += 1) {
1352
+ const term = stream.tokens[index]?.value ?? '';
1353
+ const embeddedGovernorIndex = embeddedIfGovernorIndex(stream.tokens, index);
1354
+ const targetActionIsEmbeddedGovernor = embeddedGovernorIndex !== undefined
1355
+ && embeddedGovernorIndex === targetActionIndex;
1356
+ const postposedMarker = isConditionalMarkerUse(stream.tokens, index)
1357
+ || targetActionIsEmbeddedGovernor
1358
+ || (TEMPORAL_CONDITIONAL_MARKER_TERMS.has(term)
1359
+ && !isMentionedConditionalMarker(stream.tokens, index)
1360
+ && !isFactualTemporalMarkerUse(stream.tokens, index));
1361
+ if (targetActionIndex !== undefined
1362
+ && postposedMarker) {
1363
+ if (!TEMPORAL_CONDITIONAL_MARKER_TERMS.has(term))
1364
+ return true;
1365
+ const prefix = segment.slice(0, stream.tokens[index]?.start ?? 0);
1366
+ if (!hasIndependentSimplePastAction(prefix, targetActionForms))
1367
+ return true;
1368
+ }
1369
+ if (targetActionIndex === undefined
1370
+ && targetActionForms.has(term)
1371
+ && (index === 0 || leadingSubjectPolarity(stream, 0, index) === 'positive')) {
1372
+ targetActionIndex = index;
1373
+ }
1374
+ }
1375
+ return false;
1376
+ }
1377
+ function affirmativeOutputClauses(redactedOutput, terms) {
1378
+ const actionForms = new Set(AFFIRMATIVE_ACTION_FORMS);
1379
+ const targetActionForms = new Set();
1380
+ for (const term of terms) {
1381
+ if (!term.verb)
1382
+ continue;
1383
+ for (const form of targetTermForms(term, 'must_not')) {
1384
+ actionForms.add(form);
1385
+ targetActionForms.add(form);
1386
+ }
1387
+ }
1388
+ return redactedOutput
1389
+ .split(/(?<=[.!?;])|\r?\n|\b(?:but|however|although|though)\b/gi)
1390
+ .map((clause) => clause.replace(REDACTION_MARKER_RE, ' ').trim())
1391
+ .filter((clause) => clause.length > 0)
1392
+ .flatMap((rawClause) => {
1393
+ const scopedClause = stripCommaDelimitedWithoutAdjuncts(rawClause, actionForms);
1394
+ const clause = normalize(scopedClause);
1395
+ if (!NEGATED_ACTION_RE.test(clause)
1396
+ && !WITHOUT_ACTION_RE.test(clause)
1397
+ && !NEGATIVE_SUBJECT_MARKER_RE.test(clause)
1398
+ && !hasPotentialConditionalScope(scopedClause, actionForms, targetActionForms, terms))
1399
+ return [clause];
1400
+ const parts = scopedClause.split(/\b(and|but|however|although|though|or|then|without)\b/i);
1401
+ const segments = [];
1402
+ let connector;
1403
+ for (let index = 0; index < parts.length; index += 1) {
1404
+ const part = parts[index] ?? '';
1405
+ if (index % 2 === 1) {
1406
+ connector = part.trim().toLowerCase();
1407
+ continue;
1408
+ }
1409
+ const rawText = part.trim();
1410
+ const text = normalize(rawText);
1411
+ if (!text)
1412
+ continue;
1413
+ segments.push({ ...(connector ? { connector } : {}), rawText, text });
1414
+ connector = undefined;
1415
+ }
1416
+ let requiresIndependentAction = NEGATED_ACTION_RE.test(segments[0]?.text ?? '');
1417
+ let inheritedSubjectMode = 'none';
1418
+ let inheritedSubjectAgreement = 'unknown';
1419
+ let conditionalCoordination = false;
1420
+ let conditionalCoordinationAllowsIndependentReset = false;
1421
+ return segments.flatMap((segment, index) => {
1422
+ if (segment.connector === 'without') {
1423
+ requiresIndependentAction = true;
1424
+ if (/,\s*$/u.test(segment.rawText)) {
1425
+ conditionalCoordination = false;
1426
+ conditionalCoordinationAllowsIndependentReset = false;
1427
+ }
1428
+ return [];
1429
+ }
1430
+ const postposedTargetConditional = hasTargetActionBeforePostposedConditionalMarker(segment.rawText, targetActionForms);
1431
+ const ownConditional = postposedTargetConditional
1432
+ || (predicateConditionality(segment.text, actionForms) === 'conditional'
1433
+ && !hasMatrixPredicateBeforeConditionalMarker(segment.text, actionForms));
1434
+ const inheritedConditional = conditionalCoordination
1435
+ && (segment.connector === 'and' || segment.connector === 'or' || segment.connector === 'then')
1436
+ && !(conditionalCoordinationAllowsIndependentReset
1437
+ && hasIndependentAffirmativeAction(segment.text, actionForms));
1438
+ const conditionalSegment = ownConditional || inheritedConditional;
1439
+ const conditionalAllowsIndependentReset = ownConditional
1440
+ ? postposedTargetConditional
1441
+ : conditionalCoordinationAllowsIndependentReset;
1442
+ const carriesConditionalCoordination = !/,\s*$/u.test(segment.rawText);
1443
+ if (NEGATED_ACTION_RE.test(segment.text)) {
1444
+ const affirmativePrefix = affirmativePrefixBeforeNegation(segment.text, actionForms);
1445
+ const negatedSubjectPolarity = finiteClauseSubjectPolarity(segment.text);
1446
+ const factualTails = factualTailsAfterBoundary(segment.rawText);
1447
+ const tailEvidence = factualTailEvidence(factualTails, actionForms, negatedSubjectPolarity !== 'negative');
1448
+ if (conditionalSegment) {
1449
+ conditionalCoordination = carriesConditionalCoordination;
1450
+ conditionalCoordinationAllowsIndependentReset = carriesConditionalCoordination
1451
+ && conditionalAllowsIndependentReset;
1452
+ requiresIndependentAction = true;
1453
+ return [];
1454
+ }
1455
+ conditionalCoordination = false;
1456
+ conditionalCoordinationAllowsIndependentReset = false;
1457
+ requiresIndependentAction = true;
1458
+ const explicitSubject = hasExplicitSubjectBeforeNegation(segment.text);
1459
+ const preservesInheritedSubject = segment.connector === 'and'
1460
+ && inheritedSubjectMode !== 'none'
1461
+ && startsWithSubjectElidedNegation(segment.text);
1462
+ if (negatedSubjectPolarity === 'negative') {
1463
+ inheritedSubjectMode = 'negative';
1464
+ inheritedSubjectAgreement = 'unknown';
1465
+ }
1466
+ else if (explicitSubject) {
1467
+ const auxiliaryScope = negatedAuxiliaryScope(segment.text);
1468
+ inheritedSubjectMode = auxiliaryScope === 'bare'
1469
+ ? 'negated-bare'
1470
+ : auxiliaryScope === 'past-bare'
1471
+ ? 'negated-past-bare'
1472
+ : auxiliaryScope === 'perfect'
1473
+ ? 'negated-perfect'
1474
+ : auxiliaryScope === 'progressive'
1475
+ ? 'negated-progressive'
1476
+ : 'finite';
1477
+ inheritedSubjectAgreement = subjectAgreementBeforeNegation(segment.text);
1478
+ }
1479
+ else if (!preservesInheritedSubject) {
1480
+ inheritedSubjectMode = 'none';
1481
+ inheritedSubjectAgreement = 'unknown';
1482
+ }
1483
+ if (tailEvidence.subjectMode !== 'none') {
1484
+ inheritedSubjectMode = tailEvidence.subjectMode;
1485
+ inheritedSubjectAgreement = tailEvidence.subjectAgreement;
1486
+ }
1487
+ return [affirmativePrefix, tailEvidence.affirmative]
1488
+ .filter((fragment) => fragment !== undefined);
1489
+ }
1490
+ const finiteSubjectPolarity = finiteClauseSubjectPolarity(segment.text);
1491
+ const subjectPolarity = finiteSubjectPolarity === 'absent'
1492
+ ? matchingActionSubjectPolarity(segment.text, actionForms)
1493
+ : finiteSubjectPolarity;
1494
+ if (subjectPolarity === 'negative') {
1495
+ inheritedSubjectMode = 'negative';
1496
+ inheritedSubjectAgreement = 'unknown';
1497
+ requiresIndependentAction = true;
1498
+ const factualTails = factualTailsAfterBoundary(segment.rawText);
1499
+ const tailEvidence = factualTailEvidence(factualTails, actionForms, contextualNoAdjunctAllowsImperative(segment.rawText));
1500
+ if (conditionalSegment) {
1501
+ conditionalCoordination = carriesConditionalCoordination;
1502
+ conditionalCoordinationAllowsIndependentReset = carriesConditionalCoordination
1503
+ && conditionalAllowsIndependentReset;
1504
+ return [];
1505
+ }
1506
+ conditionalCoordination = false;
1507
+ conditionalCoordinationAllowsIndependentReset = false;
1508
+ if (tailEvidence.subjectMode !== 'none') {
1509
+ inheritedSubjectMode = tailEvidence.subjectMode;
1510
+ inheritedSubjectAgreement = tailEvidence.subjectAgreement;
1511
+ }
1512
+ return tailEvidence.affirmative ? [tailEvidence.affirmative] : [];
1513
+ }
1514
+ if (conditionalSegment) {
1515
+ conditionalCoordination = carriesConditionalCoordination;
1516
+ conditionalCoordinationAllowsIndependentReset = carriesConditionalCoordination
1517
+ && conditionalAllowsIndependentReset;
1518
+ requiresIndependentAction = true;
1519
+ return [];
1520
+ }
1521
+ conditionalCoordination = false;
1522
+ conditionalCoordinationAllowsIndependentReset = false;
1523
+ if (subjectPolarity === 'positive')
1524
+ inheritedSubjectMode = 'affirmative';
1525
+ if (index === 0 || !requiresIndependentAction)
1526
+ return [segment.text];
1527
+ const followsComma = /,\s*$/u.test(segments[index - 1]?.rawText ?? '');
1528
+ if (segment.connector === 'and'
1529
+ && followsComma
1530
+ && inheritedSubjectMode !== 'negative'
1531
+ && hasIndependentAffirmativeAction(segment.text, actionForms, true))
1532
+ return [segment.text];
1533
+ if (segment.connector === 'and' && inheritedSubjectMode === 'affirmative'
1534
+ && hasIndependentAffirmativeAction(segment.text, actionForms, true))
1535
+ return [segment.text];
1536
+ const morphology = subjectElidedActionMorphology(segment.text, actionForms);
1537
+ if (segment.connector === 'and' && inheritedSubjectMode === 'negated-perfect'
1538
+ && ((morphology === 'base' && inheritedSubjectAgreement === 'base')
1539
+ || (morphology === 'third-person' && inheritedSubjectAgreement === 'third-person'))) {
1540
+ return [segment.text];
1541
+ }
1542
+ if (segment.connector === 'and' && inheritedSubjectMode === 'negated-progressive'
1543
+ && (morphology === 'participle'
1544
+ || morphology === 'simple-past'
1545
+ || (morphology === 'base' && inheritedSubjectAgreement === 'base')
1546
+ || (morphology === 'third-person' && inheritedSubjectAgreement === 'third-person'))) {
1547
+ return [segment.text];
1548
+ }
1549
+ if (segment.connector === 'and' && inheritedSubjectMode === 'negated-bare'
1550
+ && (morphology === 'simple-past'
1551
+ || (morphology === 'third-person' && inheritedSubjectAgreement === 'third-person'))) {
1552
+ return [segment.text];
1553
+ }
1554
+ if (segment.connector === 'and' && inheritedSubjectMode === 'negated-past-bare'
1555
+ && (morphology === 'participle'
1556
+ || morphology === 'simple-past'
1557
+ || (morphology === 'third-person' && inheritedSubjectAgreement === 'third-person'))) {
1558
+ return [segment.text];
1559
+ }
1560
+ if (segment.connector === 'and' && inheritedSubjectMode === 'finite'
1561
+ && startsWithSubjectElidedFiniteAction(segment.text, actionForms))
1562
+ return [segment.text];
1563
+ const thenInheritsSubject = segment.connector === 'then' && inheritedSubjectMode !== 'negative';
1564
+ return hasIndependentAffirmativeAction(segment.text, actionForms, thenInheritsSubject)
1565
+ ? [segment.text]
1566
+ : [];
1567
+ });
1568
+ });
1569
+ }
1570
+ function mustNotMatchedTerms(redactedOutput, terms) {
1571
+ return affirmativeOutputClauses(redactedOutput, terms)
1572
+ .map((clause) => {
1573
+ const matched = terms.filter((term) => termMatches(clause, term, 'must_not'));
1574
+ if (terms.some((term) => term.verb) && !matched.some((term) => term.verb))
1575
+ return [];
1576
+ return matched.map((term) => term.text);
1577
+ })
1578
+ .sort((left, right) => right.length - left.length)[0] ?? [];
1579
+ }
1580
+ function isBroadProgressiveAuxiliary(term) {
1581
+ if (!term.verb || !term.text.endsWith('ing'))
1582
+ return false;
1583
+ const lemma = verbLemma(term.text);
1584
+ return lemma !== undefined && EXACT_ONLY_PROGRESSIVE_LEMMAS.has(lemma);
1585
+ }
1586
+ const BROAD_EVIDENCE_LINK_TERMS = new Set([
1587
+ 'a', 'an', 'the', 'all', 'any', 'both', 'each', 'every', 'her', 'his', 'its', 'my', 'not', 'only', 'our',
1588
+ 'some', 'that', 'their', 'these', 'this', 'those', 'your',
1589
+ ]);
1590
+ const PASSIVE_AUXILIARY_TERMS = new Set([
1591
+ 'am', 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'has', 'have', 'had',
1592
+ ]);
1593
+ const PASSIVE_BE_AUXILIARY_TERMS = new Set([
1594
+ 'am', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
1595
+ ]);
1596
+ const PERFECT_AUXILIARY_TERMS = new Set(['had', 'has', 'have']);
1597
+ const PERFECT_BROAD_FORM_TERMS = new Set(['been', 'done', 'gone']);
1598
+ const MODAL_AUXILIARY_TERMS = new Set([
1599
+ 'can', 'could', 'may', 'might', 'must', 'shall', 'should', 'will', 'would',
1600
+ ]);
1601
+ const SEMI_MODAL_AUXILIARY_TERMS = new Set(['need', 'needed', 'needs', 'ought']);
1602
+ const INDEPENDENT_BROAD_FORM_TERMS = new Set([
1603
+ 'am', 'are', 'is', 'was', 'were', 'does', 'did', 'goes', 'went', 'has', 'have', 'had',
1604
+ ]);
1605
+ const NEGATIVE_EVIDENCE_PRONOUNS = new Set(['neither', 'nobody', 'none', 'nothing']);
1606
+ const NEGATIVE_EVIDENCE_DETERMINERS = new Set(['neither', 'no', 'zero']);
1607
+ const SUBJECT_PRONOUN_TERMS = new Set([
1608
+ 'he', 'i', 'it', 'she', 'that', 'they', 'this', 'we', 'who', 'you',
1609
+ ]);
1610
+ const SUBJECT_ARTICLE_TERMS = new Set(['a', 'an', 'the']);
1611
+ const AUXILIARY_CHAIN_MODIFIER_TERMS = new Set([
1612
+ 'already', 'also', 'always', 'ever', 'just', 'still', 'yet',
1613
+ ]);
1614
+ const LEADING_CLAUSE_MODIFIER_TERMS = new Set([
1615
+ 'eventually', 'finally', 'initially', 'later', 'subsequently',
1616
+ ]);
1617
+ // One capped token stream; each predicate probes only bounded nearby targets.
1618
+ // This keeps replay evaluation O(tokens * target terms * window) and memory linear.
1619
+ const MAX_REQUIRED_EVIDENCE_TERMS = 1_024;
1620
+ const MAX_REQUIRED_EVIDENCE_CANDIDATES = 256;
1621
+ const MAX_REQUIRED_EVIDENCE_PAIR_DISTANCE = 64;
1622
+ const MAX_REQUIRED_SUBJECT_LOOKBACK = 128;
1623
+ const PREDICATE_COORDINATOR_TERMS = new Set(['and', 'but', 'then']);
1624
+ const CONDITIONAL_MARKER_TERMS = new Set([
1625
+ 'assuming', 'if', 'once', 'provided', 'supposing', 'unless', 'when', 'while',
1626
+ ]);
1627
+ const TEMPORAL_CONDITIONAL_MARKER_TERMS = new Set(['once', 'when', 'while']);
1628
+ const CONDITIONAL_MARKER_MENTION_HEAD_TERMS = new Set(['keyword', 'term', 'word']);
1629
+ const EMBEDDED_IF_GOVERNOR_TERMS = new Set([
1630
+ 'ask', 'asked', 'asking', 'asks',
1631
+ 'check', 'checked', 'checking', 'checks',
1632
+ 'confirm', 'confirmed', 'confirming', 'confirms',
1633
+ 'decide', 'decided', 'decides', 'deciding',
1634
+ 'determine', 'determined', 'determines', 'determining',
1635
+ 'evaluate', 'evaluated', 'evaluates', 'evaluating',
1636
+ 'learn', 'learned', 'learning', 'learns',
1637
+ 'see', 'seeing', 'seen', 'sees', 'saw',
1638
+ 'test', 'tested', 'testing', 'tests',
1639
+ 'verify', 'verified', 'verifies', 'verifying',
1640
+ 'wonder', 'wondered', 'wondering', 'wonders',
1641
+ ]);
1642
+ const PREDICATE_SCOPE_RESET_TERMS = new Set([
1643
+ ...CONDITIONAL_MARKER_TERMS,
1644
+ 'although', 'as', 'because', 'however', 'once', 'though', 'when', 'whereas', 'while', 'without', 'yet',
1645
+ ]);
1646
+ const COMMA_DELIMITED_SUBORDINATOR_TERMS = new Set([
1647
+ ...CONDITIONAL_MARKER_TERMS,
1648
+ 'although', 'as', 'because', 'once', 'though', 'when', 'whereas', 'while',
1649
+ ]);
1650
+ const TARGET_SCOPE_RESET_TERMS_LOCAL = new Set([
1651
+ ...PREDICATE_SCOPE_RESET_TERMS,
1652
+ ...PREDICATE_COORDINATOR_TERMS,
1653
+ 'after', 'before', 'since', 'than', 'that',
1654
+ ]);
1655
+ const PASSIVE_SUBJECT_POSTMODIFIER_TERMS = new Set([
1656
+ 'among', 'around', 'at', 'by', 'for', 'from', 'in', 'of', 'on', 'through', 'to', 'with', 'without',
1657
+ ]);
1658
+ const COMMA_DELIMITED_PREPOSITION_TERMS = new Set([
1659
+ ...PASSIVE_SUBJECT_POSTMODIFIER_TERMS,
1660
+ 'according', 'despite', 'during', 'inside', 'outside', 'regarding', 'within',
1661
+ ]);
1662
+ const PROVIDED_OBJECT_FACTUAL_TAIL_PREFIX_TERMS = new Set([
1663
+ ...COMMA_DELIMITED_PREPOSITION_TERMS,
1664
+ ...PREDICATE_COORDINATOR_TERMS,
1665
+ 'after', 'although', 'as', 'because', 'before', 'however', 'since', 'than', 'though',
1666
+ 'whereas', 'yet',
1667
+ ]);
1668
+ const PROVIDED_OBJECT_RELATIVE_TERMS = new Set(['that', 'which', 'who']);
1669
+ const PROVIDED_OBJECT_SUBORDINATOR_TERMS = new Set(['after', 'as', 'before']);
1670
+ // Bare PP tails are POS-ambiguous; keep this bounded to known prenominal forms with paired tests.
1671
+ const PROVIDED_OBJECT_NOMINAL_MODIFIER_TERMS = new Set([
1672
+ 'access', 'audit', 'backup', 'cold', 'data', 'offline', 'ongoing',
1673
+ 'policy', 'remote', 'secure', 'security', 'token',
1674
+ ]);
1675
+ const PROVIDED_OBJECT_NOMINAL_HEAD_TERMS = new Set([
1676
+ 'access', 'approval', 'archive', 'audit', 'auditor', 'credentials', 'data', 'details',
1677
+ 'leak', 'leaks', 'metadata', 'policy', 'receipt', 'records', 'request', 'review', 'server',
1678
+ 'storage', 'system', 'transit',
1679
+ ]);
1680
+ const PROVIDED_OBJECT_MATRIX_NOMINAL_COLLOCATIONS = new Set(['access:policy']);
1681
+ const PROVIDED_OBJECT_COORDINATOR_TERMS = new Set(['and', 'or', 'then']);
1682
+ const PROVIDED_OBJECT_TRAILING_MODIFIER_TERMS = new Set([
1683
+ 'earlier', 'here', 'now', 'soon', 'today', 'tonight', 'there', 'yesterday',
1684
+ ]);
1685
+ const PROVIDED_OBJECT_MATRIX_PREDICATE_TERMS = new Set([
1686
+ 'arrive', 'arrives', 'become', 'becomes', 'exist', 'exists', 'expire', 'expires',
1687
+ 'leak', 'leaks', 'match', 'matches', 'pass', 'passes', 'remain', 'remains', 'trigger', 'triggers',
1688
+ ]);
1689
+ const TOTALITY_TARGET_MODIFIER_TERMS = new Set([
1690
+ 'complete', 'entire', 'full', 'partial', 'total', 'whole',
1691
+ ]);
1692
+ const KNOWN_EVIDENCE_ACTION_TERMS = new Set([
1693
+ ...[...IRREGULAR_VERB_FORMS.values()].flat(),
1694
+ ...GENERIC_ACTION_TERMS,
1695
+ ]);
1696
+ const KNOWN_FINITE_PREDICATE_TERMS = new Set([
1697
+ ...INDEPENDENT_BROAD_FORM_TERMS,
1698
+ ...MODAL_AUXILIARY_TERMS,
1699
+ 'said', 'told',
1700
+ ]);
1701
+ const CONTEXTUAL_ZERO_METRIC_TERMS = new Set([
1702
+ 'cost', 'downtime', 'latency', 'loss', 'overhead', 'variance',
1703
+ ]);
1704
+ const ROOT_PREDICATE_BLOCKING_MODIFIER_TERMS = new Set([
1705
+ 'allegedly', 'almost', 'apparently', 'maybe', 'nearly', 'perhaps', 'possibly', 'reportedly', 'supposedly',
1706
+ ]);
1707
+ const CLAUSE_INITIAL_EVIDENTIAL_HEDGE_TERMS = new Set([
1708
+ ...ROOT_PREDICATE_BLOCKING_MODIFIER_TERMS,
1709
+ 'likely', 'presumably', 'probably', 'purportedly',
1710
+ ]);
1711
+ const EMBEDDED_CLAIM_GOVERNOR_TERMS = new Set([
1712
+ 'allege', 'alleged', 'alleges', 'alleging',
1713
+ 'assert', 'asserted', 'asserting', 'asserts',
1714
+ 'believe', 'believed', 'believes', 'believing',
1715
+ 'claim', 'claimed', 'claims', 'claiming',
1716
+ 'deny', 'denied', 'denies', 'denying',
1717
+ 'hear', 'heard', 'hearing', 'hears',
1718
+ 'report', 'reported', 'reporting', 'reports',
1719
+ 'say', 'said', 'saying', 'says',
1720
+ 'suppose', 'supposed', 'supposes', 'supposing',
1721
+ 'tell', 'telling', 'tells', 'told',
1722
+ 'think', 'thinking', 'thinks', 'thought',
1723
+ ]);
1724
+ const UNAMBIGUOUS_EMBEDDED_CLAIM_GOVERNOR_TERMS = new Set([
1725
+ 'asserts', 'heard', 'said', 'thought', 'told',
1726
+ ]);
1727
+ const NOMINAL_CLAIM_HEAD_TERMS = new Set(['claim', 'claims', 'report', 'reports']);
1728
+ const RELATIVE_SAFE_FINITE_COMPLEMENTS = new Map([
1729
+ ['made', new Set(['progress'])],
1730
+ ['makes', new Set(['progress'])],
1731
+ ['pass', new Set(['review'])],
1732
+ ['passed', new Set(['review'])],
1733
+ ['passes', new Set(['review'])],
1734
+ ['survive', new Set(['review'])],
1735
+ ['survived', new Set(['review'])],
1736
+ ['survives', new Set(['review'])],
1737
+ ['takes', new Set(['effect'])],
1738
+ ['took', new Set(['effect'])],
1739
+ ]);
1740
+ const NON_ADVERBIAL_PARENTHETICAL_IF_NOT_TERMS = new Set(['already', 'complete']);
1741
+ const AFFIRMATIVE_PREDICATE_MODIFIER_TERMS = new Set([
1742
+ ...AUXILIARY_CHAIN_MODIFIER_TERMS,
1743
+ 'actually', 'currently', 'definitely', 'only', 'successfully',
1744
+ ]);
1745
+ const INVERTED_CONDITIONAL_AUXILIARY_TERMS = new Set([
1746
+ ...MODAL_AUXILIARY_TERMS,
1747
+ 'had', 'was', 'were',
1748
+ ]);
1749
+ const NON_SUBJECT_PREFIX_TERMS = new Set([
1750
+ ...PASSIVE_AUXILIARY_TERMS,
1751
+ ...MODAL_AUXILIARY_TERMS,
1752
+ ...SEMI_MODAL_AUXILIARY_TERMS,
1753
+ ...LEADING_CLAUSE_MODIFIER_TERMS,
1754
+ 'after', 'before', 'not', 'only', 'to',
1755
+ ]);
1756
+ function isBlockingRootPredicateModifierTerm(term) {
1757
+ return ROOT_PREDICATE_BLOCKING_MODIFIER_TERMS.has(term)
1758
+ || (term.endsWith('ly')
1759
+ && !AFFIRMATIVE_PREDICATE_MODIFIER_TERMS.has(term)
1760
+ && !LEADING_CLAUSE_MODIFIER_TERMS.has(term));
1761
+ }
1762
+ function isClauseInitialEvidentialHedgeTerm(term) {
1763
+ return CLAUSE_INITIAL_EVIDENTIAL_HEDGE_TERMS.has(term);
1764
+ }
1765
+ function isAdjectivalProvided(tokens, index) {
1766
+ if (tokens[index]?.value !== 'provided')
1767
+ return false;
1768
+ if (tokens[index + 1]?.value === 'by')
1769
+ return true;
1770
+ const clauseId = tokens[index].clause;
1771
+ let previous = index - 1;
1772
+ while (previous >= 0
1773
+ && tokens[previous]?.clause === clauseId
1774
+ && /ly$/u.test(tokens[previous]?.value ?? ''))
1775
+ previous -= 1;
1776
+ const previousTerm = tokens[previous]?.clause === clauseId ? tokens[previous]?.value ?? '' : '';
1777
+ return previousTerm !== 'not'
1778
+ && previousTerm !== 'only'
1779
+ && BROAD_EVIDENCE_LINK_TERMS.has(previousTerm);
1780
+ }
1781
+ function hasSubjectBeforeRegularPast(tokens, predicateIndex, lowerBound) {
1782
+ const clauseId = tokens[predicateIndex]?.clause;
1783
+ for (let index = predicateIndex - 1; index >= lowerBound; index -= 1) {
1784
+ const token = tokens[index];
1785
+ if (!token || token.clause !== clauseId)
1786
+ return false;
1787
+ const term = token.value;
1788
+ if (PREDICATE_COORDINATOR_TERMS.has(term) || PREDICATE_SCOPE_RESET_TERMS.has(term))
1789
+ return false;
1790
+ if (SUBJECT_PRONOUN_TERMS.has(term) || NEGATIVE_EVIDENCE_PRONOUNS.has(term))
1791
+ return true;
1792
+ if (SUBJECT_ARTICLE_TERMS.has(term)
1793
+ || BROAD_EVIDENCE_LINK_TERMS.has(term)
1794
+ || NON_SUBJECT_PREFIX_TERMS.has(term)
1795
+ || /ly$/u.test(term)
1796
+ || isAttributiveTargetModifier(term))
1797
+ continue;
1798
+ return true;
1799
+ }
1800
+ return false;
1801
+ }
1802
+ function isFinitePredicateBeforeParenthetical(tokens, index, lowerBound) {
1803
+ const term = tokens[index]?.value ?? '';
1804
+ if (KNOWN_FINITE_PREDICATE_TERMS.has(term) || /n't$/u.test(term))
1805
+ return true;
1806
+ return term.endsWith('ed') && hasSubjectBeforeRegularPast(tokens, index, lowerBound);
1807
+ }
1808
+ function isParentheticalIfNotModifier(tokens, index) {
1809
+ if (tokens[index]?.value !== 'if' || tokens[index + 1]?.value !== 'not')
1810
+ return false;
1811
+ const modifier = tokens[index + 2];
1812
+ const next = tokens[index + 3];
1813
+ const closedModifier = modifier?.clause === tokens[index]?.clause
1814
+ && (next === undefined || next.clause !== modifier.clause || next.commaBefore);
1815
+ if (!closedModifier)
1816
+ return false;
1817
+ if (/ly$/u.test(modifier.value))
1818
+ return true;
1819
+ if (!tokens[index]?.commaBefore
1820
+ || !NON_ADVERBIAL_PARENTHETICAL_IF_NOT_TERMS.has(modifier.value))
1821
+ return false;
1822
+ const lowerBound = Math.max(0, index - MAX_REQUIRED_SUBJECT_LOOKBACK);
1823
+ for (let prior = index - 1; prior >= lowerBound && tokens[prior]?.clause === modifier.clause; prior -= 1) {
1824
+ const term = tokens[prior]?.value ?? '';
1825
+ if (PREDICATE_COORDINATOR_TERMS.has(term) || PREDICATE_SCOPE_RESET_TERMS.has(term))
1826
+ return false;
1827
+ if (isFinitePredicateBeforeParenthetical(tokens, prior, lowerBound))
1828
+ return true;
1829
+ }
1830
+ return false;
1831
+ }
1832
+ function isParentheticalIfAnythingModifier(tokens, index) {
1833
+ const marker = tokens[index];
1834
+ const modifier = tokens[index + 1];
1835
+ const next = tokens[index + 2];
1836
+ if (marker?.value !== 'if'
1837
+ || modifier?.value !== 'anything'
1838
+ || modifier.clause !== marker.clause)
1839
+ return false;
1840
+ const closesBeforePostmodifier = next !== undefined
1841
+ && next.clause === modifier.clause
1842
+ && COMMA_DELIMITED_PREPOSITION_TERMS.has(next.value);
1843
+ const closesBeforeClauseBoundary = next !== undefined
1844
+ && next.clause === modifier.clause
1845
+ && TARGET_SCOPE_RESET_TERMS_LOCAL.has(next.value);
1846
+ const closedModifier = next === undefined
1847
+ || next.clause !== modifier.clause
1848
+ || next.commaBefore
1849
+ || closesBeforePostmodifier
1850
+ || closesBeforeClauseBoundary;
1851
+ return closedModifier && (index === 0
1852
+ || marker.commaBefore
1853
+ || next === undefined
1854
+ || closesBeforePostmodifier
1855
+ || closesBeforeClauseBoundary);
1856
+ }
1857
+ function isMentionedConditionalMarker(tokens, index) {
1858
+ const token = tokens[index];
1859
+ const previous = tokens[index - 1];
1860
+ return token !== undefined
1861
+ && previous?.clause === token.clause
1862
+ && CONDITIONAL_MARKER_MENTION_HEAD_TERMS.has(previous.value);
1863
+ }
1864
+ function embeddedIfGovernorIndex(tokens, index) {
1865
+ const marker = tokens[index];
1866
+ if (marker?.value !== 'if' || marker.commaBefore)
1867
+ return undefined;
1868
+ for (let previous = index - 1; previous >= 0; previous -= 1) {
1869
+ const token = tokens[previous];
1870
+ if (!token || token.clause !== marker.clause)
1871
+ return undefined;
1872
+ const term = token.value;
1873
+ if (EMBEDDED_IF_GOVERNOR_TERMS.has(term))
1874
+ return previous;
1875
+ if (PREDICATE_COORDINATOR_TERMS.has(term)
1876
+ || CONDITIONAL_MARKER_TERMS.has(term)
1877
+ || AFFIRMATIVE_ACTION_FORMS.has(term)
1878
+ || isFinitePredicateTerm(term))
1879
+ return undefined;
1880
+ if (tokens[previous + 1]?.commaBefore)
1881
+ return undefined;
1882
+ }
1883
+ return undefined;
1884
+ }
1885
+ function isEmbeddedIfComplement(tokens, index) {
1886
+ return embeddedIfGovernorIndex(tokens, index) !== undefined;
1887
+ }
1888
+ function isFactualProvidedPredicate(tokens, index) {
1889
+ const provided = tokens[index];
1890
+ if (provided?.value !== 'provided' || provided.commaBefore)
1891
+ return false;
1892
+ let segmentStart = index;
1893
+ for (let previous = index - 1; previous >= 0; previous -= 1) {
1894
+ const token = tokens[previous];
1895
+ if (!token || token.clause !== provided.clause)
1896
+ break;
1897
+ if (tokens[previous + 1]?.commaBefore || PREDICATE_COORDINATOR_TERMS.has(token.value))
1898
+ break;
1899
+ segmentStart = previous;
1900
+ }
1901
+ if (segmentStart === index)
1902
+ return false;
1903
+ for (let previous = segmentStart; previous < index; previous += 1) {
1904
+ const term = tokens[previous]?.value ?? '';
1905
+ if (AFFIRMATIVE_ACTION_FORMS.has(term)
1906
+ || isFinitePredicateTerm(term)
1907
+ || KNOWN_EVIDENCE_ACTION_TERMS.has(term))
1908
+ return false;
1909
+ }
1910
+ return hasSubjectBeforeRegularPast(tokens, index, segmentStart);
1911
+ }
1912
+ function isConditionalMarkerUse(tokens, index) {
1913
+ const term = tokens[index]?.value ?? '';
1914
+ if (!CONDITIONAL_MARKER_TERMS.has(term)
1915
+ || isAdjectivalProvided(tokens, index)
1916
+ || isFactualProvidedPredicate(tokens, index)
1917
+ || isParentheticalIfNotModifier(tokens, index)
1918
+ || isParentheticalIfAnythingModifier(tokens, index)
1919
+ || isMentionedConditionalMarker(tokens, index)
1920
+ || isEmbeddedIfComplement(tokens, index))
1921
+ return false;
1922
+ if (!TEMPORAL_CONDITIONAL_MARKER_TERMS.has(term))
1923
+ return true;
1924
+ if (isFactualTemporalMarkerUse(tokens, index))
1925
+ return false;
1926
+ const clauseId = tokens[index]?.clause;
1927
+ let hardClauseStart = index;
1928
+ while (hardClauseStart > 0 && tokens[hardClauseStart - 1]?.clause === clauseId)
1929
+ hardClauseStart -= 1;
1930
+ if (term === 'once') {
1931
+ let segmentStart = hardClauseStart;
1932
+ for (let prior = index - 1; prior >= hardClauseStart; prior -= 1) {
1933
+ if (PREDICATE_COORDINATOR_TERMS.has(tokens[prior]?.value ?? '')) {
1934
+ segmentStart = prior + 1;
1935
+ break;
1936
+ }
1937
+ }
1938
+ let nextIndex = index + 1;
1939
+ while (tokens[nextIndex]?.clause === clauseId && /ly$/u.test(tokens[nextIndex]?.value ?? ''))
1940
+ nextIndex += 1;
1941
+ const next = tokens[nextIndex];
1942
+ const nextTerm = next?.value ?? '';
1943
+ const followedByPredicate = next !== undefined
1944
+ && next.clause === clauseId
1945
+ && !next.commaBefore
1946
+ && (isFinitePredicateTerm(nextTerm)
1947
+ || INDEPENDENT_AFFIRMATIVE_AUXILIARIES.has(nextTerm)
1948
+ || KNOWN_EVIDENCE_ACTION_TERMS.has(nextTerm));
1949
+ if (followedByPredicate && hasSubjectBeforeRegularPast(tokens, index, segmentStart))
1950
+ return false;
1951
+ }
1952
+ return conditionalScopeStart(tokens, hardClauseStart, index) === index;
1953
+ }
1954
+ function isFactualTemporalMarkerUse(tokens, index) {
1955
+ const term = tokens[index]?.value ?? '';
1956
+ const previous = tokens[index - 1]?.value ?? '';
1957
+ const next = tokens[index + 1]?.value ?? '';
1958
+ return (term === 'once' && isFactualOnceUse(tokens, index))
1959
+ || (term === 'while' && (['a', 'that', 'the', 'this'].includes(previous) || next === 'later'));
1960
+ }
1961
+ function isFactualOnceUse(tokens, index) {
1962
+ if (tokens[index]?.value !== 'once')
1963
+ return false;
1964
+ const previous = tokens[index - 1]?.value ?? '';
1965
+ const next = tokens[index + 1]?.value ?? '';
1966
+ return tokens[index]?.compound === true
1967
+ || ['at', 'for', 'just'].includes(previous)
1968
+ || ['again', 'more', 'upon'].includes(next);
1969
+ }
1970
+ function hasMatrixSubjectBeforeTemporalAdjunct(source, offset) {
1971
+ const hardPrefix = source.slice(Math.max(0, offset - 256), offset).split(/[.!?;\r\n]/u).pop() ?? '';
1972
+ const segment = hardPrefix.slice(hardPrefix.lastIndexOf(',') + 1);
1973
+ const terms = normalize(segment).split(' ').filter(Boolean);
1974
+ if (terms.length === 0)
1975
+ return false;
1976
+ if (terms.some((term) => SUBJECT_PRONOUN_TERMS.has(term)))
1977
+ return true;
1978
+ if (terms.some((term, index) => SUBJECT_ARTICLE_TERMS.has(term) && index + 1 < terms.length))
1979
+ return true;
1980
+ if (terms.some((term, index) => NEGATIVE_EVIDENCE_DETERMINERS.has(term) && index + 1 < terms.length))
1981
+ return true;
1982
+ const first = terms[0] ?? '';
1983
+ return !LEADING_CLAUSE_MODIFIER_TERMS.has(first)
1984
+ && !COMMA_DELIMITED_PREPOSITION_TERMS.has(first)
1985
+ && !NON_SUBJECT_PREFIX_TERMS.has(first)
1986
+ && !/ly$/u.test(first);
1987
+ }
1988
+ function stripFactualTemporalParentheticals(source) {
1989
+ return source.replace(COMMA_DELIMITED_TEMPORAL_RE, (match, offset, whole) => {
1990
+ if (!hasMatrixSubjectBeforeTemporalAdjunct(whole, offset))
1991
+ return match;
1992
+ const suffixStart = offset + match.length;
1993
+ const suffixWindow = whole.slice(suffixStart, suffixStart + 256).split(/[.!?;\r\n]/u, 1)[0] ?? '';
1994
+ const suffixTerms = normalize(suffixWindow).split(' ').filter(Boolean);
1995
+ const keepsModality = suffixTerms.slice(0, 4).some((term) => (MODAL_AUXILIARY_TERMS.has(term) || SEMI_MODAL_AUXILIARY_TERMS.has(term)));
1996
+ return keepsModality ? match : ' ';
1997
+ });
1998
+ }
1999
+ function conditionalScopeStart(tokens, hardClauseStart, markerIndex) {
2000
+ let segmentStart = hardClauseStart;
2001
+ for (let index = markerIndex - 1; index >= hardClauseStart; index -= 1) {
2002
+ if (PREDICATE_COORDINATOR_TERMS.has(tokens[index]?.value ?? '')) {
2003
+ segmentStart = index + 1;
2004
+ break;
2005
+ }
2006
+ }
2007
+ for (let index = segmentStart; index < markerIndex; index += 1) {
2008
+ const term = tokens[index]?.value ?? '';
2009
+ if (isFinitePredicateTerm(term)
2010
+ || KNOWN_EVIDENCE_ACTION_TERMS.has(term)
2011
+ || term.endsWith('ing'))
2012
+ return segmentStart;
2013
+ }
2014
+ return markerIndex;
2015
+ }
2016
+ function lexRequiredEvidence(redactedOutput) {
2017
+ const source = stripFactualTemporalParentheticals(redactedOutput.replace(REDACTION_MARKER_RE, ' '))
2018
+ .toLowerCase()
2019
+ .replace(/[\u2018\u2019]/g, "'");
2020
+ const tokens = [];
2021
+ const matches = source.matchAll(/[a-z0-9_]+(?:'[a-z]+)?/gu);
2022
+ let previousEnd = 0;
2023
+ let clause = 0;
2024
+ for (const match of matches) {
2025
+ if (tokens.length >= MAX_REQUIRED_EVIDENCE_TERMS)
2026
+ break;
2027
+ const start = match.index;
2028
+ const end = start + match[0].length;
2029
+ const separator = source.slice(previousEnd, start);
2030
+ if (tokens.length > 0 && /[.!?;\r\n]/u.test(separator))
2031
+ clause += 1;
2032
+ tokens.push({
2033
+ value: match[0],
2034
+ start,
2035
+ end,
2036
+ clause,
2037
+ commaBefore: separator.includes(','),
2038
+ compound: source[start - 1] === '-' || source[end] === '-',
2039
+ });
2040
+ previousEnd = end;
2041
+ }
2042
+ const ignored = Array.from({ length: tokens.length }, () => false);
2043
+ const transparentCommaBefore = Array.from({ length: tokens.length }, () => false);
2044
+ const hardClauseStarts = Array.from({ length: tokens.length }, () => 0);
2045
+ const hardClauseEnds = Array.from({ length: tokens.length }, () => tokens.length);
2046
+ for (let index = 0; index < tokens.length; index += 1) {
2047
+ if (isFactualOnceUse(tokens, index))
2048
+ ignored[index] = true;
2049
+ }
2050
+ let hardClauseStart = 0;
2051
+ for (let index = 0; index < tokens.length; index += 1) {
2052
+ if (index > 0 && tokens[index - 1]?.clause !== tokens[index]?.clause)
2053
+ hardClauseStart = index;
2054
+ hardClauseStarts[index] = hardClauseStart;
2055
+ }
2056
+ let hardClauseEnd = tokens.length;
2057
+ for (let index = tokens.length - 1; index >= 0; index -= 1) {
2058
+ const token = tokens[index];
2059
+ const next = tokens[index + 1];
2060
+ if (!token)
2061
+ continue;
2062
+ if (next === undefined || next.clause !== token.clause) {
2063
+ hardClauseEnd = index + 1;
2064
+ }
2065
+ hardClauseEnds[index] = hardClauseEnd;
2066
+ }
2067
+ const failClosedSubordinateRanges = [];
2068
+ for (let index = 0; index + 1 < tokens.length; index += 1) {
2069
+ if (!isParentheticalIfNotModifier(tokens, index))
2070
+ continue;
2071
+ const clauseId = tokens[index].clause;
2072
+ let close = index + 1;
2073
+ while (close < tokens.length
2074
+ && tokens[close]?.clause === clauseId
2075
+ && !tokens[close]?.commaBefore)
2076
+ close += 1;
2077
+ if (close >= tokens.length || tokens[close]?.clause !== clauseId)
2078
+ continue;
2079
+ const atScopeStart = index === 0
2080
+ || tokens[index - 1]?.clause !== clauseId
2081
+ || tokens[index]?.commaBefore;
2082
+ if (!atScopeStart)
2083
+ continue;
2084
+ const embedded = index > 0
2085
+ && tokens[index - 1]?.clause === clauseId
2086
+ && tokens[index]?.commaBefore;
2087
+ for (let ignoredIndex = index; ignoredIndex < close; ignoredIndex += 1)
2088
+ ignored[ignoredIndex] = true;
2089
+ if (embedded) {
2090
+ transparentCommaBefore[index] = true;
2091
+ transparentCommaBefore[close] = true;
2092
+ }
2093
+ index = close - 1;
2094
+ }
2095
+ for (let index = 0; index < tokens.length; index += 1) {
2096
+ const startTerm = tokens[index]?.value ?? '';
2097
+ const structuralPrefix = COMMA_DELIMITED_SUBORDINATOR_TERMS.has(startTerm)
2098
+ || COMMA_DELIMITED_PREPOSITION_TERMS.has(startTerm)
2099
+ || isBlockingRootPredicateModifierTerm(startTerm)
2100
+ || /ly$/u.test(startTerm);
2101
+ if (ignored[index] || !structuralPrefix)
2102
+ continue;
2103
+ const clauseId = tokens[index].clause;
2104
+ let close = index + 1;
2105
+ while (close < tokens.length
2106
+ && tokens[close]?.clause === clauseId
2107
+ && !tokens[close]?.commaBefore)
2108
+ close += 1;
2109
+ if (close >= tokens.length || tokens[close]?.clause !== clauseId)
2110
+ continue;
2111
+ const atScopeStart = index === 0
2112
+ || tokens[index - 1]?.clause !== clauseId
2113
+ || tokens[index]?.commaBefore;
2114
+ if (!atScopeStart)
2115
+ continue;
2116
+ const embedded = index > 0
2117
+ && tokens[index - 1]?.clause === clauseId
2118
+ && tokens[index]?.commaBefore;
2119
+ let containsFinitePredicate = false;
2120
+ for (let spanIndex = index; spanIndex < close; spanIndex += 1) {
2121
+ if (isFinitePredicateTerm(tokens[spanIndex]?.value ?? ''))
2122
+ containsFinitePredicate = true;
2123
+ }
2124
+ if (isConditionalMarkerUse(tokens, index)) {
2125
+ failClosedSubordinateRanges.push({
2126
+ start: conditionalScopeStart(tokens, hardClauseStarts[index] ?? 0, index),
2127
+ end: hardClauseEnds[index] ?? close,
2128
+ });
2129
+ }
2130
+ else if (isClauseInitialEvidentialHedgeTerm(startTerm)) {
2131
+ failClosedSubordinateRanges.push({ start: index, end: hardClauseEnds[index] ?? close });
2132
+ }
2133
+ else if (containsFinitePredicate) {
2134
+ failClosedSubordinateRanges.push({ start: index, end: close });
2135
+ }
2136
+ else {
2137
+ for (let ignoredIndex = index; ignoredIndex < close; ignoredIndex += 1)
2138
+ ignored[ignoredIndex] = true;
2139
+ }
2140
+ if (embedded) {
2141
+ transparentCommaBefore[index] = true;
2142
+ transparentCommaBefore[close] = true;
2143
+ }
2144
+ index = close - 1;
2145
+ }
2146
+ const conditional = Array.from({ length: tokens.length }, () => false);
2147
+ // Factual subordinate predicates are ambiguous replay evidence and remain fail-closed.
2148
+ for (const range of failClosedSubordinateRanges) {
2149
+ for (let index = range.start; index < range.end; index += 1)
2150
+ conditional[index] = true;
2151
+ }
2152
+ for (let index = 0; index < tokens.length; index += 1) {
2153
+ if (ignored[index] || !isConditionalMarkerUse(tokens, index))
2154
+ continue;
2155
+ const clauseId = tokens[index].clause;
2156
+ const scopeStart = conditionalScopeStart(tokens, hardClauseStarts[index] ?? 0, index);
2157
+ const scopeEnd = hardClauseEnds[index] ?? tokens.length;
2158
+ for (let conditionalIndex = scopeStart; conditionalIndex < scopeEnd; conditionalIndex += 1) {
2159
+ const token = tokens[conditionalIndex];
2160
+ if (!token || token.clause !== clauseId)
2161
+ break;
2162
+ conditional[conditionalIndex] = true;
2163
+ }
2164
+ }
2165
+ for (let index = 0; index < tokens.length; index += 1) {
2166
+ const token = tokens[index];
2167
+ let clauseInitial = index === 0 || tokens[index - 1]?.clause !== token?.clause;
2168
+ if (!clauseInitial && token?.commaBefore) {
2169
+ clauseInitial = true;
2170
+ for (let prefix = index - 1; prefix >= 0; prefix -= 1) {
2171
+ const prefixToken = tokens[prefix];
2172
+ if (!prefixToken || prefixToken.clause !== token.clause)
2173
+ break;
2174
+ if (ignored[prefix]
2175
+ || LEADING_CLAUSE_MODIFIER_TERMS.has(prefixToken.value)
2176
+ || AFFIRMATIVE_PREDICATE_MODIFIER_TERMS.has(prefixToken.value))
2177
+ continue;
2178
+ clauseInitial = false;
2179
+ break;
2180
+ }
2181
+ }
2182
+ if (!token || !clauseInitial || !INVERTED_CONDITIONAL_AUXILIARY_TERMS.has(token.value))
2183
+ continue;
2184
+ const scopeEnd = hardClauseEnds[index] ?? tokens.length;
2185
+ for (let conditionalIndex = index; conditionalIndex < scopeEnd; conditionalIndex += 1) {
2186
+ if (tokens[conditionalIndex]?.clause !== token.clause)
2187
+ break;
2188
+ conditional[conditionalIndex] = true;
2189
+ }
2190
+ }
2191
+ return { tokens, ignored, transparentCommaBefore, conditional };
2192
+ }
2193
+ function isIgnoredRequiredToken(stream, index) {
2194
+ return stream.ignored[index] === true;
2195
+ }
2196
+ function hasEffectiveCommaBefore(stream, index) {
2197
+ return stream.tokens[index]?.commaBefore === true && stream.transparentCommaBefore[index] !== true;
2198
+ }
2199
+ function isNegativeRequiredToken(stream, index) {
2200
+ const token = stream.tokens[index];
2201
+ const lexicalNoOne = token?.value === 'no'
2202
+ && token.compound
2203
+ && stream.tokens[index + 1]?.value === 'one'
2204
+ && stream.tokens[index + 1]?.compound === true;
2205
+ const zeroMetric = token?.value === 'zero'
2206
+ && isContextualZeroMetric(stream, index, stream.tokens.length)
2207
+ && !(stream.tokens[index + 1]?.value === 'cost'
2208
+ && ['center', 'centers'].includes(stream.tokens[index + 2]?.value ?? ''));
2209
+ return token !== undefined
2210
+ && (lexicalNoOne || (!token.compound
2211
+ && !zeroMetric
2212
+ && (NEGATIVE_EVIDENCE_PRONOUNS.has(token.value) || NEGATIVE_EVIDENCE_DETERMINERS.has(token.value))));
2213
+ }
2214
+ function isContextualZeroMetric(stream, zeroIndex, end) {
2215
+ for (let index = zeroIndex + 1; index < end; index += 1) {
2216
+ if (isIgnoredRequiredToken(stream, index))
2217
+ continue;
2218
+ return CONTEXTUAL_ZERO_METRIC_TERMS.has(stream.tokens[index]?.value ?? '');
2219
+ }
2220
+ return false;
2221
+ }
2222
+ function leadingSubjectPolarity(stream, start, end) {
2223
+ for (let index = start; index < end; index += 1) {
2224
+ if (isIgnoredRequiredToken(stream, index))
2225
+ continue;
2226
+ const term = stream.tokens[index]?.value ?? '';
2227
+ if (isNegativeRequiredToken(stream, index))
2228
+ return 'negative';
2229
+ if (SUBJECT_PRONOUN_TERMS.has(term) || SUBJECT_ARTICLE_TERMS.has(term))
2230
+ return 'positive';
2231
+ if (NON_SUBJECT_PREFIX_TERMS.has(term) || /ly$/u.test(term))
2232
+ continue;
2233
+ return 'positive';
2234
+ }
2235
+ return 'absent';
2236
+ }
2237
+ function isFinitePredicateTerm(term) {
2238
+ return KNOWN_FINITE_PREDICATE_TERMS.has(term) || /(?:ed|n't)$/u.test(term);
2239
+ }
2240
+ function hasFinitePredicatePrefix(stream, start, end) {
2241
+ const terms = [];
2242
+ for (let index = start; index < end; index += 1) {
2243
+ if (!isIgnoredRequiredToken(stream, index))
2244
+ terms.push(stream.tokens[index]?.value ?? '');
2245
+ }
2246
+ while (terms.length > 0 && (/ly$/u.test(terms[0] ?? '') || LEADING_CLAUSE_MODIFIER_TERMS.has(terms[0] ?? ''))) {
2247
+ terms.shift();
2248
+ }
2249
+ if (terms.length === 0)
2250
+ return false;
2251
+ const first = terms[0] ?? '';
2252
+ if (SUBJECT_PRONOUN_TERMS.has(first) || NEGATIVE_EVIDENCE_PRONOUNS.has(first)) {
2253
+ terms.shift();
2254
+ }
2255
+ else {
2256
+ if (SUBJECT_ARTICLE_TERMS.has(first) || NEGATIVE_EVIDENCE_DETERMINERS.has(first))
2257
+ terms.shift();
2258
+ while (terms.length > 1 && isAttributiveTargetModifier(terms[0] ?? ''))
2259
+ terms.shift();
2260
+ if (terms.length > 0)
2261
+ terms.shift();
2262
+ }
2263
+ return terms.some((term) => isFinitePredicateTerm(term));
2264
+ }
2265
+ function hasNegativePredicateMarker(stream, start, end) {
2266
+ for (let index = start; index < end; index += 1) {
2267
+ if (isIgnoredRequiredToken(stream, index))
2268
+ continue;
2269
+ const term = stream.tokens[index]?.value ?? '';
2270
+ if (term === 'not') {
2271
+ let next = index + 1;
2272
+ while (next < end && isIgnoredRequiredToken(stream, next))
2273
+ next += 1;
2274
+ if (stream.tokens[next]?.value === 'only')
2275
+ continue;
2276
+ }
2277
+ if (term === 'not' || term === 'never' || term === 'cannot' || /n't$/u.test(term))
2278
+ return true;
2279
+ }
2280
+ return false;
2281
+ }
2282
+ function hasFinitePredicateInSpan(stream, start, end) {
2283
+ for (let index = start; index < end; index += 1) {
2284
+ if (!isIgnoredRequiredToken(stream, index)
2285
+ && isFinitePredicateTerm(stream.tokens[index]?.value ?? ''))
2286
+ return true;
2287
+ }
2288
+ return false;
2289
+ }
2290
+ function relativeSafeFinitePredicateEnd(stream, start, end) {
2291
+ for (let index = start; index < end; index += 1) {
2292
+ if (isIgnoredRequiredToken(stream, index))
2293
+ continue;
2294
+ const complements = RELATIVE_SAFE_FINITE_COMPLEMENTS.get(stream.tokens[index]?.value ?? '');
2295
+ if (!complements)
2296
+ return undefined;
2297
+ for (let complement = index + 1; complement < end; complement += 1) {
2298
+ if (isIgnoredRequiredToken(stream, complement))
2299
+ continue;
2300
+ return complements.has(stream.tokens[complement]?.value ?? '') ? complement + 1 : undefined;
2301
+ }
2302
+ return undefined;
2303
+ }
2304
+ return undefined;
2305
+ }
2306
+ function relativePredicateStart(stream, start, end) {
2307
+ let index = start;
2308
+ while (index < end) {
2309
+ if (isIgnoredRequiredToken(stream, index)) {
2310
+ index += 1;
2311
+ continue;
2312
+ }
2313
+ const term = stream.tokens[index]?.value ?? '';
2314
+ if (/ly$/u.test(term) || AFFIRMATIVE_PREDICATE_MODIFIER_TERMS.has(term)) {
2315
+ index += 1;
2316
+ continue;
2317
+ }
2318
+ break;
2319
+ }
2320
+ return index;
2321
+ }
2322
+ function hasBoundedRegularRelativePredicate(stream, predicateIndex, end) {
2323
+ const term = stream.tokens[predicateIndex]?.value ?? '';
2324
+ if (KNOWN_FINITE_PREDICATE_TERMS.has(term) || /n't$/u.test(term))
2325
+ return true;
2326
+ if (!term.endsWith('ed'))
2327
+ return false;
2328
+ let next = predicateIndex + 1;
2329
+ while (next < end && isIgnoredRequiredToken(stream, next))
2330
+ next += 1;
2331
+ if (next >= end)
2332
+ return true;
2333
+ const nextTerm = stream.tokens[next]?.value ?? '';
2334
+ if (BROAD_EVIDENCE_LINK_TERMS.has(nextTerm) && nextTerm !== 'that')
2335
+ return true;
2336
+ return false;
2337
+ }
2338
+ function hasBoundedRelativeFinitePredicate(stream, start, end) {
2339
+ if (hasFinitePredicatePrefix(stream, start, end))
2340
+ return true;
2341
+ const predicateIndex = relativePredicateStart(stream, start, end);
2342
+ if (predicateIndex >= end)
2343
+ return false;
2344
+ const term = stream.tokens[predicateIndex]?.value ?? '';
2345
+ if (EMBEDDED_CLAIM_GOVERNOR_TERMS.has(term))
2346
+ return false;
2347
+ if (isRequiredPassiveAuxiliary(term))
2348
+ return true;
2349
+ return relativeSafeFinitePredicateEnd(stream, predicateIndex, end) !== undefined
2350
+ || hasBoundedRegularRelativePredicate(stream, predicateIndex, end);
2351
+ }
2352
+ function hasEmbeddedClaimGovernorInSpan(stream, start, end) {
2353
+ for (let index = start; index < end; index += 1) {
2354
+ if (!isIgnoredRequiredToken(stream, index)
2355
+ && EMBEDDED_CLAIM_GOVERNOR_TERMS.has(stream.tokens[index]?.value ?? ''))
2356
+ return true;
2357
+ }
2358
+ return false;
2359
+ }
2360
+ function isExplicitNoDoubtContext(stream, start, end) {
2361
+ const values = [];
2362
+ for (let index = start; index < end; index += 1) {
2363
+ if (!isIgnoredRequiredToken(stream, index))
2364
+ values.push(stream.tokens[index]?.value ?? '');
2365
+ }
2366
+ if (values.shift() !== 'there' || !PASSIVE_BE_AUXILIARY_TERMS.has(values.shift() ?? ''))
2367
+ return false;
2368
+ while (values.length > 0 && /ly$/u.test(values[0] ?? ''))
2369
+ values.shift();
2370
+ return values.length === 2 && values[0] === 'no' && values[1] === 'doubt';
2371
+ }
2372
+ function complementSubjectScope(stream, initialStart, end) {
2373
+ let start = initialStart;
2374
+ let negativeGovernor = false;
2375
+ for (let index = start; index < end; index += 1) {
2376
+ if (isIgnoredRequiredToken(stream, index) || stream.tokens[index]?.value !== 'that')
2377
+ continue;
2378
+ if (!hasFinitePredicatePrefix(stream, start, index)) {
2379
+ // Ambiguous relative/factual subordinate evidence stays fail-closed.
2380
+ if (!hasBoundedRelativeFinitePredicate(stream, index + 1, end))
2381
+ negativeGovernor = true;
2382
+ continue;
2383
+ }
2384
+ if (!isExplicitNoDoubtContext(stream, start, index)
2385
+ || leadingSubjectPolarity(stream, start, index) === 'negative'
2386
+ || hasNegativePredicateMarker(stream, start, index))
2387
+ negativeGovernor = true;
2388
+ start = index + 1;
2389
+ }
2390
+ return { start, negativeGovernor };
2391
+ }
2392
+ function startsExplicitSubordinateClause(stream, start, verbIndex) {
2393
+ for (let index = start; index < verbIndex; index += 1) {
2394
+ if (isIgnoredRequiredToken(stream, index))
2395
+ continue;
2396
+ const term = stream.tokens[index]?.value ?? '';
2397
+ if (SUBJECT_PRONOUN_TERMS.has(term)
2398
+ || SUBJECT_ARTICLE_TERMS.has(term)
2399
+ || isNegativeRequiredToken(stream, index)
2400
+ || isRequiredPassiveAuxiliary(term)
2401
+ || INDEPENDENT_BROAD_FORM_TERMS.has(term))
2402
+ return true;
2403
+ }
2404
+ return false;
2405
+ }
2406
+ function predicateBoundaryBefore(stream, before) {
2407
+ const token = stream.tokens[before];
2408
+ if (!token)
2409
+ return { start: 0, inheritsSubject: false };
2410
+ const lowerBound = Math.max(0, before - MAX_REQUIRED_SUBJECT_LOOKBACK);
2411
+ for (let index = before - 1; index >= lowerBound; index -= 1) {
2412
+ const current = stream.tokens[index];
2413
+ if (!current || current.clause !== token.clause)
2414
+ return { start: index + 1, inheritsSubject: false };
2415
+ if (isIgnoredRequiredToken(stream, index))
2416
+ continue;
2417
+ if (hasEffectiveCommaBefore(stream, index + 1)) {
2418
+ return { start: index + 1, inheritsSubject: false };
2419
+ }
2420
+ if (PREDICATE_COORDINATOR_TERMS.has(current.value)) {
2421
+ return { start: index + 1, index, inheritsSubject: true };
2422
+ }
2423
+ if (current.value === 'after' || current.value === 'before') {
2424
+ let prefixStart = index;
2425
+ while (prefixStart > lowerBound
2426
+ && stream.tokens[prefixStart - 1]?.clause === current.clause
2427
+ && !hasEffectiveCommaBefore(stream, prefixStart))
2428
+ prefixStart -= 1;
2429
+ if (hasFinitePredicatePrefix(stream, prefixStart, index)
2430
+ && startsExplicitSubordinateClause(stream, index + 1, before)) {
2431
+ return { start: index + 1, index, inheritsSubject: false };
2432
+ }
2433
+ }
2434
+ if (PREDICATE_SCOPE_RESET_TERMS.has(current.value)) {
2435
+ return {
2436
+ start: index + 1,
2437
+ index,
2438
+ inheritsSubject: false,
2439
+ negatesPredicate: current.value === 'without',
2440
+ };
2441
+ }
2442
+ }
2443
+ return { start: lowerBound, inheritsSubject: false };
2444
+ }
2445
+ function predicateSubjectPolarity(stream, boundary, verbIndex) {
2446
+ let cursorBoundary = boundary;
2447
+ let cursorEnd = verbIndex;
2448
+ while (true) {
2449
+ const subjectScope = complementSubjectScope(stream, cursorBoundary.start, cursorEnd);
2450
+ const polarity = subjectScope.negativeGovernor
2451
+ ? 'negative'
2452
+ : leadingSubjectPolarity(stream, subjectScope.start, cursorEnd);
2453
+ if (polarity !== 'absent' || !cursorBoundary.inheritsSubject || cursorBoundary.index === undefined) {
2454
+ return polarity;
2455
+ }
2456
+ cursorEnd = cursorBoundary.index;
2457
+ cursorBoundary = predicateBoundaryBefore(stream, cursorEnd);
2458
+ }
2459
+ }
2460
+ function predicateEndAfter(stream, verbIndex) {
2461
+ const token = stream.tokens[verbIndex];
2462
+ if (!token)
2463
+ return verbIndex + 1;
2464
+ const upperBound = Math.min(stream.tokens.length, verbIndex + MAX_REQUIRED_EVIDENCE_PAIR_DISTANCE + 1);
2465
+ for (let index = verbIndex + 1; index < upperBound; index += 1) {
2466
+ const current = stream.tokens[index];
2467
+ if (!current || current.clause !== token.clause || hasEffectiveCommaBefore(stream, index))
2468
+ return index;
2469
+ if (isIgnoredRequiredToken(stream, index))
2470
+ continue;
2471
+ if (PREDICATE_COORDINATOR_TERMS.has(current.value)
2472
+ || PREDICATE_SCOPE_RESET_TERMS.has(current.value))
2473
+ return index;
2474
+ }
2475
+ return upperBound;
2476
+ }
2477
+ function predicateCandidates(stream, verbIndexes) {
2478
+ const candidates = [];
2479
+ const seenVerbIndexes = new Set();
2480
+ for (const verbIndex of verbIndexes) {
2481
+ if (candidates.length >= MAX_REQUIRED_EVIDENCE_CANDIDATES)
2482
+ break;
2483
+ if (seenVerbIndexes.has(verbIndex) || isIgnoredRequiredToken(stream, verbIndex))
2484
+ continue;
2485
+ seenVerbIndexes.add(verbIndex);
2486
+ const boundary = predicateBoundaryBefore(stream, verbIndex);
2487
+ const subjectScope = complementSubjectScope(stream, boundary.start, verbIndex);
2488
+ candidates.push({
2489
+ verbIndex,
2490
+ localStart: subjectScope.start,
2491
+ end: predicateEndAfter(stream, verbIndex),
2492
+ subjectNegative: predicateSubjectPolarity(stream, boundary, verbIndex) === 'negative',
2493
+ conditional: stream.conditional[verbIndex] === true,
2494
+ boundaryNegated: boundary.negatesPredicate === true,
2495
+ });
2496
+ }
2497
+ return candidates;
2498
+ }
2499
+ function nextRequiredToken(stream, index, end) {
2500
+ for (let next = index + 1; next <= end; next += 1) {
2501
+ if (!isIgnoredRequiredToken(stream, next))
2502
+ return stream.tokens[next]?.value;
2503
+ }
2504
+ return undefined;
2505
+ }
2506
+ function isLocalPredicateNegated(stream, candidate, through) {
2507
+ if (candidate.boundaryNegated)
2508
+ return true;
2509
+ for (let index = candidate.localStart; index <= through; index += 1) {
2510
+ if (isIgnoredRequiredToken(stream, index))
2511
+ continue;
2512
+ const term = stream.tokens[index]?.value ?? '';
2513
+ if (term === 'not' && nextRequiredToken(stream, index, through) === 'only')
2514
+ continue;
2515
+ if (term === 'not' || term === 'never' || term === 'without' || term === 'cannot' || /n't$/u.test(term)) {
2516
+ return true;
2517
+ }
2518
+ }
2519
+ return false;
2520
+ }
2521
+ function tokenMatchIndexes(stream, term, kind) {
2522
+ const forms = targetTermForms(term, kind);
2523
+ const indexes = [];
2524
+ for (let index = 0; index < stream.tokens.length; index += 1) {
2525
+ if (isIgnoredRequiredToken(stream, index))
2526
+ continue;
2527
+ if (forms.has(stream.tokens[index]?.value ?? ''))
2528
+ indexes.push(index);
2529
+ }
2530
+ return indexes;
2531
+ }
2532
+ function isAttributiveTargetModifier(term) {
2533
+ return BROAD_EVIDENCE_LINK_TERMS.has(term)
2534
+ || TOTALITY_TARGET_MODIFIER_TERMS.has(term)
2535
+ || (term.length <= 3
2536
+ && /^[a-z]+$/u.test(term)
2537
+ && !KNOWN_EVIDENCE_ACTION_TERMS.has(term)
2538
+ && !PASSIVE_SUBJECT_POSTMODIFIER_TERMS.has(term)
2539
+ && !PREDICATE_COORDINATOR_TERMS.has(term)
2540
+ && !PREDICATE_SCOPE_RESET_TERMS.has(term)
2541
+ && !NEGATIVE_EVIDENCE_PRONOUNS.has(term)
2542
+ && !NEGATIVE_EVIDENCE_DETERMINERS.has(term))
2543
+ || /(?:ed|able|al|ary|ful|ible|ic|ive|less|ory|ous)$/u.test(term);
2544
+ }
2545
+ function hasNegativePrenominalTarget(stream, targetIndex, lowerBound) {
2546
+ let sawContextualZeroMetric = false;
2547
+ for (let index = targetIndex - 1; index >= lowerBound; index -= 1) {
2548
+ if (isIgnoredRequiredToken(stream, index))
2549
+ continue;
2550
+ const term = stream.tokens[index]?.value ?? '';
2551
+ if (hasEffectiveCommaBefore(stream, index + 1) || TARGET_SCOPE_RESET_TERMS_LOCAL.has(term))
2552
+ return false;
2553
+ if (isNegativeRequiredToken(stream, index))
2554
+ return term !== 'zero' || !sawContextualZeroMetric;
2555
+ if (CONTEXTUAL_ZERO_METRIC_TERMS.has(term)) {
2556
+ sawContextualZeroMetric = true;
2557
+ continue;
2558
+ }
2559
+ if (term === 'of' || /ly$/u.test(term) || isAttributiveTargetModifier(term))
2560
+ continue;
2561
+ if (KNOWN_EVIDENCE_ACTION_TERMS.has(term) || term.endsWith('ing'))
2562
+ return false;
2563
+ }
2564
+ return false;
2565
+ }
2566
+ function hasActiveTargetLinks(stream, verbIndex, targetIndex) {
2567
+ for (let index = verbIndex + 1; index < targetIndex; index += 1) {
2568
+ if (isIgnoredRequiredToken(stream, index))
2569
+ continue;
2570
+ const term = stream.tokens[index]?.value ?? '';
2571
+ if (hasEffectiveCommaBefore(stream, index)
2572
+ || PREDICATE_COORDINATOR_TERMS.has(term)
2573
+ || PREDICATE_SCOPE_RESET_TERMS.has(term)
2574
+ || EMBEDDED_CLAIM_GOVERNOR_TERMS.has(term)
2575
+ || !isAttributiveTargetModifier(term))
2576
+ return false;
2577
+ if (term === 'not' && nextRequiredToken(stream, index, targetIndex) !== 'only')
2578
+ return false;
2579
+ }
2580
+ return true;
2581
+ }
2582
+ function nearestPredicateChainTerm(stream, candidate, skipInfinitiveMarker) {
2583
+ for (let index = candidate.verbIndex - 1; index >= candidate.localStart; index -= 1) {
2584
+ if (isIgnoredRequiredToken(stream, index))
2585
+ continue;
2586
+ const term = stream.tokens[index]?.value ?? '';
2587
+ if (term === 'only') {
2588
+ let previous = index - 1;
2589
+ while (previous >= candidate.localStart && isIgnoredRequiredToken(stream, previous))
2590
+ previous -= 1;
2591
+ if (stream.tokens[previous]?.value === 'not') {
2592
+ index = previous;
2593
+ continue;
2594
+ }
2595
+ }
2596
+ if (AFFIRMATIVE_PREDICATE_MODIFIER_TERMS.has(term))
2597
+ continue;
2598
+ if (skipInfinitiveMarker && term === 'to')
2599
+ continue;
2600
+ return term;
2601
+ }
2602
+ return undefined;
2603
+ }
2604
+ function hasBlockingRootPredicateModifier(stream, candidate) {
2605
+ for (let index = candidate.localStart; index < candidate.verbIndex; index += 1) {
2606
+ if (isIgnoredRequiredToken(stream, index))
2607
+ continue;
2608
+ const term = stream.tokens[index]?.value ?? '';
2609
+ if (isBlockingRootPredicateModifierTerm(term)
2610
+ && !isSubjectParticipleAdverb(stream, candidate, index))
2611
+ return true;
2612
+ }
2613
+ return false;
2614
+ }
2615
+ function hasExplicitEmbeddedSubjectAfter(stream, start, end) {
2616
+ for (let index = start; index < end; index += 1) {
2617
+ if (isIgnoredRequiredToken(stream, index))
2618
+ continue;
2619
+ const term = stream.tokens[index]?.value ?? '';
2620
+ if (SUBJECT_PRONOUN_TERMS.has(term)
2621
+ || SUBJECT_ARTICLE_TERMS.has(term)
2622
+ || isNegativeRequiredToken(stream, index))
2623
+ return true;
2624
+ }
2625
+ return false;
2626
+ }
2627
+ function isCompletedRelativePredicate(stream, candidate, predicateIndex) {
2628
+ for (let index = predicateIndex - 1; index >= candidate.localStart; index -= 1) {
2629
+ if (isIgnoredRequiredToken(stream, index) || stream.tokens[index]?.value !== 'that')
2630
+ continue;
2631
+ return !hasFinitePredicatePrefix(stream, candidate.localStart, index);
2632
+ }
2633
+ return false;
2634
+ }
2635
+ function isParticipialSubjectModifier(stream, candidate, modifierIndex) {
2636
+ for (let index = candidate.localStart; index < modifierIndex; index += 1) {
2637
+ if (isIgnoredRequiredToken(stream, index))
2638
+ continue;
2639
+ const term = stream.tokens[index]?.value ?? '';
2640
+ const determiner = BROAD_EVIDENCE_LINK_TERMS.has(term) && term !== 'not' && term !== 'only';
2641
+ if (determiner
2642
+ || /ly$/u.test(term)
2643
+ || /(?:ed|ing)$/u.test(term)
2644
+ || isAttributiveTargetModifier(term))
2645
+ continue;
2646
+ return false;
2647
+ }
2648
+ let sawHead = false;
2649
+ for (let index = modifierIndex + 1; index < candidate.verbIndex; index += 1) {
2650
+ if (isIgnoredRequiredToken(stream, index))
2651
+ continue;
2652
+ const term = stream.tokens[index]?.value ?? '';
2653
+ if (hasEffectiveCommaBefore(stream, index)
2654
+ || PREDICATE_COORDINATOR_TERMS.has(term)
2655
+ || PREDICATE_SCOPE_RESET_TERMS.has(term)
2656
+ || BROAD_EVIDENCE_LINK_TERMS.has(term)
2657
+ || SUBJECT_PRONOUN_TERMS.has(term)
2658
+ || term === 'that'
2659
+ || term === 'to')
2660
+ return false;
2661
+ if (isRequiredPassiveAuxiliary(term)) {
2662
+ if (!sawHead)
2663
+ return false;
2664
+ continue;
2665
+ }
2666
+ if (KNOWN_EVIDENCE_ACTION_TERMS.has(term)) {
2667
+ const baseActionNoun = !sawHead
2668
+ && (IRREGULAR_VERB_FORMS.has(term) || GENERIC_ACTION_TERMS.has(term))
2669
+ && !EXACT_ONLY_PROGRESSIVE_LEMMAS.has(term);
2670
+ if (!baseActionNoun)
2671
+ return false;
2672
+ sawHead = true;
2673
+ continue;
2674
+ }
2675
+ if (/ly$/u.test(term))
2676
+ continue;
2677
+ if (/(?:ed|ing)$/u.test(term)) {
2678
+ if (sawHead)
2679
+ return false;
2680
+ continue;
2681
+ }
2682
+ sawHead = true;
2683
+ }
2684
+ return sawHead;
2685
+ }
2686
+ function isRelativeNominalClaimHead(stream, candidate, claimIndex) {
2687
+ if (!NOMINAL_CLAIM_HEAD_TERMS.has(stream.tokens[claimIndex]?.value ?? ''))
2688
+ return false;
2689
+ for (let index = candidate.localStart; index < claimIndex; index += 1) {
2690
+ if (isIgnoredRequiredToken(stream, index))
2691
+ continue;
2692
+ const term = stream.tokens[index]?.value ?? '';
2693
+ const determiner = BROAD_EVIDENCE_LINK_TERMS.has(term) && term !== 'not' && term !== 'only';
2694
+ if (determiner || isNegativeRequiredToken(stream, index) || /ly$/u.test(term)
2695
+ || isAttributiveTargetModifier(term))
2696
+ continue;
2697
+ return false;
2698
+ }
2699
+ let relativeStart = claimIndex + 1;
2700
+ while (relativeStart < candidate.verbIndex && isIgnoredRequiredToken(stream, relativeStart)) {
2701
+ relativeStart += 1;
2702
+ }
2703
+ if (stream.tokens[relativeStart]?.value !== 'that')
2704
+ return false;
2705
+ const relativePredicate = relativePredicateStart(stream, relativeStart + 1, candidate.verbIndex);
2706
+ if (relativePredicate >= candidate.verbIndex)
2707
+ return false;
2708
+ const firstRelativeValue = stream.tokens[relativePredicate]?.value ?? '';
2709
+ if (EMBEDDED_CLAIM_GOVERNOR_TERMS.has(firstRelativeValue))
2710
+ return false;
2711
+ const copularRelative = isRequiredPassiveAuxiliary(firstRelativeValue);
2712
+ if (!copularRelative) {
2713
+ const safeEnd = relativeSafeFinitePredicateEnd(stream, relativePredicate, candidate.verbIndex);
2714
+ if (safeEnd !== undefined) {
2715
+ return !hasEmbeddedClaimGovernorInSpan(stream, safeEnd, candidate.verbIndex);
2716
+ }
2717
+ return hasBoundedRegularRelativePredicate(stream, relativePredicate, candidate.verbIndex);
2718
+ }
2719
+ for (let index = relativePredicate; index < candidate.verbIndex; index += 1) {
2720
+ if (isIgnoredRequiredToken(stream, index))
2721
+ continue;
2722
+ const term = stream.tokens[index]?.value ?? '';
2723
+ if (isRequiredPassiveAuxiliary(term)
2724
+ || AFFIRMATIVE_PREDICATE_MODIFIER_TERMS.has(term)
2725
+ || term === 'not'
2726
+ || term === 'never')
2727
+ continue;
2728
+ if (EMBEDDED_CLAIM_GOVERNOR_TERMS.has(term))
2729
+ return false;
2730
+ return true;
2731
+ }
2732
+ return false;
2733
+ }
2734
+ function isSubjectParticipleAdverb(stream, candidate, adverbIndex) {
2735
+ let modifierIndex = adverbIndex + 1;
2736
+ while (modifierIndex < candidate.verbIndex && isIgnoredRequiredToken(stream, modifierIndex)) {
2737
+ modifierIndex += 1;
2738
+ }
2739
+ return /(?:ed|ing)$/u.test(stream.tokens[modifierIndex]?.value ?? '')
2740
+ && isParticipialSubjectModifier(stream, candidate, modifierIndex);
2741
+ }
2742
+ function hasEmbeddedClaimGovernor(stream, candidate) {
2743
+ for (let index = candidate.localStart; index < candidate.verbIndex; index += 1) {
2744
+ if (isIgnoredRequiredToken(stream, index))
2745
+ continue;
2746
+ const term = stream.tokens[index]?.value ?? '';
2747
+ if (!EMBEDDED_CLAIM_GOVERNOR_TERMS.has(term))
2748
+ continue;
2749
+ if (isRelativeNominalClaimHead(stream, candidate, index))
2750
+ continue;
2751
+ if (/(?:ed|ing)$/u.test(term) && isParticipialSubjectModifier(stream, candidate, index))
2752
+ continue;
2753
+ if (hasExplicitEmbeddedSubjectAfter(stream, index + 1, candidate.verbIndex)
2754
+ || hasFinitePredicateInSpan(stream, index + 1, candidate.verbIndex))
2755
+ return true;
2756
+ }
2757
+ return false;
2758
+ }
2759
+ function hasPriorNonAuxiliaryGovernor(stream, candidate) {
2760
+ if (hasEmbeddedClaimGovernor(stream, candidate))
2761
+ return true;
2762
+ for (let index = candidate.localStart; index < candidate.verbIndex; index += 1) {
2763
+ if (isIgnoredRequiredToken(stream, index))
2764
+ continue;
2765
+ const term = stream.tokens[index]?.value ?? '';
2766
+ if (isRequiredPassiveAuxiliary(term)
2767
+ || AFFIRMATIVE_PREDICATE_MODIFIER_TERMS.has(term)
2768
+ || NON_SUBJECT_PREFIX_TERMS.has(term))
2769
+ continue;
2770
+ if (isRelativeNominalClaimHead(stream, candidate, index))
2771
+ continue;
2772
+ if (UNAMBIGUOUS_EMBEDDED_CLAIM_GOVERNOR_TERMS.has(term))
2773
+ return true;
2774
+ if (/(?:ed|ing)$/u.test(term)) {
2775
+ if (isParticipialSubjectModifier(stream, candidate, index))
2776
+ continue;
2777
+ if (isCompletedRelativePredicate(stream, candidate, index))
2778
+ continue;
2779
+ return true;
2780
+ }
2781
+ if (term.length > 3
2782
+ && term.endsWith('s')
2783
+ && hasExplicitEmbeddedSubjectAfter(stream, index + 1, candidate.verbIndex))
2784
+ return true;
2785
+ }
2786
+ return false;
2787
+ }
2788
+ function hasFiniteActivePredicate(stream, candidate) {
2789
+ const verb = stream.tokens[candidate.verbIndex]?.value ?? '';
2790
+ if (hasBlockingRootPredicateModifier(stream, candidate)
2791
+ || hasPriorNonAuxiliaryGovernor(stream, candidate))
2792
+ return false;
2793
+ if (INDEPENDENT_BROAD_FORM_TERMS.has(verb))
2794
+ return true;
2795
+ if (verb.endsWith('ing')) {
2796
+ if (candidate.verbIndex === candidate.localStart)
2797
+ return true;
2798
+ const license = nearestPredicateChainTerm(stream, candidate, false);
2799
+ return license !== undefined
2800
+ && (PASSIVE_BE_AUXILIARY_TERMS.has(license)
2801
+ || MODAL_AUXILIARY_TERMS.has(license)
2802
+ || SEMI_MODAL_AUXILIARY_TERMS.has(license));
2803
+ }
2804
+ const license = nearestPredicateChainTerm(stream, candidate, true);
2805
+ if (PERFECT_BROAD_FORM_TERMS.has(verb)) {
2806
+ return license !== undefined && PERFECT_AUXILIARY_TERMS.has(license);
2807
+ }
2808
+ return license !== undefined
2809
+ && (MODAL_AUXILIARY_TERMS.has(license) || SEMI_MODAL_AUXILIARY_TERMS.has(license));
2810
+ }
2811
+ function isRequiredPassiveAuxiliary(term) {
2812
+ return PASSIVE_AUXILIARY_TERMS.has(term)
2813
+ || MODAL_AUXILIARY_TERMS.has(term)
2814
+ || SEMI_MODAL_AUXILIARY_TERMS.has(term);
2815
+ }
2816
+ function hasPassiveTargetLinks(stream, targetIndex, verbIndex) {
2817
+ const links = [];
2818
+ for (let index = targetIndex + 1; index < verbIndex; index += 1) {
2819
+ if (isIgnoredRequiredToken(stream, index))
2820
+ continue;
2821
+ const term = stream.tokens[index]?.value ?? '';
2822
+ if (hasEffectiveCommaBefore(stream, index)
2823
+ || PREDICATE_COORDINATOR_TERMS.has(term)
2824
+ || PREDICATE_SCOPE_RESET_TERMS.has(term))
2825
+ return false;
2826
+ links.push(term);
2827
+ }
2828
+ const firstAuxiliary = links.findIndex((term) => isRequiredPassiveAuxiliary(term));
2829
+ if (firstAuxiliary < 0)
2830
+ return false;
2831
+ const postmodifier = links.slice(0, firstAuxiliary);
2832
+ if (postmodifier.length > 0
2833
+ && !PASSIVE_SUBJECT_POSTMODIFIER_TERMS.has(postmodifier[0] ?? ''))
2834
+ return false;
2835
+ if (postmodifier.some((term) => NEGATIVE_EVIDENCE_PRONOUNS.has(term)
2836
+ || NEGATIVE_EVIDENCE_DETERMINERS.has(term)))
2837
+ return false;
2838
+ const auxiliaryChain = links.slice(firstAuxiliary);
2839
+ if (!auxiliaryChain.some((term) => PASSIVE_BE_AUXILIARY_TERMS.has(term)))
2840
+ return false;
2841
+ return auxiliaryChain.every((term, index) => ((term !== 'not' || auxiliaryChain[index + 1] === 'only')
2842
+ && (isRequiredPassiveAuxiliary(term)
2843
+ || term === 'to'
2844
+ || BROAD_EVIDENCE_LINK_TERMS.has(term)
2845
+ || AFFIRMATIVE_PREDICATE_MODIFIER_TERMS.has(term))));
2846
+ }
2847
+ function nearestTargetAfter(candidate, indexes) {
2848
+ return indexes.find((index) => index > candidate.verbIndex
2849
+ && index < candidate.end
2850
+ && index - candidate.verbIndex <= MAX_REQUIRED_EVIDENCE_PAIR_DISTANCE);
2851
+ }
2852
+ function nearestTargetBefore(candidate, indexes) {
2853
+ for (let offset = indexes.length - 1; offset >= 0; offset -= 1) {
2854
+ const index = indexes[offset];
2855
+ if (index < candidate.localStart)
2856
+ return undefined;
2857
+ if (index < candidate.verbIndex
2858
+ && candidate.verbIndex - index <= MAX_REQUIRED_EVIDENCE_PAIR_DISTANCE)
2859
+ return index;
2860
+ }
2861
+ return undefined;
2862
+ }
2863
+ function hasNegativePredicateTail(stream, start, end) {
2864
+ for (let index = start; index < end; index += 1) {
2865
+ if (isIgnoredRequiredToken(stream, index))
2866
+ continue;
2867
+ const term = stream.tokens[index]?.value ?? '';
2868
+ if (isNegativeRequiredToken(stream, index))
2869
+ return true;
2870
+ if (term === 'not' && nextRequiredToken(stream, index, end - 1) === 'only')
2871
+ continue;
2872
+ if (term === 'not' || term === 'never' || term === 'cannot' || /n't$/u.test(term))
2873
+ return true;
2874
+ }
2875
+ return false;
2876
+ }
2877
+ function hasLaterFinitePredicate(stream, start, candidate) {
2878
+ return hasFinitePredicateInSpan(stream, start, candidate.end);
2879
+ }
2880
+ function hasNonIgnoredHardClauseTail(stream, start, clause) {
2881
+ for (let index = start; index < stream.tokens.length; index += 1) {
2882
+ const token = stream.tokens[index];
2883
+ if (!token || token.clause !== clause)
2884
+ break;
2885
+ if (!isIgnoredRequiredToken(stream, index))
2886
+ return true;
2887
+ }
2888
+ return false;
2889
+ }
2890
+ function standaloneGerundTailStart(stream, candidate) {
2891
+ let start = candidate.verbIndex + 1;
2892
+ while (start < stream.tokens.length && isIgnoredRequiredToken(stream, start))
2893
+ start += 1;
2894
+ if (stream.tokens[start]?.value === 'it')
2895
+ start += 1;
2896
+ return start;
2897
+ }
2898
+ function isPassiveTargetOwnedByPriorNominal(stream, targetIndex, lowerBound) {
2899
+ let index = targetIndex - 1;
2900
+ while (index >= lowerBound) {
2901
+ if (isIgnoredRequiredToken(stream, index)) {
2902
+ index -= 1;
2903
+ continue;
2904
+ }
2905
+ const term = stream.tokens[index]?.value ?? '';
2906
+ if (isAttributiveTargetModifier(term) || /ly$/u.test(term)) {
2907
+ index -= 1;
2908
+ continue;
2909
+ }
2910
+ const contextualMetric = CONTEXTUAL_ZERO_METRIC_TERMS.has(term);
2911
+ for (let ownerIndex = index - 1; ownerIndex >= lowerBound; ownerIndex -= 1) {
2912
+ if (isIgnoredRequiredToken(stream, ownerIndex))
2913
+ continue;
2914
+ const owner = stream.tokens[ownerIndex]?.value ?? '';
2915
+ if (contextualMetric && owner === 'zero')
2916
+ return false;
2917
+ if (SUBJECT_ARTICLE_TERMS.has(owner)
2918
+ || isNegativeRequiredToken(stream, ownerIndex)
2919
+ || isAttributiveTargetModifier(owner)
2920
+ || /ly$/u.test(owner))
2921
+ continue;
2922
+ return true;
2923
+ }
2924
+ return false;
2925
+ }
2926
+ return false;
2927
+ }
2928
+ function hasContextualZeroMetricTarget(stream, targetIndex, lowerBound) {
2929
+ for (let index = targetIndex - 1; index >= lowerBound; index -= 1) {
2930
+ if (isIgnoredRequiredToken(stream, index))
2931
+ continue;
2932
+ const term = stream.tokens[index]?.value ?? '';
2933
+ if (hasEffectiveCommaBefore(stream, index + 1) || TARGET_SCOPE_RESET_TERMS_LOCAL.has(term))
2934
+ return false;
2935
+ if (term === 'zero' && isNegativeRequiredToken(stream, index)) {
2936
+ return isContextualZeroMetric(stream, index, targetIndex);
2937
+ }
2938
+ }
2939
+ return false;
2940
+ }
2941
+ function provesStandaloneBroadEvidence(stream, candidate) {
2942
+ const verb = stream.tokens[candidate.verbIndex]?.value ?? '';
2943
+ const rootGerund = verb.endsWith('ing') && candidate.verbIndex === candidate.localStart;
2944
+ const invalidTail = rootGerund
2945
+ ? hasNonIgnoredHardClauseTail(stream, standaloneGerundTailStart(stream, candidate), stream.tokens[candidate.verbIndex]?.clause ?? -1)
2946
+ : hasLaterFinitePredicate(stream, candidate.verbIndex + 1, candidate);
2947
+ return !candidate.conditional
2948
+ && !candidate.subjectNegative
2949
+ && hasFiniteActivePredicate(stream, candidate)
2950
+ && !isLocalPredicateNegated(stream, candidate, candidate.verbIndex)
2951
+ && !invalidTail
2952
+ && !hasNegativePredicateTail(stream, candidate.verbIndex + 1, candidate.end);
2953
+ }
2954
+ function provesRoleAwareBroadEvidence(stream, lemma, verbIndexes, evidenceIndexes) {
2955
+ let pairCount = 0;
2956
+ for (const candidate of predicateCandidates(stream, verbIndexes)) {
2957
+ if (candidate.conditional)
2958
+ continue;
2959
+ for (const indexes of evidenceIndexes) {
2960
+ if (pairCount >= MAX_REQUIRED_EVIDENCE_CANDIDATES)
2961
+ return false;
2962
+ pairCount += 1;
2963
+ const activeTarget = nearestTargetAfter(candidate, indexes);
2964
+ const activeVerb = stream.tokens[candidate.verbIndex]?.value ?? '';
2965
+ const rootGerund = activeVerb.endsWith('ing') && candidate.verbIndex === candidate.localStart;
2966
+ if (activeTarget !== undefined
2967
+ && !candidate.subjectNegative
2968
+ && hasFiniteActivePredicate(stream, candidate)
2969
+ && !isLocalPredicateNegated(stream, candidate, activeTarget)
2970
+ && !hasNegativePrenominalTarget(stream, activeTarget, candidate.verbIndex + 1)
2971
+ && hasActiveTargetLinks(stream, candidate.verbIndex, activeTarget)
2972
+ && !(rootGerund && hasNonIgnoredHardClauseTail(stream, activeTarget + 1, stream.tokens[candidate.verbIndex]?.clause ?? -1))
2973
+ && !(!rootGerund
2974
+ && activeVerb.endsWith('ing')
2975
+ && hasLaterFinitePredicate(stream, activeTarget + 1, candidate))
2976
+ && !hasNegativePredicateTail(stream, activeTarget + 1, candidate.end))
2977
+ return true;
2978
+ if (lemma !== 'do' || stream.tokens[candidate.verbIndex]?.value !== 'done')
2979
+ continue;
2980
+ const passiveTarget = nearestTargetBefore(candidate, indexes);
2981
+ if (passiveTarget !== undefined
2982
+ && (!candidate.subjectNegative
2983
+ || hasContextualZeroMetricTarget(stream, passiveTarget, candidate.localStart))
2984
+ && !hasEmbeddedClaimGovernor(stream, candidate)
2985
+ && !isLocalPredicateNegated(stream, candidate, candidate.verbIndex)
2986
+ && !hasNegativePrenominalTarget(stream, passiveTarget, candidate.localStart)
2987
+ && !isPassiveTargetOwnedByPriorNominal(stream, passiveTarget, candidate.localStart)
2988
+ && !hasNegativePredicateTail(stream, candidate.verbIndex + 1, candidate.end)
2989
+ && hasPassiveTargetLinks(stream, passiveTarget, candidate.verbIndex))
2990
+ return true;
2991
+ }
2992
+ }
2993
+ return false;
2994
+ }
2995
+ function mustMatchedTerms(stream, terms) {
2996
+ const indexedTerms = terms.map((term) => ({
2997
+ term,
2998
+ indexes: tokenMatchIndexes(stream, term, 'must'),
2999
+ }));
3000
+ const matched = new Set(indexedTerms
3001
+ .filter((entry) => !isBroadProgressiveAuxiliary(entry.term) && entry.indexes.length > 0)
3002
+ .map((entry) => entry.term.text));
3003
+ for (const entry of indexedTerms) {
3004
+ if (!isBroadProgressiveAuxiliary(entry.term) || entry.indexes.length === 0)
3005
+ continue;
3006
+ if (terms.length === 1) {
3007
+ const exactIndexes = entry.indexes.filter((index) => stream.tokens[index]?.value === entry.term.text);
3008
+ if (predicateCandidates(stream, exactIndexes).some((candidate) => (provesStandaloneBroadEvidence(stream, candidate))))
3009
+ matched.add(entry.term.text);
3010
+ continue;
3011
+ }
3012
+ const lemma = verbLemma(entry.term.text);
3013
+ if (lemma && provesRoleAwareBroadEvidence(stream, lemma, entry.indexes, indexedTerms.filter((other) => other !== entry).map((other) => other.indexes)))
3014
+ matched.add(entry.term.text);
3015
+ }
3016
+ return terms.filter((term) => matched.has(term.text)).map((term) => term.text);
3017
+ }
3018
+ export function detectConstraintViolations(output, constraints) {
3019
+ const redactedOutput = redactConstraintText(output);
3020
+ const evidenceHash = sha256(redactedOutput);
3021
+ const outputSensitiveClasses = new Set(sensitiveClassesForValue(output));
3022
+ const violations = [];
3023
+ let requiredEvidence;
3024
+ for (const constraint of constraints) {
3025
+ const terms = targetTerms(constraint.redactedText, constraint.kind);
3026
+ const sensitiveMatch = constraint.kind === 'must_not'
3027
+ && constraint.sensitiveClasses.some((sensitiveClass) => outputSensitiveClasses.has(sensitiveClass));
3028
+ if (terms.length === 0 && !sensitiveMatch)
3029
+ continue;
3030
+ const matchedTerms = constraint.kind === 'must_not'
3031
+ ? mustNotMatchedTerms(redactedOutput, terms)
3032
+ : mustMatchedTerms(requiredEvidence ??= lexRequiredEvidence(redactedOutput), terms);
3033
+ if (!sensitiveMatch && !isViolated(constraint.kind, terms, matchedTerms, isContrastiveConstraint(constraint.redactedText)))
3034
+ continue;
3035
+ violations.push({
3036
+ constraintId: constraint.id,
3037
+ kind: constraint.kind,
3038
+ severity: severityFor(constraint.kind),
3039
+ evidenceHash,
3040
+ matchedTerms: sensitiveMatch && matchedTerms.length === 0 ? ['sensitive_value'] : matchedTerms,
3041
+ });
3042
+ }
3043
+ return violations;
3044
+ }
3045
+ export function computeConstraintAblationScore(input) {
3046
+ const ablationCount = Math.max(0, input.ablationCount);
3047
+ const baselineViolationCount = input.baselineViolations.length;
3048
+ const ablatedViolationCount = input.ablatedViolations.length;
3049
+ const removedConstraintIds = new Set(input.removedConstraintIds);
3050
+ const relevantBaselineViolations = input.baselineViolations.filter((violation) => removedConstraintIds.has(violation.constraintId));
3051
+ const relevantAblatedViolations = input.ablatedViolations.filter((violation) => removedConstraintIds.has(violation.constraintId));
3052
+ const denominator = Math.max(1, removedConstraintIds.size);
3053
+ const sensitivity = Math.max(0, Math.min(1, (relevantAblatedViolations.length - relevantBaselineViolations.length) / denominator));
3054
+ const threshold = input.sensitivityThreshold ?? 0.5;
3055
+ const sensitive = sensitivity >= threshold;
3056
+ return {
3057
+ source: 'constraint_ablation_replay',
3058
+ sensitivity,
3059
+ ablationCount,
3060
+ baselineViolationCount,
3061
+ ablatedViolationCount,
3062
+ mustViolationCount: relevantAblatedViolations.filter((v) => v.kind === 'must').length,
3063
+ mustNotViolationCount: relevantAblatedViolations.filter((v) => v.kind === 'must_not').length,
3064
+ taskSuccess: input.taskSuccess,
3065
+ comparison: compareSensitivityWithSuccess(input.taskSuccess.status, sensitive),
3066
+ };
3067
+ }
3068
+ function compareSensitivityWithSuccess(status, sensitive) {
3069
+ if (status === 'unknown')
3070
+ return 'unknown_success';
3071
+ if (status === 'success')
3072
+ return sensitive ? 'success_constraint_sensitive' : 'success_constraint_insensitive';
3073
+ return sensitive ? 'failure_constraint_sensitive' : 'failure_constraint_insensitive';
3074
+ }