@fro.bot/systematic 3.18.3 → 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 +10 -16
- 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/package.json +1 -1
- 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 +3277 -58
|
@@ -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) {
|
|
@@ -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 }
|