@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.
- package/HARNESSES.md +8 -4
- package/dist/ce-review-validator.d.ts +43 -2
- package/dist/cli.js +12 -18
- package/dist/{index-nrgffx9y.js → index-be0zwq50.js} +26 -26
- package/dist/index.js +1 -1
- package/dist/lib/review-artifact-schema.d.ts +18 -1
- package/dist/lib/review-pipeline-contract.d.ts +1446 -0
- package/dist/lib/review-pipeline.d.ts +812 -0
- package/dist/lib/review-return-validator.d.ts +19 -0
- package/dist/pi.js +26 -26
- package/package.json +3 -3
- package/skills/ce-review/SKILL.md +54 -83
- package/skills/ce-review/references/pipeline-invocation.md +263 -0
- package/skills/ce-review/references/review-output-template.md +2 -1
- package/skills/ce-review/references/review-pipeline-schema.json +391 -0
- package/skills/ce-review/references/subagent-template.md +1 -1
- package/skills/ce-review/references/synthesis-artifact-contract.md +57 -68
- package/skills/ce-review/scripts/validate-review.mjs +3305 -86
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
1726
|
+
patch: 2,
|
|
1726
1727
|
}
|
|
1727
1728
|
|
|
1728
|
-
// node_modules/.bun/zod@4.6.
|
|
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
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
}
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 =
|
|
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 >
|
|
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/
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
|
|
7157
|
-
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
|
|
7165
|
-
|
|
7166
|
-
|
|
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
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
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
|
-
|
|
7178
|
-
|
|
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
|
-
|
|
7181
|
-
|
|
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
|
|
7611
|
+
return Buffer.byteLength(JSON.stringify(rawInput), 'utf8')
|
|
7184
7612
|
} catch {
|
|
7185
7613
|
return
|
|
7186
7614
|
}
|
|
7187
7615
|
}
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
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 }
|