@fro.bot/systematic 3.18.2 → 3.18.4

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.
@@ -2,6 +2,7 @@
2
2
  // biome-ignore-all assist/source/organizeImports: generated bundle
3
3
  // Generated by scripts/generate-ce-review-validator.ts; do not edit by hand.
4
4
  // Run `bun run ce-review-validator:build` to regenerate.
5
+ // source-digest: e190da64b4101889aec4ed850be0ef1316410ad46f7f05c626071592bad384b9
5
6
  var __defProp = Object.defineProperty
6
7
  var __returnValue = (v) => v
7
8
  function __exportSetter(name, newValue) {
@@ -172,7 +173,7 @@ function resolveReviewArtifactPath(input, cwd, options = {}) {
172
173
  return { ok: true, path: canonicalTarget }
173
174
  }
174
175
 
175
- // node_modules/.bun/zod@4.6.1/node_modules/zod/v4/core/util.js
176
+ // node_modules/.bun/zod@4.6.2/node_modules/zod/v4/core/util.js
176
177
  function getEnumValues(entries) {
177
178
  const numericValues = Object.values(entries).filter(
178
179
  (v) => typeof v === 'number',
@@ -796,7 +797,7 @@ function constantCatch(value) {
796
797
  return fn
797
798
  }
798
799
 
799
- // node_modules/.bun/zod@4.6.1/node_modules/zod/v4/core/core.js
800
+ // node_modules/.bun/zod@4.6.2/node_modules/zod/v4/core/core.js
800
801
  var _a
801
802
  var _zodDesc = { value: undefined, enumerable: false }
802
803
  var _E = 'captureStackTrace' in Error ? Error : null
@@ -911,7 +912,7 @@ function config(newConfig) {
911
912
  if (newConfig) Object.assign(globalConfig, newConfig)
912
913
  return globalConfig
913
914
  }
914
- // node_modules/.bun/zod@4.6.1/node_modules/zod/v4/core/errors.js
915
+ // node_modules/.bun/zod@4.6.2/node_modules/zod/v4/core/errors.js
915
916
  function _getMessage() {
916
917
  const internals = this._zod
917
918
  internals.message ??
@@ -1048,7 +1049,7 @@ function formatError(error, mapper = (issue) => issue.message) {
1048
1049
  return fieldErrors
1049
1050
  }
1050
1051
 
1051
- // node_modules/.bun/zod@4.6.1/node_modules/zod/v4/core/parse.js
1052
+ // node_modules/.bun/zod@4.6.2/node_modules/zod/v4/core/parse.js
1052
1053
  function finalizeParams(callee, params) {
1053
1054
  return { callee: params?.callee ?? callee, Err: params?.Err }
1054
1055
  }
@@ -1210,7 +1211,7 @@ var _safeEncodeAsync = (_Err) => async (schema, value, _ctx) => {
1210
1211
  var _safeDecodeAsync = (_Err) => async (schema, value, _ctx) => {
1211
1212
  return _safeParseAsync(_Err)(schema, value, _ctx)
1212
1213
  }
1213
- // node_modules/.bun/zod@4.6.1/node_modules/zod/v4/core/regexes.js
1214
+ // node_modules/.bun/zod@4.6.2/node_modules/zod/v4/core/regexes.js
1214
1215
  var cuid = /^[cC][0-9a-z]{6,}$/
1215
1216
  var cuid2 = /^[0-9a-z]+$/
1216
1217
  var ulid = /^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$/
@@ -1287,7 +1288,7 @@ var boolean = /^(?:true|false)$/i
1287
1288
  var lowercase = /^[^A-Z]*$/
1288
1289
  var uppercase = /^[^a-z]*$/
1289
1290
 
1290
- // node_modules/.bun/zod@4.6.1/node_modules/zod/v4/core/checks.js
1291
+ // node_modules/.bun/zod@4.6.2/node_modules/zod/v4/core/checks.js
1291
1292
  var $ZodCheck = /* @__PURE__ */ $constructor('$ZodCheck', (inst, def) => {
1292
1293
  var _a
1293
1294
  inst._zod ?? (inst._zod = {})
@@ -1667,7 +1668,7 @@ var $ZodCheckOverwrite = /* @__PURE__ */ $constructor(
1667
1668
  },
1668
1669
  )
1669
1670
 
1670
- // node_modules/.bun/zod@4.6.1/node_modules/zod/v4/core/doc.js
1671
+ // node_modules/.bun/zod@4.6.2/node_modules/zod/v4/core/doc.js
1671
1672
  class Doc {
1672
1673
  constructor(args = [], closed = {}) {
1673
1674
  this.content = []
@@ -1718,14 +1719,14 @@ ${content.join(`
1718
1719
  }
1719
1720
  }
1720
1721
 
1721
- // node_modules/.bun/zod@4.6.1/node_modules/zod/v4/core/versions.js
1722
+ // node_modules/.bun/zod@4.6.2/node_modules/zod/v4/core/versions.js
1722
1723
  var version = {
1723
1724
  major: 4,
1724
1725
  minor: 6,
1725
- patch: 1,
1726
+ patch: 2,
1726
1727
  }
1727
1728
 
1728
- // node_modules/.bun/zod@4.6.1/node_modules/zod/v4/core/schemas.js
1729
+ // node_modules/.bun/zod@4.6.2/node_modules/zod/v4/core/schemas.js
1729
1730
  var $ZodType = /* @__PURE__ */ $constructor(
1730
1731
  '$ZodType',
1731
1732
  (inst, def) => {
@@ -2355,7 +2356,7 @@ function handlePropertyResult(result, final, key, input, optin, optout) {
2355
2356
  return
2356
2357
  }
2357
2358
  if (result.value === undefined) {
2358
- if (isPresent) {
2359
+ if (isPresent || (optin === 'defaulted' && !isOptionalOut)) {
2359
2360
  final.value[key] = undefined
2360
2361
  }
2361
2362
  } else {
@@ -2609,16 +2610,16 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor(
2609
2610
  doc.write(`
2610
2611
  if (${id}.issues.length) {${prefixStr(id, k)}
2611
2612
  }
2612
-
2613
- if (${id}.value === undefined) {
2614
- if (${isPresent}) {
2615
- newResult[${k}] = undefined;
2616
- }
2617
- } else {
2613
+ `)
2614
+ if (optin === 'defaulted') {
2615
+ doc.write(`newResult[${k}] = ${id}.value;`)
2616
+ } else {
2617
+ doc.write(`
2618
+ if (${id}.value !== undefined || ${isPresent}) {
2618
2619
  newResult[${k}] = ${id}.value;
2619
2620
  }
2620
-
2621
2621
  `)
2622
+ }
2622
2623
  }
2623
2624
  }
2624
2625
  doc.write(`payload.value = newResult;`)
@@ -3370,7 +3371,7 @@ var $ZodProperties = /* @__PURE__ */ $constructor(
3370
3371
  },
3371
3372
  },
3372
3373
  )
3373
- // node_modules/.bun/zod@4.6.1/node_modules/zod/v4/core/memoizer.js
3374
+ // node_modules/.bun/zod@4.6.2/node_modules/zod/v4/core/memoizer.js
3374
3375
  class $ZodCyclicError extends Error {
3375
3376
  constructor() {
3376
3377
  super(`Cannot parse a reference cycle that closes through a transform`)
@@ -3632,7 +3633,7 @@ function isBackEdge(ctx, value) {
3632
3633
  const backEdges = ctx[STATE]?.backEdges
3633
3634
  return backEdges !== undefined && isRef(value) && backEdges.has(value)
3634
3635
  }
3635
- // node_modules/.bun/zod@4.6.1/node_modules/zod/v4/locales/en.js
3636
+ // node_modules/.bun/zod@4.6.2/node_modules/zod/v4/locales/en.js
3636
3637
  var error = () => {
3637
3638
  const Sizable = {
3638
3639
  string: { unit: 'characters', verb: 'to have' },
@@ -3761,7 +3762,7 @@ function en_default() {
3761
3762
  localeError: error(),
3762
3763
  }
3763
3764
  }
3764
- // node_modules/.bun/zod@4.6.1/node_modules/zod/v4/core/registries.js
3765
+ // node_modules/.bun/zod@4.6.2/node_modules/zod/v4/core/registries.js
3765
3766
  var _a2
3766
3767
  class $ZodRegistry {
3767
3768
  constructor() {
@@ -3809,7 +3810,7 @@ function registry() {
3809
3810
  ;(_a2 = globalThis).__zod_globalRegistry ??
3810
3811
  (_a2.__zod_globalRegistry = registry())
3811
3812
  var globalRegistry = globalThis.__zod_globalRegistry
3812
- // node_modules/.bun/zod@4.6.1/node_modules/zod/v4/core/api.js
3813
+ // node_modules/.bun/zod@4.6.2/node_modules/zod/v4/core/api.js
3813
3814
  function _string(Class, params) {
3814
3815
  return new Class({
3815
3816
  type: 'string',
@@ -4257,7 +4258,7 @@ function _check(fn, params) {
4257
4258
  ch._zod.check = fn
4258
4259
  return ch
4259
4260
  }
4260
- // node_modules/.bun/zod@4.6.1/node_modules/zod/v4/core/to-json-schema.js
4261
+ // node_modules/.bun/zod@4.6.2/node_modules/zod/v4/core/to-json-schema.js
4261
4262
  function assignProps(target, ...sources) {
4262
4263
  for (const source of sources) {
4263
4264
  for (const key of Reflect.ownKeys(source)) {
@@ -4812,7 +4813,7 @@ var createStandardJSONSchemaMethod =
4812
4813
  extractDefs(ctx, schema)
4813
4814
  return finalize(ctx, schema)
4814
4815
  }
4815
- // node_modules/.bun/zod@4.6.1/node_modules/zod/v4/core/json-schema-processors.js
4816
+ // node_modules/.bun/zod@4.6.2/node_modules/zod/v4/core/json-schema-processors.js
4816
4817
  var narrowMin = (agg, key, value) => {
4817
4818
  if (agg[key] === undefined || value > agg[key]) agg[key] = value
4818
4819
  }
@@ -5302,7 +5303,7 @@ var optionalProcessor = (schema, ctx, _json, params) => {
5302
5303
  const seen = ctx.seen.get(schema)
5303
5304
  seen.ref = def.innerType
5304
5305
  }
5305
- // node_modules/.bun/zod@4.6.1/node_modules/zod/v4/classic/errors.js
5306
+ // node_modules/.bun/zod@4.6.2/node_modules/zod/v4/classic/errors.js
5306
5307
  var _installedErrorProtos = /* @__PURE__ */ new WeakSet([
5307
5308
  Object.prototype,
5308
5309
  Error.prototype,
@@ -5366,7 +5367,7 @@ var ZodRealError = /* @__PURE__ */ $constructor(
5366
5367
  },
5367
5368
  )
5368
5369
 
5369
- // node_modules/.bun/zod@4.6.1/node_modules/zod/v4/classic/parse.js
5370
+ // node_modules/.bun/zod@4.6.2/node_modules/zod/v4/classic/parse.js
5370
5371
  var parse2 = /* @__PURE__ */ _parse(ZodRealError)
5371
5372
  var parseAsync = /* @__PURE__ */ _parseAsync(ZodRealError)
5372
5373
  var safeParse = /* @__PURE__ */ _safeParse(ZodRealError)
@@ -5380,7 +5381,7 @@ var safeDecode = /* @__PURE__ */ _safeDecode(ZodRealError)
5380
5381
  var safeEncodeAsync = /* @__PURE__ */ _safeEncodeAsync(ZodRealError)
5381
5382
  var safeDecodeAsync = /* @__PURE__ */ _safeDecodeAsync(ZodRealError)
5382
5383
 
5383
- // node_modules/.bun/zod@4.6.1/node_modules/zod/v4/classic/schemas.js
5384
+ // node_modules/.bun/zod@4.6.2/node_modules/zod/v4/classic/schemas.js
5384
5385
  function _ensureDefaultLocale() {
5385
5386
  if (!globalConfig.localeError) config(en_default())
5386
5387
  }
@@ -6349,7 +6350,7 @@ function refine(fn, _params = {}) {
6349
6350
  function superRefine(fn, params) {
6350
6351
  return _superRefine(fn, params)
6351
6352
  }
6352
- // node_modules/.bun/zod@4.6.1/node_modules/zod/v4/classic/iso.js
6353
+ // node_modules/.bun/zod@4.6.2/node_modules/zod/v4/classic/iso.js
6353
6354
  var exports_iso = {}
6354
6355
  __export(exports_iso, {
6355
6356
  ZodISODate: () => ZodISODate,
@@ -6400,7 +6401,6 @@ var REVIEW_ARTIFACT_CUSTOM_MESSAGES = [
6400
6401
  'provenance.agreement_credit must not contain duplicate reviewers',
6401
6402
  'provenance.agreement_credit must not overlap provenance.submitters',
6402
6403
  'provenance.agreement_credit requires an eligible returned persona with admitted evidence',
6403
- 'satisfied risk coverage must cite a validated finding on the lost persona selection surface',
6404
6404
  'satisfied risk coverage must cite an admitted ledger row owned by another persona',
6405
6405
  ]
6406
6406
  var boundedText = (maxLength) => string2().min(1).max(maxLength).regex(/\S/)
@@ -6840,24 +6840,6 @@ var ReviewArtifactSchema = object({
6840
6840
  return
6841
6841
  }
6842
6842
  if (owner === coverage.persona) {
6843
- ctx.addIssue({
6844
- code: 'custom',
6845
- path: ['risk_coverage', coverageIndex, 'input_finding_id'],
6846
- message: REVIEW_ARTIFACT_CUSTOM_MESSAGES[19],
6847
- })
6848
- return
6849
- }
6850
- const lostDispatch = artifact.dispatches.find(
6851
- (dispatch) => dispatch.persona === coverage.persona,
6852
- )
6853
- const surface = lostDispatch?.selection_surface ?? []
6854
- const covered = artifact.findings.some(
6855
- (finding) =>
6856
- finding.input_finding_ids.includes(citedId) &&
6857
- finding.validated !== false &&
6858
- surface.includes(finding.file),
6859
- )
6860
- if (!covered) {
6861
6843
  ctx.addIssue({
6862
6844
  code: 'custom',
6863
6845
  path: ['risk_coverage', coverageIndex, 'input_finding_id'],
@@ -7018,6 +7000,351 @@ function runClaudeCodeValidator(options) {
7018
7000
  if (false) {
7019
7001
  }
7020
7002
 
7003
+ // src/lib/review-pipeline.ts
7004
+ import path2 from 'node:path'
7005
+
7006
+ // src/lib/review-pipeline-contract.ts
7007
+ var boundedText2 = (maxLength) => string2().min(1).max(maxLength).regex(/\S/)
7008
+ var PipelineInputIdSchema = boundedText2(MAX_INPUT_ID_LENGTH)
7009
+ var PipelineReasonSchema = boundedText2(MAX_REASON_LENGTH)
7010
+ var RejectedSummaryDispatchOutcomeSchema2 = DispatchOutcomeSchema.exclude([
7011
+ 'validation_unavailable',
7012
+ ])
7013
+ var AdmittedScreenDispositionSchema = DispositionSchema.exclude(['rejected'])
7014
+ var ScreenInputSchema = object({
7015
+ raw_return: SubAgentReturnSchema,
7016
+ expected_reviewer: SubAgentReturnSchema.shape.reviewer,
7017
+ invoking_harness: HarnessSchema,
7018
+ }).strict()
7019
+ var AdmittedScreenFindingSchema = ParentFindingSchema.extend({
7020
+ input_id: PipelineInputIdSchema,
7021
+ disposition: AdmittedScreenDispositionSchema,
7022
+ }).strict()
7023
+ var ScreenRejectedSummarySchema = object({
7024
+ dispatch_outcome: RejectedSummaryDispatchOutcomeSchema2,
7025
+ rejected_finding_count: number2().int().positive().max(MAX_FINDINGS),
7026
+ rejected_severities: array(SeveritySchema).max(MAX_FINDINGS),
7027
+ reason: PipelineReasonSchema,
7028
+ })
7029
+ .strict()
7030
+ .superRefine((summary, ctx) => {
7031
+ if (summary.rejected_severities.length !== summary.rejected_finding_count) {
7032
+ ctx.addIssue({
7033
+ code: 'custom',
7034
+ path: ['rejected_severities'],
7035
+ message: 'severity count must match rejected finding count',
7036
+ })
7037
+ }
7038
+ })
7039
+ var ScreenOutputSchema = object({
7040
+ dispatch_outcome: DispatchOutcomeSchema,
7041
+ admitted_findings: array(AdmittedScreenFindingSchema).max(MAX_FINDINGS),
7042
+ rejected_summary: ScreenRejectedSummarySchema.optional(),
7043
+ residual_risks: SubAgentReturnSchema.shape.residual_risks,
7044
+ testing_gaps: SubAgentReturnSchema.shape.testing_gaps,
7045
+ harness: HarnessSchema,
7046
+ }).strict()
7047
+ var ScreenResultSchema = object({
7048
+ reviewer: SubAgentReturnSchema.shape.reviewer,
7049
+ result: ScreenOutputSchema,
7050
+ }).strict()
7051
+ var SelectedDispatchSchema = object({
7052
+ persona: SubAgentReturnSchema.shape.reviewer,
7053
+ dispatch_outcome: DispatchOutcomeSchema,
7054
+ selection_surface: array(RepoRelativePathSchema).max(MAX_FINDINGS).optional(),
7055
+ selection_reason: PipelineReasonSchema.optional(),
7056
+ }).strict()
7057
+ var PrepareInputSchema = object({
7058
+ screen_results: array(ScreenResultSchema).max(MAX_PERSONAS),
7059
+ selected_dispatches: array(SelectedDispatchSchema).max(MAX_PERSONAS),
7060
+ }).strict()
7061
+ var ConfidenceDispositionSchema = object({
7062
+ input_id: PipelineInputIdSchema,
7063
+ disposition: DispositionSchema.extract(['surviving', 'suppressed']),
7064
+ confidence: ParentFindingSchema.shape.confidence,
7065
+ reason: PipelineReasonSchema.optional(),
7066
+ }).strict()
7067
+ var CandidateGroupMemberSchema = object({
7068
+ input_id: PipelineInputIdSchema,
7069
+ line: ParentFindingSchema.shape.line,
7070
+ }).strict()
7071
+ var CandidateGroupSchema = object({
7072
+ file: RepoRelativePathSchema,
7073
+ members: array(CandidateGroupMemberSchema).min(2).max(MAX_FINDINGS),
7074
+ }).strict()
7075
+ var SurvivingAdmittedFindingSchema = ParentFindingSchema.extend({
7076
+ input_id: PipelineInputIdSchema,
7077
+ reviewer: SubAgentReturnSchema.shape.reviewer,
7078
+ }).strict()
7079
+ var PrepareOutputSchema = object({
7080
+ confidence_dispositions: array(ConfidenceDispositionSchema).max(
7081
+ MAX_FINDINGS * MAX_PERSONAS,
7082
+ ),
7083
+ coverage_union: array(RepoRelativePathSchema).max(
7084
+ MAX_FINDINGS * MAX_PERSONAS,
7085
+ ),
7086
+ singletons: array(PipelineInputIdSchema).max(MAX_FINDINGS * MAX_PERSONAS),
7087
+ candidate_groups: array(CandidateGroupSchema).max(MAX_FINDINGS),
7088
+ surviving_findings: array(SurvivingAdmittedFindingSchema).max(
7089
+ MAX_FINDINGS * MAX_PERSONAS,
7090
+ ),
7091
+ }).strict()
7092
+ var PER_FINDING_BYTE_ASSUMPTION = 8192
7093
+ var AGGREGATE_BYTE_CAP_HEADROOM = 65536
7094
+ var AGGREGATE_STDIN_BYTE_CAP =
7095
+ MAX_PERSONAS * MAX_FINDINGS * PER_FINDING_BYTE_ASSUMPTION +
7096
+ AGGREGATE_BYTE_CAP_HEADROOM
7097
+ var AutofixClassRouteSchema = ParentFindingSchema.shape.autofix_class
7098
+ var OwnerRouteSchema = ParentFindingSchema.shape.owner
7099
+ var AUTOFIX_CLASS_NARROWS_TO = {
7100
+ safe_auto: ['safe_auto', 'gated_auto', 'manual', 'advisory'],
7101
+ gated_auto: ['gated_auto', 'manual', 'advisory'],
7102
+ manual: ['manual', 'advisory'],
7103
+ advisory: ['advisory'],
7104
+ }
7105
+ var OWNER_NARROWS_TO = {
7106
+ 'review-fixer': ['review-fixer', 'downstream-resolver', 'human', 'release'],
7107
+ 'downstream-resolver': ['downstream-resolver', 'release'],
7108
+ human: ['human', 'release'],
7109
+ release: ['release'],
7110
+ }
7111
+ var REQUIRES_VERIFICATION_NARROWS_TO = {
7112
+ false: ['false', 'true'],
7113
+ true: ['true'],
7114
+ }
7115
+ var ROUTE_REFUSAL_TABLE = {
7116
+ autofix_class: AUTOFIX_CLASS_NARROWS_TO,
7117
+ owner: OWNER_NARROWS_TO,
7118
+ requires_verification: REQUIRES_VERIFICATION_NARROWS_TO,
7119
+ }
7120
+ var isRouteTransitionAllowed = (from, to) => {
7121
+ const autofixAllowed = ROUTE_REFUSAL_TABLE.autofix_class[
7122
+ from.autofix_class
7123
+ ].includes(to.autofix_class)
7124
+ const ownerAllowed = ROUTE_REFUSAL_TABLE.owner[from.owner].includes(to.owner)
7125
+ const fromVerification = from.requires_verification ? 'true' : 'false'
7126
+ const toVerification = to.requires_verification ? 'true' : 'false'
7127
+ const verificationAllowed =
7128
+ ROUTE_REFUSAL_TABLE.requires_verification[fromVerification].includes(
7129
+ toVerification,
7130
+ )
7131
+ return autofixAllowed && ownerAllowed && verificationAllowed
7132
+ }
7133
+ var AgreementCreditSchema = SubAgentReturnSchema.shape.reviewer
7134
+ var DisagreementFactsSchema = array(PipelineReasonSchema).max(MAX_FINDINGS)
7135
+ var ProposedRouteSchema = object({
7136
+ autofix_class: AutofixClassRouteSchema,
7137
+ owner: OwnerRouteSchema,
7138
+ requires_verification: ParentFindingSchema.shape.requires_verification,
7139
+ }).strict()
7140
+ var MergedDecisionSchema = object({
7141
+ decision_id: PipelineInputIdSchema,
7142
+ disposition: literal('merged'),
7143
+ input_finding_ids: array(PipelineInputIdSchema).min(2).max(MAX_FINDINGS),
7144
+ title: ParentFindingSchema.shape.title,
7145
+ why_it_matters: ParentFindingSchema.shape.why_it_matters,
7146
+ evidence: ParentFindingSchema.shape.evidence,
7147
+ suggested_fix: ParentFindingSchema.shape.suggested_fix,
7148
+ line: ParentFindingSchema.shape.line,
7149
+ disagreement_facts: DisagreementFactsSchema.optional(),
7150
+ eligible_agreement_credit: array(AgreementCreditSchema)
7151
+ .max(MAX_PERSONAS)
7152
+ .optional(),
7153
+ proposed_route: ProposedRouteSchema.optional(),
7154
+ route_narrowing_reason: PipelineReasonSchema.optional(),
7155
+ }).strict()
7156
+ var DeclinedDecisionSchema = object({
7157
+ decision_id: PipelineInputIdSchema,
7158
+ disposition: literal('declined'),
7159
+ input_finding_id: PipelineInputIdSchema,
7160
+ declined_reason: PipelineReasonSchema,
7161
+ disagreement_facts: DisagreementFactsSchema.optional(),
7162
+ proposed_route: ProposedRouteSchema.optional(),
7163
+ route_narrowing_reason: PipelineReasonSchema.optional(),
7164
+ }).strict()
7165
+ var MergeDecisionSchema = discriminatedUnion('disposition', [
7166
+ MergedDecisionSchema,
7167
+ DeclinedDecisionSchema,
7168
+ ])
7169
+ var AdjudicationEnvelopeSchema = object({
7170
+ decisions: array(MergeDecisionSchema).max(MAX_FINDINGS),
7171
+ })
7172
+ .strict()
7173
+ .superRefine((envelope, ctx) => {
7174
+ const seenDecisionIds = new Set()
7175
+ const seenInputIds = new Set()
7176
+ envelope.decisions.forEach((decision, decisionIndex) => {
7177
+ if (seenDecisionIds.has(decision.decision_id)) {
7178
+ ctx.addIssue({
7179
+ code: 'custom',
7180
+ path: ['decisions', decisionIndex, 'decision_id'],
7181
+ message: 'duplicate decision ID',
7182
+ })
7183
+ }
7184
+ seenDecisionIds.add(decision.decision_id)
7185
+ if (decision.proposed_route && !decision.route_narrowing_reason) {
7186
+ ctx.addIssue({
7187
+ code: 'custom',
7188
+ path: ['decisions', decisionIndex, 'route_narrowing_reason'],
7189
+ message:
7190
+ 'route_narrowing_reason is required when proposed_route is present',
7191
+ })
7192
+ }
7193
+ if (decision.disposition === 'merged') {
7194
+ decision.input_finding_ids.forEach((inputId, inputIndex) => {
7195
+ if (seenInputIds.has(inputId)) {
7196
+ ctx.addIssue({
7197
+ code: 'custom',
7198
+ path: [
7199
+ 'decisions',
7200
+ decisionIndex,
7201
+ 'input_finding_ids',
7202
+ inputIndex,
7203
+ ],
7204
+ message: 'input finding ID already claimed by another decision',
7205
+ })
7206
+ }
7207
+ seenInputIds.add(inputId)
7208
+ })
7209
+ return
7210
+ }
7211
+ if (seenInputIds.has(decision.input_finding_id)) {
7212
+ ctx.addIssue({
7213
+ code: 'custom',
7214
+ path: ['decisions', decisionIndex, 'input_finding_id'],
7215
+ message: 'input finding ID already claimed by another decision',
7216
+ })
7217
+ }
7218
+ seenInputIds.add(decision.input_finding_id)
7219
+ })
7220
+ })
7221
+ var MergeInputSchema = object({
7222
+ prepared: PrepareOutputSchema,
7223
+ adjudication: AdjudicationEnvelopeSchema,
7224
+ }).strict()
7225
+ var ValidatorRequestSchema = object({
7226
+ finding_id: PipelineInputIdSchema,
7227
+ file: RepoRelativePathSchema,
7228
+ line: ParentFindingSchema.shape.line,
7229
+ }).strict()
7230
+ var MergedFindingSchema = object({
7231
+ finding_id: PipelineInputIdSchema,
7232
+ file: RepoRelativePathSchema,
7233
+ title: ParentFindingSchema.shape.title,
7234
+ why_it_matters: ParentFindingSchema.shape.why_it_matters,
7235
+ line: ParentFindingSchema.shape.line,
7236
+ autofix_class: ParentFindingSchema.shape.autofix_class,
7237
+ owner: ParentFindingSchema.shape.owner,
7238
+ requires_verification: ParentFindingSchema.shape.requires_verification,
7239
+ evidence: ParentFindingSchema.shape.evidence,
7240
+ suggested_fix: ParentFindingSchema.shape.suggested_fix,
7241
+ input_finding_ids: array(PipelineInputIdSchema).min(1).max(MAX_FINDINGS),
7242
+ agreement_credit: array(AgreementCreditSchema).max(MAX_PERSONAS).optional(),
7243
+ severity: ParentFindingSchema.shape.severity,
7244
+ confidence: ParentFindingSchema.shape.confidence,
7245
+ pre_existing: ParentFindingSchema.shape.pre_existing,
7246
+ fingerprint: ProvenanceSchema.shape.fingerprint,
7247
+ submitters: ProvenanceSchema.shape.submitters,
7248
+ }).strict()
7249
+ var MergeOutputSchema = object({
7250
+ merged_findings: array(MergedFindingSchema).max(MAX_FINDINGS),
7251
+ validator_requests: array(ValidatorRequestSchema).max(MAX_FINDINGS),
7252
+ disagreement_facts: DisagreementFactsSchema,
7253
+ }).strict()
7254
+ var ValidatorLifecycleResultSchema = discriminatedUnion('outcome', [
7255
+ object({ outcome: literal('true') }).strict(),
7256
+ object({ outcome: literal('false'), reason: PipelineReasonSchema }).strict(),
7257
+ object({ outcome: literal('failed'), reason: PipelineReasonSchema }).strict(),
7258
+ object({
7259
+ outcome: literal('unavailable'),
7260
+ reason: PipelineReasonSchema,
7261
+ }).strict(),
7262
+ ])
7263
+ var ValidatorLifecycleRecordSchema = object({
7264
+ finding_id: PipelineInputIdSchema,
7265
+ result: ValidatorLifecycleResultSchema,
7266
+ }).strict()
7267
+ var ValidatorLifecycleResultsSchema = array(ValidatorLifecycleRecordSchema).max(
7268
+ MAX_FINDINGS,
7269
+ )
7270
+ var PlanAssessmentResultSchema = object({
7271
+ kind: _enum(['explicit_unmet_requirement', 'inferred_gap']),
7272
+ description: PipelineReasonSchema,
7273
+ }).strict()
7274
+ var PlanAssessmentEnvelopeSchema = object({
7275
+ verdict: ReviewArtifactSchema.shape.verdict,
7276
+ results: array(PlanAssessmentResultSchema).max(MAX_FINDINGS),
7277
+ }).strict()
7278
+ var ParentRunModeSchema = _enum([
7279
+ ...ReviewArtifactSchema.shape.mode.options,
7280
+ 'report-only',
7281
+ ])
7282
+ var ParentRunMetadataSchema = object({
7283
+ run_id: ReviewArtifactSchema.shape.run_id,
7284
+ mode: ParentRunModeSchema,
7285
+ harness: ReviewArtifactSchema.shape.harness,
7286
+ branch: ReviewArtifactSchema.shape.branch,
7287
+ head_sha: ReviewArtifactSchema.shape.head_sha,
7288
+ selected_dispatches: array(SelectedDispatchSchema).max(MAX_PERSONAS),
7289
+ timestamps: object({
7290
+ started_at: ReviewArtifactSchema.shape.completed_at,
7291
+ completed_at: ReviewArtifactSchema.shape.completed_at,
7292
+ }).strict(),
7293
+ validation: ReviewArtifactSchema.shape.validation.unwrap(),
7294
+ applied_fixes: ReviewArtifactSchema.shape.applied_fixes,
7295
+ }).strict()
7296
+ var FinalizeInputSchema = object({
7297
+ merge: MergeOutputSchema,
7298
+ prepared: PrepareOutputSchema,
7299
+ screen_results: PrepareInputSchema.shape.screen_results,
7300
+ dispatch_records: array(SelectedDispatchSchema).max(MAX_PERSONAS),
7301
+ validator_lifecycle_results: ValidatorLifecycleResultsSchema,
7302
+ plan_assessment: PlanAssessmentEnvelopeSchema,
7303
+ parent_run_metadata: ParentRunMetadataSchema,
7304
+ }).strict()
7305
+ var FinalizedInputDispositionSchema = object({
7306
+ input_id: PipelineInputIdSchema,
7307
+ disposition: DispositionSchema.extract([
7308
+ 'suppressed',
7309
+ 'filtered',
7310
+ 'merged',
7311
+ 'surviving',
7312
+ ]),
7313
+ reason: PipelineReasonSchema.optional(),
7314
+ }).strict()
7315
+ var ReportQueueEntrySchema = object({
7316
+ finding_id: PipelineInputIdSchema,
7317
+ unconfirmed: boolean2(),
7318
+ }).strict()
7319
+ var ReportQueuesSchema = object({
7320
+ fixer: array(ReportQueueEntrySchema).max(MAX_FINDINGS),
7321
+ residual: array(ReportQueueEntrySchema).max(MAX_FINDINGS),
7322
+ report_only: array(ReportQueueEntrySchema).max(MAX_FINDINGS),
7323
+ }).strict()
7324
+ var ReportProjectionSchema = object({
7325
+ verdict: ReviewArtifactSchema.shape.verdict,
7326
+ findings: ReviewArtifactSchema.shape.findings,
7327
+ applied_fixes: ReviewArtifactSchema.shape.applied_fixes,
7328
+ residual_actionable_work: ReviewArtifactSchema.shape.residual_actionable_work,
7329
+ advisory_outputs: ReviewArtifactSchema.shape.advisory_outputs,
7330
+ coverage: ReviewArtifactSchema.shape.coverage,
7331
+ input_dispositions: array(FinalizedInputDispositionSchema).max(
7332
+ MAX_FINDINGS * MAX_PERSONAS,
7333
+ ),
7334
+ disposition_counts: ReviewArtifactSchema.shape.disposition_counts,
7335
+ queues: ReportQueuesSchema,
7336
+ pre_existing_findings: array(ReportQueueEntrySchema).max(MAX_FINDINGS),
7337
+ risk_coverage: ReviewArtifactSchema.shape.risk_coverage,
7338
+ }).strict()
7339
+ var FinalizeOutputSchema = discriminatedUnion('kind', [
7340
+ object({
7341
+ kind: literal('writing'),
7342
+ artifact: ReviewArtifactSchema,
7343
+ report: ReportProjectionSchema,
7344
+ }).strict(),
7345
+ ReportProjectionSchema.extend({ kind: literal('report_only') }).strict(),
7346
+ ])
7347
+
7021
7348
  // src/lib/review-return-validator.ts
7022
7349
  import fs2 from 'node:fs'
7023
7350
  var VALIDATE_REVIEW_RETURN_USAGE = 'Usage: systematic validate-review-return'
@@ -7074,11 +7401,11 @@ function readChunkWithRetry(fd, buffer, toRead, readChunk) {
7074
7401
  }
7075
7402
  }
7076
7403
  }
7077
- function readBoundedStdin(fd, readChunk) {
7404
+ function readBoundedStdin(fd, readChunk, maxBytes = MAX_REVIEW_RETURN_BYTES) {
7078
7405
  const chunks = []
7079
7406
  let total = 0
7080
7407
  while (true) {
7081
- const remaining = MAX_REVIEW_RETURN_BYTES + 1 - total
7408
+ const remaining = maxBytes + 1 - total
7082
7409
  if (remaining <= 0) return { status: 'oversized' }
7083
7410
  const toRead = Math.min(READ_CHUNK_BYTES, remaining)
7084
7411
  const buffer = Buffer.allocUnsafe(toRead)
@@ -7087,7 +7414,7 @@ function readBoundedStdin(fd, readChunk) {
7087
7414
  if (read.bytesRead <= 0) break
7088
7415
  total += read.bytesRead
7089
7416
  chunks.push(buffer.subarray(0, read.bytesRead))
7090
- if (total > MAX_REVIEW_RETURN_BYTES) return { status: 'oversized' }
7417
+ if (total > maxBytes) return { status: 'oversized' }
7091
7418
  }
7092
7419
  return { buffer: Buffer.concat(chunks, total), status: 'ok' }
7093
7420
  }
@@ -7150,48 +7477,2940 @@ function runReviewReturnValidator(options) {
7150
7477
  return 1
7151
7478
  }
7152
7479
 
7153
- // src/ce-review-validator.ts
7154
- var CE_REVIEW_VALIDATOR_USAGE =
7155
- 'Usage: node validate-review.mjs <return|artifact> [...]'
7156
- function runCeReviewValidator(options) {
7157
- const outputSink = options.outputSink ?? ((message) => console.log(message))
7158
- const errorSink = options.errorSink ?? ((message) => console.error(message))
7159
- const subcommand = options.argv[0]
7160
- if (subcommand === 'return') {
7161
- return runReviewReturnValidator({
7162
- argv: ['systematic', 'validate-review-return', ...options.argv.slice(1)],
7163
- isTTY: options.isTTY,
7164
- readChunk: options.readChunk,
7165
- outputSink,
7166
- errorSink,
7167
- })
7480
+ // src/lib/review-pipeline.ts
7481
+ function normalizeRepoRelativePath(filePath) {
7482
+ return path2.posix.normalize(filePath.replaceAll('\\', '/'))
7483
+ }
7484
+ var JSON_ROOT_PATH = '$'
7485
+ var RECOGNIZED_SEVERITIES = ['P0', 'P1', 'P2', 'P3']
7486
+ function classifyRejectedSeverity(value) {
7487
+ return typeof value === 'string' && RECOGNIZED_SEVERITIES.includes(value)
7488
+ ? value
7489
+ : 'unknown'
7490
+ }
7491
+ function extractRejectedSeverities(rawValue) {
7492
+ if (typeof rawValue !== 'object' || rawValue === null) return
7493
+ const findings = rawValue.findings
7494
+ if (!Array.isArray(findings)) return
7495
+ if (findings.length > MAX_FINDINGS) return
7496
+ return findings.map((finding) =>
7497
+ typeof finding === 'object' && finding !== null
7498
+ ? classifyRejectedSeverity(finding.severity)
7499
+ : 'unknown',
7500
+ )
7501
+ }
7502
+ function formatDiagnostic(persona, fieldPath, reason) {
7503
+ const jsonPath =
7504
+ typeof fieldPath === 'string'
7505
+ ? fieldPath
7506
+ : formatReviewArtifactIssuePath(fieldPath)
7507
+ return `Rejected persona ${persona} return: field ${jsonPath} failed ${reason}.`
7508
+ }
7509
+ function truncateReason(reason) {
7510
+ if (reason.length <= MAX_REASON_LENGTH) return reason
7511
+ return reason.slice(0, MAX_REASON_LENGTH)
7512
+ }
7513
+ function parseRawReturn(rawReturn) {
7514
+ if (typeof rawReturn !== 'string') return { ok: true, value: rawReturn }
7515
+ try {
7516
+ return { ok: true, value: JSON.parse(rawReturn) }
7517
+ } catch {
7518
+ return { ok: false }
7519
+ }
7520
+ }
7521
+ function wholePayloadRejection(
7522
+ persona,
7523
+ fieldPath,
7524
+ reason,
7525
+ rejectedSeverities,
7526
+ harness,
7527
+ ) {
7528
+ const rejectedFindingCount = rejectedSeverities?.length ?? 0
7529
+ return ScreenOutputSchema.parse({
7530
+ admitted_findings: [],
7531
+ dispatch_outcome: 'malformed',
7532
+ ...(rejectedFindingCount > 0
7533
+ ? {
7534
+ rejected_summary: {
7535
+ dispatch_outcome: 'malformed',
7536
+ reason: truncateReason(
7537
+ formatDiagnostic(persona, fieldPath, reason),
7538
+ ),
7539
+ rejected_finding_count: rejectedFindingCount,
7540
+ rejected_severities: rejectedSeverities,
7541
+ },
7542
+ }
7543
+ : {}),
7544
+ residual_risks: [],
7545
+ testing_gaps: [],
7546
+ harness,
7547
+ })
7548
+ }
7549
+ function screenReviewReturn(input) {
7550
+ const persona = input.expected_reviewer
7551
+ const harness = input.invoking_harness
7552
+ const parsed = parseRawReturn(input.raw_return)
7553
+ if (!parsed.ok) {
7554
+ return wholePayloadRejection(
7555
+ persona,
7556
+ JSON_ROOT_PATH,
7557
+ 'malformed JSON',
7558
+ undefined,
7559
+ harness,
7560
+ )
7168
7561
  }
7169
- if (subcommand === 'artifact') {
7170
- return runClaudeCodeValidator({
7171
- argv: options.argv.slice(1),
7172
- cwd: options.cwd,
7173
- outputSink,
7174
- errorSink,
7175
- })
7562
+ const validation = validateReviewReturnValue(parsed.value)
7563
+ if (!validation.ok) {
7564
+ const fieldPath = validation.issues[0]?.path ?? JSON_ROOT_PATH
7565
+ return wholePayloadRejection(
7566
+ persona,
7567
+ fieldPath,
7568
+ 'schema validation',
7569
+ extractRejectedSeverities(parsed.value),
7570
+ harness,
7571
+ )
7572
+ }
7573
+ const raw = SubAgentReturnSchema.parse(parsed.value)
7574
+ if (raw.reviewer !== persona) {
7575
+ return wholePayloadRejection(
7576
+ persona,
7577
+ 'reviewer',
7578
+ 'schema validation',
7579
+ raw.findings.map((finding) => finding.severity),
7580
+ harness,
7581
+ )
7176
7582
  }
7177
- errorSink(CE_REVIEW_VALIDATOR_USAGE)
7178
- return 2
7583
+ const admittedFindings = raw.findings.map((finding, originalIndex) => ({
7584
+ ...finding,
7585
+ disposition: 'surviving',
7586
+ input_id: `${persona}#${originalIndex}`,
7587
+ }))
7588
+ const dispatchOutcome = raw.findings.length === 0 ? 'empty' : 'findings'
7589
+ return ScreenOutputSchema.parse({
7590
+ admitted_findings: admittedFindings,
7591
+ dispatch_outcome: dispatchOutcome,
7592
+ residual_risks: raw.residual_risks,
7593
+ testing_gaps: raw.testing_gaps,
7594
+ harness,
7595
+ })
7179
7596
  }
7180
- function resolveRealPath(candidate) {
7181
- if (candidate === undefined) return
7597
+ var STANDARD_CONFIDENCE_FLOOR = 0.6
7598
+ var P0_CONFIDENCE_FLOOR = 0.5
7599
+ var CONFIDENCE_GATE_SUPPRESSED_REASON = 'confidence below gate threshold'
7600
+ function rejectPrepare(path, reason) {
7601
+ return { ok: false, rejection: { path, reason } }
7602
+ }
7603
+ function compareStrings(a, b) {
7604
+ if (a < b) return -1
7605
+ if (a > b) return 1
7606
+ return 0
7607
+ }
7608
+ function payloadByteLength(rawInput) {
7609
+ if (typeof rawInput === 'string') return Buffer.byteLength(rawInput, 'utf8')
7182
7610
  try {
7183
- return fs3.realpathSync(candidate)
7611
+ return Buffer.byteLength(JSON.stringify(rawInput), 'utf8')
7184
7612
  } catch {
7185
7613
  return
7186
7614
  }
7187
7615
  }
7188
- var entryPath = resolveRealPath(process.argv[1])
7189
- var modulePath = resolveRealPath(fileURLToPath(import.meta.url))
7190
- var isMainModule =
7191
- entryPath !== undefined &&
7192
- modulePath !== undefined &&
7193
- entryPath === modulePath
7194
- if (isMainModule) {
7195
- process.exitCode = runCeReviewValidator({ argv: process.argv.slice(2) })
7616
+ function survivesConfidenceGate(finding) {
7617
+ if (finding.confidence >= STANDARD_CONFIDENCE_FLOOR) return true
7618
+ return finding.severity === 'P0' && finding.confidence >= P0_CONFIDENCE_FLOOR
7619
+ }
7620
+ function validatePrepareStructure(screenResults, selectedDispatches) {
7621
+ const seenPersonas = new Set()
7622
+ for (const [index, entry] of screenResults.entries()) {
7623
+ if (seenPersonas.has(entry.reviewer)) {
7624
+ return {
7625
+ path: formatReviewArtifactIssuePath([
7626
+ 'screen_results',
7627
+ index,
7628
+ 'reviewer',
7629
+ ]),
7630
+ reason: 'duplicate persona outcome',
7631
+ }
7632
+ }
7633
+ seenPersonas.add(entry.reviewer)
7634
+ }
7635
+ const seenInputIds = new Set()
7636
+ for (const [screenIndex, entry] of screenResults.entries()) {
7637
+ for (const [
7638
+ findingIndex,
7639
+ finding,
7640
+ ] of entry.result.admitted_findings.entries()) {
7641
+ if (seenInputIds.has(finding.input_id)) {
7642
+ return {
7643
+ path: formatReviewArtifactIssuePath([
7644
+ 'screen_results',
7645
+ screenIndex,
7646
+ 'result',
7647
+ 'admitted_findings',
7648
+ findingIndex,
7649
+ 'input_id',
7650
+ ]),
7651
+ reason: 'duplicate input id',
7652
+ }
7653
+ }
7654
+ seenInputIds.add(finding.input_id)
7655
+ }
7656
+ }
7657
+ const selectedPersonas = new Set(
7658
+ selectedDispatches.map((dispatch) => dispatch.persona),
7659
+ )
7660
+ for (const [index, entry] of screenResults.entries()) {
7661
+ if (!selectedPersonas.has(entry.reviewer)) {
7662
+ return {
7663
+ path: formatReviewArtifactIssuePath([
7664
+ 'screen_results',
7665
+ index,
7666
+ 'reviewer',
7667
+ ]),
7668
+ reason: 'unselected persona screen result',
7669
+ }
7670
+ }
7671
+ }
7672
+ const screenedPersonas = new Set(screenResults.map((entry) => entry.reviewer))
7673
+ for (const [index, dispatch] of selectedDispatches.entries()) {
7674
+ if (!screenedPersonas.has(dispatch.persona)) {
7675
+ return {
7676
+ path: formatReviewArtifactIssuePath([
7677
+ 'selected_dispatches',
7678
+ index,
7679
+ 'persona',
7680
+ ]),
7681
+ reason: 'missing screen result for selected dispatch',
7682
+ }
7683
+ }
7684
+ }
7685
+ return
7686
+ }
7687
+ function applyConfidenceGate(screenResults) {
7688
+ const confidenceDispositions = []
7689
+ const survivors = []
7690
+ const survivingFindings = []
7691
+ for (const entry of screenResults) {
7692
+ for (const finding of entry.result.admitted_findings) {
7693
+ if (survivesConfidenceGate(finding)) {
7694
+ confidenceDispositions.push({
7695
+ input_id: finding.input_id,
7696
+ disposition: 'surviving',
7697
+ confidence: finding.confidence,
7698
+ })
7699
+ survivors.push({
7700
+ inputId: finding.input_id,
7701
+ persona: entry.reviewer,
7702
+ normalizedFile: normalizeRepoRelativePath(finding.file),
7703
+ line: finding.line,
7704
+ })
7705
+ survivingFindings.push({
7706
+ ...finding,
7707
+ reviewer: entry.reviewer,
7708
+ })
7709
+ } else {
7710
+ confidenceDispositions.push({
7711
+ input_id: finding.input_id,
7712
+ disposition: 'suppressed',
7713
+ confidence: finding.confidence,
7714
+ reason: CONFIDENCE_GATE_SUPPRESSED_REASON,
7715
+ })
7716
+ }
7717
+ }
7718
+ }
7719
+ confidenceDispositions.sort((a, b) => compareStrings(a.input_id, b.input_id))
7720
+ survivingFindings.sort((a, b) => compareStrings(a.input_id, b.input_id))
7721
+ return { confidenceDispositions, survivors, survivingFindings }
7722
+ }
7723
+ function computeCoverageUnion(selectedDispatches) {
7724
+ const coverageSet = new Set()
7725
+ for (const dispatch of selectedDispatches) {
7726
+ for (const surface of dispatch.selection_surface ?? []) {
7727
+ coverageSet.add(surface)
7728
+ }
7729
+ }
7730
+ return [...coverageSet].sort(compareStrings)
7731
+ }
7732
+ function groupCandidates(survivors) {
7733
+ const groupsByFile = new Map()
7734
+ for (const survivor of survivors) {
7735
+ const bucket = groupsByFile.get(survivor.normalizedFile)
7736
+ if (bucket) {
7737
+ bucket.push(survivor)
7738
+ } else {
7739
+ groupsByFile.set(survivor.normalizedFile, [survivor])
7740
+ }
7741
+ }
7742
+ const candidateGroups = []
7743
+ const singletonIds = []
7744
+ for (const [file, members] of groupsByFile) {
7745
+ const distinctPersonas = new Set(members.map((member) => member.persona))
7746
+ if (distinctPersonas.size >= 2) {
7747
+ const sortedMembers = [...members].sort((a, b) => {
7748
+ if (a.line !== b.line) return a.line - b.line
7749
+ return compareStrings(a.inputId, b.inputId)
7750
+ })
7751
+ candidateGroups.push({
7752
+ file,
7753
+ members: sortedMembers.map((member) => ({
7754
+ input_id: member.inputId,
7755
+ line: member.line,
7756
+ })),
7757
+ })
7758
+ } else {
7759
+ for (const member of members) {
7760
+ singletonIds.push(member.inputId)
7761
+ }
7762
+ }
7763
+ }
7764
+ candidateGroups.sort((a, b) => compareStrings(a.file, b.file))
7765
+ singletonIds.sort(compareStrings)
7766
+ return { candidateGroups, singletonIds }
7767
+ }
7768
+ function prepareReviewCandidates(input) {
7769
+ const byteLength = payloadByteLength(input.raw_input)
7770
+ if (byteLength !== undefined && byteLength > AGGREGATE_STDIN_BYTE_CAP) {
7771
+ return rejectPrepare(JSON_ROOT_PATH, 'aggregate payload exceeds byte cap')
7772
+ }
7773
+ const parsed = parseRawReturn(input.raw_input)
7774
+ if (!parsed.ok) {
7775
+ return rejectPrepare(JSON_ROOT_PATH, 'malformed JSON')
7776
+ }
7777
+ const validation = PrepareInputSchema.safeParse(parsed.value)
7778
+ if (!validation.success) {
7779
+ const issue = validation.error.issues[0]
7780
+ const issuePath = issue
7781
+ ? formatReviewArtifactIssuePath(issue.path)
7782
+ : JSON_ROOT_PATH
7783
+ return rejectPrepare(issuePath, 'schema validation')
7784
+ }
7785
+ const {
7786
+ screen_results: screenResults,
7787
+ selected_dispatches: selectedDispatches,
7788
+ } = validation.data
7789
+ const structuralViolation = validatePrepareStructure(
7790
+ screenResults,
7791
+ selectedDispatches,
7792
+ )
7793
+ if (structuralViolation) {
7794
+ return rejectPrepare(structuralViolation.path, structuralViolation.reason)
7795
+ }
7796
+ const { confidenceDispositions, survivors, survivingFindings } =
7797
+ applyConfidenceGate(screenResults)
7798
+ const coverageUnion = computeCoverageUnion(selectedDispatches)
7799
+ const { candidateGroups, singletonIds } = groupCandidates(survivors)
7800
+ const output = PrepareOutputSchema.parse({
7801
+ candidate_groups: candidateGroups,
7802
+ confidence_dispositions: confidenceDispositions,
7803
+ coverage_union: coverageUnion,
7804
+ singletons: singletonIds,
7805
+ surviving_findings: survivingFindings,
7806
+ })
7807
+ return { ok: true, value: output }
7808
+ }
7809
+ function rejectAdjudication(path, reason) {
7810
+ return { ok: false, rejection: { path, reason } }
7811
+ }
7812
+ function citedInputIds(decision) {
7813
+ return decision.disposition === 'merged'
7814
+ ? decision.input_finding_ids
7815
+ : [decision.input_finding_id]
7816
+ }
7817
+ function citationPath(decision, decisionIndex, citedIndex) {
7818
+ return decision.disposition === 'merged'
7819
+ ? formatReviewArtifactIssuePath([
7820
+ 'decisions',
7821
+ decisionIndex,
7822
+ 'input_finding_ids',
7823
+ citedIndex,
7824
+ ])
7825
+ : formatReviewArtifactIssuePath([
7826
+ 'decisions',
7827
+ decisionIndex,
7828
+ 'input_finding_id',
7829
+ ])
7830
+ }
7831
+ function buildCandidateIndex(candidateGroups) {
7832
+ const groupIndexByInputId = new Map()
7833
+ candidateGroups.forEach((group, groupIndex) => {
7834
+ for (const member of group.members) {
7835
+ groupIndexByInputId.set(member.input_id, groupIndex)
7836
+ }
7837
+ })
7838
+ return { groupIndexByInputId }
7839
+ }
7840
+ function buildSuppressedSet(confidenceDispositions) {
7841
+ const suppressed = new Set()
7842
+ for (const disposition of confidenceDispositions) {
7843
+ if (disposition.disposition === 'suppressed') {
7844
+ suppressed.add(disposition.input_id)
7845
+ }
7846
+ }
7847
+ return suppressed
7848
+ }
7849
+ function validateCitations(decisions, index, suppressed) {
7850
+ const seen = new Set()
7851
+ for (const [decisionIndex, decision] of decisions.entries()) {
7852
+ for (const [citedIndex, inputId] of citedInputIds(decision).entries()) {
7853
+ const path = citationPath(decision, decisionIndex, citedIndex)
7854
+ if (suppressed.has(inputId)) {
7855
+ return { path, reason: 'suppressed input id' }
7856
+ }
7857
+ if (!index.groupIndexByInputId.has(inputId)) {
7858
+ return { path, reason: 'unknown input id' }
7859
+ }
7860
+ if (seen.has(inputId)) {
7861
+ return { path, reason: 'duplicate input id citation' }
7862
+ }
7863
+ seen.add(inputId)
7864
+ }
7865
+ }
7866
+ return
7867
+ }
7868
+ function validateNoOmissions(candidateGroups, citedIds) {
7869
+ for (const [groupIndex, group] of candidateGroups.entries()) {
7870
+ for (const [memberIndex, member] of group.members.entries()) {
7871
+ if (!citedIds.has(member.input_id)) {
7872
+ return {
7873
+ path: formatReviewArtifactIssuePath([
7874
+ 'candidate_groups',
7875
+ groupIndex,
7876
+ 'members',
7877
+ memberIndex,
7878
+ 'input_id',
7879
+ ]),
7880
+ reason: 'omitted eligible input id',
7881
+ }
7882
+ }
7883
+ }
7884
+ }
7885
+ return
7886
+ }
7887
+ function collectCitedIds(decisions) {
7888
+ const cited = new Set()
7889
+ for (const decision of decisions) {
7890
+ for (const inputId of citedInputIds(decision)) cited.add(inputId)
7891
+ }
7892
+ return cited
7893
+ }
7894
+ function validateMergedDecisionConsistency(
7895
+ decision,
7896
+ decisionIndex,
7897
+ index,
7898
+ candidateGroups,
7899
+ ) {
7900
+ const ids = decision.input_finding_ids
7901
+ const firstId = ids[0]
7902
+ if (firstId === undefined) return
7903
+ const expectedGroupIndex = index.groupIndexByInputId.get(firstId)
7904
+ if (expectedGroupIndex === undefined) return
7905
+ for (const [citedIndex, inputId] of ids.entries()) {
7906
+ const groupIndex = index.groupIndexByInputId.get(inputId)
7907
+ if (groupIndex !== expectedGroupIndex) {
7908
+ return {
7909
+ path: formatReviewArtifactIssuePath([
7910
+ 'decisions',
7911
+ decisionIndex,
7912
+ 'input_finding_ids',
7913
+ citedIndex,
7914
+ ]),
7915
+ reason: 'cross-group input id citation',
7916
+ }
7917
+ }
7918
+ }
7919
+ const group = candidateGroups[expectedGroupIndex]
7920
+ if (group && !group.members.some((member) => member.line === decision.line)) {
7921
+ return {
7922
+ path: formatReviewArtifactIssuePath(['decisions', decisionIndex, 'line']),
7923
+ reason: 'representative line mismatch',
7924
+ }
7925
+ }
7926
+ return
7927
+ }
7928
+ function validateMergedDecisions(decisions, index, candidateGroups) {
7929
+ for (const [decisionIndex, decision] of decisions.entries()) {
7930
+ if (decision.disposition !== 'merged') continue
7931
+ const violation = validateMergedDecisionConsistency(
7932
+ decision,
7933
+ decisionIndex,
7934
+ index,
7935
+ candidateGroups,
7936
+ )
7937
+ if (violation) return violation
7938
+ }
7939
+ return
7940
+ }
7941
+ function resolveDecisionFile(decision, index, candidateGroups) {
7942
+ const firstId = citedInputIds(decision)[0]
7943
+ if (firstId === undefined) return
7944
+ const groupIndex = index.groupIndexByInputId.get(firstId)
7945
+ if (groupIndex === undefined) return
7946
+ return candidateGroups[groupIndex]?.file
7947
+ }
7948
+ function buildValidatedPartition(
7949
+ decisions,
7950
+ index,
7951
+ candidateGroups,
7952
+ singletons,
7953
+ ) {
7954
+ const merged = []
7955
+ const declined = []
7956
+ for (const decision of decisions) {
7957
+ const file = resolveDecisionFile(decision, index, candidateGroups) ?? ''
7958
+ if (decision.disposition === 'merged') {
7959
+ merged.push({ decision, file })
7960
+ } else {
7961
+ declined.push({ decision, file })
7962
+ }
7963
+ }
7964
+ merged.sort((a, b) =>
7965
+ compareStrings(a.decision.decision_id, b.decision.decision_id),
7966
+ )
7967
+ declined.sort((a, b) =>
7968
+ compareStrings(a.decision.decision_id, b.decision.decision_id),
7969
+ )
7970
+ return {
7971
+ merged,
7972
+ declined,
7973
+ singletons: [...singletons].sort(compareStrings),
7974
+ }
7975
+ }
7976
+ function validateAdjudication(prepared, decisions) {
7977
+ if (prepared.candidate_groups.length === 0) {
7978
+ if (decisions.length > 0) {
7979
+ return rejectAdjudication(
7980
+ formatReviewArtifactIssuePath(['decisions']),
7981
+ 'unexpected decisions for empty candidate set',
7982
+ )
7983
+ }
7984
+ return {
7985
+ ok: true,
7986
+ value: buildValidatedPartition(
7987
+ decisions,
7988
+ buildCandidateIndex([]),
7989
+ [],
7990
+ prepared.singletons,
7991
+ ),
7992
+ }
7993
+ }
7994
+ const index = buildCandidateIndex(prepared.candidate_groups)
7995
+ const suppressed = buildSuppressedSet(prepared.confidence_dispositions)
7996
+ const citationViolation = validateCitations(decisions, index, suppressed)
7997
+ if (citationViolation) {
7998
+ return { ok: false, rejection: citationViolation }
7999
+ }
8000
+ const citedIds = collectCitedIds(decisions)
8001
+ const omissionViolation = validateNoOmissions(
8002
+ prepared.candidate_groups,
8003
+ citedIds,
8004
+ )
8005
+ if (omissionViolation) {
8006
+ return { ok: false, rejection: omissionViolation }
8007
+ }
8008
+ const mergedViolation = validateMergedDecisions(
8009
+ decisions,
8010
+ index,
8011
+ prepared.candidate_groups,
8012
+ )
8013
+ if (mergedViolation) {
8014
+ return { ok: false, rejection: mergedViolation }
8015
+ }
8016
+ return {
8017
+ ok: true,
8018
+ value: buildValidatedPartition(
8019
+ decisions,
8020
+ index,
8021
+ prepared.candidate_groups,
8022
+ prepared.singletons,
8023
+ ),
8024
+ }
8025
+ }
8026
+ function rejectMergedFinding(path, reason) {
8027
+ return { ok: false, rejection: { path, reason } }
8028
+ }
8029
+ var SEVERITY_RANK = {
8030
+ P0: 0,
8031
+ P1: 1,
8032
+ P2: 2,
8033
+ P3: 3,
8034
+ }
8035
+ function deriveSeverity(contributing) {
8036
+ return contributing.reduce(
8037
+ (highest, finding) =>
8038
+ SEVERITY_RANK[finding.severity] < SEVERITY_RANK[highest]
8039
+ ? finding.severity
8040
+ : highest,
8041
+ contributing[0].severity,
8042
+ )
8043
+ }
8044
+ function deriveSubmitters(contributing) {
8045
+ return [...new Set(contributing.map((finding) => finding.reviewer))].sort(
8046
+ compareStrings,
8047
+ )
8048
+ }
8049
+ function deriveAgreementCredit(claimed, submitters, returnedReviewers) {
8050
+ if (!claimed || claimed.length === 0) return { ok: true, value: [] }
8051
+ const credited = new Set()
8052
+ for (const [index, reviewer] of claimed.entries()) {
8053
+ const path = formatReviewArtifactIssuePath([
8054
+ 'eligible_agreement_credit',
8055
+ index,
8056
+ ])
8057
+ if (credited.has(reviewer)) {
8058
+ return rejectMergedFinding(path, 'duplicate agreement credit reviewer')
8059
+ }
8060
+ if (submitters.has(reviewer)) {
8061
+ return rejectMergedFinding(
8062
+ path,
8063
+ 'agreement credit reviewer already a submitter',
8064
+ )
8065
+ }
8066
+ if (!returnedReviewers.has(reviewer)) {
8067
+ return rejectMergedFinding(
8068
+ path,
8069
+ 'agreement credit reviewer did not return',
8070
+ )
8071
+ }
8072
+ credited.add(reviewer)
8073
+ }
8074
+ return { ok: true, value: [...credited].sort(compareStrings) }
8075
+ }
8076
+ var CONFIDENCE_AGREEMENT_BOOST = 0.1
8077
+ var MAX_CONFIDENCE = 1
8078
+ function deriveConfidence(contributing, distinctReviewerCount) {
8079
+ const highest = contributing.reduce(
8080
+ (max, finding) => Math.max(max, finding.confidence),
8081
+ contributing[0].confidence,
8082
+ )
8083
+ if (distinctReviewerCount < 2) return highest
8084
+ const boosted = Math.round((highest + CONFIDENCE_AGREEMENT_BOOST) * 100) / 100
8085
+ return Math.min(MAX_CONFIDENCE, boosted)
8086
+ }
8087
+ function derivePreExisting(contributing) {
8088
+ return contributing.every((finding) => finding.pre_existing)
8089
+ }
8090
+ function deriveFingerprint(normalizedFile, representativeLine, severity) {
8091
+ return `${normalizedFile}:${representativeLine}:${severity}`
8092
+ }
8093
+ function meetOverNarrowsTo(narrowsTo, values) {
8094
+ const domain = Object.keys(narrowsTo)
8095
+ const validCandidates = domain.filter((candidate) =>
8096
+ values.every((value) => narrowsTo[value].includes(candidate)),
8097
+ )
8098
+ return validCandidates.find((candidate) =>
8099
+ validCandidates.every((other) => narrowsTo[candidate].includes(other)),
8100
+ )
8101
+ }
8102
+ function deriveRouteMeet(contributing) {
8103
+ const autofixClasses = contributing.map((finding) => finding.autofix_class)
8104
+ const owners = contributing.map((finding) => finding.owner)
8105
+ const verifications = contributing.map((finding) =>
8106
+ finding.requires_verification ? 'true' : 'false',
8107
+ )
8108
+ return {
8109
+ autofix_class:
8110
+ meetOverNarrowsTo(ROUTE_REFUSAL_TABLE.autofix_class, autofixClasses) ??
8111
+ 'advisory',
8112
+ owner: meetOverNarrowsTo(ROUTE_REFUSAL_TABLE.owner, owners) ?? 'release',
8113
+ requires_verification:
8114
+ (meetOverNarrowsTo(
8115
+ ROUTE_REFUSAL_TABLE.requires_verification,
8116
+ verifications,
8117
+ ) ?? 'true') === 'true',
8118
+ }
8119
+ }
8120
+ function deriveRoute(contributing, decision) {
8121
+ const meet = deriveRouteMeet(contributing)
8122
+ if (!decision.proposed_route) return { ok: true, value: meet }
8123
+ if (!decision.route_narrowing_reason) {
8124
+ return rejectMergedFinding(
8125
+ formatReviewArtifactIssuePath(['route_narrowing_reason']),
8126
+ 'route narrowing missing reason',
8127
+ )
8128
+ }
8129
+ if (!isRouteTransitionAllowed(meet, decision.proposed_route)) {
8130
+ return rejectMergedFinding(
8131
+ formatReviewArtifactIssuePath(['proposed_route']),
8132
+ 'route widening',
8133
+ )
8134
+ }
8135
+ return { ok: true, value: decision.proposed_route }
8136
+ }
8137
+ function deriveMergedFindingFields(input) {
8138
+ const submitters = deriveSubmitters(input.contributing)
8139
+ const submitterSet = new Set(submitters)
8140
+ const returnedReviewerSet = new Set(input.returned_reviewers)
8141
+ const agreementCreditResult = deriveAgreementCredit(
8142
+ input.decision.eligible_agreement_credit,
8143
+ submitterSet,
8144
+ returnedReviewerSet,
8145
+ )
8146
+ if (!agreementCreditResult.ok) return agreementCreditResult
8147
+ const routeResult = deriveRoute(input.contributing, input.decision)
8148
+ if (!routeResult.ok) return routeResult
8149
+ const severity = deriveSeverity(input.contributing)
8150
+ const distinctReviewerCount =
8151
+ submitters.length + agreementCreditResult.value.length
8152
+ const confidence = deriveConfidence(input.contributing, distinctReviewerCount)
8153
+ const preExisting = derivePreExisting(input.contributing)
8154
+ const normalizedFile = normalizeRepoRelativePath(input.contributing[0].file)
8155
+ const fingerprint = deriveFingerprint(
8156
+ normalizedFile,
8157
+ input.decision.line,
8158
+ severity,
8159
+ )
8160
+ return {
8161
+ ok: true,
8162
+ value: {
8163
+ severity,
8164
+ submitters,
8165
+ confidence,
8166
+ agreement_credit: agreementCreditResult.value,
8167
+ pre_existing: preExisting,
8168
+ fingerprint,
8169
+ route: routeResult.value,
8170
+ },
8171
+ }
8172
+ }
8173
+ function reviewerFromInputId(inputId) {
8174
+ const separatorIndex = inputId.indexOf('#')
8175
+ return separatorIndex === -1 ? inputId : inputId.slice(0, separatorIndex)
8176
+ }
8177
+ function deriveReturnedReviewers(prepared) {
8178
+ const reviewers = new Set()
8179
+ for (const finding of prepared.surviving_findings) {
8180
+ reviewers.add(finding.reviewer)
8181
+ }
8182
+ for (const disposition of prepared.confidence_dispositions) {
8183
+ if (disposition.disposition === 'suppressed') {
8184
+ reviewers.add(reviewerFromInputId(disposition.input_id))
8185
+ }
8186
+ }
8187
+ return [...reviewers]
8188
+ }
8189
+ function buildSurvivingFindingIndex(prepared) {
8190
+ return new Map(
8191
+ prepared.surviving_findings.map((finding) => [finding.input_id, finding]),
8192
+ )
8193
+ }
8194
+ function requireSurvivingFinding(index, inputId) {
8195
+ const finding = index.get(inputId)
8196
+ if (!finding) {
8197
+ throw new Error(
8198
+ `applyReviewAdjudication: no surviving finding for input ID ${inputId}`,
8199
+ )
8200
+ }
8201
+ return finding
8202
+ }
8203
+ function toContributingTuple(findings) {
8204
+ const [first, second, ...rest] = findings
8205
+ if (!first || !second) {
8206
+ throw new Error(
8207
+ 'applyReviewAdjudication: contributing findings require at least two entries',
8208
+ )
8209
+ }
8210
+ return [first, second, ...rest]
8211
+ }
8212
+ function assemblyFromDerivation(base, derived) {
8213
+ return {
8214
+ ...base,
8215
+ agreement_credit:
8216
+ derived.agreement_credit.length > 0
8217
+ ? derived.agreement_credit
8218
+ : undefined,
8219
+ severity: derived.severity,
8220
+ confidence: derived.confidence,
8221
+ pre_existing: derived.pre_existing,
8222
+ fingerprint: derived.fingerprint,
8223
+ submitters: derived.submitters,
8224
+ autofix_class: derived.route.autofix_class,
8225
+ owner: derived.route.owner,
8226
+ requires_verification: derived.route.requires_verification,
8227
+ }
8228
+ }
8229
+ function assembleMergedGroupFinding(group, survivingIndex, returnedReviewers) {
8230
+ const decision = group.decision
8231
+ const sortedInputIds = [...decision.input_finding_ids].sort(compareStrings)
8232
+ const contributing = toContributingTuple(
8233
+ sortedInputIds.map((inputId) =>
8234
+ requireSurvivingFinding(survivingIndex, inputId),
8235
+ ),
8236
+ )
8237
+ const derived = deriveMergedFindingFields({
8238
+ contributing,
8239
+ decision: {
8240
+ line: decision.line,
8241
+ eligible_agreement_credit: decision.eligible_agreement_credit,
8242
+ proposed_route: decision.proposed_route,
8243
+ route_narrowing_reason: decision.route_narrowing_reason,
8244
+ },
8245
+ returned_reviewers: returnedReviewers,
8246
+ })
8247
+ if (!derived.ok) return derived
8248
+ return {
8249
+ ok: true,
8250
+ value: assemblyFromDerivation(
8251
+ {
8252
+ finding_id: decision.decision_id,
8253
+ file: group.file,
8254
+ title: decision.title,
8255
+ why_it_matters: decision.why_it_matters,
8256
+ line: decision.line,
8257
+ evidence: decision.evidence,
8258
+ suggested_fix: decision.suggested_fix,
8259
+ input_finding_ids: sortedInputIds,
8260
+ },
8261
+ derived.value,
8262
+ ),
8263
+ }
8264
+ }
8265
+ function assembleSingletonFinding(
8266
+ findingId,
8267
+ finding,
8268
+ decisionFields,
8269
+ returnedReviewers,
8270
+ ) {
8271
+ const contributing = [finding, finding]
8272
+ const derived = deriveMergedFindingFields({
8273
+ contributing,
8274
+ decision: {
8275
+ line: finding.line,
8276
+ proposed_route: decisionFields.proposed_route,
8277
+ route_narrowing_reason: decisionFields.route_narrowing_reason,
8278
+ },
8279
+ returned_reviewers: returnedReviewers,
8280
+ })
8281
+ if (!derived.ok) return derived
8282
+ return {
8283
+ ok: true,
8284
+ value: assemblyFromDerivation(
8285
+ {
8286
+ finding_id: findingId,
8287
+ file: normalizeRepoRelativePath(finding.file),
8288
+ title: finding.title,
8289
+ why_it_matters: finding.why_it_matters,
8290
+ line: finding.line,
8291
+ evidence: finding.evidence,
8292
+ suggested_fix: finding.suggested_fix,
8293
+ input_finding_ids: [finding.input_id],
8294
+ },
8295
+ derived.value,
8296
+ ),
8297
+ }
8298
+ }
8299
+ function compareMergedFindingAssembly(a, b) {
8300
+ const severityDelta = SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity]
8301
+ if (severityDelta !== 0) return severityDelta
8302
+ if (a.confidence !== b.confidence) return b.confidence - a.confidence
8303
+ const pathDelta = compareStrings(a.file, b.file)
8304
+ if (pathDelta !== 0) return pathDelta
8305
+ if (a.line !== b.line) return a.line - b.line
8306
+ const fingerprintDelta = compareStrings(a.fingerprint, b.fingerprint)
8307
+ if (fingerprintDelta !== 0) return fingerprintDelta
8308
+ return compareStrings(a.finding_id, b.finding_id)
8309
+ }
8310
+ function requiresValidatorRequest(assembly) {
8311
+ return (
8312
+ assembly.severity === 'P0' ||
8313
+ assembly.severity === 'P1' ||
8314
+ assembly.requires_verification
8315
+ )
8316
+ }
8317
+ function toMergedFindingWireShape(assembly) {
8318
+ return {
8319
+ finding_id: assembly.finding_id,
8320
+ file: assembly.file,
8321
+ title: assembly.title,
8322
+ why_it_matters: assembly.why_it_matters,
8323
+ line: assembly.line,
8324
+ autofix_class: assembly.autofix_class,
8325
+ owner: assembly.owner,
8326
+ requires_verification: assembly.requires_verification,
8327
+ evidence: assembly.evidence,
8328
+ suggested_fix: assembly.suggested_fix,
8329
+ input_finding_ids: [...assembly.input_finding_ids],
8330
+ severity: assembly.severity,
8331
+ confidence: assembly.confidence,
8332
+ pre_existing: assembly.pre_existing,
8333
+ fingerprint: assembly.fingerprint,
8334
+ submitters: [...assembly.submitters],
8335
+ ...(assembly.agreement_credit
8336
+ ? { agreement_credit: [...assembly.agreement_credit] }
8337
+ : {}),
8338
+ }
8339
+ }
8340
+ function mergeFindingIdCollisionReason(first, second) {
8341
+ if (first === second) {
8342
+ if (first === 'merged group') return 'duplicate merged group decision id'
8343
+ if (first === 'declined decision') return 'duplicate declined decision id'
8344
+ return 'duplicate passthrough singleton input id'
8345
+ }
8346
+ const pair = [first, second].sort(compareStrings).join('|')
8347
+ if (pair === 'declined decision|merged group') {
8348
+ return 'declined decision id collides with merged group decision id'
8349
+ }
8350
+ if (pair === 'declined decision|passthrough singleton') {
8351
+ return 'declined decision id collides with passthrough singleton input id'
8352
+ }
8353
+ return 'passthrough singleton input id collides with merged group decision id'
8354
+ }
8355
+ function checkNoDuplicateAssembledFindingIds(origins) {
8356
+ const seen = new Map()
8357
+ for (const [index, entry] of origins.entries()) {
8358
+ const priorOrigin = seen.get(entry.finding_id)
8359
+ if (priorOrigin !== undefined) {
8360
+ return {
8361
+ path: formatReviewArtifactIssuePath([
8362
+ 'merge_assembly',
8363
+ index,
8364
+ 'finding_id',
8365
+ ]),
8366
+ reason: mergeFindingIdCollisionReason(priorOrigin, entry.origin),
8367
+ }
8368
+ }
8369
+ seen.set(entry.finding_id, entry.origin)
8370
+ }
8371
+ return
8372
+ }
8373
+ function applyReviewAdjudication(input) {
8374
+ const validated = validateAdjudication(input.prepared, input.decisions)
8375
+ if (!validated.ok) return validated
8376
+ const survivingIndex = buildSurvivingFindingIndex(input.prepared)
8377
+ const returnedReviewers = deriveReturnedReviewers(input.prepared)
8378
+ const assemblies = []
8379
+ const assemblyOrigins = []
8380
+ const disagreementFacts = []
8381
+ for (const group of validated.value.merged) {
8382
+ const result = assembleMergedGroupFinding(
8383
+ group,
8384
+ survivingIndex,
8385
+ returnedReviewers,
8386
+ )
8387
+ if (!result.ok) return result
8388
+ assemblies.push(result.value)
8389
+ assemblyOrigins.push({
8390
+ finding_id: result.value.finding_id,
8391
+ origin: 'merged group',
8392
+ })
8393
+ if (group.decision.disagreement_facts) {
8394
+ disagreementFacts.push(...group.decision.disagreement_facts)
8395
+ }
8396
+ }
8397
+ for (const singleton of validated.value.declined) {
8398
+ const finding = requireSurvivingFinding(
8399
+ survivingIndex,
8400
+ singleton.decision.input_finding_id,
8401
+ )
8402
+ const result = assembleSingletonFinding(
8403
+ singleton.decision.decision_id,
8404
+ finding,
8405
+ {
8406
+ proposed_route: singleton.decision.proposed_route,
8407
+ route_narrowing_reason: singleton.decision.route_narrowing_reason,
8408
+ },
8409
+ returnedReviewers,
8410
+ )
8411
+ if (!result.ok) return result
8412
+ assemblies.push(result.value)
8413
+ assemblyOrigins.push({
8414
+ finding_id: result.value.finding_id,
8415
+ origin: 'declined decision',
8416
+ })
8417
+ disagreementFacts.push(singleton.decision.declined_reason)
8418
+ if (singleton.decision.disagreement_facts) {
8419
+ disagreementFacts.push(...singleton.decision.disagreement_facts)
8420
+ }
8421
+ }
8422
+ for (const inputId of validated.value.singletons) {
8423
+ const finding = requireSurvivingFinding(survivingIndex, inputId)
8424
+ const result = assembleSingletonFinding(
8425
+ inputId,
8426
+ finding,
8427
+ {},
8428
+ returnedReviewers,
8429
+ )
8430
+ if (!result.ok) return result
8431
+ assemblies.push(result.value)
8432
+ assemblyOrigins.push({
8433
+ finding_id: result.value.finding_id,
8434
+ origin: 'passthrough singleton',
8435
+ })
8436
+ }
8437
+ const collision = checkNoDuplicateAssembledFindingIds(assemblyOrigins)
8438
+ if (collision) return { ok: false, rejection: collision }
8439
+ assemblies.sort(compareMergedFindingAssembly)
8440
+ disagreementFacts.sort(compareStrings)
8441
+ const mergedFindings = assemblies.map(toMergedFindingWireShape)
8442
+ const validatorRequests = assemblies
8443
+ .filter(requiresValidatorRequest)
8444
+ .map((assembly) => ({
8445
+ finding_id: assembly.finding_id,
8446
+ file: assembly.file,
8447
+ line: assembly.line,
8448
+ }))
8449
+ return {
8450
+ ok: true,
8451
+ value: MergeOutputSchema.parse({
8452
+ merged_findings: mergedFindings,
8453
+ validator_requests: validatorRequests,
8454
+ disagreement_facts: disagreementFacts,
8455
+ }),
8456
+ }
8457
+ }
8458
+ function rejectReconcile(path, reason) {
8459
+ return { ok: false, rejection: { path, reason } }
8460
+ }
8461
+ function indexLifecycleResults(results, requestedIds) {
8462
+ const resultsByFindingId = new Map()
8463
+ for (const [index, record] of results.entries()) {
8464
+ const path = formatReviewArtifactIssuePath([
8465
+ 'validator_lifecycle_results',
8466
+ index,
8467
+ 'finding_id',
8468
+ ])
8469
+ if (resultsByFindingId.has(record.finding_id)) {
8470
+ return rejectReconcile(path, 'duplicate validator result')
8471
+ }
8472
+ if (!requestedIds.has(record.finding_id)) {
8473
+ return rejectReconcile(path, 'unrequested validator result')
8474
+ }
8475
+ resultsByFindingId.set(record.finding_id, record.result)
8476
+ }
8477
+ return { ok: true, value: resultsByFindingId }
8478
+ }
8479
+ function validateNoMissingResults(validatorRequests, resultsByFindingId) {
8480
+ for (const [index, request] of validatorRequests.entries()) {
8481
+ if (!resultsByFindingId.has(request.finding_id)) {
8482
+ return {
8483
+ path: formatReviewArtifactIssuePath([
8484
+ 'validator_requests',
8485
+ index,
8486
+ 'finding_id',
8487
+ ]),
8488
+ reason: 'missing validator result',
8489
+ }
8490
+ }
8491
+ }
8492
+ return
8493
+ }
8494
+ function classifyFinding(finding, result) {
8495
+ if (!result) return { finding: { ...finding }, filtered: false }
8496
+ if (result.outcome === 'true') {
8497
+ return { finding: { ...finding, validated: true }, filtered: false }
8498
+ }
8499
+ if (result.outcome === 'false') {
8500
+ return {
8501
+ finding: {
8502
+ ...finding,
8503
+ validated: false,
8504
+ validation_reason: result.reason,
8505
+ },
8506
+ filtered: true,
8507
+ }
8508
+ }
8509
+ return {
8510
+ finding: { ...finding },
8511
+ filtered: false,
8512
+ failure: {
8513
+ finding_id: finding.finding_id,
8514
+ outcome: result.outcome,
8515
+ reason: result.reason,
8516
+ },
8517
+ }
8518
+ }
8519
+ function reconcileValidatorResults(input) {
8520
+ const requestedIds = new Set(
8521
+ input.merge.validator_requests.map((request) => request.finding_id),
8522
+ )
8523
+ const indexed = indexLifecycleResults(
8524
+ input.validator_lifecycle_results,
8525
+ requestedIds,
8526
+ )
8527
+ if (!indexed.ok) return indexed
8528
+ const missingViolation = validateNoMissingResults(
8529
+ input.merge.validator_requests,
8530
+ indexed.value,
8531
+ )
8532
+ if (missingViolation) return { ok: false, rejection: missingViolation }
8533
+ const findings = []
8534
+ const filteredFindingIds = new Set()
8535
+ const filteredInputIds = new Set()
8536
+ const lifecycleFailures = []
8537
+ for (const finding of input.merge.merged_findings) {
8538
+ const classified = classifyFinding(
8539
+ finding,
8540
+ indexed.value.get(finding.finding_id),
8541
+ )
8542
+ findings.push(classified.finding)
8543
+ if (classified.filtered) {
8544
+ filteredFindingIds.add(finding.finding_id)
8545
+ for (const inputId of finding.input_finding_ids) {
8546
+ filteredInputIds.add(inputId)
8547
+ }
8548
+ }
8549
+ if (classified.failure) lifecycleFailures.push(classified.failure)
8550
+ }
8551
+ lifecycleFailures.sort((a, b) => compareStrings(a.finding_id, b.finding_id))
8552
+ return {
8553
+ ok: true,
8554
+ value: {
8555
+ findings,
8556
+ filtered_finding_ids: [...filteredFindingIds].sort(compareStrings),
8557
+ filtered_input_ids: [...filteredInputIds].sort(compareStrings),
8558
+ lifecycle_failures: lifecycleFailures,
8559
+ degraded: lifecycleFailures.length > 0,
8560
+ },
8561
+ }
8562
+ }
8563
+ function rejectFinalizeContext(path, reason) {
8564
+ return { ok: false, rejection: { path, reason } }
8565
+ }
8566
+ function normalizeSelectionSurface(surface) {
8567
+ if (surface === undefined) return
8568
+ return [...surface].map(normalizeRepoRelativePath).sort(compareStrings)
8569
+ }
8570
+ function selectionSurfaceHasDuplicates(surface) {
8571
+ if (surface === undefined) return false
8572
+ const normalized = surface.map(normalizeRepoRelativePath)
8573
+ return new Set(normalized).size !== normalized.length
8574
+ }
8575
+ function dispatchRecordsEqual(a, b) {
8576
+ return (
8577
+ a.persona === b.persona &&
8578
+ a.dispatch_outcome === b.dispatch_outcome &&
8579
+ JSON.stringify(normalizeSelectionSurface(a.selection_surface) ?? []) ===
8580
+ JSON.stringify(normalizeSelectionSurface(b.selection_surface) ?? []) &&
8581
+ a.selection_reason === b.selection_reason
8582
+ )
8583
+ }
8584
+ var FINALIZE_CONTEXT_LOSS_DISPATCH_OUTCOMES = new Set([
8585
+ 'malformed',
8586
+ 'never_returned',
8587
+ 'validation_unavailable',
8588
+ ])
8589
+ var FINALIZE_CONTEXT_LOSS_SEVERITIES = new Set(['P0', 'P1', 'unknown'])
8590
+ function checkNoDuplicateMergedFindingIds(mergedFindings) {
8591
+ const seenFindingIds = new Set()
8592
+ for (const [index, finding] of mergedFindings.entries()) {
8593
+ if (seenFindingIds.has(finding.finding_id)) {
8594
+ return rejectFinalizeContext(
8595
+ formatReviewArtifactIssuePath([
8596
+ 'merge',
8597
+ 'merged_findings',
8598
+ index,
8599
+ 'finding_id',
8600
+ ]),
8601
+ 'duplicate merged finding ID',
8602
+ )
8603
+ }
8604
+ seenFindingIds.add(finding.finding_id)
8605
+ }
8606
+ return { ok: true, value: seenFindingIds }
8607
+ }
8608
+ function checkSurvivorPartition(
8609
+ mergedFindings,
8610
+ survivingFindings,
8611
+ survivingIndex,
8612
+ ) {
8613
+ const coveredInputIds = new Set()
8614
+ const contributingByFindingId = new Map()
8615
+ for (const [findingIndex, finding] of mergedFindings.entries()) {
8616
+ const sortedIds = [...finding.input_finding_ids].sort(compareStrings)
8617
+ const resolved = []
8618
+ for (const [idIndex, inputId] of sortedIds.entries()) {
8619
+ const survivor = survivingIndex.get(inputId)
8620
+ if (!survivor) {
8621
+ return rejectFinalizeContext(
8622
+ formatReviewArtifactIssuePath([
8623
+ 'merge',
8624
+ 'merged_findings',
8625
+ findingIndex,
8626
+ 'input_finding_ids',
8627
+ idIndex,
8628
+ ]),
8629
+ 'merged finding references unknown survivor',
8630
+ )
8631
+ }
8632
+ if (coveredInputIds.has(inputId)) {
8633
+ return rejectFinalizeContext(
8634
+ formatReviewArtifactIssuePath([
8635
+ 'merge',
8636
+ 'merged_findings',
8637
+ findingIndex,
8638
+ 'input_finding_ids',
8639
+ idIndex,
8640
+ ]),
8641
+ 'survivor claimed by multiple merged findings',
8642
+ )
8643
+ }
8644
+ resolved.push(survivor)
8645
+ coveredInputIds.add(inputId)
8646
+ }
8647
+ contributingByFindingId.set(finding.finding_id, resolved)
8648
+ }
8649
+ for (const [survivorIndex, survivor] of survivingFindings.entries()) {
8650
+ if (!coveredInputIds.has(survivor.input_id)) {
8651
+ return rejectFinalizeContext(
8652
+ formatReviewArtifactIssuePath([
8653
+ 'prepared',
8654
+ 'surviving_findings',
8655
+ survivorIndex,
8656
+ 'input_id',
8657
+ ]),
8658
+ 'survivor missing from merge inputs',
8659
+ )
8660
+ }
8661
+ }
8662
+ return { ok: true, value: contributingByFindingId }
8663
+ }
8664
+ function validatorRequestMismatches(request, findingById, expectedFindingIds) {
8665
+ const finding = findingById.get(request.finding_id)
8666
+ return (
8667
+ !finding ||
8668
+ !expectedFindingIds.has(request.finding_id) ||
8669
+ request.file !== finding.file ||
8670
+ request.line !== finding.line
8671
+ )
8672
+ }
8673
+ function checkValidatorRequestsResolve(validatorRequests, mergedFindings) {
8674
+ const findingById = new Map(
8675
+ mergedFindings.map((finding) => [finding.finding_id, finding]),
8676
+ )
8677
+ const expectedFindingIds = new Set(
8678
+ mergedFindings
8679
+ .filter((finding) => requiresValidatorRequest(finding))
8680
+ .map((finding) => finding.finding_id),
8681
+ )
8682
+ const seenFindingIds = new Set()
8683
+ for (const [index, request] of validatorRequests.entries()) {
8684
+ const path = formatReviewArtifactIssuePath([
8685
+ 'merge',
8686
+ 'validator_requests',
8687
+ index,
8688
+ 'finding_id',
8689
+ ])
8690
+ if (
8691
+ seenFindingIds.has(request.finding_id) ||
8692
+ validatorRequestMismatches(request, findingById, expectedFindingIds)
8693
+ ) {
8694
+ return {
8695
+ path,
8696
+ reason: 'validator request references unknown merged finding',
8697
+ }
8698
+ }
8699
+ seenFindingIds.add(request.finding_id)
8700
+ }
8701
+ if (seenFindingIds.size !== expectedFindingIds.size) {
8702
+ return {
8703
+ path: formatReviewArtifactIssuePath(['merge', 'validator_requests']),
8704
+ reason: 'validator request references unknown merged finding',
8705
+ }
8706
+ }
8707
+ return
8708
+ }
8709
+ function checkDispatchRecordsJoin(dispatchRecords, selectedDispatches) {
8710
+ if (dispatchRecords.length !== selectedDispatches.length) {
8711
+ return rejectFinalizeContext(
8712
+ formatReviewArtifactIssuePath(['dispatch_records']),
8713
+ 'dispatch record mismatch',
8714
+ )
8715
+ }
8716
+ const selectedByPersona = new Map(
8717
+ selectedDispatches.map((dispatch) => [dispatch.persona, dispatch]),
8718
+ )
8719
+ if (selectedByPersona.size !== selectedDispatches.length) {
8720
+ return rejectFinalizeContext(
8721
+ formatReviewArtifactIssuePath([
8722
+ 'parent_run_metadata',
8723
+ 'selected_dispatches',
8724
+ ]),
8725
+ 'dispatch record mismatch',
8726
+ )
8727
+ }
8728
+ const seenPersonas = new Set()
8729
+ for (const [index, record] of dispatchRecords.entries()) {
8730
+ const selected = selectedByPersona.get(record.persona)
8731
+ if (seenPersonas.has(record.persona) || !selected) {
8732
+ return rejectFinalizeContext(
8733
+ formatReviewArtifactIssuePath(['dispatch_records', index]),
8734
+ 'dispatch record mismatch',
8735
+ )
8736
+ }
8737
+ if (
8738
+ selectionSurfaceHasDuplicates(record.selection_surface) ||
8739
+ selectionSurfaceHasDuplicates(selected.selection_surface)
8740
+ ) {
8741
+ return rejectFinalizeContext(
8742
+ formatReviewArtifactIssuePath([
8743
+ 'dispatch_records',
8744
+ index,
8745
+ 'selection_surface',
8746
+ ]),
8747
+ 'duplicate selection surface entry',
8748
+ )
8749
+ }
8750
+ if (!dispatchRecordsEqual(record, selected)) {
8751
+ return rejectFinalizeContext(
8752
+ formatReviewArtifactIssuePath(['dispatch_records', index]),
8753
+ 'dispatch record mismatch',
8754
+ )
8755
+ }
8756
+ seenPersonas.add(record.persona)
8757
+ }
8758
+ return { ok: true, value: selectedByPersona }
8759
+ }
8760
+ var SCREEN_RESULT_EXEMPT_DISPATCH_OUTCOMES = new Set([
8761
+ 'validation_unavailable',
8762
+ 'never_returned',
8763
+ ])
8764
+ function checkScreenResultsJoin(
8765
+ screenResults,
8766
+ dispatchRecords,
8767
+ selectedByPersona,
8768
+ ) {
8769
+ const screenByReviewer = new Map()
8770
+ for (const [index, result] of screenResults.entries()) {
8771
+ if (
8772
+ screenByReviewer.has(result.reviewer) ||
8773
+ !selectedByPersona.has(result.reviewer)
8774
+ ) {
8775
+ return rejectFinalizeContext(
8776
+ formatReviewArtifactIssuePath(['screen_results', index, 'reviewer']),
8777
+ 'unexpected screen result for persona',
8778
+ )
8779
+ }
8780
+ screenByReviewer.set(result.reviewer, result)
8781
+ }
8782
+ for (const [index, record] of dispatchRecords.entries()) {
8783
+ const screenResult = screenByReviewer.get(record.persona)
8784
+ if (!screenResult) {
8785
+ if (SCREEN_RESULT_EXEMPT_DISPATCH_OUTCOMES.has(record.dispatch_outcome)) {
8786
+ continue
8787
+ }
8788
+ return rejectFinalizeContext(
8789
+ formatReviewArtifactIssuePath(['dispatch_records', index, 'persona']),
8790
+ 'screen result missing for selected persona',
8791
+ )
8792
+ }
8793
+ if (screenResult.result.dispatch_outcome !== record.dispatch_outcome) {
8794
+ return rejectFinalizeContext(
8795
+ formatReviewArtifactIssuePath([
8796
+ 'dispatch_records',
8797
+ index,
8798
+ 'dispatch_outcome',
8799
+ ]),
8800
+ 'screen result outcome does not match dispatch record',
8801
+ )
8802
+ }
8803
+ }
8804
+ return { ok: true, value: screenByReviewer }
8805
+ }
8806
+ function mergedFindingFileDiverges(finding, contributingSurvivors) {
8807
+ const normalizedFindingFile = normalizeRepoRelativePath(finding.file)
8808
+ return contributingSurvivors.some(
8809
+ (survivor) =>
8810
+ normalizeRepoRelativePath(survivor.file) !== normalizedFindingFile,
8811
+ )
8812
+ }
8813
+ function mergedFindingLineDiverges(finding, contributingSurvivors) {
8814
+ return !contributingSurvivors.some(
8815
+ (survivor) => survivor.line === finding.line,
8816
+ )
8817
+ }
8818
+ function eligibleAgreementCreditClaim(finding) {
8819
+ return finding.input_finding_ids.length >= 2
8820
+ ? finding.agreement_credit
8821
+ : undefined
8822
+ }
8823
+ function mergedFindingDivergesFromDerivation(
8824
+ finding,
8825
+ contributingByFindingId,
8826
+ returnedReviewers,
8827
+ ) {
8828
+ const resolved = contributingByFindingId.get(finding.finding_id) ?? []
8829
+ const contributing = toContributingTuple(
8830
+ resolved.length >= 2 ? resolved : [...resolved, ...resolved],
8831
+ )
8832
+ const derivation = deriveMergedFindingFields({
8833
+ contributing,
8834
+ decision: {
8835
+ line: finding.line,
8836
+ eligible_agreement_credit: eligibleAgreementCreditClaim(finding),
8837
+ proposed_route: {
8838
+ autofix_class: finding.autofix_class,
8839
+ owner: finding.owner,
8840
+ requires_verification: finding.requires_verification,
8841
+ },
8842
+ route_narrowing_reason: 'carried route verification',
8843
+ },
8844
+ returned_reviewers: returnedReviewers,
8845
+ })
8846
+ return (
8847
+ mergedFindingFileDiverges(finding, resolved) ||
8848
+ mergedFindingLineDiverges(finding, resolved) ||
8849
+ !derivation.ok ||
8850
+ derivation.value.severity !== finding.severity ||
8851
+ derivation.value.confidence !== finding.confidence ||
8852
+ derivation.value.pre_existing !== finding.pre_existing ||
8853
+ derivation.value.fingerprint !== finding.fingerprint ||
8854
+ JSON.stringify(derivation.value.submitters) !==
8855
+ JSON.stringify(finding.submitters) ||
8856
+ JSON.stringify(derivation.value.agreement_credit) !==
8857
+ JSON.stringify(finding.agreement_credit ?? []) ||
8858
+ derivation.value.route.autofix_class !== finding.autofix_class ||
8859
+ derivation.value.route.owner !== finding.owner ||
8860
+ derivation.value.route.requires_verification !==
8861
+ finding.requires_verification
8862
+ )
8863
+ }
8864
+ function checkMergedFindingsMatchDerivation(
8865
+ mergedFindings,
8866
+ contributingByFindingId,
8867
+ returnedReviewers,
8868
+ ) {
8869
+ for (const [index, finding] of mergedFindings.entries()) {
8870
+ if (
8871
+ mergedFindingDivergesFromDerivation(
8872
+ finding,
8873
+ contributingByFindingId,
8874
+ returnedReviewers,
8875
+ )
8876
+ ) {
8877
+ return {
8878
+ path: formatReviewArtifactIssuePath([
8879
+ 'merge',
8880
+ 'merged_findings',
8881
+ index,
8882
+ ]),
8883
+ reason: 'merged finding fields diverge from derivation',
8884
+ }
8885
+ }
8886
+ }
8887
+ return
8888
+ }
8889
+ function checkNoDuplicateConfidenceDispositions(confidenceDispositions) {
8890
+ const seen = new Set()
8891
+ for (const [index, entry] of confidenceDispositions.entries()) {
8892
+ if (seen.has(entry.input_id)) {
8893
+ return {
8894
+ path: formatReviewArtifactIssuePath([
8895
+ 'prepared',
8896
+ 'confidence_dispositions',
8897
+ index,
8898
+ 'input_id',
8899
+ ]),
8900
+ reason: 'duplicate confidence disposition',
8901
+ }
8902
+ }
8903
+ seen.add(entry.input_id)
8904
+ }
8905
+ return
8906
+ }
8907
+ function checkConfidenceDispositionsResolveScreenedFindings(
8908
+ prepared,
8909
+ screenResults,
8910
+ ) {
8911
+ const screenedCounts = new Map()
8912
+ const screenedByInputId = new Map()
8913
+ for (const result of screenResults) {
8914
+ for (const finding of result.result.admitted_findings) {
8915
+ screenedCounts.set(
8916
+ finding.input_id,
8917
+ (screenedCounts.get(finding.input_id) ?? 0) + 1,
8918
+ )
8919
+ screenedByInputId.set(finding.input_id, {
8920
+ reviewer: result.reviewer,
8921
+ confidence: finding.confidence,
8922
+ file: finding.file,
8923
+ line: finding.line,
8924
+ severity: finding.severity,
8925
+ })
8926
+ }
8927
+ }
8928
+ for (const [
8929
+ index,
8930
+ disposition,
8931
+ ] of prepared.confidence_dispositions.entries()) {
8932
+ if (screenedCounts.get(disposition.input_id) !== 1) {
8933
+ return {
8934
+ path: formatReviewArtifactIssuePath([
8935
+ 'prepared',
8936
+ 'confidence_dispositions',
8937
+ index,
8938
+ 'input_id',
8939
+ ]),
8940
+ reason: 'confidence disposition references unscreened finding',
8941
+ }
8942
+ }
8943
+ }
8944
+ for (const [index, survivor] of prepared.surviving_findings.entries()) {
8945
+ const screened = screenedByInputId.get(survivor.input_id)
8946
+ if (screened === undefined) {
8947
+ return {
8948
+ path: formatReviewArtifactIssuePath([
8949
+ 'prepared',
8950
+ 'surviving_findings',
8951
+ index,
8952
+ 'input_id',
8953
+ ]),
8954
+ reason: 'surviving finding references unscreened input',
8955
+ }
8956
+ }
8957
+ if (
8958
+ screened.reviewer !== survivor.reviewer ||
8959
+ screened.confidence !== survivor.confidence ||
8960
+ normalizeRepoRelativePath(screened.file) !==
8961
+ normalizeRepoRelativePath(survivor.file) ||
8962
+ screened.line !== survivor.line ||
8963
+ screened.severity !== survivor.severity
8964
+ ) {
8965
+ return {
8966
+ path: formatReviewArtifactIssuePath([
8967
+ 'prepared',
8968
+ 'surviving_findings',
8969
+ index,
8970
+ ]),
8971
+ reason: 'surviving finding diverges from its screened finding',
8972
+ }
8973
+ }
8974
+ }
8975
+ return
8976
+ }
8977
+ function findUnavailableCitedReviewer(
8978
+ finding,
8979
+ findingIndex,
8980
+ survivingIndex,
8981
+ dispatchByPersona,
8982
+ ) {
8983
+ const citedReviewers = new Set()
8984
+ for (const [idIndex, inputId] of finding.input_finding_ids.entries()) {
8985
+ const survivor = survivingIndex.get(inputId)
8986
+ if (!survivor) continue
8987
+ citedReviewers.add(survivor.reviewer)
8988
+ if (
8989
+ dispatchByPersona.get(survivor.reviewer)?.dispatch_outcome ===
8990
+ 'validation_unavailable'
8991
+ ) {
8992
+ return {
8993
+ citedReviewers,
8994
+ rejection: {
8995
+ path: formatReviewArtifactIssuePath([
8996
+ 'merge',
8997
+ 'merged_findings',
8998
+ findingIndex,
8999
+ 'input_finding_ids',
9000
+ idIndex,
9001
+ ]),
9002
+ reason: 'merged finding cites input from unavailable reviewer',
9003
+ },
9004
+ }
9005
+ }
9006
+ }
9007
+ return { citedReviewers }
9008
+ }
9009
+ function findUnsupportedSubmitter(finding, findingIndex, citedReviewers) {
9010
+ for (const [submitterIndex, submitter] of finding.submitters.entries()) {
9011
+ if (!citedReviewers.has(submitter)) {
9012
+ return {
9013
+ path: formatReviewArtifactIssuePath([
9014
+ 'merge',
9015
+ 'merged_findings',
9016
+ findingIndex,
9017
+ 'submitters',
9018
+ submitterIndex,
9019
+ ]),
9020
+ reason: 'merged finding submitter not a cited reviewer',
9021
+ }
9022
+ }
9023
+ }
9024
+ return
9025
+ }
9026
+ function findOverlappingAgreementCredit(finding, findingIndex) {
9027
+ const submitterSet = new Set(finding.submitters)
9028
+ const agreementCredit = finding.agreement_credit ?? []
9029
+ for (const [creditIndex, credit] of agreementCredit.entries()) {
9030
+ if (submitterSet.has(credit)) {
9031
+ return {
9032
+ path: formatReviewArtifactIssuePath([
9033
+ 'merge',
9034
+ 'merged_findings',
9035
+ findingIndex,
9036
+ 'agreement_credit',
9037
+ creditIndex,
9038
+ ]),
9039
+ reason: 'merged finding agreement credit overlaps submitters',
9040
+ }
9041
+ }
9042
+ }
9043
+ return
9044
+ }
9045
+ function checkMergedFindingProvenance(
9046
+ mergedFindings,
9047
+ survivingIndex,
9048
+ dispatchByPersona,
9049
+ ) {
9050
+ for (const [findingIndex, finding] of mergedFindings.entries()) {
9051
+ const unavailable = findUnavailableCitedReviewer(
9052
+ finding,
9053
+ findingIndex,
9054
+ survivingIndex,
9055
+ dispatchByPersona,
9056
+ )
9057
+ if (unavailable.rejection) return unavailable.rejection
9058
+ const unsupportedSubmitter = findUnsupportedSubmitter(
9059
+ finding,
9060
+ findingIndex,
9061
+ unavailable.citedReviewers,
9062
+ )
9063
+ if (unsupportedSubmitter) return unsupportedSubmitter
9064
+ const overlappingCredit = findOverlappingAgreementCredit(
9065
+ finding,
9066
+ findingIndex,
9067
+ )
9068
+ if (overlappingCredit) return overlappingCredit
9069
+ }
9070
+ return
9071
+ }
9072
+ function deriveRejectedPayloadWeights(screenResults) {
9073
+ const sorted = [...screenResults].sort((a, b) =>
9074
+ compareStrings(a.reviewer, b.reviewer),
9075
+ )
9076
+ const weights = []
9077
+ for (const result of sorted) {
9078
+ const summary = result.result.rejected_summary
9079
+ if (summary) {
9080
+ weights.push({ rejected_finding_count: summary.rejected_finding_count })
9081
+ }
9082
+ }
9083
+ return weights
9084
+ }
9085
+ function deriveLostRiskCriticalPersonas(dispatchRecords, screenByReviewer) {
9086
+ const riskCriticalPersonas = new Set(RISK_CRITICAL_PERSONAS)
9087
+ const lostPersonas = []
9088
+ for (const record of dispatchRecords) {
9089
+ if (!riskCriticalPersonas.has(record.persona)) continue
9090
+ const selectionSurface = record.selection_surface ?? []
9091
+ if (FINALIZE_CONTEXT_LOSS_DISPATCH_OUTCOMES.has(record.dispatch_outcome)) {
9092
+ lostPersonas.push({
9093
+ persona: record.persona,
9094
+ selection_surface: selectionSurface,
9095
+ })
9096
+ continue
9097
+ }
9098
+ const rejectedSeverities =
9099
+ screenByReviewer.get(record.persona)?.result.rejected_summary
9100
+ ?.rejected_severities ?? []
9101
+ if (
9102
+ rejectedSeverities.some((severity) =>
9103
+ FINALIZE_CONTEXT_LOSS_SEVERITIES.has(severity),
9104
+ )
9105
+ ) {
9106
+ lostPersonas.push({
9107
+ persona: record.persona,
9108
+ selection_surface: selectionSurface,
9109
+ })
9110
+ }
9111
+ }
9112
+ return [...lostPersonas].sort((a, b) => compareStrings(a.persona, b.persona))
9113
+ }
9114
+ function deriveFinalizeContext(input) {
9115
+ if (input.parent_run_metadata.validation.status !== 'not_attempted') {
9116
+ return rejectFinalizeContext(
9117
+ formatReviewArtifactIssuePath([
9118
+ 'parent_run_metadata',
9119
+ 'validation',
9120
+ 'status',
9121
+ ]),
9122
+ 'validation must be not_attempted at finalize',
9123
+ )
9124
+ }
9125
+ const findingIds = checkNoDuplicateMergedFindingIds(
9126
+ input.merge.merged_findings,
9127
+ )
9128
+ if (!findingIds.ok) return findingIds
9129
+ const survivingIndex = buildSurvivingFindingIndex(input.prepared)
9130
+ const partition = checkSurvivorPartition(
9131
+ input.merge.merged_findings,
9132
+ input.prepared.surviving_findings,
9133
+ survivingIndex,
9134
+ )
9135
+ if (!partition.ok) return partition
9136
+ const requestViolation = checkValidatorRequestsResolve(
9137
+ input.merge.validator_requests,
9138
+ input.merge.merged_findings,
9139
+ )
9140
+ if (requestViolation) return { ok: false, rejection: requestViolation }
9141
+ const dispatchJoin = checkDispatchRecordsJoin(
9142
+ input.dispatch_records,
9143
+ input.parent_run_metadata.selected_dispatches,
9144
+ )
9145
+ if (!dispatchJoin.ok) return dispatchJoin
9146
+ const screenJoin = checkScreenResultsJoin(
9147
+ input.screen_results,
9148
+ input.dispatch_records,
9149
+ dispatchJoin.value,
9150
+ )
9151
+ if (!screenJoin.ok) return screenJoin
9152
+ const returnedReviewers = deriveReturnedReviewers(input.prepared)
9153
+ const derivationViolation = checkMergedFindingsMatchDerivation(
9154
+ input.merge.merged_findings,
9155
+ partition.value,
9156
+ returnedReviewers,
9157
+ )
9158
+ if (derivationViolation) return { ok: false, rejection: derivationViolation }
9159
+ const duplicateDispositionViolation = checkNoDuplicateConfidenceDispositions(
9160
+ input.prepared.confidence_dispositions,
9161
+ )
9162
+ if (duplicateDispositionViolation) {
9163
+ return { ok: false, rejection: duplicateDispositionViolation }
9164
+ }
9165
+ const dispositionJoinViolation =
9166
+ checkConfidenceDispositionsResolveScreenedFindings(
9167
+ input.prepared,
9168
+ input.screen_results,
9169
+ )
9170
+ if (dispositionJoinViolation) {
9171
+ return { ok: false, rejection: dispositionJoinViolation }
9172
+ }
9173
+ const provenanceViolation = checkMergedFindingProvenance(
9174
+ input.merge.merged_findings,
9175
+ survivingIndex,
9176
+ dispatchJoin.value,
9177
+ )
9178
+ if (provenanceViolation) return { ok: false, rejection: provenanceViolation }
9179
+ return {
9180
+ ok: true,
9181
+ value: {
9182
+ rejected_payloads: deriveRejectedPayloadWeights(input.screen_results),
9183
+ lost_risk_critical_personas: deriveLostRiskCriticalPersonas(
9184
+ input.dispatch_records,
9185
+ screenJoin.value,
9186
+ ),
9187
+ },
9188
+ }
9189
+ }
9190
+ function deriveInputDispositions(
9191
+ prepared,
9192
+ reconciled,
9193
+ reviewerIndex,
9194
+ unavailablePersonas,
9195
+ ) {
9196
+ const filteredInputIds = new Set(reconciled.filtered_input_ids)
9197
+ const findingByInputId = new Map()
9198
+ for (const finding of reconciled.findings) {
9199
+ for (const inputId of finding.input_finding_ids) {
9200
+ findingByInputId.set(inputId, finding)
9201
+ }
9202
+ }
9203
+ const dispositions = []
9204
+ for (const [index, entry] of prepared.confidence_dispositions.entries()) {
9205
+ const reviewer = reviewerIndex.get(entry.input_id)
9206
+ if (reviewer !== undefined && unavailablePersonas.has(reviewer)) continue
9207
+ if (entry.disposition === 'suppressed') {
9208
+ dispositions.push({
9209
+ input_id: entry.input_id,
9210
+ disposition: 'suppressed',
9211
+ reason: entry.reason,
9212
+ })
9213
+ continue
9214
+ }
9215
+ if (filteredInputIds.has(entry.input_id)) {
9216
+ dispositions.push({ input_id: entry.input_id, disposition: 'filtered' })
9217
+ continue
9218
+ }
9219
+ const finding = findingByInputId.get(entry.input_id)
9220
+ if (finding === undefined) {
9221
+ return {
9222
+ ok: false,
9223
+ rejection: {
9224
+ path: formatReviewArtifactIssuePath([
9225
+ 'prepared',
9226
+ 'confidence_dispositions',
9227
+ index,
9228
+ 'input_id',
9229
+ ]),
9230
+ reason: 'survivor missing from merged findings',
9231
+ },
9232
+ }
9233
+ }
9234
+ const isMerged = finding.input_finding_ids.length > 1
9235
+ dispositions.push({
9236
+ input_id: entry.input_id,
9237
+ disposition: isMerged ? 'merged' : 'surviving',
9238
+ })
9239
+ }
9240
+ return {
9241
+ ok: true,
9242
+ value: [...dispositions].sort((a, b) =>
9243
+ compareStrings(a.input_id, b.input_id),
9244
+ ),
9245
+ }
9246
+ }
9247
+ function computeDispositionCounts(inputDispositions, rejectedPayloads) {
9248
+ let surviving = 0
9249
+ let merged = 0
9250
+ let suppressed = 0
9251
+ let filtered = 0
9252
+ for (const entry of inputDispositions) {
9253
+ if (entry.disposition === 'surviving') surviving += 1
9254
+ else if (entry.disposition === 'merged') merged += 1
9255
+ else if (entry.disposition === 'suppressed') suppressed += 1
9256
+ else filtered += 1
9257
+ }
9258
+ const rejected = rejectedPayloads.reduce(
9259
+ (total, payload) => total + payload.rejected_finding_count,
9260
+ 0,
9261
+ )
9262
+ return { surviving, merged, suppressed, filtered, rejected }
9263
+ }
9264
+ function routeForOwner(owner) {
9265
+ if (owner === 'review-fixer') return 'fixer'
9266
+ if (owner === 'release') return 'report_only'
9267
+ return 'residual'
9268
+ }
9269
+ function partitionFindings(reconciled) {
9270
+ const filteredFindingIds = new Set(reconciled.filtered_finding_ids)
9271
+ const unconfirmedFindingIds = new Set(
9272
+ reconciled.lifecycle_failures.map((failure) => failure.finding_id),
9273
+ )
9274
+ const preExistingFindings = []
9275
+ const newFindings = []
9276
+ const fixer = []
9277
+ const residual = []
9278
+ const reportOnly = []
9279
+ for (const finding of reconciled.findings) {
9280
+ if (filteredFindingIds.has(finding.finding_id)) continue
9281
+ const unconfirmed = unconfirmedFindingIds.has(finding.finding_id)
9282
+ const entry = { finding_id: finding.finding_id, unconfirmed }
9283
+ if (finding.pre_existing) {
9284
+ preExistingFindings.push(entry)
9285
+ continue
9286
+ }
9287
+ newFindings.push(entry)
9288
+ if (unconfirmed) continue
9289
+ const route = routeForOwner(finding.owner)
9290
+ if (route === 'fixer') fixer.push(entry)
9291
+ else if (route === 'residual') residual.push(entry)
9292
+ else reportOnly.push(entry)
9293
+ }
9294
+ const byFindingId = (a, b) => compareStrings(a.finding_id, b.finding_id)
9295
+ return {
9296
+ preExistingFindings: [...preExistingFindings].sort(byFindingId),
9297
+ newFindings: [...newFindings].sort(byFindingId),
9298
+ queues: {
9299
+ fixer: [...fixer].sort(byFindingId),
9300
+ residual: [...residual].sort(byFindingId),
9301
+ report_only: [...reportOnly].sort(byFindingId),
9302
+ },
9303
+ }
9304
+ }
9305
+ function finalizeReviewDispositions(input) {
9306
+ const reviewerIndex = buildAdmittedReviewerIndex(
9307
+ input.prepared,
9308
+ input.screen_results,
9309
+ )
9310
+ const unavailablePersonas = buildValidationUnavailablePersonas(
9311
+ input.dispatch_records,
9312
+ )
9313
+ const inputDispositionsResult = deriveInputDispositions(
9314
+ input.prepared,
9315
+ input.reconciled,
9316
+ reviewerIndex,
9317
+ unavailablePersonas,
9318
+ )
9319
+ if (!inputDispositionsResult.ok) return inputDispositionsResult
9320
+ const dispositionCounts = computeDispositionCounts(
9321
+ inputDispositionsResult.value,
9322
+ input.rejected_payloads,
9323
+ )
9324
+ const { preExistingFindings, newFindings, queues } = partitionFindings(
9325
+ input.reconciled,
9326
+ )
9327
+ return {
9328
+ ok: true,
9329
+ value: {
9330
+ input_dispositions: inputDispositionsResult.value,
9331
+ disposition_counts: dispositionCounts,
9332
+ pre_existing_findings: preExistingFindings,
9333
+ new_findings: newFindings,
9334
+ queues,
9335
+ },
9336
+ }
9337
+ }
9338
+ function buildSurvivingReviewerIndex(prepared) {
9339
+ const index = new Map()
9340
+ for (const finding of prepared.surviving_findings) {
9341
+ index.set(finding.input_id, finding.reviewer)
9342
+ }
9343
+ return index
9344
+ }
9345
+ function buildRequestedUncertainFindingIds(reconciled) {
9346
+ return new Set(
9347
+ reconciled.lifecycle_failures.map((failure) => failure.finding_id),
9348
+ )
9349
+ }
9350
+ function findingOwners(finding, survivingReviewerIndex) {
9351
+ const owners = new Set()
9352
+ for (const inputId of finding.input_finding_ids) {
9353
+ const reviewer = survivingReviewerIndex.get(inputId)
9354
+ if (reviewer !== undefined) owners.add(reviewer)
9355
+ }
9356
+ return owners
9357
+ }
9358
+ function isValidationBandEligible(finding, requestedUncertainFindingIds) {
9359
+ if (finding.validated === true) return true
9360
+ if (finding.validated === false) return false
9361
+ return !requestedUncertainFindingIds.has(finding.finding_id)
9362
+ }
9363
+ function isEligibleRiskCoverageCandidate(
9364
+ finding,
9365
+ lostPersona,
9366
+ normalizedSurface,
9367
+ survivingReviewerIndex,
9368
+ requestedUncertainFindingIds,
9369
+ ) {
9370
+ if (finding.validated === false) return false
9371
+ if (!normalizedSurface.has(normalizeRepoRelativePath(finding.file))) {
9372
+ return false
9373
+ }
9374
+ const owners = findingOwners(finding, survivingReviewerIndex)
9375
+ const hasCrossPersonaOwner = [...owners].some(
9376
+ (owner) => owner !== lostPersona,
9377
+ )
9378
+ if (!hasCrossPersonaOwner) return false
9379
+ return isValidationBandEligible(finding, requestedUncertainFindingIds)
9380
+ }
9381
+ function citedInputIdForLostPersona(
9382
+ finding,
9383
+ lostPersona,
9384
+ survivingReviewerIndex,
9385
+ ) {
9386
+ const crossPersonaInputIds = finding.input_finding_ids.filter((inputId) => {
9387
+ const reviewer = survivingReviewerIndex.get(inputId)
9388
+ return reviewer !== undefined && reviewer !== lostPersona
9389
+ })
9390
+ if (crossPersonaInputIds.length === 0) return
9391
+ return [...crossPersonaInputIds].sort(compareStrings)[0]
9392
+ }
9393
+ function deriveCoverageForLostPersona(
9394
+ lostPersona,
9395
+ reconciled,
9396
+ survivingReviewerIndex,
9397
+ requestedUncertainFindingIds,
9398
+ ) {
9399
+ const normalizedSurface = new Set(
9400
+ lostPersona.selection_surface.map(normalizeRepoRelativePath),
9401
+ )
9402
+ const eligible = reconciled.findings
9403
+ .filter((finding) =>
9404
+ isEligibleRiskCoverageCandidate(
9405
+ finding,
9406
+ lostPersona.persona,
9407
+ normalizedSurface,
9408
+ survivingReviewerIndex,
9409
+ requestedUncertainFindingIds,
9410
+ ),
9411
+ )
9412
+ .sort(compareMergedFindingAssembly)
9413
+ for (const candidate of eligible) {
9414
+ const citedInputId = citedInputIdForLostPersona(
9415
+ candidate,
9416
+ lostPersona.persona,
9417
+ survivingReviewerIndex,
9418
+ )
9419
+ if (citedInputId !== undefined) {
9420
+ return {
9421
+ persona: lostPersona.persona,
9422
+ satisfied: true,
9423
+ finding_id: candidate.finding_id,
9424
+ input_finding_id: citedInputId,
9425
+ }
9426
+ }
9427
+ }
9428
+ return { persona: lostPersona.persona, satisfied: false }
9429
+ }
9430
+ function deriveRiskCoverage(input) {
9431
+ const survivingReviewerIndex = buildSurvivingReviewerIndex(input.prepared)
9432
+ const requestedUncertainFindingIds = buildRequestedUncertainFindingIds(
9433
+ input.reconciled,
9434
+ )
9435
+ return input.lost_risk_critical_personas
9436
+ .map((lostPersona) =>
9437
+ deriveCoverageForLostPersona(
9438
+ lostPersona,
9439
+ input.reconciled,
9440
+ survivingReviewerIndex,
9441
+ requestedUncertainFindingIds,
9442
+ ),
9443
+ )
9444
+ .sort((a, b) => compareStrings(a.persona, b.persona))
9445
+ }
9446
+ function routePlanAssessment(input) {
9447
+ const residualActionableWork = []
9448
+ const advisoryOutputs = []
9449
+ for (const result of input.results) {
9450
+ if (result.kind === 'explicit_unmet_requirement') {
9451
+ residualActionableWork.push(result.description)
9452
+ } else {
9453
+ advisoryOutputs.push(result.description)
9454
+ }
9455
+ }
9456
+ residualActionableWork.sort(compareStrings)
9457
+ advisoryOutputs.sort(compareStrings)
9458
+ return {
9459
+ residual_actionable_work: residualActionableWork,
9460
+ advisory_outputs: advisoryOutputs,
9461
+ gated_by_explicit_unmet_requirement: residualActionableWork.length > 0,
9462
+ }
9463
+ }
9464
+ function deriveVerdict(planAssessment, riskCoverage, reconciled) {
9465
+ const blockingReasons = []
9466
+ for (const description of planAssessment.residual_actionable_work) {
9467
+ blockingReasons.push({
9468
+ kind: 'explicit_unmet_plan_requirement',
9469
+ description,
9470
+ })
9471
+ }
9472
+ for (const coverage of riskCoverage) {
9473
+ if (!coverage.satisfied) {
9474
+ blockingReasons.push({
9475
+ kind: 'unsatisfied_risk_coverage',
9476
+ persona: coverage.persona,
9477
+ })
9478
+ }
9479
+ }
9480
+ for (const failure of reconciled.lifecycle_failures) {
9481
+ blockingReasons.push({
9482
+ kind: 'degraded_validator_lifecycle',
9483
+ finding_id: failure.finding_id,
9484
+ outcome: failure.outcome,
9485
+ })
9486
+ }
9487
+ return {
9488
+ clean: blockingReasons.length === 0,
9489
+ blocking_reasons: blockingReasons,
9490
+ }
9491
+ }
9492
+ function runReviewPipeline(input) {
9493
+ const reconciled = reconcileValidatorResults({
9494
+ merge: input.merge,
9495
+ validator_lifecycle_results: input.validator_lifecycle_results,
9496
+ })
9497
+ if (!reconciled.ok) return reconciled
9498
+ const finalized = finalizeReviewDispositions({
9499
+ prepared: input.prepared,
9500
+ reconciled: reconciled.value,
9501
+ rejected_payloads: input.rejected_payloads,
9502
+ screen_results: input.screen_results,
9503
+ dispatch_records: input.dispatch_records,
9504
+ })
9505
+ if (!finalized.ok) return finalized
9506
+ const riskCoverage = deriveRiskCoverage({
9507
+ lost_risk_critical_personas: input.lost_risk_critical_personas,
9508
+ prepared: input.prepared,
9509
+ reconciled: reconciled.value,
9510
+ })
9511
+ const planAssessment = routePlanAssessment(input.plan_assessment)
9512
+ const verdict = deriveVerdict(planAssessment, riskCoverage, reconciled.value)
9513
+ return {
9514
+ ok: true,
9515
+ value: {
9516
+ reconciled: reconciled.value,
9517
+ finalized: finalized.value,
9518
+ risk_coverage: riskCoverage,
9519
+ plan_assessment: planAssessment,
9520
+ verdict,
9521
+ },
9522
+ }
9523
+ }
9524
+ var LEDGER_ADMITTED_REASON =
9525
+ 'This input finding passed synthesis and was carried into the run.'
9526
+ var LEDGER_FILTERED_FALLBACK_REASON =
9527
+ 'A validator disproved the synthesized finding this input contributed to.'
9528
+ function buildAdmittedReviewerIndex(prepared, screenResults) {
9529
+ const index = new Map()
9530
+ for (const finding of prepared.surviving_findings) {
9531
+ index.set(finding.input_id, finding.reviewer)
9532
+ }
9533
+ for (const result of screenResults) {
9534
+ for (const finding of result.result.admitted_findings) {
9535
+ if (!index.has(finding.input_id)) {
9536
+ index.set(finding.input_id, result.reviewer)
9537
+ }
9538
+ }
9539
+ }
9540
+ return index
9541
+ }
9542
+ function buildConfidenceReasonIndex(prepared) {
9543
+ const index = new Map()
9544
+ for (const entry of prepared.confidence_dispositions) {
9545
+ if (entry.disposition === 'suppressed' && entry.reason !== undefined) {
9546
+ index.set(entry.input_id, entry.reason)
9547
+ }
9548
+ }
9549
+ return index
9550
+ }
9551
+ function buildFilteredReasonIndex(reconciled) {
9552
+ const index = new Map()
9553
+ for (const finding of reconciled.findings) {
9554
+ if (
9555
+ finding.validated !== false ||
9556
+ finding.validation_reason === undefined
9557
+ ) {
9558
+ continue
9559
+ }
9560
+ for (const inputId of finding.input_finding_ids) {
9561
+ index.set(inputId, finding.validation_reason)
9562
+ }
9563
+ }
9564
+ return index
9565
+ }
9566
+ function buildValidationUnavailablePersonas(dispatchRecords) {
9567
+ return new Set(
9568
+ dispatchRecords
9569
+ .filter((record) => record.dispatch_outcome === 'validation_unavailable')
9570
+ .map((record) => record.persona),
9571
+ )
9572
+ }
9573
+ function admittedLedgerReason(
9574
+ inputId,
9575
+ disposition,
9576
+ confidenceReasons,
9577
+ filteredReasons,
9578
+ ) {
9579
+ if (disposition === 'suppressed') {
9580
+ return confidenceReasons.get(inputId) ?? CONFIDENCE_GATE_SUPPRESSED_REASON
9581
+ }
9582
+ if (disposition === 'filtered') {
9583
+ return filteredReasons.get(inputId) ?? LEDGER_FILTERED_FALLBACK_REASON
9584
+ }
9585
+ return LEDGER_ADMITTED_REASON
9586
+ }
9587
+ function buildAdmittedLedgerRows(input, unavailablePersonas) {
9588
+ const reviewerIndex = buildAdmittedReviewerIndex(
9589
+ input.prepared,
9590
+ input.screen_results,
9591
+ )
9592
+ const dispositionIndex = new Map(
9593
+ input.finalized.input_dispositions.map((entry) => [entry.input_id, entry]),
9594
+ )
9595
+ const confidenceReasons = buildConfidenceReasonIndex(input.prepared)
9596
+ const filteredReasons = buildFilteredReasonIndex(input.reconciled)
9597
+ const rows = []
9598
+ for (const entry of input.prepared.confidence_dispositions) {
9599
+ const reviewer = reviewerIndex.get(entry.input_id)
9600
+ if (reviewer === undefined) {
9601
+ throw new Error(
9602
+ 'buildInputLedger: confidence disposition has no resolvable reviewer',
9603
+ )
9604
+ }
9605
+ if (unavailablePersonas.has(reviewer)) continue
9606
+ const finalDisposition = dispositionIndex.get(entry.input_id)?.disposition
9607
+ if (finalDisposition === undefined) continue
9608
+ rows.push({
9609
+ record_type: 'admitted',
9610
+ input_id: entry.input_id,
9611
+ reviewer,
9612
+ confidence: entry.confidence,
9613
+ disposition: finalDisposition,
9614
+ reason: admittedLedgerReason(
9615
+ entry.input_id,
9616
+ finalDisposition,
9617
+ confidenceReasons,
9618
+ filteredReasons,
9619
+ ),
9620
+ })
9621
+ }
9622
+ return [...rows].sort((a, b) => compareStrings(a.input_id, b.input_id))
9623
+ }
9624
+ function buildRejectedLedgerRows(screenResults, unavailablePersonas) {
9625
+ const rows = []
9626
+ for (const result of screenResults) {
9627
+ if (unavailablePersonas.has(result.reviewer)) continue
9628
+ const summary = result.result.rejected_summary
9629
+ if (!summary) continue
9630
+ rows.push({
9631
+ record_type: 'rejected_summary',
9632
+ reviewer: result.reviewer,
9633
+ dispatch_outcome: summary.dispatch_outcome,
9634
+ rejected_finding_count: summary.rejected_finding_count,
9635
+ rejected_severities: summary.rejected_severities,
9636
+ disposition: 'rejected',
9637
+ reason: summary.reason,
9638
+ })
9639
+ }
9640
+ return [...rows].sort((a, b) => compareStrings(a.reviewer, b.reviewer))
9641
+ }
9642
+ function buildInputLedger(input) {
9643
+ const unavailablePersonas = buildValidationUnavailablePersonas(
9644
+ input.dispatch_records,
9645
+ )
9646
+ return [
9647
+ ...buildAdmittedLedgerRows(input, unavailablePersonas),
9648
+ ...buildRejectedLedgerRows(input.screen_results, unavailablePersonas),
9649
+ ]
9650
+ }
9651
+ function dedupeSortedStrings(values) {
9652
+ return [...new Set(values)].sort(compareStrings)
9653
+ }
9654
+ function checkCoverageArrayBound(field, values) {
9655
+ if (values.length <= MAX_PERSONAS) return
9656
+ return {
9657
+ path: formatReviewArtifactIssuePath(['coverage', field]),
9658
+ reason: 'coverage array exceeds bound',
9659
+ }
9660
+ }
9661
+ function buildReviewCoverage(input) {
9662
+ const residualRisks = dedupeSortedStrings(
9663
+ input.screen_results.flatMap((result) => result.result.residual_risks),
9664
+ )
9665
+ const testingGaps = dedupeSortedStrings(
9666
+ input.screen_results.flatMap((result) => result.result.testing_gaps),
9667
+ )
9668
+ const failedReviewers = dedupeSortedStrings(
9669
+ input.dispatch_records
9670
+ .filter((record) =>
9671
+ FINALIZE_CONTEXT_LOSS_DISPATCH_OUTCOMES.has(record.dispatch_outcome),
9672
+ )
9673
+ .map((record) => record.persona),
9674
+ )
9675
+ const validatorFailures = input.reconciled.lifecycle_failures.map(
9676
+ (failure) => failure.reason,
9677
+ )
9678
+ const intentUncertainty = [...input.merge.disagreement_facts]
9679
+ const bounded = [
9680
+ ['residual_risks', residualRisks],
9681
+ ['testing_gaps', testingGaps],
9682
+ ['failed_reviewers', failedReviewers],
9683
+ ['validator_failures', validatorFailures],
9684
+ ['intent_uncertainty', intentUncertainty],
9685
+ ]
9686
+ for (const [field, values] of bounded) {
9687
+ const violation = checkCoverageArrayBound(field, values)
9688
+ if (violation) return { ok: false, rejection: violation }
9689
+ }
9690
+ return {
9691
+ ok: true,
9692
+ value: {
9693
+ reviewers: input.dispatch_records.length,
9694
+ validators: input.validator_lifecycle_results.length,
9695
+ residual_risks: residualRisks,
9696
+ testing_gaps: testingGaps,
9697
+ failed_reviewers: failedReviewers,
9698
+ validator_failures: validatorFailures,
9699
+ intent_uncertainty: intentUncertainty,
9700
+ },
9701
+ }
9702
+ }
9703
+ function projectOneSynthesizedFinding(finding) {
9704
+ return {
9705
+ title: finding.title,
9706
+ severity: finding.severity,
9707
+ file: finding.file,
9708
+ line: finding.line,
9709
+ why_it_matters: finding.why_it_matters,
9710
+ autofix_class: finding.autofix_class,
9711
+ owner: finding.owner,
9712
+ requires_verification: finding.requires_verification,
9713
+ confidence: finding.confidence,
9714
+ evidence: finding.evidence,
9715
+ pre_existing: finding.pre_existing,
9716
+ ...(finding.suggested_fix !== undefined
9717
+ ? { suggested_fix: finding.suggested_fix }
9718
+ : {}),
9719
+ ...(finding.validated !== undefined
9720
+ ? { validated: finding.validated }
9721
+ : {}),
9722
+ ...(finding.validation_reason !== undefined
9723
+ ? { validation_reason: finding.validation_reason }
9724
+ : {}),
9725
+ input_finding_ids: finding.input_finding_ids,
9726
+ provenance: {
9727
+ fingerprint: finding.fingerprint,
9728
+ submitters: finding.submitters,
9729
+ agreement_credit: finding.agreement_credit ?? [],
9730
+ },
9731
+ }
9732
+ }
9733
+ function projectSynthesizedFindings(input) {
9734
+ return input.findings.map((finding) => projectOneSynthesizedFinding(finding))
9735
+ }
9736
+ var ARTIFACT_NON_CLEAN_VERDICT =
9737
+ 'Review did not reach a clean verdict; see blocking reasons and coverage for detail.'
9738
+ function deriveArtifactRunStatus(
9739
+ reconciledDegraded,
9740
+ dispatchRecords,
9741
+ lostRiskCriticalPersonas,
9742
+ ) {
9743
+ if (reconciledDegraded) return 'degraded'
9744
+ if (lostRiskCriticalPersonas.length > 0) return 'degraded'
9745
+ const hasFailedReviewer = dispatchRecords.some((record) =>
9746
+ FINALIZE_CONTEXT_LOSS_DISPATCH_OUTCOMES.has(record.dispatch_outcome),
9747
+ )
9748
+ return hasFailedReviewer ? 'degraded' : 'completed'
9749
+ }
9750
+ function deriveArtifactVerdictText(
9751
+ planAssessmentVerdict,
9752
+ runVerdict,
9753
+ runStatus,
9754
+ ) {
9755
+ if (runVerdict.clean && runStatus === 'completed') {
9756
+ return planAssessmentVerdict
9757
+ }
9758
+ return ARTIFACT_NON_CLEAN_VERDICT
9759
+ }
9760
+ function buildScreenResultIndex(screenResults) {
9761
+ const index = new Map()
9762
+ for (const result of screenResults) {
9763
+ index.set(result.reviewer, result)
9764
+ }
9765
+ return index
9766
+ }
9767
+ function buildDispatchEntry(record, screenByReviewer) {
9768
+ const screenResult = screenByReviewer.get(record.persona)
9769
+ const inputFindingCount =
9770
+ record.dispatch_outcome === 'validation_unavailable'
9771
+ ? 0
9772
+ : (screenResult?.result.admitted_findings.length ?? 0)
9773
+ const rejectionReason = screenResult?.result.rejected_summary?.reason
9774
+ const selectionSurface = record.selection_surface
9775
+ const selectionReason = record.selection_reason
9776
+ return {
9777
+ persona: record.persona,
9778
+ dispatch_outcome: record.dispatch_outcome,
9779
+ input_finding_count: inputFindingCount,
9780
+ ...(rejectionReason !== undefined
9781
+ ? { rejection_reason: rejectionReason }
9782
+ : {}),
9783
+ ...(selectionSurface !== undefined && selectionSurface.length > 0
9784
+ ? { selection_surface: selectionSurface }
9785
+ : {}),
9786
+ ...(selectionReason !== undefined
9787
+ ? { selection_reason: selectionReason }
9788
+ : {}),
9789
+ }
9790
+ }
9791
+ function buildArtifactDispatches(dispatchRecords, screenByReviewer) {
9792
+ return [...dispatchRecords]
9793
+ .map((record) => buildDispatchEntry(record, screenByReviewer))
9794
+ .sort((a, b) => compareStrings(a.persona, b.persona))
9795
+ }
9796
+ function checkRiskCoverageSemantics(artifactCandidate) {
9797
+ const coverage = artifactCandidate.risk_coverage
9798
+ if (coverage === undefined) return { ok: true }
9799
+ for (const [coverageIndex, entry] of coverage.entries()) {
9800
+ if (!entry.satisfied || entry.input_finding_id === undefined) continue
9801
+ const citedId = entry.input_finding_id
9802
+ const dispatch = artifactCandidate.dispatches.find(
9803
+ (record) => record.persona === entry.persona,
9804
+ )
9805
+ const normalizedSurface = new Set(
9806
+ (dispatch?.selection_surface ?? []).map(normalizeRepoRelativePath),
9807
+ )
9808
+ const covered = artifactCandidate.findings.some((finding) => {
9809
+ if (!finding.input_finding_ids.includes(citedId)) return false
9810
+ const inValidationBand =
9811
+ finding.severity === 'P0' ||
9812
+ finding.severity === 'P1' ||
9813
+ finding.requires_verification
9814
+ const validationSatisfied = inValidationBand
9815
+ ? finding.validated === true
9816
+ : finding.validated !== false
9817
+ if (!validationSatisfied) return false
9818
+ return normalizedSurface.has(normalizeRepoRelativePath(finding.file))
9819
+ })
9820
+ if (!covered) {
9821
+ return {
9822
+ ok: false,
9823
+ rejection: {
9824
+ path: formatReviewArtifactIssuePath([
9825
+ 'risk_coverage',
9826
+ coverageIndex,
9827
+ 'input_finding_id',
9828
+ ]),
9829
+ reason:
9830
+ 'satisfied risk coverage must cite a validated finding on the lost persona selection surface',
9831
+ },
9832
+ }
9833
+ }
9834
+ }
9835
+ return { ok: true }
9836
+ }
9837
+ function checkDispositionCountsReconcileLedger(dispositionCounts, ledger) {
9838
+ const admittedLedgerRowCount = ledger.filter(
9839
+ (row) => row.record_type === 'admitted',
9840
+ ).length
9841
+ const dispositionAdmittedSum =
9842
+ dispositionCounts.surviving +
9843
+ dispositionCounts.merged +
9844
+ dispositionCounts.suppressed +
9845
+ dispositionCounts.filtered
9846
+ if (dispositionAdmittedSum !== admittedLedgerRowCount) {
9847
+ return {
9848
+ ok: false,
9849
+ rejection: {
9850
+ path: formatReviewArtifactIssuePath(['disposition_counts']),
9851
+ reason:
9852
+ 'disposition counts do not reconcile with the admitted input ledger',
9853
+ },
9854
+ }
9855
+ }
9856
+ return { ok: true }
9857
+ }
9858
+ function projectRiskCoverage(riskCoverage) {
9859
+ if (riskCoverage.length === 0) return
9860
+ return riskCoverage.map((entry) => ({
9861
+ persona: entry.persona,
9862
+ satisfied: entry.satisfied,
9863
+ ...(entry.input_finding_id !== undefined
9864
+ ? { input_finding_id: entry.input_finding_id }
9865
+ : {}),
9866
+ }))
9867
+ }
9868
+ function buildReportProjection(
9869
+ verdictText,
9870
+ findings,
9871
+ appliedFixes,
9872
+ pipelineOutput,
9873
+ coverage,
9874
+ riskCoverage,
9875
+ ) {
9876
+ return {
9877
+ verdict: verdictText,
9878
+ findings,
9879
+ applied_fixes: appliedFixes,
9880
+ residual_actionable_work:
9881
+ pipelineOutput.plan_assessment.residual_actionable_work,
9882
+ advisory_outputs: pipelineOutput.plan_assessment.advisory_outputs,
9883
+ coverage,
9884
+ input_dispositions: pipelineOutput.finalized.input_dispositions,
9885
+ disposition_counts: pipelineOutput.finalized.disposition_counts,
9886
+ queues: pipelineOutput.finalized.queues,
9887
+ pre_existing_findings: pipelineOutput.finalized.pre_existing_findings,
9888
+ ...(riskCoverage !== undefined ? { risk_coverage: riskCoverage } : {}),
9889
+ }
9890
+ }
9891
+ function parseFinalizeOutput(output) {
9892
+ const parsed = FinalizeOutputSchema.safeParse(output)
9893
+ if (!parsed.success) {
9894
+ const issue = parsed.error.issues[0]
9895
+ return {
9896
+ ok: false,
9897
+ rejection: {
9898
+ path: issue
9899
+ ? formatReviewArtifactIssuePath(issue.path)
9900
+ : JSON_ROOT_PATH,
9901
+ reason: 'finalize output failed schema validation',
9902
+ },
9903
+ }
9904
+ }
9905
+ return { ok: true, value: parsed.data }
9906
+ }
9907
+ function finalizeReview(input) {
9908
+ const context = deriveFinalizeContext({
9909
+ merge: input.merge,
9910
+ prepared: input.prepared,
9911
+ screen_results: input.screen_results,
9912
+ dispatch_records: input.dispatch_records,
9913
+ parent_run_metadata: {
9914
+ selected_dispatches: input.parent_run_metadata.selected_dispatches,
9915
+ validation: input.parent_run_metadata.validation,
9916
+ },
9917
+ })
9918
+ if (!context.ok) return context
9919
+ const pipeline = runReviewPipeline({
9920
+ merge: input.merge,
9921
+ validator_lifecycle_results: input.validator_lifecycle_results,
9922
+ prepared: input.prepared,
9923
+ rejected_payloads: context.value.rejected_payloads,
9924
+ lost_risk_critical_personas: context.value.lost_risk_critical_personas,
9925
+ plan_assessment: input.plan_assessment,
9926
+ screen_results: input.screen_results,
9927
+ dispatch_records: input.dispatch_records,
9928
+ })
9929
+ if (!pipeline.ok) return pipeline
9930
+ const coverage = buildReviewCoverage({
9931
+ dispatch_records: input.dispatch_records,
9932
+ validator_lifecycle_results: input.validator_lifecycle_results,
9933
+ screen_results: input.screen_results,
9934
+ reconciled: pipeline.value.reconciled,
9935
+ merge: input.merge,
9936
+ })
9937
+ if (!coverage.ok) return coverage
9938
+ const findings = projectSynthesizedFindings({
9939
+ findings: pipeline.value.reconciled.findings,
9940
+ })
9941
+ const runStatus = deriveArtifactRunStatus(
9942
+ pipeline.value.reconciled.degraded,
9943
+ input.dispatch_records,
9944
+ context.value.lost_risk_critical_personas,
9945
+ )
9946
+ const verdictText = deriveArtifactVerdictText(
9947
+ input.plan_assessment.verdict,
9948
+ pipeline.value.verdict,
9949
+ runStatus,
9950
+ )
9951
+ const screenByReviewer = buildScreenResultIndex(input.screen_results)
9952
+ const dispatches = buildArtifactDispatches(
9953
+ input.dispatch_records,
9954
+ screenByReviewer,
9955
+ )
9956
+ const riskCoverage = projectRiskCoverage(pipeline.value.risk_coverage)
9957
+ const riskCoverageSemantics = checkRiskCoverageSemantics({
9958
+ dispatches,
9959
+ findings,
9960
+ risk_coverage: riskCoverage,
9961
+ })
9962
+ if (!riskCoverageSemantics.ok) return riskCoverageSemantics
9963
+ const report = buildReportProjection(
9964
+ verdictText,
9965
+ findings,
9966
+ input.parent_run_metadata.applied_fixes,
9967
+ pipeline.value,
9968
+ coverage.value,
9969
+ riskCoverage,
9970
+ )
9971
+ if (input.parent_run_metadata.mode === 'report-only') {
9972
+ return parseFinalizeOutput({ kind: 'report_only', ...report })
9973
+ }
9974
+ const ledger = buildInputLedger({
9975
+ prepared: input.prepared,
9976
+ screen_results: input.screen_results,
9977
+ dispatch_records: input.dispatch_records,
9978
+ finalized: pipeline.value.finalized,
9979
+ reconciled: pipeline.value.reconciled,
9980
+ })
9981
+ const dispositionReconciliation = checkDispositionCountsReconcileLedger(
9982
+ pipeline.value.finalized.disposition_counts,
9983
+ ledger,
9984
+ )
9985
+ if (!dispositionReconciliation.ok) return dispositionReconciliation
9986
+ const artifact = {
9987
+ schema_version: 1,
9988
+ run_id: input.parent_run_metadata.run_id,
9989
+ branch: input.parent_run_metadata.branch,
9990
+ head_sha: input.parent_run_metadata.head_sha,
9991
+ mode: input.parent_run_metadata.mode,
9992
+ harness: input.parent_run_metadata.harness,
9993
+ run_status: runStatus,
9994
+ verdict: verdictText,
9995
+ completed_at: input.parent_run_metadata.timestamps.completed_at,
9996
+ dispatches,
9997
+ input_findings: ledger,
9998
+ findings,
9999
+ disposition_counts: pipeline.value.finalized.disposition_counts,
10000
+ applied_fixes: input.parent_run_metadata.applied_fixes,
10001
+ residual_actionable_work:
10002
+ pipeline.value.plan_assessment.residual_actionable_work,
10003
+ advisory_outputs: pipeline.value.plan_assessment.advisory_outputs,
10004
+ coverage: coverage.value,
10005
+ validation: input.parent_run_metadata.validation,
10006
+ ...(riskCoverage !== undefined ? { risk_coverage: riskCoverage } : {}),
10007
+ }
10008
+ const parsedArtifact = ReviewArtifactSchema.safeParse(artifact)
10009
+ if (!parsedArtifact.success) {
10010
+ const issue = parsedArtifact.error.issues[0]
10011
+ return {
10012
+ ok: false,
10013
+ rejection: {
10014
+ path: issue
10015
+ ? formatReviewArtifactIssuePath(issue.path)
10016
+ : JSON_ROOT_PATH,
10017
+ reason: 'artifact failed schema validation',
10018
+ },
10019
+ }
10020
+ }
10021
+ return parseFinalizeOutput({
10022
+ kind: 'writing',
10023
+ artifact: parsedArtifact.data,
10024
+ report,
10025
+ })
10026
+ }
10027
+
10028
+ // src/ce-review-validator.ts
10029
+ var CE_REVIEW_VALIDATOR_USAGE =
10030
+ 'Usage: node validate-review.mjs <return|artifact|screen|prepare|merge|finalize> [...]'
10031
+ var CE_REVIEW_SCREEN_USAGE =
10032
+ 'Usage: node validate-review.mjs screen --reviewer <name> --harness <opencode|pi|claude-code>'
10033
+ var CE_REVIEW_SCREEN_STDIN_TTY_MESSAGE =
10034
+ 'screen reads one raw reviewer return from stdin; interactive input is not supported'
10035
+ var CE_REVIEW_SCREEN_STDIN_READ_FAILED_MESSAGE =
10036
+ 'screen could not read the reviewer return from stdin'
10037
+ var CE_REVIEW_SCREEN_STDIN_OVERSIZED_MESSAGE =
10038
+ 'screen input exceeds the 1 MiB limit'
10039
+ var CE_REVIEW_SCREEN_STDIN_INVALID_UTF8_MESSAGE =
10040
+ 'screen input is not valid UTF-8'
10041
+ var CE_REVIEW_SCREEN_STDIN_EMPTY_MESSAGE = 'screen received empty stdin'
10042
+ var CE_REVIEW_SCREEN_REJECTED_MESSAGE = 'screen rejected the reviewer return'
10043
+ var CE_REVIEW_SCREEN_INTERNAL_ERROR_MESSAGE =
10044
+ 'ce-review-validator: internal error'
10045
+ var CE_REVIEW_PREPARE_USAGE = 'Usage: node validate-review.mjs prepare'
10046
+ var CE_REVIEW_PREPARE_STDIN_TTY_MESSAGE =
10047
+ 'prepare reads one aggregate JSON envelope from stdin; interactive input is not supported'
10048
+ var CE_REVIEW_PREPARE_STDIN_READ_FAILED_MESSAGE =
10049
+ 'prepare could not read the aggregate envelope from stdin'
10050
+ var CE_REVIEW_PREPARE_STDIN_OVERSIZED_MESSAGE =
10051
+ 'prepare input exceeds the aggregate byte cap'
10052
+ var CE_REVIEW_PREPARE_STDIN_INVALID_UTF8_MESSAGE =
10053
+ 'prepare input is not valid UTF-8'
10054
+ var CE_REVIEW_PREPARE_STDIN_EMPTY_MESSAGE = 'prepare received empty stdin'
10055
+ var CE_REVIEW_PREPARE_REJECTED_MESSAGE =
10056
+ 'prepare rejected the aggregate envelope'
10057
+ var CE_REVIEW_MERGE_USAGE = 'Usage: node validate-review.mjs merge'
10058
+ var CE_REVIEW_MERGE_STDIN_TTY_MESSAGE =
10059
+ 'merge reads one aggregate JSON envelope from stdin; interactive input is not supported'
10060
+ var CE_REVIEW_MERGE_STDIN_READ_FAILED_MESSAGE =
10061
+ 'merge could not read the aggregate envelope from stdin'
10062
+ var CE_REVIEW_MERGE_STDIN_OVERSIZED_MESSAGE =
10063
+ 'merge input exceeds the aggregate byte cap'
10064
+ var CE_REVIEW_MERGE_STDIN_INVALID_UTF8_MESSAGE =
10065
+ 'merge input is not valid UTF-8'
10066
+ var CE_REVIEW_MERGE_STDIN_EMPTY_MESSAGE = 'merge received empty stdin'
10067
+ var CE_REVIEW_MERGE_REJECTED_MESSAGE = 'merge rejected the aggregate envelope'
10068
+ var CE_REVIEW_FINALIZE_USAGE = 'Usage: node validate-review.mjs finalize'
10069
+ var CE_REVIEW_FINALIZE_STDIN_TTY_MESSAGE =
10070
+ 'finalize reads one aggregate JSON envelope from stdin; interactive input is not supported'
10071
+ var CE_REVIEW_FINALIZE_STDIN_READ_FAILED_MESSAGE =
10072
+ 'finalize could not read the aggregate envelope from stdin'
10073
+ var CE_REVIEW_FINALIZE_STDIN_OVERSIZED_MESSAGE =
10074
+ 'finalize input exceeds the aggregate byte cap'
10075
+ var CE_REVIEW_FINALIZE_STDIN_INVALID_UTF8_MESSAGE =
10076
+ 'finalize input is not valid UTF-8'
10077
+ var CE_REVIEW_FINALIZE_STDIN_EMPTY_MESSAGE = 'finalize received empty stdin'
10078
+ var CE_REVIEW_FINALIZE_REJECTED_MESSAGE =
10079
+ 'finalize rejected the aggregate envelope'
10080
+ var SCREEN_FLAGS = ['--reviewer', '--harness']
10081
+ function isScreenFlag(token) {
10082
+ return SCREEN_FLAGS.includes(token)
10083
+ }
10084
+ function parseScreenFlags(argv) {
10085
+ const values = new Map()
10086
+ let index = 0
10087
+ while (index < argv.length) {
10088
+ const token = argv[index]
10089
+ if (token === undefined) break
10090
+ if (!isScreenFlag(token)) return { ok: false }
10091
+ if (values.has(token)) return { ok: false }
10092
+ const value = argv[index + 1]
10093
+ if (value === undefined || value.startsWith('--')) return { ok: false }
10094
+ values.set(token, value)
10095
+ index += 2
10096
+ }
10097
+ const reviewer = values.get('--reviewer')
10098
+ const rawHarness = values.get('--harness')
10099
+ if (reviewer === undefined || rawHarness === undefined) return { ok: false }
10100
+ const harnessParse = HarnessSchema.safeParse(rawHarness)
10101
+ if (!harnessParse.success) return { ok: false }
10102
+ const harness = harnessParse.data
10103
+ if (!ScreenInputSchema.shape.expected_reviewer.safeParse(reviewer).success) {
10104
+ return { ok: false }
10105
+ }
10106
+ return { harness, ok: true, reviewer }
10107
+ }
10108
+ function formatAggregateRejectionMessage(rejectedMessage, rejection) {
10109
+ return `${rejectedMessage}: ${rejection.reason} at ${rejection.path}`
10110
+ }
10111
+ function runScreenSubcommand(options, outputSink, errorSink) {
10112
+ const flags = parseScreenFlags(options.argv.slice(1))
10113
+ if (!flags.ok) {
10114
+ errorSink(CE_REVIEW_SCREEN_USAGE)
10115
+ return 2
10116
+ }
10117
+ const fd = 0
10118
+ const isTTY = options.isTTY ?? process.stdin.isTTY === true
10119
+ if (isTTY) {
10120
+ errorSink(CE_REVIEW_SCREEN_STDIN_TTY_MESSAGE)
10121
+ return 2
10122
+ }
10123
+ const read = readBoundedStdin(fd, options.readChunk ?? defaultReadChunk)
10124
+ if (read.status === 'read-error') {
10125
+ errorSink(CE_REVIEW_SCREEN_STDIN_READ_FAILED_MESSAGE)
10126
+ return 2
10127
+ }
10128
+ if (read.status === 'oversized') {
10129
+ errorSink(CE_REVIEW_SCREEN_STDIN_OVERSIZED_MESSAGE)
10130
+ return 1
10131
+ }
10132
+ let text
10133
+ try {
10134
+ text = new TextDecoder('utf-8', { fatal: true }).decode(read.buffer)
10135
+ } catch {
10136
+ errorSink(CE_REVIEW_SCREEN_STDIN_INVALID_UTF8_MESSAGE)
10137
+ return 1
10138
+ }
10139
+ if (text.trim() === '') {
10140
+ errorSink(CE_REVIEW_SCREEN_STDIN_EMPTY_MESSAGE)
10141
+ return 2
10142
+ }
10143
+ const result = screenReviewReturn({
10144
+ expected_reviewer: flags.reviewer,
10145
+ invoking_harness: flags.harness,
10146
+ raw_return: text,
10147
+ })
10148
+ if (result.dispatch_outcome === 'malformed') {
10149
+ errorSink(
10150
+ result.rejected_summary?.reason ?? CE_REVIEW_SCREEN_REJECTED_MESSAGE,
10151
+ )
10152
+ return 1
10153
+ }
10154
+ outputSink(JSON.stringify(result))
10155
+ return 0
10156
+ }
10157
+ function runPrepareSubcommand(options, outputSink, errorSink) {
10158
+ if (options.argv.slice(1).length > 0) {
10159
+ errorSink(CE_REVIEW_PREPARE_USAGE)
10160
+ return 2
10161
+ }
10162
+ const fd = 0
10163
+ const isTTY = options.isTTY ?? process.stdin.isTTY === true
10164
+ if (isTTY) {
10165
+ errorSink(CE_REVIEW_PREPARE_STDIN_TTY_MESSAGE)
10166
+ return 2
10167
+ }
10168
+ const read = readBoundedStdin(
10169
+ fd,
10170
+ options.readChunk ?? defaultReadChunk,
10171
+ AGGREGATE_STDIN_BYTE_CAP,
10172
+ )
10173
+ if (read.status === 'read-error') {
10174
+ errorSink(CE_REVIEW_PREPARE_STDIN_READ_FAILED_MESSAGE)
10175
+ return 2
10176
+ }
10177
+ if (read.status === 'oversized') {
10178
+ errorSink(CE_REVIEW_PREPARE_STDIN_OVERSIZED_MESSAGE)
10179
+ return 1
10180
+ }
10181
+ let text
10182
+ try {
10183
+ text = new TextDecoder('utf-8', { fatal: true }).decode(read.buffer)
10184
+ } catch {
10185
+ errorSink(CE_REVIEW_PREPARE_STDIN_INVALID_UTF8_MESSAGE)
10186
+ return 1
10187
+ }
10188
+ if (text.trim() === '') {
10189
+ errorSink(CE_REVIEW_PREPARE_STDIN_EMPTY_MESSAGE)
10190
+ return 2
10191
+ }
10192
+ const result = prepareReviewCandidates({ raw_input: text })
10193
+ if (!result.ok) {
10194
+ errorSink(
10195
+ formatAggregateRejectionMessage(
10196
+ CE_REVIEW_PREPARE_REJECTED_MESSAGE,
10197
+ result.rejection,
10198
+ ),
10199
+ )
10200
+ return 1
10201
+ }
10202
+ outputSink(JSON.stringify(result.value))
10203
+ return 0
10204
+ }
10205
+ function runAggregateStdinSubcommand(options, outputSink, errorSink, spec) {
10206
+ if (options.argv.slice(1).length > 0) {
10207
+ errorSink(spec.usage)
10208
+ return 2
10209
+ }
10210
+ const fd = 0
10211
+ const isTTY = options.isTTY ?? process.stdin.isTTY === true
10212
+ if (isTTY) {
10213
+ errorSink(spec.ttyMessage)
10214
+ return 2
10215
+ }
10216
+ const read = readBoundedStdin(
10217
+ fd,
10218
+ options.readChunk ?? defaultReadChunk,
10219
+ AGGREGATE_STDIN_BYTE_CAP,
10220
+ )
10221
+ if (read.status === 'read-error') {
10222
+ errorSink(spec.readFailedMessage)
10223
+ return 2
10224
+ }
10225
+ if (read.status === 'oversized') {
10226
+ errorSink(spec.oversizedMessage)
10227
+ return 1
10228
+ }
10229
+ let text
10230
+ try {
10231
+ text = new TextDecoder('utf-8', { fatal: true }).decode(read.buffer)
10232
+ } catch {
10233
+ errorSink(spec.invalidUtf8Message)
10234
+ return 1
10235
+ }
10236
+ if (text.trim() === '') {
10237
+ errorSink(spec.emptyMessage)
10238
+ return 2
10239
+ }
10240
+ let value
10241
+ try {
10242
+ value = JSON.parse(text)
10243
+ } catch {
10244
+ errorSink(spec.rejectedMessage)
10245
+ return 1
10246
+ }
10247
+ const parsed = spec.schema.safeParse(value)
10248
+ if (!parsed.success) {
10249
+ const issue = parsed.error.issues[0]
10250
+ errorSink(
10251
+ issue
10252
+ ? formatAggregateRejectionMessage(spec.rejectedMessage, {
10253
+ path: formatReviewArtifactIssuePath(issue.path),
10254
+ reason: issue.code,
10255
+ })
10256
+ : spec.rejectedMessage,
10257
+ )
10258
+ return 1
10259
+ }
10260
+ const result = spec.execute(parsed.data)
10261
+ if (!result.ok) {
10262
+ errorSink(
10263
+ formatAggregateRejectionMessage(spec.rejectedMessage, result.rejection),
10264
+ )
10265
+ return 1
10266
+ }
10267
+ outputSink(JSON.stringify(result.value))
10268
+ return 0
10269
+ }
10270
+ function runMergeSubcommand(options, outputSink, errorSink) {
10271
+ return runAggregateStdinSubcommand(options, outputSink, errorSink, {
10272
+ emptyMessage: CE_REVIEW_MERGE_STDIN_EMPTY_MESSAGE,
10273
+ execute: (input) =>
10274
+ applyReviewAdjudication({
10275
+ prepared: input.prepared,
10276
+ decisions: input.adjudication.decisions,
10277
+ }),
10278
+ invalidUtf8Message: CE_REVIEW_MERGE_STDIN_INVALID_UTF8_MESSAGE,
10279
+ oversizedMessage: CE_REVIEW_MERGE_STDIN_OVERSIZED_MESSAGE,
10280
+ readFailedMessage: CE_REVIEW_MERGE_STDIN_READ_FAILED_MESSAGE,
10281
+ rejectedMessage: CE_REVIEW_MERGE_REJECTED_MESSAGE,
10282
+ schema: MergeInputSchema,
10283
+ ttyMessage: CE_REVIEW_MERGE_STDIN_TTY_MESSAGE,
10284
+ usage: CE_REVIEW_MERGE_USAGE,
10285
+ })
10286
+ }
10287
+ function runFinalizeSubcommand(options, outputSink, errorSink) {
10288
+ return runAggregateStdinSubcommand(options, outputSink, errorSink, {
10289
+ emptyMessage: CE_REVIEW_FINALIZE_STDIN_EMPTY_MESSAGE,
10290
+ execute: (input) => finalizeReview(input),
10291
+ invalidUtf8Message: CE_REVIEW_FINALIZE_STDIN_INVALID_UTF8_MESSAGE,
10292
+ oversizedMessage: CE_REVIEW_FINALIZE_STDIN_OVERSIZED_MESSAGE,
10293
+ readFailedMessage: CE_REVIEW_FINALIZE_STDIN_READ_FAILED_MESSAGE,
10294
+ rejectedMessage: CE_REVIEW_FINALIZE_REJECTED_MESSAGE,
10295
+ schema: FinalizeInputSchema,
10296
+ ttyMessage: CE_REVIEW_FINALIZE_STDIN_TTY_MESSAGE,
10297
+ usage: CE_REVIEW_FINALIZE_USAGE,
10298
+ })
10299
+ }
10300
+ var processExceptionBoundaryInstalled = false
10301
+ var activeInvocation = {
10302
+ errorSink: (message) => console.error(message),
10303
+ phase: 'unknown',
10304
+ }
10305
+ function formatInternalErrorMessage(phase) {
10306
+ return `ce-review-validator: internal error in ${phase}`
10307
+ }
10308
+ function installProcessExceptionBoundary() {
10309
+ if (processExceptionBoundaryInstalled) return
10310
+ processExceptionBoundaryInstalled = true
10311
+ const handleFatal = () => {
10312
+ activeInvocation.errorSink(
10313
+ formatInternalErrorMessage(activeInvocation.phase),
10314
+ )
10315
+ process.exitCode = 1
10316
+ }
10317
+ process.on('unhandledRejection', handleFatal)
10318
+ process.on('uncaughtException', handleFatal)
10319
+ }
10320
+ function runCeReviewValidator(options) {
10321
+ const outputSink = options.outputSink ?? ((message) => console.log(message))
10322
+ const errorSink = options.errorSink ?? ((message) => console.error(message))
10323
+ const subcommand = options.argv[0]
10324
+ activeInvocation = { errorSink, phase: subcommand ?? 'unknown' }
10325
+ installProcessExceptionBoundary()
10326
+ try {
10327
+ if (subcommand === 'return') {
10328
+ return runReviewReturnValidator({
10329
+ argv: [
10330
+ 'systematic',
10331
+ 'validate-review-return',
10332
+ ...options.argv.slice(1),
10333
+ ],
10334
+ isTTY: options.isTTY,
10335
+ readChunk: options.readChunk,
10336
+ outputSink,
10337
+ errorSink,
10338
+ })
10339
+ }
10340
+ if (subcommand === 'artifact') {
10341
+ return runClaudeCodeValidator({
10342
+ argv: options.argv.slice(1),
10343
+ cwd: options.cwd,
10344
+ outputSink,
10345
+ errorSink,
10346
+ })
10347
+ }
10348
+ if (subcommand === 'screen') {
10349
+ return runScreenSubcommand(options, outputSink, errorSink)
10350
+ }
10351
+ if (subcommand === 'prepare') {
10352
+ return runPrepareSubcommand(options, outputSink, errorSink)
10353
+ }
10354
+ if (subcommand === 'merge') {
10355
+ return runMergeSubcommand(options, outputSink, errorSink)
10356
+ }
10357
+ if (subcommand === 'finalize') {
10358
+ return runFinalizeSubcommand(options, outputSink, errorSink)
10359
+ }
10360
+ errorSink(CE_REVIEW_VALIDATOR_USAGE)
10361
+ return 2
10362
+ } catch {
10363
+ errorSink(formatInternalErrorMessage(activeInvocation.phase))
10364
+ return 1
10365
+ }
10366
+ }
10367
+ function resolveRealPath(candidate) {
10368
+ if (candidate === undefined) return
10369
+ try {
10370
+ return fs3.realpathSync(candidate)
10371
+ } catch {
10372
+ return
10373
+ }
10374
+ }
10375
+ var entryPath = resolveRealPath(process.argv[1])
10376
+ var modulePath = resolveRealPath(fileURLToPath(import.meta.url))
10377
+ var isMainModule =
10378
+ entryPath !== undefined &&
10379
+ modulePath !== undefined &&
10380
+ entryPath === modulePath
10381
+ if (isMainModule) {
10382
+ process.exitCode = runCeReviewValidator({ argv: process.argv.slice(2) })
10383
+ }
10384
+ export {
10385
+ CE_REVIEW_FINALIZE_REJECTED_MESSAGE,
10386
+ CE_REVIEW_FINALIZE_STDIN_EMPTY_MESSAGE,
10387
+ CE_REVIEW_FINALIZE_STDIN_INVALID_UTF8_MESSAGE,
10388
+ CE_REVIEW_FINALIZE_STDIN_OVERSIZED_MESSAGE,
10389
+ CE_REVIEW_FINALIZE_STDIN_READ_FAILED_MESSAGE,
10390
+ CE_REVIEW_FINALIZE_STDIN_TTY_MESSAGE,
10391
+ CE_REVIEW_FINALIZE_USAGE,
10392
+ CE_REVIEW_MERGE_REJECTED_MESSAGE,
10393
+ CE_REVIEW_MERGE_STDIN_EMPTY_MESSAGE,
10394
+ CE_REVIEW_MERGE_STDIN_INVALID_UTF8_MESSAGE,
10395
+ CE_REVIEW_MERGE_STDIN_OVERSIZED_MESSAGE,
10396
+ CE_REVIEW_MERGE_STDIN_READ_FAILED_MESSAGE,
10397
+ CE_REVIEW_MERGE_STDIN_TTY_MESSAGE,
10398
+ CE_REVIEW_MERGE_USAGE,
10399
+ CE_REVIEW_PREPARE_REJECTED_MESSAGE,
10400
+ CE_REVIEW_PREPARE_STDIN_EMPTY_MESSAGE,
10401
+ CE_REVIEW_PREPARE_STDIN_INVALID_UTF8_MESSAGE,
10402
+ CE_REVIEW_PREPARE_STDIN_OVERSIZED_MESSAGE,
10403
+ CE_REVIEW_PREPARE_STDIN_READ_FAILED_MESSAGE,
10404
+ CE_REVIEW_PREPARE_STDIN_TTY_MESSAGE,
10405
+ CE_REVIEW_PREPARE_USAGE,
10406
+ CE_REVIEW_SCREEN_INTERNAL_ERROR_MESSAGE,
10407
+ CE_REVIEW_SCREEN_REJECTED_MESSAGE,
10408
+ CE_REVIEW_SCREEN_STDIN_EMPTY_MESSAGE,
10409
+ CE_REVIEW_SCREEN_STDIN_INVALID_UTF8_MESSAGE,
10410
+ CE_REVIEW_SCREEN_STDIN_OVERSIZED_MESSAGE,
10411
+ CE_REVIEW_SCREEN_STDIN_READ_FAILED_MESSAGE,
10412
+ CE_REVIEW_SCREEN_STDIN_TTY_MESSAGE,
10413
+ CE_REVIEW_SCREEN_USAGE,
10414
+ CE_REVIEW_VALIDATOR_USAGE,
10415
+ runCeReviewValidator,
7196
10416
  }
7197
- export { CE_REVIEW_VALIDATOR_USAGE, runCeReviewValidator }