review-council 4.1.10 → 4.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +15 -0
  2. package/dist/consensus/claim-contract.d.ts +9 -0
  3. package/dist/consensus/claim-contract.js +307 -0
  4. package/dist/consensus/claim-contract.js.map +1 -0
  5. package/dist/consensus/claim-identity.d.ts +5 -0
  6. package/dist/consensus/claim-identity.js +39 -0
  7. package/dist/consensus/claim-identity.js.map +1 -0
  8. package/dist/consensus/semantic-deduper.d.ts +10 -0
  9. package/dist/consensus/semantic-deduper.js +70 -0
  10. package/dist/consensus/semantic-deduper.js.map +1 -0
  11. package/dist/consensus/types.d.ts +3 -0
  12. package/dist/converge/attempt-budget.d.ts +7 -2
  13. package/dist/converge/attempt-budget.js +26 -12
  14. package/dist/converge/attempt-budget.js.map +1 -1
  15. package/dist/converge/cycle-remote.d.ts +6 -0
  16. package/dist/converge/cycle-remote.js +41 -0
  17. package/dist/converge/cycle-remote.js.map +1 -0
  18. package/dist/converge/fresh-review.d.ts +37 -0
  19. package/dist/converge/fresh-review.js +351 -0
  20. package/dist/converge/fresh-review.js.map +1 -0
  21. package/dist/converge/launch-guard.d.ts +13 -3
  22. package/dist/converge/launch-guard.js +83 -2
  23. package/dist/converge/launch-guard.js.map +1 -1
  24. package/dist/converge/review-cycle.d.ts +49 -0
  25. package/dist/converge/review-cycle.js +26 -0
  26. package/dist/converge/review-cycle.js.map +1 -0
  27. package/dist/converge/run-state.d.ts +6 -3
  28. package/dist/converge/run-state.js +25 -2
  29. package/dist/converge/run-state.js.map +1 -1
  30. package/dist/converge/stale-report.js +8 -1
  31. package/dist/converge/stale-report.js.map +1 -1
  32. package/dist/dispatch/async-lane.d.ts +1 -1
  33. package/dist/dispatch/async-lane.js +3 -1
  34. package/dist/dispatch/async-lane.js.map +1 -1
  35. package/dist/evidence/claim-recovery/proof-storage.d.ts +6 -0
  36. package/dist/evidence/claim-recovery/proof-storage.js +97 -0
  37. package/dist/evidence/claim-recovery/proof-storage.js.map +1 -0
  38. package/dist/evidence/claim-recovery/validation/claims.d.ts +20 -0
  39. package/dist/evidence/claim-recovery/validation/claims.js +108 -0
  40. package/dist/evidence/claim-recovery/validation/claims.js.map +1 -0
  41. package/dist/evidence/claim-recovery/validation/lexical.d.ts +18 -0
  42. package/dist/evidence/claim-recovery/validation/lexical.js +91 -0
  43. package/dist/evidence/claim-recovery/validation/lexical.js.map +1 -0
  44. package/dist/evidence/claim-recovery/validation/materials.d.ts +19 -0
  45. package/dist/evidence/claim-recovery/validation/materials.js +160 -0
  46. package/dist/evidence/claim-recovery/validation/materials.js.map +1 -0
  47. package/dist/evidence/claim-recovery/validation/recovery-json.d.ts +8 -0
  48. package/dist/evidence/claim-recovery/validation/recovery-json.js +42 -0
  49. package/dist/evidence/claim-recovery/validation/recovery-json.js.map +1 -0
  50. package/dist/evidence/original-run/decode.d.ts +6 -0
  51. package/dist/evidence/original-run/decode.js +30 -1
  52. package/dist/evidence/original-run/decode.js.map +1 -1
  53. package/dist/index.js +86 -19
  54. package/dist/index.js.map +1 -1
  55. package/dist/report/run-header.d.ts +4 -1
  56. package/dist/report/run-header.js +9 -1
  57. package/dist/report/run-header.js.map +1 -1
  58. package/dist/telemetry/envelope-validation.js +1 -0
  59. package/dist/telemetry/envelope-validation.js.map +1 -1
  60. package/dist/telemetry/recovery/source.d.ts +6 -5
  61. package/dist/telemetry/recovery/source.js +1 -0
  62. package/dist/telemetry/recovery/source.js.map +1 -1
  63. package/dist/telemetry/scrub.d.ts +2 -2
  64. package/dist/telemetry/scrub.js +5 -5
  65. package/dist/telemetry/scrub.js.map +1 -1
  66. package/dist/telemetry/sink.d.ts +3 -0
  67. package/dist/telemetry/sink.js +14 -0
  68. package/dist/telemetry/sink.js.map +1 -1
  69. package/package.json +1 -1
package/README.md CHANGED
@@ -91,6 +91,7 @@ Review a PR, a local diff, or uncommitted work.
91
91
  | `--expect-head-sha <sha>` | Fail fast unless the resolved head commit equals this SHA |
92
92
  | `--spec-source <source>` | Where `--spec` came from: `flag`, `repo_file`, or `harness_issue:<ID>` |
93
93
  | `--converge-target <key>` / `--round <n>` / `--attempt <n>` | Converge context recorded in the report (or `RCL_CONVERGE_TARGET` / `_ROUND` / `_ATTEMPT`) |
94
+ | `--start-over` | Explicitly start a fresh PR review with a new normal budget; preserve all prior spending and evidence |
94
95
  | `--guarded-converge` | Validate and claim inside this process; derive the next round from native admitted state |
95
96
  | `--launch-intent <intent>` | Guarded intent: `review` (default), `stop-upstream`, `stop-review`, or `retry-delivery` |
96
97
  | `--retry-reason <reason>` | Explicit bounded recovery decision for a failed/unknown launch; preserves spent attempts |
@@ -165,6 +166,20 @@ rcl roles show <name> # Show system prompt and details for a role
165
166
 
166
167
  ---
167
168
 
169
+ ### Start a fresh review
170
+
171
+ ```bash
172
+ rcl review owner/repo#123 --start-over
173
+ ```
174
+
175
+ This reviews the full current inputs again, even on an unchanged head or after an exhausted previous cycle. RCL archives the original native state and spending, creates a Harness review cycle, and starts at attempt 1 / round 1 with the normal 20-attempt / 15-round budget. Previous approvals, dismissals and reviewer responses stay historical. Explicit `--max-attempts` / `--max-rounds` select different limits for the new cycle; old overrides are not inherited.
176
+
177
+ The command enables guarded launch and chooses private JSON/Markdown paths under the Git common directory when omitted. It prints the cycle and cumulative prior spending. Keep the files in place. A captured patch can use the same flag with `--for-pr owner/repo#123 --head-sha <captured-head>`. Full Harness evidence and a user/API actor credential are required; unbound local and attested starts are refused.
178
+
179
+ An unfinished start resumes with the same command and operation, keeping spent claims. A durable terminal dispatch record marks completion, including a recorded failure. If the command output or acknowledgement is lost or uncertain, inspect the native current operation and retained launch/report before retrying; reuse a completed result instead of blindly repeating `--start-over`. An invocation after terminal completion represents a new request; identical command text cannot distinguish an acknowledgement retry from a later deliberate fresh request. No user-supplied operation ID or additional confirmation is required. Ordinary `rcl review owner/repo#123` discovers the local cycle and continues its existing budget; it does not replenish it. A concurrent fresh request cannot silently create another cycle after waiting for the first.
180
+
181
+ Stop an active review through its recorded host task before replacing it. Late reports and verdicts remain bound to their original cycle. After checking reviewer health and exact-head freshness, admit the report with `converge-report`; use `converge-verdict --run-id <report-run-id>` for triage in a fresh cycle. All native, enforced and CI merge gates still apply. To complete missing reviewers while preserving successful work, use the supported recovery workflow instead of starting over.
182
+
168
183
  ### Guarded convergence launches
169
184
 
170
185
  ```bash
@@ -0,0 +1,9 @@
1
+ import type { Finding } from './types.js';
2
+ import type { ClaimDescriptor } from '../evidence/claim-recovery/validation/claims.js';
3
+ type ClaimText = Pick<Finding, 'file' | 'title' | 'description' | 'suggestedFix'>;
4
+ /** Every accepted sentence is parsed; no subset of a compound claim qualifies. */
5
+ export declare function describeContract(f: ClaimText): ClaimDescriptor | undefined;
6
+ export declare function isContractDescriptor(descriptor: ClaimDescriptor): boolean;
7
+ /** Applies only to explicitly generated contracts, never to retained free text. */
8
+ export declare function compareContractDescriptors(a: ClaimDescriptor, b: ClaimDescriptor): 'exact_descriptor' | 'supported_paraphrase' | undefined;
9
+ export {};
@@ -0,0 +1,307 @@
1
+ const PREFIX = 'rcl-claim-contract-v1:';
2
+ const CONTRACTS = {
3
+ 'embedded-authentication-key': {
4
+ fields: ['authentication purpose', 'constant binding', 'configuration binding'],
5
+ condition: 'The named authentication key is embedded in source instead of loaded from external configuration.',
6
+ },
7
+ 'sql-value-interpolation': {
8
+ fields: ['operation binding', 'query action', 'table', 'column', 'operator', 'input binding'],
9
+ condition: 'The named input value is interpolated into SQL syntax instead of passed as a bound parameter.',
10
+ },
11
+ 'absent-owner-authorization': {
12
+ fields: ['principal', 'action', 'resource', 'required owner condition'],
13
+ condition: 'Authentication alone permits deletion of another user resource without checking ownership.',
14
+ },
15
+ 'unbounded-collection-materialization': {
16
+ fields: ['collection', 'read operation', 'absent bound'],
17
+ condition: 'Reading the named collection materializes all records without a row bound.',
18
+ },
19
+ 'label-derived-privilege': {
20
+ fields: ['guard binding', 'principal field', 'comparison operator', 'comparison literal', 'required condition'],
21
+ condition: 'The privilege decision compares the principal username to a label instead of checking role membership.',
22
+ },
23
+ 'unguarded-map-field-access': {
24
+ fields: ['helper', 'receiver', 'field', 'guarded sibling fields', 'access operation', 'failure condition'],
25
+ condition: 'Strict map field access without a presence guard raises KeyError when the named key is absent.',
26
+ },
27
+ };
28
+ function evidence(kind, subject) {
29
+ return CONTRACTS[kind].fields.map((label, index) => `${label}: ${subject[index]}`).join('; ');
30
+ }
31
+ const text = (value) => value.trim().replace(/\s+/g, ' ').replace(/[.!]$/, '');
32
+ const sentences = (value) => value.trim().split(/(?<=[.!?])\s+(?=[A-Z])/).map(text);
33
+ /**
34
+ * Bounded grammar productions for the same key-externalization predicate.
35
+ * Referents are local to the parsed key. Neither arbitrary explanatory prose
36
+ * nor a consequence involving another token purpose is thrown away.
37
+ */
38
+ function keyExternalizationClause(sentence, purpose) {
39
+ const key = '(?:this(?: (?:key|secret))?|(?:the )?(?:key|secret)s?)';
40
+ const source = '(?:source(?: code)?|code)';
41
+ const external = '(?:(?:an? )?environment variables?|external configuration|a secrets manager)';
42
+ const destination = `${external}(?: or ${external})?`;
43
+ const motivation = '(?: to prevent secret exposure in source control)?';
44
+ const modal = '(?:must|should)';
45
+ const externalize = new RegExp(`^${key} ${modal} (?:never be hardcoded in ${source} and ${modal} )?be loaded from ${destination}${motivation}$`, 'i');
46
+ const imperative = new RegExp(`^(?:load ${key} from ${destination}|use ${destination})$`, 'i');
47
+ if (externalize.test(sentence) || imperative.test(sentence))
48
+ return true;
49
+ const actor = '(?:anyone who reads the source code|an attacker with source access)';
50
+ const capability = '(?:can|could|may)';
51
+ const tokens = `(?:${purpose}s|${purpose} tokens)`;
52
+ const forge = new RegExp(`^${actor} ${capability} (?:forge|create) valid ${tokens} and impersonate (?:any user|users)$`, 'i');
53
+ return forge.test(sentence);
54
+ }
55
+ function hardcodedKey(f) {
56
+ const title = /^Hardcoded (?:([A-Z][A-Z0-9_]*) )?secret(?: key)?(?: in ([A-Z][A-Z0-9_]*) verification| exposes authentication to attack)?$/.exec(text(f.title));
57
+ if (!title)
58
+ return;
59
+ const purpose = title[1] ?? title[2];
60
+ if (!purpose)
61
+ return;
62
+ const binding = /^const ([A-Za-z_$][\w$]*) = process\.env\.([A-Za-z_$][\w$]*)/.exec((f.suggestedFix ?? '').trim());
63
+ if (!binding)
64
+ return;
65
+ const parts = sentences(f.description);
66
+ const literal = /^A hardcoded secret ('[^']*'|"[^"]*") is used for ([A-Z][A-Z0-9_]*) signing and verification$/.exec(parts[0] ?? '');
67
+ const staticKey = /^The ([A-Z][A-Z0-9_]*)(?: signing)? (?:secret|key) (?:is|was|remains) (?:hardcoded (?:as a string literal|in (?:the )?(?:source|code))|a hardcoded string constant)$/.exec(parts[0] ?? '');
68
+ if ((literal?.[2] ?? staticKey?.[1]) !== purpose)
69
+ return;
70
+ if (!parts.slice(1).every(part => keyExternalizationClause(part, purpose)))
71
+ return;
72
+ return { kind: 'embedded-authentication-key', subject: [`${purpose} signing and verification`, binding[1], binding[2]],
73
+ condition: 'The named authentication key is embedded in source instead of loaded from external configuration.',
74
+ // The original finding retains its source observation. A fresh semantic
75
+ // descriptor needs only its presence, never another copy of a credential.
76
+ observations: literal ? ['Source literal observed'] : [] };
77
+ }
78
+ /** Accept a direct query argument or an immediately used immutable binding. */
79
+ function sqlParameter(fix, query) {
80
+ const before = fix.slice(0, query.index);
81
+ const after = fix.slice(query.index + query[0].length);
82
+ const call = '[A-Za-z_$][\\w$]*(?:\\.[A-Za-z_$][\\w$]*)*';
83
+ if (before.trimEnd().endsWith('(')) {
84
+ return /^\s*,\s*\[\s*([A-Za-z_$][\w$]*)\s*\]\s*\)/.exec(after)?.[1];
85
+ }
86
+ const statement = before.slice(Math.max(before.lastIndexOf(';'), before.lastIndexOf('\n')) + 1);
87
+ const declaration = /^\s*const ([A-Za-z_$][\w$]*)\s*=\s*$/.exec(statement);
88
+ if (!declaration)
89
+ return;
90
+ const binding = declaration[1].replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
91
+ return new RegExp(`^\\s*;\\s*(?:return\\s+|await\\s+)?${call}\\(\\s*${binding}\\s*,\\s*\\[\\s*([A-Za-z_$][\\w$]*)\\s*\\]\\s*\\)(?:\\s*;)?$`).exec(after.trimEnd())?.[1];
92
+ }
93
+ function sqlInterpolation(f) {
94
+ const title = /^SQL injection(?: vulnerability)? (?:in|via string interpolation in) ([A-Za-z_$][\w$]*|DELETE endpoint)$/i.exec(text(f.title));
95
+ if (!title)
96
+ return;
97
+ const fix = f.suggestedFix ?? '';
98
+ const queries = [...fix.matchAll(/(['"])(SELECT \* FROM ([A-Za-z_][\w]*) WHERE ([A-Za-z_][\w]*) = (?:\$1|\?)|DELETE FROM ([A-Za-z_][\w]*) WHERE ([A-Za-z_][\w]*) = \$1)\1/gi)];
99
+ if (queries.length !== 1)
100
+ return;
101
+ const query = queries[0];
102
+ const parameter = sqlParameter(fix, query);
103
+ if (!parameter)
104
+ return;
105
+ const operation = query[3] ? 'SELECT' : 'DELETE';
106
+ const table = query[3] ?? query[5];
107
+ const column = query[4] ?? query[6];
108
+ const parts = sentences(f.description);
109
+ const core = /^(User-controlled input|The ([A-Za-z_$][\w$]*) parameter|The user ID from the URL parameter|The ([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+) value) is (?:directly )?interpolated(?: directly)? into (?:a |the )?(SQL query string|SQL string|SQL query|SQL DELETE statement)(?: without sanitization(?: or parameterization)?)?$/i.exec(parts[0] ?? '');
110
+ if (!core)
111
+ return;
112
+ const namedSource = core[2] ?? core[3]?.split('.').at(-1);
113
+ if (namedSource && namedSource !== parameter)
114
+ return;
115
+ if (core[4]?.toUpperCase() === 'SQL DELETE STATEMENT' && operation !== 'DELETE')
116
+ return;
117
+ if (title[1]?.toUpperCase() === 'DELETE ENDPOINT' && operation !== 'DELETE')
118
+ return;
119
+ if (!parts.slice(1).every(part => /^This (?:is a (?:classic|textbook) SQL injection vulnerability|allows SQL injection attacks)$/i.test(part)))
120
+ return;
121
+ return { kind: 'sql-value-interpolation', subject: [title[1], operation, table, column, '=', parameter],
122
+ condition: 'The named input value is interpolated into SQL syntax instead of passed as a bound parameter.', observations: [] };
123
+ }
124
+ function ownerAuthorization(f) {
125
+ const title = /^(?:IDOR: missing authorization on DELETE endpoint|Missing authorization check allows any user to delete any ([a-z]+)|IDOR vulnerability [—–-] missing ownership check on delete)$/i.exec(text(f.title));
126
+ if (!title)
127
+ return;
128
+ const parts = sentences(f.description);
129
+ let deletion = false;
130
+ let ownership = false;
131
+ let resource;
132
+ // A named route is supported only when it names this file's resource and
133
+ // its sole parameter is the target id. Other routes/conditions stay raw.
134
+ const fileResource = f.file.split('/').at(-1)?.replace(/\.[^.]+$/, '').toLowerCase();
135
+ for (const part of parts) {
136
+ const effect = /^Any authenticated user can delete any (?:other user's ([a-z]+)(?: by supplying a different user ID in the URL)?|([a-z]+) including other users' ([a-z]+)s)$/i.exec(part);
137
+ if (effect) {
138
+ const target = (effect[1] ?? effect[3]).toLowerCase();
139
+ const namedResource = effect[2]?.toLowerCase();
140
+ if (resource && resource !== target ||
141
+ namedResource && namedResource !== target && namedResource !== `${target}s`)
142
+ return;
143
+ resource = target;
144
+ deletion = true;
145
+ }
146
+ else if (/^The endpoint only (?:checks|validates) authentication but not authorization$/i.test(part)) {
147
+ ownership = true;
148
+ }
149
+ else {
150
+ const route = /^The DELETE \/([A-Za-z_][\w]*)\/:id endpoint only (?:checks|validates) authentication but not authorization$/i.exec(part);
151
+ if (route) {
152
+ if (route[1].toLowerCase() !== fileResource)
153
+ return;
154
+ ownership = true;
155
+ continue;
156
+ }
157
+ const owner = /^There is no check that the requesting user owns the ([a-z]+) being deleted$/i.exec(part);
158
+ const ownerResource = owner?.[1]?.toLowerCase();
159
+ if (!ownerResource || resource && resource !== ownerResource)
160
+ return;
161
+ resource = ownerResource;
162
+ ownership = true;
163
+ }
164
+ }
165
+ if (!deletion || !ownership || !resource || title[1] && title[1].toLowerCase() !== resource)
166
+ return;
167
+ return { kind: 'absent-owner-authorization', subject: ['authenticated user', 'DELETE', resource, 'requesting user owns target resource'],
168
+ condition: 'Authentication alone permits deletion of another user resource without checking ownership.', observations: [] };
169
+ }
170
+ function unboundedCollection(f) {
171
+ const title = /^(?:Missing pagination on ([A-Za-z_][\w]*) listing endpoint|Unbounded query on ([A-Za-z_][\w]*) table)$/i.exec(text(f.title));
172
+ if (!title)
173
+ return;
174
+ const parts = sentences(f.description);
175
+ const route = /^The GET \/([A-Za-z_][\w]*) endpoint fetches all ([A-Za-z_][\w]*) without pagination$/i.exec(parts[0] ?? '');
176
+ const sql = /^SELECT \* FROM ([A-Za-z_][\w]*) without a LIMIT clause will return all records$/i.exec(parts[0] ?? '');
177
+ if (!route && !sql)
178
+ return;
179
+ if (route && route[1].toLowerCase() !== route[2].toLowerCase())
180
+ return;
181
+ const resource = (route?.[1] ?? sql[1]).toLowerCase();
182
+ if ((title[1] ?? title[2]).toLowerCase().replace(/s$/, '') !== resource.replace(/s$/, ''))
183
+ return;
184
+ if (!parts.slice(1).every(part => /^(?:This will cause performance issues and excessive memory usage at scale|Add pagination to prevent memory exhaustion)$/i.test(part)))
185
+ return;
186
+ return { kind: 'unbounded-collection-materialization', subject: [resource, 'read all records', 'no pagination or row limit'],
187
+ condition: 'Reading the named collection materializes all records without a row bound.', observations: [] };
188
+ }
189
+ function labelPrivilege(f) {
190
+ const title = /^(?:([a-z]+) check uses username instead of role|Broken ([a-z]+) role check [—–-] compares username instead of role)$/i.exec(text(f.title));
191
+ if (!title)
192
+ return;
193
+ const parts = sentences(f.description);
194
+ const core = /^The ([A-Za-z_$][\w$]*) (?:middleware|guard) checks (?:if )?([A-Za-z_$][\w$]*\.[A-Za-z_$][\w$]*) === ('[^']*'|"[^"]*") (?:instead of checking a proper role field|which is a broken security check)$/i.exec(parts[0] ?? '');
195
+ if (!core)
196
+ return;
197
+ if (core[2].split('.').at(-1)?.toLowerCase() !== 'username')
198
+ return;
199
+ const literal = core[3].slice(1, -1);
200
+ if ((title[1] ?? title[2]).toLowerCase() !== literal.toLowerCase())
201
+ return;
202
+ // The effects and remedy must refer to the same privilege named in the
203
+ // compared literal. Other principals, conditions, or extra duties refuse.
204
+ const escaped = literal.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
205
+ const effects = new RegExp(`^Any user named ['"]${escaped}['"] would get ${escaped} access, and actual ${escaped}s with different usernames would be denied$`, 'i');
206
+ const flag = `is${literal[0].toUpperCase()}${literal.slice(1)}`;
207
+ if (!parts.slice(1).every(part => effects.test(part) || part === `It should verify a roles array or an ${flag} flag`))
208
+ return;
209
+ return { kind: 'label-derived-privilege', subject: [core[1], core[2], '===', literal, 'role membership'],
210
+ condition: 'The privilege decision compares the principal username to a label instead of checking role membership.', observations: [] };
211
+ }
212
+ /** Parse the access and its absent-key precondition, including contextual guards. */
213
+ function mapFieldAccess(f) {
214
+ if ((f.description.match(/`/g) ?? []).length % 2 !== 0)
215
+ return;
216
+ const parts = sentences(f.description.replaceAll('`', ''));
217
+ if (parts.length !== 2)
218
+ return;
219
+ const first = /^([A-Za-z_][\w]*(?:\/\d+)?) safely Map\.gets :([A-Za-z_][\w]*) \/ :([A-Za-z_][\w]*), then uses ([A-Za-z_][\w]*)\.([A-Za-z_][\w]*) with dot access for the final fallback$/.exec(parts[0]) ??
220
+ /^([A-Za-z_][\w]*(?:\/\d+)?) retrieves :([A-Za-z_][\w]*) and :([A-Za-z_][\w]*) defensively using Map\.get\/2, but accesses ([A-Za-z_][\w]*)\.([A-Za-z_][\w]*) using dot syntax$/.exec(parts[0]);
221
+ if (!first)
222
+ return;
223
+ const [, helper, guardedA, guardedB, receiver, field] = first;
224
+ const title = /^([A-Za-z_][\w]*(?:\/\d+)?) mixes Map\.get with hard field access$/.exec(text(f.title));
225
+ const accessTitle = /^Unsafe map field access ([A-Za-z_][\w]*)\.([A-Za-z_][\w]*) in ([A-Za-z_][\w]*(?:\/\d+)?)$/.exec(text(f.title));
226
+ if (!title && !accessTitle)
227
+ return;
228
+ const titleHelper = title?.[1] ?? accessTitle[3];
229
+ if (titleHelper.includes('/') ? titleHelper !== helper : titleHelper !== helper.split('/')[0])
230
+ return;
231
+ if (accessTitle && (accessTitle[1] !== receiver || accessTitle[2] !== field))
232
+ return;
233
+ const absent = /^A bare ([A-Za-z_][\w]*) map missing :([A-Za-z_][\w]*) will raise KeyError(?: despite the comment that this ("[^"]+"|“[^”]+”))?$/.exec(parts[1]);
234
+ const conditional = /^If ([A-Za-z_][\w]*) is missing the :([A-Za-z_][\w]*) key, evaluating ([A-Za-z_][\w]*)\.([A-Za-z_][\w]*) will raise a KeyError$/.exec(parts[1]);
235
+ if (!absent && !conditional)
236
+ return;
237
+ if ((absent?.[1] ?? conditional[1]) !== receiver || (absent?.[2] ?? conditional[2]) !== field)
238
+ return;
239
+ if (conditional && (conditional[3] !== receiver || conditional[4] !== field))
240
+ return;
241
+ return { kind: 'unguarded-map-field-access', subject: [helper, receiver, field, [guardedA, guardedB].sort().join(','), 'strict dot access', 'absent key raises KeyError'],
242
+ condition: CONTRACTS['unguarded-map-field-access'].condition,
243
+ // A quoted comment is attributed context, not an asserted extra runtime
244
+ // obligation. Preserve it verbatim in its immutable sighting descriptor.
245
+ observations: absent?.[3] ? [`Quoted comment observed: ${absent[3]}`] : [] };
246
+ }
247
+ /** Every accepted sentence is parsed; no subset of a compound claim qualifies. */
248
+ export function describeContract(f) {
249
+ const contract = hardcodedKey(f) ?? sqlInterpolation(f) ?? ownerAuthorization(f) ?? unboundedCollection(f) ?? labelPrivilege(f) ?? mapFieldAccess(f);
250
+ if (!contract)
251
+ return;
252
+ const descriptor = {
253
+ version: 1,
254
+ operation: `${f.file} :: ${PREFIX}${JSON.stringify([contract.kind, contract.subject])}`,
255
+ invariant: contract.condition,
256
+ evidence: [evidence(contract.kind, contract.subject), ...contract.observations],
257
+ };
258
+ if ([descriptor.operation, descriptor.invariant, ...descriptor.evidence].some(value => [...value].length > 500))
259
+ return;
260
+ return descriptor;
261
+ }
262
+ export function isContractDescriptor(descriptor) {
263
+ return descriptor.operation.includes(` :: ${PREFIX}`);
264
+ }
265
+ /** Applies only to explicitly generated contracts, never to retained free text. */
266
+ export function compareContractDescriptors(a, b) {
267
+ const parse = (descriptor) => {
268
+ const marker = descriptor.operation.indexOf(` :: ${PREFIX}`);
269
+ if (marker <= 0)
270
+ return;
271
+ try {
272
+ const parsed = JSON.parse(descriptor.operation.slice(marker + ` :: ${PREFIX}`.length));
273
+ if (!Array.isArray(parsed) || parsed.length !== 2 || typeof parsed[0] !== 'string' ||
274
+ !Object.hasOwn(CONTRACTS, parsed[0]) || !Array.isArray(parsed[1]))
275
+ return;
276
+ const kind = parsed[0];
277
+ const subject = parsed[1];
278
+ if (subject.length !== CONTRACTS[kind].fields.length || !subject.every(value => typeof value === 'string' && value.length > 0))
279
+ return;
280
+ if (descriptor.invariant !== CONTRACTS[kind].condition || descriptor.evidence[0] !== evidence(kind, subject))
281
+ return;
282
+ if (!['embedded-authentication-key', 'unguarded-map-field-access'].includes(kind) && descriptor.evidence.length !== 1)
283
+ return;
284
+ if (descriptor.evidence.length > 2)
285
+ return;
286
+ return kind;
287
+ }
288
+ catch {
289
+ return;
290
+ }
291
+ };
292
+ const kind = parse(a);
293
+ if (!kind || parse(b) !== kind)
294
+ return;
295
+ if (a.operation !== b.operation || a.invariant !== b.invariant || a.evidence[0] !== b.evidence[0])
296
+ return;
297
+ // Embedding any literal violates this contract. New descriptors retain only
298
+ // the observation's presence; historical literal observations stay readable
299
+ // and are compared without rewriting their immutable descriptor bytes.
300
+ const observation = kind === 'unguarded-map-field-access'
301
+ ? /^Quoted comment observed: ("[^"]+"|“[^”]+”)$/
302
+ : /^Source literal observed(?:: ('[^']*'|"[^"]*"))?$/;
303
+ if (![...a.evidence.slice(1), ...b.evidence.slice(1)].every(value => observation.test(value)))
304
+ return;
305
+ return JSON.stringify(a.evidence) === JSON.stringify(b.evidence) ? 'exact_descriptor' : 'supported_paraphrase';
306
+ }
307
+ //# sourceMappingURL=claim-contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claim-contract.js","sourceRoot":"","sources":["../../src/consensus/claim-contract.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { Finding } from './types.js';
2
+ import { type ClaimDescriptor } from '../evidence/claim-recovery/validation/claims.js';
3
+ export { claimDescriptorSchema, descriptorKey, semanticFindingKey, compareClaims } from '../evidence/claim-recovery/validation/claims.js';
4
+ export type { ClaimDescriptor, MatchRationale } from '../evidence/claim-recovery/validation/claims.js';
5
+ export declare function describeClaim(finding: Pick<Finding, 'file' | 'title' | 'description' | 'suggestedFix'>): ClaimDescriptor;
@@ -0,0 +1,39 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { normalizeGeneratedText, scrubText } from '../telemetry/scrub.js';
3
+ import { claimDescriptorSchema } from '../evidence/claim-recovery/validation/claims.js';
4
+ import { describeContract } from './claim-contract.js';
5
+ export { claimDescriptorSchema, descriptorKey, semanticFindingKey, compareClaims } from '../evidence/claim-recovery/validation/claims.js';
6
+ /** Do this before report serialization, never while replaying an original. */
7
+ function clean(s) {
8
+ return normalizeGeneratedText(s).trim();
9
+ }
10
+ function bounded(s) {
11
+ const value = clean(scrubText(clean(s), Math.max(500, [...s].length + 1)));
12
+ const points = [...value];
13
+ if (points.length <= 500)
14
+ return value;
15
+ // Retain a commitment to the omitted text. Two long claims must not become
16
+ // identical merely because their first 500 code points agree.
17
+ const suffix = ` [sha256:${createHash('sha256').update(value).digest('hex')}]`;
18
+ return points.slice(0, 500 - suffix.length).join('') + suffix;
19
+ }
20
+ function anchors(s) {
21
+ return [...new Set([...s.matchAll(/`([^`]+)`|\b([A-Za-z_$][\w.$]*(?:\/\d+|(?=\()))/g)]
22
+ .map(m => m[1] ?? m[2]))].sort();
23
+ }
24
+ export function describeClaim(finding) {
25
+ const contract = describeContract(finding);
26
+ if (contract)
27
+ return claimDescriptorSchema.parse({ ...contract, operation: bounded(contract.operation),
28
+ invariant: bounded(contract.invariant), evidence: contract.evidence.map(bounded) });
29
+ const title = bounded(finding.title);
30
+ const description = bounded(finding.description);
31
+ // Bound regex work before extracting anchors, retaining the omitted-text
32
+ // commitment so long claims with different endings cannot collapse.
33
+ const symbols = anchors(`${title} ${description}`);
34
+ const operation = symbols.length ? `${finding.file} :: ${symbols.join(', ')}` : `${finding.file} :: ${title}`;
35
+ const invariant = description || title || '[insufficient-evidence: no reviewer invariant]';
36
+ const evidence = [...new Set([title, description, bounded(finding.suggestedFix ?? '')].filter(Boolean))];
37
+ return claimDescriptorSchema.parse({ version: 1, operation: bounded(operation), invariant, evidence: evidence.length ? evidence : [invariant] });
38
+ }
39
+ //# sourceMappingURL=claim-identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claim-identity.js","sourceRoot":"","sources":["../../src/consensus/claim-identity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,sBAAsB,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAE,qBAAqB,EAAwB,MAAM,iDAAiD,CAAC;AAC9G,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,iDAAiD,CAAC;AAG1I,8EAA8E;AAC9E,SAAS,KAAK,CAAC,CAAS;IACtB,OAAO,sBAAsB,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAC1C,CAAC;AACD,SAAS,OAAO,CAAC,CAAS;IACxB,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3E,MAAM,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;IAC1B,IAAI,MAAM,CAAC,MAAM,IAAI,GAAG;QAAE,OAAO,KAAK,CAAC;IACvC,2EAA2E;IAC3E,8DAA8D;IAC9D,MAAM,MAAM,GAAG,YAAY,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC;IAC/E,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC;AAChE,CAAC;AACD,SAAS,OAAO,CAAC,CAAS;IACxB,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,kDAAkD,CAAC,CAAC;aACnF,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AACtC,CAAC;AACD,MAAM,UAAU,aAAa,CAAC,OAAyE;IACrG,MAAM,QAAQ,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC3C,IAAI,QAAQ;QAAE,OAAO,qBAAqB,CAAC,KAAK,CAAC,EAAE,GAAG,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;YACpG,SAAS,EAAE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACtF,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACrC,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACjD,yEAAyE;IACzE,oEAAoE;IACpE,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,KAAK,IAAI,WAAW,EAAE,CAAC,CAAC;IACnD,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,OAAO,KAAK,EAAE,CAAC;IAC9G,MAAM,SAAS,GAAG,WAAW,IAAI,KAAK,IAAI,gDAAgD,CAAC;IAC3F,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACzG,OAAO,qBAAqB,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;AACnJ,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { ModelReview, DeduplicatedGroup } from './types.js';
2
+ /**
3
+ * Location selects candidates; only positive, complete claim evidence can merge
4
+ * them. Lexical/taxonomy scores remain available for diagnostics and disputes,
5
+ * but cannot discard an allegation or lend it another reviewer's agreement.
6
+ *
7
+ * Keep the positional threshold arguments compatible with existing callers.
8
+ * The consensus threshold still applies in the voter/reporting stage.
9
+ */
10
+ export declare function deduplicateSemanticFindings(reviews: ModelReview[], _jaccardThreshold?: number, lineWindow?: number, _minConsensusScore?: number): DeduplicatedGroup[];
@@ -0,0 +1,70 @@
1
+ import { DEFAULT_THRESHOLDS } from '../config/defaults.js';
2
+ import { compareClaims, describeClaim } from './claim-identity.js';
3
+ import { linesOverlap } from './deduper.js';
4
+ // The producer selects this path only after validating recovered native proof.
5
+ // Ordinary reviews retain the legacy deduper and its configuration semantics.
6
+ const SEVERITY_ORDER = { critical: 0, important: 1, minor: 2, nitpick: 3 };
7
+ function compareMembers(a, b) {
8
+ const severity = SEVERITY_ORDER[a.finding.severity] - SEVERITY_ORDER[b.finding.severity];
9
+ if (severity !== 0)
10
+ return severity;
11
+ const detail = b.finding.description.length - a.finding.description.length;
12
+ if (detail !== 0)
13
+ return detail;
14
+ const location = a.finding.file.localeCompare(b.finding.file) ||
15
+ a.finding.startLine - b.finding.startLine || a.finding.endLine - b.finding.endLine;
16
+ if (location !== 0)
17
+ return location;
18
+ const key = (member) => {
19
+ const f = member.finding;
20
+ const p = f.locationProvenance;
21
+ return JSON.stringify([f.file, f.startLine, f.endLine, f.title, f.description, f.suggestedFix,
22
+ f.id, f.severity, f.category, p ? [p.version, p.source, p.reason, p.originalStartLine, p.originalEndLine] : null,
23
+ member.model, member.role]);
24
+ };
25
+ const left = key(a);
26
+ const right = key(b);
27
+ return left < right ? -1 : left > right ? 1 : 0;
28
+ }
29
+ /**
30
+ * Location selects candidates; only positive, complete claim evidence can merge
31
+ * them. Lexical/taxonomy scores remain available for diagnostics and disputes,
32
+ * but cannot discard an allegation or lend it another reviewer's agreement.
33
+ *
34
+ * Keep the positional threshold arguments compatible with existing callers.
35
+ * The consensus threshold still applies in the voter/reporting stage.
36
+ */
37
+ export function deduplicateSemanticFindings(reviews, _jaccardThreshold = DEFAULT_THRESHOLDS.jaccardThreshold, lineWindow = DEFAULT_THRESHOLDS.dedupeLineWindow, _minConsensusScore = DEFAULT_THRESHOLDS.minConsensusScore) {
38
+ const members = reviews.filter(review => review.status === 'success')
39
+ .flatMap(review => review.findings.map(finding => ({ finding, model: review.model, role: review.role })))
40
+ .sort(compareMembers);
41
+ const descriptors = new Map(members.map(member => [member.finding, describeClaim(member.finding)]));
42
+ const groups = [];
43
+ for (const member of members) {
44
+ const matches = groups.filter(group => group.every(other => member.finding.file === other.finding.file &&
45
+ linesOverlap(member.finding, other.finding, lineWindow) &&
46
+ compareClaims(descriptors.get(member.finding), descriptors.get(other.finding)) !== undefined));
47
+ // Ambiguous attachment must not choose a neighbor by incidental input order.
48
+ if (matches.length === 1)
49
+ matches[0].push(member);
50
+ else
51
+ groups.push([member]);
52
+ }
53
+ const result = groups.map(group => {
54
+ const distinct = new Map();
55
+ for (const member of group) {
56
+ const key = JSON.stringify([member.model, member.role]);
57
+ // Members are sorted by severity/detail/stable content. Count one vote
58
+ // from a reviewer without dropping a stronger version of this claim.
59
+ if (!distinct.has(key))
60
+ distinct.set(key, member);
61
+ }
62
+ return { representative: group[0].finding, members: [...distinct.values()] };
63
+ });
64
+ return result.sort((a, b) => SEVERITY_ORDER[a.representative.severity] - SEVERITY_ORDER[b.representative.severity] ||
65
+ a.representative.file.localeCompare(b.representative.file) ||
66
+ a.representative.startLine - b.representative.startLine ||
67
+ a.representative.title.localeCompare(b.representative.title) ||
68
+ a.representative.id.localeCompare(b.representative.id));
69
+ }
70
+ //# sourceMappingURL=semantic-deduper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semantic-deduper.js","sourceRoot":"","sources":["../../src/consensus/semantic-deduper.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import type { ClaimDescriptor } from './claim-identity.js';
1
2
  /** The original numeric interval when a model returned its endpoints reversed. */
2
3
  export interface LocationProvenance {
3
4
  version: 1;
@@ -115,6 +116,8 @@ export interface ConsensusInfo {
115
116
  modelWeights?: Record<string, number>;
116
117
  }
117
118
  export interface ConsensusFinding extends Finding {
119
+ /** Materialized before hashing the original report. Never added during replay. */
120
+ claimDescriptor?: ClaimDescriptor;
118
121
  consensus: ConsensusInfo;
119
122
  /**
120
123
  * Report-sighting key, allocated uniquely across kept and appendix groups.
@@ -1,6 +1,6 @@
1
1
  import { type NativeTargetOwnership } from './target-ownership.js';
2
+ import { type NativeReviewCycle } from './review-cycle.js';
2
3
  export declare const DEFAULT_CONVERGE_ATTEMPT_CAP = 20;
3
- declare const STATE_VERSION = 2;
4
4
  export interface ConvergeAttemptRecord {
5
5
  attempt: number;
6
6
  claimedAt: string;
@@ -8,7 +8,8 @@ export interface ConvergeAttemptRecord {
8
8
  source: 'claim';
9
9
  }
10
10
  export interface ConvergeAttemptState {
11
- version: typeof STATE_VERSION;
11
+ version: 2 | 3;
12
+ cycle?: NativeReviewCycle;
12
13
  target: string;
13
14
  cap: number;
14
15
  migratedAttempts: number;
@@ -23,6 +24,7 @@ export interface ConvergeAttemptClaim {
23
24
  cap: number;
24
25
  stateFile: string;
25
26
  warning?: string;
27
+ cycle?: NativeReviewCycle;
26
28
  }
27
29
  export declare class ConvergeAttemptBudgetExceededError extends Error {
28
30
  readonly target: string;
@@ -54,7 +56,10 @@ interface ClaimOptions {
54
56
  targetLockRetryMs?: number;
55
57
  beforeClaim?: (ownership: NativeTargetOwnership) => Promise<void>;
56
58
  afterClaim?: (claim: ConvergeAttemptClaim, ownership: NativeTargetOwnership) => Promise<void>;
59
+ ownership?: NativeTargetOwnership;
60
+ freshReviewOperation?: string;
57
61
  }
62
+ export declare function validateAttemptCap(maxAttempts: number): number;
58
63
  export declare function convergeAttemptStatePath(gitCommonDir: string, target: string): string;
59
64
  export declare function validateConvergeAttemptState(value: unknown, expectedTarget: string, stateFile: string): ConvergeAttemptState;
60
65
  /**