gentle-pi 2.1.2 → 2.3.0
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/README.md +108 -79
- package/assets/agents/gentle-ai-worker.md +7 -3
- package/assets/agents/jd-fix-agent.md +1 -1
- package/assets/agents/jd-judge-a.md +3 -1
- package/assets/agents/jd-judge-b.md +3 -1
- package/assets/agents/review-readability.md +4 -3
- package/assets/agents/review-reliability.md +4 -3
- package/assets/agents/review-resilience.md +4 -3
- package/assets/agents/review-risk.md +4 -3
- package/assets/agents/sdd-apply.md +11 -4
- package/assets/agents/sdd-archive.md +6 -1
- package/assets/agents/sdd-design.md +6 -1
- package/assets/agents/sdd-explore.md +6 -2
- package/assets/agents/sdd-init.md +10 -2
- package/assets/agents/sdd-onboard.md +6 -1
- package/assets/agents/sdd-proposal.md +8 -1
- package/assets/agents/sdd-research.md +54 -0
- package/assets/agents/sdd-spec.md +6 -1
- package/assets/agents/sdd-status.md +10 -5
- package/assets/agents/sdd-sync.md +6 -1
- package/assets/agents/sdd-tasks.md +8 -4
- package/assets/agents/sdd-verify.md +27 -2
- package/assets/chains/4r-review.chain.md +2 -0
- package/assets/chains/sdd-full.chain.md +2 -2
- package/assets/chains/sdd-plan.chain.md +1 -1
- package/assets/chains/sdd-verify.chain.md +2 -2
- package/assets/orchestrator-delegation.md +145 -160
- package/assets/orchestrator-memory.md +2 -0
- package/assets/orchestrator.md +25 -48
- package/assets/sdd-orchestrator-workflow.md +163 -25
- package/assets/support/sdd-status-contract.md +24 -6
- package/contracts/review-integration/v1/fixtures/consent.fixture.json +3 -3
- package/contracts/review-integration/v1/fixtures/start-v2.fixture.json +19 -28
- package/contracts/review-integration/v1/fixtures/start.fixture.json +1 -10
- package/contracts/review-integration/v1/fixtures/status-v2.fixture.json +12 -21
- package/contracts/review-integration/v1/schemas/correction-plan-request.schema.json +49 -0
- package/contracts/review-integration/v1/schemas/operation.schema.json +76 -0
- package/contracts/review-integration/v1/schemas/repair.schema.json +39 -0
- package/contracts/review-integration/v1/schemas/status-v2.schema.json +4 -2
- package/contracts/review-integration/v1/schemas/status.schema.json +4 -2
- package/contracts/review-integration/v1/schemas/transition-execution.schema.json +42 -0
- package/contracts/review-integration/v2/fixtures/consent.fixture.json +1 -1
- package/contracts/review-integration/v2/fixtures/start.fixture.json +1 -10
- package/contracts/review-integration/v2/fixtures/status.fixture.json +1 -10
- package/contracts/review-integration/v2/schemas/failure.schema.json +5 -1
- package/contracts/review-integration/v2/schemas/last-event-closure.schema.json +66 -0
- package/contracts/review-integration/v2/schemas/opencode-provider-role.schema.json +14 -0
- package/contracts/review-integration/v2/schemas/operation.schema.json +6 -1
- package/contracts/review-integration/v2/schemas/repair.schema.json +4 -2
- package/contracts/review-integration/v2/schemas/start.schema.json +5 -2
- package/contracts/review-integration/v2/schemas/status.schema.json +4 -2
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +30 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/README.md +12 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/manifest.json +65 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/lens.schema.json +16 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/refuter.schema.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/targeted-validator.schema.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/lens.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/refuter.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/targeted-validator.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/generated/provider-capabilities.baseline.json +15 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/generated/provider-roles.baseline.json +42 -0
- package/docs/native-authority-architecture.md +14 -16
- package/docs/review-integration.md +27 -353
- package/extensions/ask-user-choice.ts +151 -0
- package/extensions/gentle-ai.ts +2710 -3211
- package/extensions/quiet-tools.ts +515 -32
- package/extensions/sdd-init.ts +21 -12
- package/extensions/skill-registry.ts +10 -2
- package/extensions/startup-banner.ts +10 -4
- package/lib/gentle-ai-binary.ts +173 -2
- package/lib/gentle-ai-renderer.ts +70 -0
- package/lib/model-routing-authority.ts +133 -0
- package/lib/native-review-cli.ts +676 -781
- package/lib/opaque-pi-reviewer-adapter.ts +284 -0
- package/lib/provider-contract-bundle.ts +704 -0
- package/lib/review-candidate-view.ts +836 -118
- package/lib/review-compact-contract.ts +59 -248
- package/lib/review-host-relay.ts +578 -0
- package/lib/review-integration-v2.ts +1254 -221
- package/lib/review-last-event-controller.ts +35 -0
- package/lib/review-relay-contract.ts +16 -0
- package/lib/sdd-preflight.ts +230 -67
- package/lib/sdd-status.ts +66 -111
- package/lib/terminal-theme.ts +1 -1
- package/package.json +83 -79
- package/runtime/gentle-ai-binary.mjs +174 -3
- package/runtime/native-review-cli.mjs +610 -715
- package/runtime/review-integration-v2.mjs +1223 -190
- package/runtime/review-relay-contract.mjs +17 -0
- package/scripts/{build-git-commit-transaction-runner.mjs → build-runtime-modules.mjs} +5 -5
- package/scripts/check-provider-contract.mjs +138 -0
- package/scripts/gentle-ai-installer.mjs +85 -22
- package/scripts/maintainer/provider-relay-matrix.mjs +403 -0
- package/scripts/mirror-provider-contract.mjs +143 -0
- package/scripts/test-packed-runner.mjs +20 -9
- package/scripts/verify-package-files.mjs +119 -43
- package/skills/_shared/review-ledger-contract.md +11 -19
- package/skills/chained-pr/SKILL.md +3 -0
- package/skills/cognitive-doc-design/SKILL.md +1 -1
- package/skills/comment-writer/SKILL.md +1 -1
- package/skills/gentle-ai/SKILL.md +7 -74
- package/skills/issue-creation/SKILL.md +94 -168
- package/skills/judgment-day/SKILL.md +9 -5
- package/skills/judgment-day/references/prompts-and-formats.md +2 -0
- package/skills/rdd-defect-workflow/SKILL.md +54 -0
- package/skills/release/SKILL.md +3 -3
- package/skills/skill-registry/SKILL.md +1 -1
- package/skills/work-unit-commits/SKILL.md +3 -1
- package/tests/artifact-language.test.ts +24 -11
- package/tests/ask-user-choice.test.ts +264 -0
- package/tests/background-subagents.test.ts +771 -0
- package/tests/codegraph-tools.test.ts +3 -3
- package/tests/crosslane/cross-lane.mjs +16 -0
- package/tests/delegated-key-learnings-contract.test.ts +240 -0
- package/tests/devbinary/native-review-parity.devtest.ts +167 -250
- package/tests/devbinary/pi-host-relay.devtest.ts +867 -0
- package/tests/fixtures/devbinary/capabilities-v2.1.derived.json +331 -0
- package/tests/fixtures/devbinary/capabilities-v2.2.captured.json +340 -0
- package/tests/fixtures/devbinary/consent-v3.captured.json +37 -0
- package/tests/fixtures/devbinary/failure-v2-capture-evidence.captured.json +16 -0
- package/tests/fixtures/devbinary/last-event-capture-correction-plan.captured.json +10 -0
- package/tests/fixtures/devbinary/last-event-capture-refuter-approved.captured.json +20 -0
- package/tests/fixtures/devbinary/last-event-capture-refuter-correction-required.captured.json +26 -0
- package/tests/fixtures/devbinary/last-event-capture-result-approved.captured.json +8 -0
- package/tests/fixtures/devbinary/last-event-capture-result-correction-required.captured.json +26 -0
- package/tests/fixtures/devbinary/last-event-capture-validation-approved.captured.json +8 -0
- package/tests/fixtures/devbinary/last-event-closure.provenance.md +13 -0
- package/tests/fixtures/devbinary/result-artifact-v2-path.captured.json +12 -0
- package/tests/fixtures/devbinary/result-artifact-v2.captured.json +12 -0
- package/tests/fixtures/devbinary/review-acknowledged-v1.captured.json +9 -0
- package/tests/fixtures/devbinary/review-acknowledged.provenance.md +31 -0
- package/tests/fixtures/devbinary/start-v3-consent-declined.captured.json +19 -0
- package/tests/fixtures/devbinary/start-v3-consent-granted.captured.json +109 -0
- package/tests/fixtures/devbinary/start-v3-zero-lens-closed.captured.json +21 -0
- package/tests/fixtures/devbinary/status-v5-capture-result-submission.captured.json +184 -0
- package/tests/fixtures/devbinary/status-v5-repository-context.captured.json +138 -0
- package/tests/fixtures/devbinary/status-v5.captured.json +88 -0
- package/tests/fixtures/native-review-cli/v2.5.0-rc.1/PROVENANCE.txt +15 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/README.md +12 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/manifest.json +65 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/lens.schema.json +16 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/refuter.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/targeted-validator.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/lens.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/refuter.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/targeted-validator.json +1 -0
- package/tests/gentle-ai-binary.test.ts +83 -13
- package/tests/gentle-ai-dev-binary-surfacing.test.ts +195 -0
- package/tests/gentle-ai-dev-binary.test.ts +159 -0
- package/tests/gentle-ai-installer.test.ts +138 -48
- package/tests/gentle-ai.test.ts +820 -131
- package/tests/gentle-theme.test.ts +133 -0
- package/tests/maintainer/provider-relay.maintest.ts +601 -0
- package/tests/model-routing-authority.test.ts +257 -0
- package/tests/native-review-capability-contract.test.ts +75 -2
- package/tests/native-review-cli.test.ts +588 -908
- package/tests/native-review-consent.test.ts +283 -38
- package/tests/native-review-parity-runtime.test.ts +108 -350
- package/tests/native-review-parity.test.ts +684 -732
- package/tests/native-sdd-attempt-authority.test.ts +235 -0
- package/tests/opaque-pi-reviewer-adapter.test.ts +266 -0
- package/tests/orchestrator-budget.test.ts +158 -89
- package/tests/orchestrator-rdd-ownership.test.ts +103 -0
- package/tests/package-manifest.test.ts +128 -100
- package/tests/provider-contract-bundle.test.ts +385 -0
- package/tests/provider-contract-mirror.test.ts +206 -0
- package/tests/provider-defect-handoff.test.ts +252 -0
- package/tests/quiet-tool-rendering.test.ts +1055 -28
- package/tests/review-actor-tool-deny.test.ts +12 -13
- package/tests/review-authority-recovery-docs.test.ts +1 -2
- package/tests/review-candidate-view.test.ts +898 -12
- package/tests/review-compact-contract.test.ts +29 -122
- package/tests/review-controller-lock-status.test.ts +2 -2
- package/tests/review-controller-native-recovery.test.ts +366 -857
- package/tests/review-controller-native-routing.test.ts +1253 -4199
- package/tests/review-controller-retired-ops.test.ts +1 -1
- package/tests/review-controller-workspace-root.test.ts +236 -70
- package/tests/review-controller.test.ts +26 -816
- package/tests/review-corrected-finalize-binding.test.ts +134 -0
- package/tests/review-dispatch-hydration-gap.test.ts +145 -0
- package/tests/review-gate.test.ts +0 -45
- package/tests/review-host-relay-restart-parity.test.ts +360 -0
- package/tests/review-host-relay-routing.test.ts +352 -0
- package/tests/review-host-relay.test.ts +754 -0
- package/tests/review-integration-v2-forward.test.ts +654 -0
- package/tests/review-integration-v2.test.ts +317 -129
- package/tests/review-last-event-closure.test.ts +408 -0
- package/tests/review-ledger-contract.test.ts +106 -60
- package/tests/review-recovered-lineage-routing.test.ts +199 -0
- package/tests/review-relay-transport-agent.test.ts +322 -0
- package/tests/review-snapshot.test.ts +3 -2
- package/tests/runtime-harness.mjs +573 -180
- package/tests/sdd-agent-tools.test.ts +53 -32
- package/tests/sdd-preflight.test.ts +81 -15
- package/tests/sdd-status.test.ts +109 -110
- package/tests/skill-collision-prefixes.test.ts +6 -8
- package/tests/skill-registry.test.ts +50 -1
- package/tests/verify-package-files.test.ts +62 -0
- package/tests/writer-edit-surface-scope.test.ts +230 -0
- package/themes/Gentleman-Cute.json +94 -0
- package/themes/Gentleman-Sexy.json +92 -0
- package/assets/agents/review-refuter.md +0 -40
- package/assets/agents/review-validator.md +0 -23
- package/lib/git-commit-transaction.ts +0 -801
- package/lib/native-review-remediation.ts +0 -49
- package/lib/review-compact.ts +0 -947
- package/lib/review-refuter-adapter.ts +0 -129
- package/lib/review-runtime-contract.ts +0 -68
- package/prompts/gcl.md +0 -54
- package/prompts/gis.md +0 -25
- package/prompts/gpr.md +0 -41
- package/prompts/gwr.md +0 -31
- package/runtime/git-commit-transaction.mjs +0 -802
- package/scripts/run-git-commit-transaction.mjs +0 -35
- package/tests/fixtures/native-review-cli/v2.1.2/bind-sdd.json +0 -25
- package/tests/fixtures/native-review-cli/v2.1.2/finalize.json +0 -8
- package/tests/fixtures/native-review-cli/v2.1.2/sdd-status-engram.json +0 -139
- package/tests/fixtures/native-review-cli/v2.1.2/sdd-status.json +0 -200
- package/tests/fixtures/native-review-cli/v2.1.2/start.json +0 -12
- package/tests/fixtures/native-review-cli/v2.1.2/validate-allow.json +0 -24
- package/tests/fixtures/native-review-cli/v2.1.2/validate-deny-empty-context.json +0 -20
- package/tests/fixtures/native-review-cli/v2.1.2/validate-deny.json +0 -28
- package/tests/fixtures/native-review-cli/v2.1.3/sdd-status-engram.json +0 -139
- package/tests/fixtures/native-review-cli/v2.1.3/sdd-status.json +0 -200
- package/tests/git-commit-transaction.test.ts +0 -302
- package/tests/review-compact.test.ts +0 -243
- package/tests/review-refuter-adapter.test.ts +0 -89
package/lib/review-compact.ts
DELETED
|
@@ -1,947 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { canonicalJsonV1, domainHashV1 } from "./review-canonical.ts";
|
|
3
|
-
import {
|
|
4
|
-
REVIEW_RISK_TIER,
|
|
5
|
-
correctionBudget,
|
|
6
|
-
type ReviewRiskTier,
|
|
7
|
-
} from "./review-risk.ts";
|
|
8
|
-
import {
|
|
9
|
-
REVIEW_MODE,
|
|
10
|
-
type CorrectionSnapshotV1,
|
|
11
|
-
type SnapshotV1,
|
|
12
|
-
} from "./review-snapshot.ts";
|
|
13
|
-
import {
|
|
14
|
-
FULL_4R_LENSES,
|
|
15
|
-
REVIEW_LENS,
|
|
16
|
-
type ReviewLens,
|
|
17
|
-
} from "./review-triggers.ts";
|
|
18
|
-
import {
|
|
19
|
-
assertReviewRuntimeIdentity,
|
|
20
|
-
loadedReviewRuntimeIdentity,
|
|
21
|
-
type LoadedReviewRuntimeIdentityV1,
|
|
22
|
-
} from "./review-runtime-contract.ts";
|
|
23
|
-
|
|
24
|
-
export const COMPACT_REVIEW_STATE = {
|
|
25
|
-
REVIEWING: "reviewing",
|
|
26
|
-
CORRECTION_REQUIRED: "correction_required",
|
|
27
|
-
VALIDATING: "validating",
|
|
28
|
-
APPROVED: "approved",
|
|
29
|
-
ESCALATED: "escalated",
|
|
30
|
-
} as const;
|
|
31
|
-
|
|
32
|
-
export type CompactReviewStateName =
|
|
33
|
-
(typeof COMPACT_REVIEW_STATE)[keyof typeof COMPACT_REVIEW_STATE];
|
|
34
|
-
|
|
35
|
-
export const CAUSAL_DISPOSITION = {
|
|
36
|
-
INTRODUCED: "introduced",
|
|
37
|
-
BEHAVIOR_ACTIVATED: "behavior-activated",
|
|
38
|
-
WORSENED: "worsened",
|
|
39
|
-
PRE_EXISTING: "pre-existing",
|
|
40
|
-
BASE_ONLY: "base-only",
|
|
41
|
-
UNKNOWN: "unknown",
|
|
42
|
-
} as const;
|
|
43
|
-
|
|
44
|
-
export type CausalDisposition =
|
|
45
|
-
(typeof CAUSAL_DISPOSITION)[keyof typeof CAUSAL_DISPOSITION];
|
|
46
|
-
|
|
47
|
-
export const COMPACT_EVIDENCE_CLASS = {
|
|
48
|
-
DETERMINISTIC: "deterministic",
|
|
49
|
-
INFERENTIAL: "inferential",
|
|
50
|
-
INSUFFICIENT: "insufficient",
|
|
51
|
-
INFO: "info",
|
|
52
|
-
} as const;
|
|
53
|
-
|
|
54
|
-
export type CompactEvidenceClass =
|
|
55
|
-
(typeof COMPACT_EVIDENCE_CLASS)[keyof typeof COMPACT_EVIDENCE_CLASS];
|
|
56
|
-
|
|
57
|
-
export const COMPACT_FINDING_OUTCOME = {
|
|
58
|
-
CORROBORATED: "corroborated",
|
|
59
|
-
REFUTED: "refuted",
|
|
60
|
-
INCONCLUSIVE: "inconclusive",
|
|
61
|
-
INFO: "info",
|
|
62
|
-
} as const;
|
|
63
|
-
|
|
64
|
-
export type CompactFindingOutcome =
|
|
65
|
-
(typeof COMPACT_FINDING_OUTCOME)[keyof typeof COMPACT_FINDING_OUTCOME];
|
|
66
|
-
|
|
67
|
-
export const COMPACT_SEVERITY = {
|
|
68
|
-
BLOCKER: "BLOCKER",
|
|
69
|
-
CRITICAL: "CRITICAL",
|
|
70
|
-
WARNING: "WARNING",
|
|
71
|
-
SUGGESTION: "SUGGESTION",
|
|
72
|
-
} as const;
|
|
73
|
-
|
|
74
|
-
export type CompactSeverity =
|
|
75
|
-
(typeof COMPACT_SEVERITY)[keyof typeof COMPACT_SEVERITY];
|
|
76
|
-
|
|
77
|
-
export const CAUSAL_PROOF_KIND = {
|
|
78
|
-
CHANGED_HUNK: "changed-hunk",
|
|
79
|
-
CANDIDATE_CREATED_PATH: "candidate-created-path",
|
|
80
|
-
DIFFERENTIAL_TEST: "differential-test",
|
|
81
|
-
BEFORE_AFTER: "before-after",
|
|
82
|
-
} as const;
|
|
83
|
-
|
|
84
|
-
export type CausalProofKind =
|
|
85
|
-
(typeof CAUSAL_PROOF_KIND)[keyof typeof CAUSAL_PROOF_KIND];
|
|
86
|
-
|
|
87
|
-
export interface CompactFindingInput {
|
|
88
|
-
id?: string;
|
|
89
|
-
lens?: string;
|
|
90
|
-
location?: string;
|
|
91
|
-
severity?: string;
|
|
92
|
-
claim?: string;
|
|
93
|
-
evidence_class?: string;
|
|
94
|
-
causal_disposition?: string;
|
|
95
|
-
proof_refs?: string[];
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export interface CompactFinding {
|
|
99
|
-
id: string;
|
|
100
|
-
lens: ReviewLens;
|
|
101
|
-
location: string;
|
|
102
|
-
severity: CompactSeverity;
|
|
103
|
-
claim: string;
|
|
104
|
-
evidence_class: CompactEvidenceClass;
|
|
105
|
-
causal_disposition: CausalDisposition;
|
|
106
|
-
proof_refs: string[];
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export interface CompactLensResultInput {
|
|
110
|
-
lens?: string;
|
|
111
|
-
findings: CompactFindingInput[];
|
|
112
|
-
evidence: string[];
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export interface CompactLensResult {
|
|
116
|
-
lens: ReviewLens;
|
|
117
|
-
findings: CompactFinding[];
|
|
118
|
-
evidence: string[];
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export interface CompactRefuterResultInput {
|
|
122
|
-
finding_id: string;
|
|
123
|
-
outcome: string;
|
|
124
|
-
proof_refs: string[];
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export interface CompactRefuterResult {
|
|
128
|
-
finding_id: string;
|
|
129
|
-
outcome: Exclude<CompactFindingOutcome, "info">;
|
|
130
|
-
proof_refs: string[];
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export interface CompactFollowUp {
|
|
134
|
-
finding_id: string;
|
|
135
|
-
location: string;
|
|
136
|
-
summary: string;
|
|
137
|
-
proof_refs: string[];
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export interface CompactReviewResultInput {
|
|
141
|
-
lens_results: CompactLensResultInput[];
|
|
142
|
-
refuter_request_hash?: string;
|
|
143
|
-
refuter_results?: CompactRefuterResultInput[];
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export interface CompactRefuterRequest {
|
|
147
|
-
request_hash: string;
|
|
148
|
-
findings: CompactFinding[];
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export interface CompactValidationCheckInput {
|
|
152
|
-
passed: boolean;
|
|
153
|
-
evidence: string[];
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export interface CompactValidationProofInput {
|
|
157
|
-
original_criteria: CompactValidationCheckInput;
|
|
158
|
-
correction_regression: CompactValidationCheckInput;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export const COMPACT_VALIDATOR_PURPOSE = { TARGETED_CORRECTION: "targeted-correction-validation" } as const;
|
|
162
|
-
|
|
163
|
-
export const COMPACT_VALIDATOR_SCOPE = { ORIGINAL_CRITERIA: "original-criteria", CORRECTION_REGRESSION: "correction-regression" } as const;
|
|
164
|
-
|
|
165
|
-
export interface CompactValidatorRequestBody {
|
|
166
|
-
schema: "gentle-ai.compact-validator-request/v1";
|
|
167
|
-
purpose: (typeof COMPACT_VALIDATOR_PURPOSE)[keyof typeof COMPACT_VALIDATOR_PURPOSE];
|
|
168
|
-
scope: Array<(typeof COMPACT_VALIDATOR_SCOPE)[keyof typeof COMPACT_VALIDATOR_SCOPE]>;
|
|
169
|
-
lineage_id: string;
|
|
170
|
-
candidate_tree: string;
|
|
171
|
-
fix_diff_hash: string;
|
|
172
|
-
correction_ids: string[];
|
|
173
|
-
correction_paths: string[];
|
|
174
|
-
frozen_rows: CompactFinding[];
|
|
175
|
-
frozen_ledger_hash: string;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
export interface CompactValidatorRequest {
|
|
179
|
-
body: CompactValidatorRequestBody;
|
|
180
|
-
request_hash: string;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
export interface CompactTargetedValidationInput {
|
|
184
|
-
request_hash: string;
|
|
185
|
-
correction_ids: string[];
|
|
186
|
-
original_criteria: CompactValidationCheckInput;
|
|
187
|
-
correction_regression: CompactValidationCheckInput;
|
|
188
|
-
fix_caused_findings?: CompactFindingInput[];
|
|
189
|
-
follow_ups: CompactFollowUp[];
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
export interface CompactTargetedValidation {
|
|
193
|
-
correction_ids: string[];
|
|
194
|
-
original_criteria: CompactValidationCheckInput;
|
|
195
|
-
correction_regression: CompactValidationCheckInput;
|
|
196
|
-
follow_ups: CompactFollowUp[];
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
export interface CompactCorrectionRecord {
|
|
200
|
-
candidate_tree: string;
|
|
201
|
-
changed_paths: string[];
|
|
202
|
-
changed_lines: number;
|
|
203
|
-
fix_diff_hash: string;
|
|
204
|
-
correction_ids: string[];
|
|
205
|
-
intended_untracked: string[];
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
export interface CompactReviewStateV2 {
|
|
209
|
-
schema: "gentle-ai.review-state/v2";
|
|
210
|
-
lineage_id: string;
|
|
211
|
-
generation: 1;
|
|
212
|
-
mode: typeof REVIEW_MODE.ORDINARY;
|
|
213
|
-
state: CompactReviewStateName;
|
|
214
|
-
initial_snapshot: SnapshotV1;
|
|
215
|
-
current_candidate_tree: string;
|
|
216
|
-
genesis_paths: string[];
|
|
217
|
-
intended_untracked: string[];
|
|
218
|
-
policy_hash: string;
|
|
219
|
-
risk_tier: ReviewRiskTier;
|
|
220
|
-
selected_lenses: readonly ReviewLens[];
|
|
221
|
-
original_changed_lines: number;
|
|
222
|
-
correction_budget: number;
|
|
223
|
-
lens_results: CompactLensResult[];
|
|
224
|
-
findings: CompactFinding[];
|
|
225
|
-
outcomes: Record<string, CompactFindingOutcome>;
|
|
226
|
-
correction_ids: string[];
|
|
227
|
-
follow_ups: CompactFollowUp[];
|
|
228
|
-
correction_line_forecast?: number;
|
|
229
|
-
validator_request?: CompactValidatorRequest;
|
|
230
|
-
runtime_identity: LoadedReviewRuntimeIdentityV1;
|
|
231
|
-
correction?: CompactCorrectionRecord;
|
|
232
|
-
validation?: CompactTargetedValidation;
|
|
233
|
-
final_evidence_hash?: string;
|
|
234
|
-
escalation_reasons: string[];
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
export interface CompactReceiptBodyV2 {
|
|
238
|
-
schema: "gentle-ai.review-receipt-body/v2";
|
|
239
|
-
lineage_id: string;
|
|
240
|
-
generation: 1;
|
|
241
|
-
authority_revision: string;
|
|
242
|
-
base_tree: string;
|
|
243
|
-
initial_review_tree: string;
|
|
244
|
-
final_candidate_tree: string;
|
|
245
|
-
genesis_paths_hash: string;
|
|
246
|
-
intended_untracked_hash: string;
|
|
247
|
-
policy_hash: string;
|
|
248
|
-
risk_tier: ReviewRiskTier;
|
|
249
|
-
selected_lenses: readonly ReviewLens[];
|
|
250
|
-
original_changed_lines: number;
|
|
251
|
-
correction_budget: number;
|
|
252
|
-
correction_ids: string[];
|
|
253
|
-
fix_diff_hash: string;
|
|
254
|
-
evidence_hash: string;
|
|
255
|
-
terminal_state: "approved" | "escalated";
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
export interface CompactReceiptEnvelopeV2 {
|
|
259
|
-
body: CompactReceiptBodyV2;
|
|
260
|
-
receipt_hash: string;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const DIGEST = /^[0-9a-f]{64}$/;
|
|
264
|
-
const OBJECT_ID = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
|
|
265
|
-
const LINEAGE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
266
|
-
const FINDING_ID = /^[A-Z][A-Z0-9-]*-[0-9]{3,}$/;
|
|
267
|
-
const PROOF_PREFIX = new RegExp(`^(?:${Object.values(CAUSAL_PROOF_KIND).join("|")}):\\S`);
|
|
268
|
-
const CANDIDATE_CAUSED = new Set<CausalDisposition>([
|
|
269
|
-
CAUSAL_DISPOSITION.INTRODUCED,
|
|
270
|
-
CAUSAL_DISPOSITION.BEHAVIOR_ACTIVATED,
|
|
271
|
-
CAUSAL_DISPOSITION.WORSENED,
|
|
272
|
-
]);
|
|
273
|
-
const SEVERE = new Set<CompactSeverity>([
|
|
274
|
-
COMPACT_SEVERITY.BLOCKER,
|
|
275
|
-
COMPACT_SEVERITY.CRITICAL,
|
|
276
|
-
]);
|
|
277
|
-
const LENS_PREFIX: Record<ReviewLens, string> = {
|
|
278
|
-
[REVIEW_LENS.RISK]: "RISK",
|
|
279
|
-
[REVIEW_LENS.RESILIENCE]: "RESILIENCE",
|
|
280
|
-
[REVIEW_LENS.READABILITY]: "READABILITY",
|
|
281
|
-
[REVIEW_LENS.RELIABILITY]: "RELIABILITY",
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
function clone<T>(value: T): T {
|
|
285
|
-
return JSON.parse(canonicalJsonV1(value)) as T;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
function canonicalStrings(values: readonly string[], label: string): string[] {
|
|
289
|
-
if (values.some((value) => typeof value !== "string" || value.trim() !== value || value.length === 0)) {
|
|
290
|
-
throw new Error(`${label} must contain non-empty canonical strings`);
|
|
291
|
-
}
|
|
292
|
-
return [...new Set(values)].toSorted();
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function equal(left: unknown, right: unknown): boolean {
|
|
296
|
-
return canonicalJsonV1(left) === canonicalJsonV1(right);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
function expectedLenses(tier: ReviewRiskTier): readonly ReviewLens[] {
|
|
300
|
-
if (tier === REVIEW_RISK_TIER.LOW) return [];
|
|
301
|
-
if (tier === REVIEW_RISK_TIER.HIGH) return FULL_4R_LENSES;
|
|
302
|
-
return [];
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
function assertSelectedLenses(
|
|
306
|
-
tier: ReviewRiskTier,
|
|
307
|
-
lenses: readonly ReviewLens[],
|
|
308
|
-
): void {
|
|
309
|
-
if (tier === REVIEW_RISK_TIER.MEDIUM) {
|
|
310
|
-
if (lenses.length !== 1 || !Object.values(REVIEW_LENS).includes(lenses[0]!)) {
|
|
311
|
-
throw new Error("Medium compact review requires exactly one selected lens");
|
|
312
|
-
}
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
if (!equal(lenses, expectedLenses(tier))) {
|
|
316
|
-
throw new Error("Compact selected lenses do not match the frozen risk tier");
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
function isConcreteProof(proofRefs: readonly string[]): boolean {
|
|
321
|
-
return proofRefs.length > 0 && proofRefs.every((proof) => PROOF_PREFIX.test(proof));
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
function normalizeSeverity(value: string | undefined): CompactSeverity | undefined {
|
|
325
|
-
const normalized = value?.trim().toUpperCase();
|
|
326
|
-
return Object.values(COMPACT_SEVERITY).find((severity) => severity === normalized);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function normalizeEvidenceClass(value: string | undefined): CompactEvidenceClass | undefined {
|
|
330
|
-
const normalized = value?.trim().toLowerCase();
|
|
331
|
-
if (normalized === "inferential-severe") return COMPACT_EVIDENCE_CLASS.INFERENTIAL;
|
|
332
|
-
return Object.values(COMPACT_EVIDENCE_CLASS).find((item) => item === normalized);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
function normalizeDisposition(value: string | undefined): CausalDisposition | undefined {
|
|
336
|
-
const normalized = value?.trim().toLowerCase();
|
|
337
|
-
return Object.values(CAUSAL_DISPOSITION).find((item) => item === normalized);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
function canonicalizeLensResults(
|
|
341
|
-
selectedLenses: readonly ReviewLens[],
|
|
342
|
-
inputs: readonly CompactLensResultInput[],
|
|
343
|
-
): { results: CompactLensResult[]; findings: CompactFinding[]; reasons: string[] } {
|
|
344
|
-
if (inputs.length !== selectedLenses.length) {
|
|
345
|
-
throw new Error(`Compact review requires all ${selectedLenses.length} selected lens results`);
|
|
346
|
-
}
|
|
347
|
-
const reasons: string[] = [];
|
|
348
|
-
const usedIds = new Set<string>();
|
|
349
|
-
const results = inputs.map((input, lensIndex): CompactLensResult => {
|
|
350
|
-
const lens = selectedLenses[lensIndex]!;
|
|
351
|
-
if (!Array.isArray(input.findings) || !Array.isArray(input.evidence)) {
|
|
352
|
-
throw new Error(`Compact lens result ${lensIndex + 1} requires explicit findings and evidence arrays`);
|
|
353
|
-
}
|
|
354
|
-
if (input.lens !== undefined && input.lens !== lens) {
|
|
355
|
-
reasons.push(`Lens result ${lensIndex + 1} claimed ${input.lens} instead of selected lens ${lens}.`);
|
|
356
|
-
}
|
|
357
|
-
const sorted = input.findings.map((finding, index) => ({ finding, index })).toSorted((left, right) => {
|
|
358
|
-
const leftKey = [left.finding.location ?? "", left.finding.claim ?? "", left.finding.severity ?? "", left.index];
|
|
359
|
-
const rightKey = [right.finding.location ?? "", right.finding.claim ?? "", right.finding.severity ?? "", right.index];
|
|
360
|
-
return canonicalJsonV1(leftKey).localeCompare(canonicalJsonV1(rightKey));
|
|
361
|
-
});
|
|
362
|
-
let nextId = 1;
|
|
363
|
-
const findings = sorted.map(({ finding }): CompactFinding => {
|
|
364
|
-
const severity = normalizeSeverity(finding.severity) ?? COMPACT_SEVERITY.CRITICAL;
|
|
365
|
-
const severe = SEVERE.has(severity);
|
|
366
|
-
const evidenceClass = severe
|
|
367
|
-
? normalizeEvidenceClass(finding.evidence_class) ?? COMPACT_EVIDENCE_CLASS.INSUFFICIENT
|
|
368
|
-
: COMPACT_EVIDENCE_CLASS.INFO;
|
|
369
|
-
const disposition = severe
|
|
370
|
-
? normalizeDisposition(finding.causal_disposition) ?? CAUSAL_DISPOSITION.UNKNOWN
|
|
371
|
-
: normalizeDisposition(finding.causal_disposition) ?? CAUSAL_DISPOSITION.UNKNOWN;
|
|
372
|
-
const proofRefs = Array.isArray(finding.proof_refs)
|
|
373
|
-
? finding.proof_refs.filter((proof): proof is string => typeof proof === "string").toSorted()
|
|
374
|
-
: [];
|
|
375
|
-
let id = finding.id?.trim().toUpperCase() ?? "";
|
|
376
|
-
while (!id || !FINDING_ID.test(id) || usedIds.has(id)) {
|
|
377
|
-
if (id && severe) reasons.push(`Finding ID ${id} was malformed or duplicated and was replaced natively.`);
|
|
378
|
-
id = `${LENS_PREFIX[lens]}-${String(nextId).padStart(3, "0")}`;
|
|
379
|
-
nextId += 1;
|
|
380
|
-
if (!usedIds.has(id)) break;
|
|
381
|
-
}
|
|
382
|
-
usedIds.add(id);
|
|
383
|
-
const location = finding.location?.trim() || "unknown:0";
|
|
384
|
-
const claim = finding.claim?.trim() || "Malformed severe finding without a concrete claim.";
|
|
385
|
-
if (severe && (
|
|
386
|
-
!normalizeSeverity(finding.severity) ||
|
|
387
|
-
!finding.location?.trim() ||
|
|
388
|
-
!finding.claim?.trim() ||
|
|
389
|
-
!normalizeEvidenceClass(finding.evidence_class) ||
|
|
390
|
-
!normalizeDisposition(finding.causal_disposition) ||
|
|
391
|
-
!isConcreteProof(proofRefs)
|
|
392
|
-
)) {
|
|
393
|
-
reasons.push(`${id} is a malformed or insufficient severe claim.`);
|
|
394
|
-
}
|
|
395
|
-
return {
|
|
396
|
-
id,
|
|
397
|
-
lens,
|
|
398
|
-
location,
|
|
399
|
-
severity,
|
|
400
|
-
claim,
|
|
401
|
-
evidence_class: evidenceClass,
|
|
402
|
-
causal_disposition: disposition,
|
|
403
|
-
proof_refs: proofRefs,
|
|
404
|
-
};
|
|
405
|
-
});
|
|
406
|
-
return {
|
|
407
|
-
lens,
|
|
408
|
-
findings: findings.toSorted((left, right) => left.id.localeCompare(right.id)),
|
|
409
|
-
evidence: input.evidence.filter((item): item is string => typeof item === "string").toSorted(),
|
|
410
|
-
};
|
|
411
|
-
});
|
|
412
|
-
return { results, findings: results.flatMap(({ findings }) => findings), reasons };
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
function refuterRequest(
|
|
416
|
-
current: CompactReviewStateV2,
|
|
417
|
-
canonical: ReturnType<typeof canonicalizeLensResults>,
|
|
418
|
-
): CompactRefuterRequest | undefined {
|
|
419
|
-
const findings = canonical.findings.filter((finding) =>
|
|
420
|
-
SEVERE.has(finding.severity) &&
|
|
421
|
-
finding.evidence_class === COMPACT_EVIDENCE_CLASS.INFERENTIAL &&
|
|
422
|
-
CANDIDATE_CAUSED.has(finding.causal_disposition) &&
|
|
423
|
-
isConcreteProof(finding.proof_refs)
|
|
424
|
-
);
|
|
425
|
-
if (findings.length === 0) return undefined;
|
|
426
|
-
return {
|
|
427
|
-
request_hash: domainHashV1("compact-refuter-request", {
|
|
428
|
-
lineage_id: current.lineage_id,
|
|
429
|
-
candidate_tree: current.current_candidate_tree,
|
|
430
|
-
lens_results: canonical.results,
|
|
431
|
-
finding_ids: findings.map(({ id }) => id),
|
|
432
|
-
}),
|
|
433
|
-
findings: clone(findings),
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
export function createCompactRefuterRequest(
|
|
438
|
-
current: CompactReviewStateV2,
|
|
439
|
-
lensResults: readonly CompactLensResultInput[],
|
|
440
|
-
): CompactRefuterRequest | undefined {
|
|
441
|
-
assertCompactReviewState(current);
|
|
442
|
-
if (current.state !== COMPACT_REVIEW_STATE.REVIEWING) throw new Error(`Cannot request compact refutation from ${current.state}`);
|
|
443
|
-
return refuterRequest(current, canonicalizeLensResults(current.selected_lenses, lensResults));
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
function causalFollowUp(finding: CompactFinding): CompactFollowUp {
|
|
447
|
-
return {
|
|
448
|
-
finding_id: finding.id,
|
|
449
|
-
location: finding.location,
|
|
450
|
-
summary: finding.claim,
|
|
451
|
-
proof_refs: [...finding.proof_refs],
|
|
452
|
-
};
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
function validateRefuterBatch(
|
|
456
|
-
inferentialIds: readonly string[],
|
|
457
|
-
input: readonly CompactRefuterResultInput[] | undefined,
|
|
458
|
-
): { results: CompactRefuterResult[]; reasons: string[] } {
|
|
459
|
-
if (inferentialIds.length === 0) {
|
|
460
|
-
return {
|
|
461
|
-
results: [],
|
|
462
|
-
reasons: input && input.length > 0
|
|
463
|
-
? ["A refuter batch was supplied without inferential candidate-caused severe findings."]
|
|
464
|
-
: [],
|
|
465
|
-
};
|
|
466
|
-
}
|
|
467
|
-
if (!input) {
|
|
468
|
-
return { results: [], reasons: ["Inferential severe findings require exactly one complete refuter batch."] };
|
|
469
|
-
}
|
|
470
|
-
const expected = new Set(inferentialIds);
|
|
471
|
-
const seen = new Set<string>();
|
|
472
|
-
const reasons: string[] = [];
|
|
473
|
-
const results: CompactRefuterResult[] = [];
|
|
474
|
-
for (const item of input as readonly unknown[]) {
|
|
475
|
-
if (typeof item !== "object" || item === null || Array.isArray(item)) {
|
|
476
|
-
reasons.push("Refuter result <malformed> is malformed, duplicated, or outside the complete batch.");
|
|
477
|
-
continue;
|
|
478
|
-
}
|
|
479
|
-
const row = item as Partial<CompactRefuterResultInput>;
|
|
480
|
-
const id = typeof row.finding_id === "string" ? row.finding_id.trim().toUpperCase() : "";
|
|
481
|
-
const outcome = typeof row.outcome === "string" ? Object.values(COMPACT_FINDING_OUTCOME).find((value) => value === row.outcome) as CompactFindingOutcome | undefined : undefined;
|
|
482
|
-
const proofRefs = Array.isArray(row.proof_refs) && row.proof_refs.every((proof) => typeof proof === "string") ? row.proof_refs : undefined;
|
|
483
|
-
if (!expected.has(id) || seen.has(id) || outcome === undefined || outcome === COMPACT_FINDING_OUTCOME.INFO || !proofRefs || !isConcreteProof(proofRefs)) {
|
|
484
|
-
reasons.push(`Refuter result ${id || "<missing>"} is malformed, duplicated, or outside the complete batch.`);
|
|
485
|
-
continue;
|
|
486
|
-
}
|
|
487
|
-
seen.add(id);
|
|
488
|
-
results.push({
|
|
489
|
-
finding_id: id,
|
|
490
|
-
outcome,
|
|
491
|
-
proof_refs: [...proofRefs].toSorted(),
|
|
492
|
-
});
|
|
493
|
-
}
|
|
494
|
-
for (const id of inferentialIds) {
|
|
495
|
-
if (!seen.has(id)) reasons.push(`Refuter batch omitted inferential finding ${id}.`);
|
|
496
|
-
}
|
|
497
|
-
return { results: results.toSorted((left, right) => left.finding_id.localeCompare(right.finding_id)), reasons };
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
export function createCompactReviewState(input: {
|
|
501
|
-
lineageId: string;
|
|
502
|
-
snapshot: SnapshotV1;
|
|
503
|
-
policyHash: string;
|
|
504
|
-
}): CompactReviewStateV2 {
|
|
505
|
-
if (!LINEAGE_ID.test(input.lineageId)) throw new Error("Compact review lineage ID is invalid");
|
|
506
|
-
if (input.snapshot.mode !== REVIEW_MODE.ORDINARY) throw new Error("Compact reviews require ordinary mode");
|
|
507
|
-
if (!DIGEST.test(input.policyHash)) throw new Error("Compact review policy hash is invalid");
|
|
508
|
-
const state: CompactReviewStateV2 = {
|
|
509
|
-
schema: "gentle-ai.review-state/v2",
|
|
510
|
-
lineage_id: input.lineageId,
|
|
511
|
-
generation: 1,
|
|
512
|
-
mode: REVIEW_MODE.ORDINARY,
|
|
513
|
-
state: COMPACT_REVIEW_STATE.REVIEWING,
|
|
514
|
-
initial_snapshot: clone(input.snapshot),
|
|
515
|
-
current_candidate_tree: input.snapshot.initial_review_tree,
|
|
516
|
-
genesis_paths: [...(input.snapshot.genesis_paths ?? [])],
|
|
517
|
-
intended_untracked: [...input.snapshot.intended_untracked],
|
|
518
|
-
policy_hash: input.policyHash,
|
|
519
|
-
risk_tier: input.snapshot.risk_tier,
|
|
520
|
-
selected_lenses: Object.freeze([...input.snapshot.lenses]),
|
|
521
|
-
original_changed_lines: input.snapshot.original_changed_lines,
|
|
522
|
-
correction_budget: input.snapshot.correction_budget,
|
|
523
|
-
lens_results: [],
|
|
524
|
-
findings: [],
|
|
525
|
-
outcomes: {},
|
|
526
|
-
correction_ids: [],
|
|
527
|
-
follow_ups: [],
|
|
528
|
-
runtime_identity: loadedReviewRuntimeIdentity(),
|
|
529
|
-
escalation_reasons: [],
|
|
530
|
-
};
|
|
531
|
-
assertCompactReviewState(state);
|
|
532
|
-
return state;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
export function completeCompactReview(
|
|
536
|
-
current: CompactReviewStateV2,
|
|
537
|
-
input: CompactReviewResultInput,
|
|
538
|
-
): CompactReviewStateV2 {
|
|
539
|
-
assertCompactReviewState(current);
|
|
540
|
-
if (current.state !== COMPACT_REVIEW_STATE.REVIEWING) {
|
|
541
|
-
throw new Error(`Cannot complete compact review from ${current.state}`);
|
|
542
|
-
}
|
|
543
|
-
const next = clone(current);
|
|
544
|
-
const canonical = canonicalizeLensResults(current.selected_lenses, input.lens_results);
|
|
545
|
-
const request = refuterRequest(current, canonical);
|
|
546
|
-
if (request?.request_hash !== input.refuter_request_hash) {
|
|
547
|
-
throw new Error("Compact refuter request hash is missing or does not match the identical canonical lens input");
|
|
548
|
-
}
|
|
549
|
-
next.lens_results = canonical.results;
|
|
550
|
-
next.findings = canonical.findings;
|
|
551
|
-
next.escalation_reasons.push(...canonical.reasons);
|
|
552
|
-
const inferential: string[] = [];
|
|
553
|
-
for (const finding of next.findings) {
|
|
554
|
-
if (!SEVERE.has(finding.severity)) {
|
|
555
|
-
next.outcomes[finding.id] = COMPACT_FINDING_OUTCOME.INFO;
|
|
556
|
-
continue;
|
|
557
|
-
}
|
|
558
|
-
if (finding.evidence_class === COMPACT_EVIDENCE_CLASS.INSUFFICIENT || !isConcreteProof(finding.proof_refs)) {
|
|
559
|
-
next.outcomes[finding.id] = COMPACT_FINDING_OUTCOME.INCONCLUSIVE;
|
|
560
|
-
next.escalation_reasons.push(`${finding.id} has insufficient or malformed severe evidence.`);
|
|
561
|
-
continue;
|
|
562
|
-
}
|
|
563
|
-
if (
|
|
564
|
-
finding.causal_disposition === CAUSAL_DISPOSITION.PRE_EXISTING ||
|
|
565
|
-
finding.causal_disposition === CAUSAL_DISPOSITION.BASE_ONLY
|
|
566
|
-
) {
|
|
567
|
-
next.outcomes[finding.id] = COMPACT_FINDING_OUTCOME.INFO;
|
|
568
|
-
next.follow_ups.push(causalFollowUp(finding));
|
|
569
|
-
continue;
|
|
570
|
-
}
|
|
571
|
-
if (finding.causal_disposition === CAUSAL_DISPOSITION.UNKNOWN) {
|
|
572
|
-
next.outcomes[finding.id] = COMPACT_FINDING_OUTCOME.INCONCLUSIVE;
|
|
573
|
-
next.escalation_reasons.push(`${finding.id} has unknown causal disposition.`);
|
|
574
|
-
continue;
|
|
575
|
-
}
|
|
576
|
-
if (!CANDIDATE_CAUSED.has(finding.causal_disposition)) {
|
|
577
|
-
next.outcomes[finding.id] = COMPACT_FINDING_OUTCOME.INCONCLUSIVE;
|
|
578
|
-
next.escalation_reasons.push(`${finding.id} has unsupported causal disposition.`);
|
|
579
|
-
continue;
|
|
580
|
-
}
|
|
581
|
-
if (finding.evidence_class === COMPACT_EVIDENCE_CLASS.DETERMINISTIC) {
|
|
582
|
-
next.outcomes[finding.id] = COMPACT_FINDING_OUTCOME.CORROBORATED;
|
|
583
|
-
next.correction_ids.push(finding.id);
|
|
584
|
-
} else if (finding.evidence_class === COMPACT_EVIDENCE_CLASS.INFERENTIAL) {
|
|
585
|
-
inferential.push(finding.id);
|
|
586
|
-
} else {
|
|
587
|
-
next.outcomes[finding.id] = COMPACT_FINDING_OUTCOME.INCONCLUSIVE;
|
|
588
|
-
next.escalation_reasons.push(`${finding.id} has insufficient severe evidence.`);
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
const refuter = validateRefuterBatch(inferential, input.refuter_results);
|
|
592
|
-
next.escalation_reasons.push(...refuter.reasons);
|
|
593
|
-
for (const result of refuter.results) {
|
|
594
|
-
next.outcomes[result.finding_id] = result.outcome;
|
|
595
|
-
if (result.outcome === COMPACT_FINDING_OUTCOME.CORROBORATED) {
|
|
596
|
-
next.correction_ids.push(result.finding_id);
|
|
597
|
-
} else if (result.outcome === COMPACT_FINDING_OUTCOME.INCONCLUSIVE) {
|
|
598
|
-
next.escalation_reasons.push(`${result.finding_id} remained inconclusive after the only refuter batch.`);
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
for (const id of inferential) {
|
|
602
|
-
if (next.outcomes[id] === undefined) next.outcomes[id] = COMPACT_FINDING_OUTCOME.INCONCLUSIVE;
|
|
603
|
-
}
|
|
604
|
-
next.correction_ids = canonicalStrings(next.correction_ids, "Compact correction IDs");
|
|
605
|
-
next.follow_ups = next.follow_ups.toSorted((left, right) => left.finding_id.localeCompare(right.finding_id));
|
|
606
|
-
next.state = next.escalation_reasons.length > 0
|
|
607
|
-
? COMPACT_REVIEW_STATE.ESCALATED
|
|
608
|
-
: next.correction_ids.length > 0
|
|
609
|
-
? COMPACT_REVIEW_STATE.CORRECTION_REQUIRED
|
|
610
|
-
: COMPACT_REVIEW_STATE.VALIDATING;
|
|
611
|
-
assertCompactReviewState(next);
|
|
612
|
-
return next;
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
export function createCompactValidatorRequest(
|
|
616
|
-
current: CompactReviewStateV2,
|
|
617
|
-
snapshot: CorrectionSnapshotV1,
|
|
618
|
-
): CompactValidatorRequest {
|
|
619
|
-
assertCompactReviewState(current);
|
|
620
|
-
if (current.state !== COMPACT_REVIEW_STATE.CORRECTION_REQUIRED || current.correction_line_forecast === undefined) {
|
|
621
|
-
throw new Error(`Cannot create compact validator request from ${current.state}`);
|
|
622
|
-
}
|
|
623
|
-
const frozenRows = current.findings.filter((finding) => current.correction_ids.includes(finding.id));
|
|
624
|
-
const body: CompactValidatorRequestBody = {
|
|
625
|
-
schema: "gentle-ai.compact-validator-request/v1",
|
|
626
|
-
purpose: COMPACT_VALIDATOR_PURPOSE.TARGETED_CORRECTION,
|
|
627
|
-
scope: Object.values(COMPACT_VALIDATOR_SCOPE),
|
|
628
|
-
lineage_id: current.lineage_id,
|
|
629
|
-
candidate_tree: snapshot.candidate_tree,
|
|
630
|
-
fix_diff_hash: snapshot.fix_diff_hash,
|
|
631
|
-
correction_ids: [...current.correction_ids],
|
|
632
|
-
correction_paths: [...snapshot.changed_paths],
|
|
633
|
-
frozen_rows: clone(frozenRows),
|
|
634
|
-
frozen_ledger_hash: domainHashV1("compact-findings", current.findings),
|
|
635
|
-
};
|
|
636
|
-
return { body, request_hash: domainHashV1("compact-validator-request", body) };
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
export function freezeCompactValidatorRequest(
|
|
640
|
-
current: CompactReviewStateV2,
|
|
641
|
-
request: CompactValidatorRequest,
|
|
642
|
-
): CompactReviewStateV2 {
|
|
643
|
-
assertCompactReviewState(current);
|
|
644
|
-
if (current.state !== COMPACT_REVIEW_STATE.CORRECTION_REQUIRED || current.correction_line_forecast === undefined) {
|
|
645
|
-
throw new Error(`Cannot freeze compact validator request from ${current.state}`);
|
|
646
|
-
}
|
|
647
|
-
if (current.validator_request !== undefined) {
|
|
648
|
-
if (!equal(current.validator_request, request)) throw new Error("Targeted validation does not match the frozen native validator request");
|
|
649
|
-
return clone(current);
|
|
650
|
-
}
|
|
651
|
-
const next = clone(current);
|
|
652
|
-
next.validator_request = clone(request);
|
|
653
|
-
assertCompactReviewState(next);
|
|
654
|
-
return next;
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
export function beginCompactCorrection(
|
|
658
|
-
current: CompactReviewStateV2,
|
|
659
|
-
forecast: number,
|
|
660
|
-
): CompactReviewStateV2 {
|
|
661
|
-
assertCompactReviewState(current);
|
|
662
|
-
if (
|
|
663
|
-
current.state !== COMPACT_REVIEW_STATE.CORRECTION_REQUIRED ||
|
|
664
|
-
current.correction_line_forecast !== undefined
|
|
665
|
-
) {
|
|
666
|
-
throw new Error(`Cannot begin compact correction from ${current.state}`);
|
|
667
|
-
}
|
|
668
|
-
if (!Number.isSafeInteger(forecast) || forecast <= 0) {
|
|
669
|
-
throw new Error("Compact correction requires a positive changed-line forecast before editing");
|
|
670
|
-
}
|
|
671
|
-
const next = clone(current);
|
|
672
|
-
next.correction_line_forecast = forecast;
|
|
673
|
-
if (forecast > next.correction_budget) {
|
|
674
|
-
next.state = COMPACT_REVIEW_STATE.ESCALATED;
|
|
675
|
-
next.escalation_reasons.push(`Correction forecast ${forecast} exceeds frozen budget ${next.correction_budget}.`);
|
|
676
|
-
}
|
|
677
|
-
assertCompactReviewState(next);
|
|
678
|
-
return next;
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
export function completeCompactCorrection(
|
|
682
|
-
current: CompactReviewStateV2,
|
|
683
|
-
snapshot: CorrectionSnapshotV1,
|
|
684
|
-
intendedUntracked: readonly string[],
|
|
685
|
-
input: CompactTargetedValidationInput,
|
|
686
|
-
): CompactReviewStateV2 {
|
|
687
|
-
assertCompactReviewState(current);
|
|
688
|
-
if (
|
|
689
|
-
current.state !== COMPACT_REVIEW_STATE.CORRECTION_REQUIRED ||
|
|
690
|
-
current.correction_line_forecast === undefined
|
|
691
|
-
) {
|
|
692
|
-
throw new Error(`Cannot complete compact correction from ${current.state}`);
|
|
693
|
-
}
|
|
694
|
-
if (current.correction_line_forecast > current.correction_budget) {
|
|
695
|
-
throw new Error("Compact correction forecast exceeds the frozen budget");
|
|
696
|
-
}
|
|
697
|
-
if (snapshot.changed_lines > current.correction_budget) {
|
|
698
|
-
throw new Error(`Actual correction is ${snapshot.changed_lines} changed lines, exceeding frozen budget ${current.correction_budget}`);
|
|
699
|
-
}
|
|
700
|
-
if (!equal(canonicalStrings(snapshot.changed_paths, "Correction paths"), snapshot.changed_paths)) {
|
|
701
|
-
throw new Error("Correction paths are not canonical");
|
|
702
|
-
}
|
|
703
|
-
if (snapshot.changed_paths.some((path) => !current.genesis_paths.includes(path))) {
|
|
704
|
-
throw new Error("Compact correction touches a path outside the frozen original scope");
|
|
705
|
-
}
|
|
706
|
-
if (!equal(canonicalStrings(intendedUntracked, "Correction untracked paths"), current.intended_untracked)) {
|
|
707
|
-
throw new Error("Compact correction changed the frozen untracked path set");
|
|
708
|
-
}
|
|
709
|
-
if (!DIGEST.test(input.request_hash)) throw new Error("Targeted validation request hash is invalid");
|
|
710
|
-
if (current.validator_request === undefined || input.request_hash !== current.validator_request.request_hash) {
|
|
711
|
-
throw new Error("Targeted validation does not match the frozen native validator request");
|
|
712
|
-
}
|
|
713
|
-
if (
|
|
714
|
-
snapshot.candidate_tree !== current.validator_request.body.candidate_tree ||
|
|
715
|
-
snapshot.fix_diff_hash !== current.validator_request.body.fix_diff_hash ||
|
|
716
|
-
!equal(snapshot.changed_paths, current.validator_request.body.correction_paths)
|
|
717
|
-
) {
|
|
718
|
-
throw new Error("Recaptured correction does not match the frozen validator request");
|
|
719
|
-
}
|
|
720
|
-
const correctionIds = canonicalStrings(input.correction_ids, "Targeted validation correction IDs");
|
|
721
|
-
if (!equal(correctionIds, current.correction_ids)) {
|
|
722
|
-
throw new Error("Targeted validation must cover exactly the frozen correction IDs");
|
|
723
|
-
}
|
|
724
|
-
if (
|
|
725
|
-
!Array.isArray(input.original_criteria?.evidence) ||
|
|
726
|
-
input.original_criteria.evidence.length === 0 ||
|
|
727
|
-
!Array.isArray(input.correction_regression?.evidence) ||
|
|
728
|
-
input.correction_regression.evidence.length === 0
|
|
729
|
-
) {
|
|
730
|
-
throw new Error("Targeted validation requires original criteria and correction regression evidence");
|
|
731
|
-
}
|
|
732
|
-
const next = clone(current);
|
|
733
|
-
next.current_candidate_tree = snapshot.candidate_tree;
|
|
734
|
-
next.correction = {
|
|
735
|
-
candidate_tree: snapshot.candidate_tree,
|
|
736
|
-
changed_paths: [...snapshot.changed_paths],
|
|
737
|
-
changed_lines: snapshot.changed_lines,
|
|
738
|
-
fix_diff_hash: snapshot.fix_diff_hash,
|
|
739
|
-
correction_ids: [...current.correction_ids],
|
|
740
|
-
intended_untracked: [...intendedUntracked],
|
|
741
|
-
};
|
|
742
|
-
next.validation = {
|
|
743
|
-
correction_ids: correctionIds,
|
|
744
|
-
original_criteria: clone(input.original_criteria),
|
|
745
|
-
correction_regression: clone(input.correction_regression),
|
|
746
|
-
follow_ups: clone(input.follow_ups ?? []).toSorted((left, right) => left.finding_id.localeCompare(right.finding_id)),
|
|
747
|
-
};
|
|
748
|
-
next.follow_ups.push(...next.validation.follow_ups);
|
|
749
|
-
if ((input.fix_caused_findings?.length ?? 0) > 0) {
|
|
750
|
-
next.escalation_reasons.push("Targeted validation attempted to add correction-caused findings or scope.");
|
|
751
|
-
}
|
|
752
|
-
if (!input.original_criteria.passed) next.escalation_reasons.push("Original criteria failed during targeted validation.");
|
|
753
|
-
if (!input.correction_regression.passed) next.escalation_reasons.push("Correction regression check failed.");
|
|
754
|
-
next.state = next.escalation_reasons.length > 0
|
|
755
|
-
? COMPACT_REVIEW_STATE.ESCALATED
|
|
756
|
-
: COMPACT_REVIEW_STATE.VALIDATING;
|
|
757
|
-
assertCompactReviewState(next);
|
|
758
|
-
return next;
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
export function completeCompactVerification(
|
|
762
|
-
current: CompactReviewStateV2,
|
|
763
|
-
evidence: string | Uint8Array,
|
|
764
|
-
approved: boolean,
|
|
765
|
-
): CompactReviewStateV2 {
|
|
766
|
-
assertCompactReviewState(current);
|
|
767
|
-
if (current.state !== COMPACT_REVIEW_STATE.VALIDATING) {
|
|
768
|
-
throw new Error(`Cannot complete compact verification from ${current.state}`);
|
|
769
|
-
}
|
|
770
|
-
const bytes = typeof evidence === "string" ? new TextEncoder().encode(evidence) : evidence;
|
|
771
|
-
if (bytes.byteLength === 0) throw new Error("Compact final verification evidence is required");
|
|
772
|
-
const next = clone(current);
|
|
773
|
-
next.final_evidence_hash = createHash("sha256").update(bytes).digest("hex");
|
|
774
|
-
next.state = approved ? COMPACT_REVIEW_STATE.APPROVED : COMPACT_REVIEW_STATE.ESCALATED;
|
|
775
|
-
if (!approved) next.escalation_reasons.push("Final verification failed.");
|
|
776
|
-
assertCompactReviewState(next);
|
|
777
|
-
return next;
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
export function assertCompactReviewState(state: CompactReviewStateV2): void {
|
|
781
|
-
if (state.schema !== "gentle-ai.review-state/v2" || state.mode !== REVIEW_MODE.ORDINARY) {
|
|
782
|
-
throw new Error("Unsupported compact review state schema or mode");
|
|
783
|
-
}
|
|
784
|
-
if (!LINEAGE_ID.test(state.lineage_id) || state.generation !== 1) throw new Error("Compact review identity is invalid");
|
|
785
|
-
if (!OBJECT_ID.test(state.initial_snapshot.base_tree) || !OBJECT_ID.test(state.initial_snapshot.initial_review_tree) || !OBJECT_ID.test(state.current_candidate_tree)) {
|
|
786
|
-
throw new Error("Compact review tree identity is invalid");
|
|
787
|
-
}
|
|
788
|
-
if (!DIGEST.test(state.policy_hash)) throw new Error("Compact policy hash is invalid");
|
|
789
|
-
if (state.initial_snapshot.mode !== REVIEW_MODE.ORDINARY) throw new Error("Compact initial snapshot mode is invalid");
|
|
790
|
-
const genesis = canonicalStrings(state.genesis_paths, "Compact genesis paths");
|
|
791
|
-
if (!equal(genesis, state.genesis_paths) || !equal(genesis, state.initial_snapshot.genesis_paths ?? [])) {
|
|
792
|
-
throw new Error("Compact genesis paths do not match the frozen snapshot scope");
|
|
793
|
-
}
|
|
794
|
-
const untracked = canonicalStrings(state.intended_untracked, "Compact intended-untracked paths");
|
|
795
|
-
if (!equal(untracked, state.intended_untracked) || !equal(untracked, state.initial_snapshot.intended_untracked)) {
|
|
796
|
-
throw new Error("Compact intended-untracked paths do not match the frozen snapshot");
|
|
797
|
-
}
|
|
798
|
-
if (state.risk_tier !== state.initial_snapshot.risk_tier || state.original_changed_lines !== state.initial_snapshot.original_changed_lines || state.correction_budget !== state.initial_snapshot.correction_budget) {
|
|
799
|
-
throw new Error("Compact authored-risk baseline changed");
|
|
800
|
-
}
|
|
801
|
-
if (state.correction_budget !== correctionBudget(state.original_changed_lines)) {
|
|
802
|
-
throw new Error("Compact correction budget does not match original changed lines");
|
|
803
|
-
}
|
|
804
|
-
assertSelectedLenses(state.risk_tier, state.selected_lenses);
|
|
805
|
-
if (!equal(state.selected_lenses, state.initial_snapshot.lenses)) {
|
|
806
|
-
throw new Error("Compact selected lenses changed from the frozen snapshot");
|
|
807
|
-
}
|
|
808
|
-
if (!Array.isArray(state.lens_results) || !Array.isArray(state.findings) || !Array.isArray(state.correction_ids) || !Array.isArray(state.follow_ups) || !Array.isArray(state.escalation_reasons)) {
|
|
809
|
-
throw new Error("Compact review collections must be explicit");
|
|
810
|
-
}
|
|
811
|
-
if (state.lens_results.length > state.selected_lenses.length) throw new Error("Compact review has extra lens results");
|
|
812
|
-
for (const [index, result] of state.lens_results.entries()) {
|
|
813
|
-
if (result.lens !== state.selected_lenses[index]) throw new Error("Compact lens results are not in selected-lens order");
|
|
814
|
-
if (!equal(result.evidence, [...result.evidence].toSorted())) throw new Error("Compact lens evidence is not canonical");
|
|
815
|
-
if (!equal(result.findings, [...result.findings].toSorted((left, right) => left.id.localeCompare(right.id)))) throw new Error("Compact lens findings are not canonical");
|
|
816
|
-
if (result.findings.some((finding) => finding.lens !== result.lens)) throw new Error("Compact finding lens does not match its selected result");
|
|
817
|
-
}
|
|
818
|
-
if (!equal(state.findings, state.lens_results.flatMap(({ findings }) => findings))) {
|
|
819
|
-
throw new Error("Compact findings do not match canonical lens result concatenation");
|
|
820
|
-
}
|
|
821
|
-
if (new Set(state.findings.map(({ id }) => id)).size !== state.findings.length) throw new Error("Compact finding IDs are duplicated");
|
|
822
|
-
if (!equal(canonicalStrings(state.correction_ids, "Compact correction IDs"), state.correction_ids)) {
|
|
823
|
-
throw new Error("Compact correction IDs are not canonical");
|
|
824
|
-
}
|
|
825
|
-
if (state.state !== COMPACT_REVIEW_STATE.REVIEWING) {
|
|
826
|
-
const findingIds = state.findings.map(({ id }) => id).toSorted();
|
|
827
|
-
if (!equal(Object.keys(state.outcomes).toSorted(), findingIds)) throw new Error("Compact finding outcomes are missing or contain unknown IDs");
|
|
828
|
-
const expectedCorrectionIds: string[] = [];
|
|
829
|
-
let unresolved = false;
|
|
830
|
-
for (const finding of state.findings) {
|
|
831
|
-
const outcome = state.outcomes[finding.id];
|
|
832
|
-
if (!SEVERE.has(finding.severity)) {
|
|
833
|
-
if (outcome !== COMPACT_FINDING_OUTCOME.INFO) throw new Error("Non-severe compact findings must remain informational");
|
|
834
|
-
continue;
|
|
835
|
-
}
|
|
836
|
-
if (finding.causal_disposition === CAUSAL_DISPOSITION.PRE_EXISTING || finding.causal_disposition === CAUSAL_DISPOSITION.BASE_ONLY) {
|
|
837
|
-
if (outcome !== COMPACT_FINDING_OUTCOME.INFO || !state.follow_ups.some(({ finding_id }) => finding_id === finding.id)) throw new Error("Non-candidate severe finding is not an inert follow-up");
|
|
838
|
-
continue;
|
|
839
|
-
}
|
|
840
|
-
if (finding.causal_disposition === CAUSAL_DISPOSITION.UNKNOWN || finding.evidence_class === COMPACT_EVIDENCE_CLASS.INSUFFICIENT || outcome === COMPACT_FINDING_OUTCOME.INCONCLUSIVE) {
|
|
841
|
-
if (outcome !== COMPACT_FINDING_OUTCOME.INCONCLUSIVE) throw new Error("Unresolved compact severe finding must be inconclusive");
|
|
842
|
-
unresolved = true;
|
|
843
|
-
continue;
|
|
844
|
-
}
|
|
845
|
-
if (CANDIDATE_CAUSED.has(finding.causal_disposition) && outcome === COMPACT_FINDING_OUTCOME.CORROBORATED) expectedCorrectionIds.push(finding.id);
|
|
846
|
-
else if (outcome !== COMPACT_FINDING_OUTCOME.REFUTED) throw new Error("Candidate-caused severe finding has an invalid outcome");
|
|
847
|
-
}
|
|
848
|
-
if (!equal(expectedCorrectionIds.toSorted(), state.correction_ids)) throw new Error("Compact correction IDs do not match causal corroborated findings");
|
|
849
|
-
if (unresolved && state.state !== COMPACT_REVIEW_STATE.ESCALATED) throw new Error("Unresolved severe findings must escalate");
|
|
850
|
-
} else if (Object.keys(state.outcomes).length > 0) {
|
|
851
|
-
throw new Error("Reviewing compact state contains finding outcomes");
|
|
852
|
-
}
|
|
853
|
-
if (state.correction_line_forecast !== undefined && (!Number.isSafeInteger(state.correction_line_forecast) || state.correction_line_forecast <= 0)) {
|
|
854
|
-
throw new Error("Compact correction forecast must be positive");
|
|
855
|
-
}
|
|
856
|
-
assertReviewRuntimeIdentity(state.runtime_identity);
|
|
857
|
-
if (state.validator_request !== undefined) {
|
|
858
|
-
const request = state.validator_request;
|
|
859
|
-
if (
|
|
860
|
-
state.correction_line_forecast === undefined ||
|
|
861
|
-
request.request_hash !== domainHashV1("compact-validator-request", request.body) ||
|
|
862
|
-
request.body.purpose !== COMPACT_VALIDATOR_PURPOSE.TARGETED_CORRECTION ||
|
|
863
|
-
!equal(request.body.scope, Object.values(COMPACT_VALIDATOR_SCOPE)) ||
|
|
864
|
-
request.body.lineage_id !== state.lineage_id ||
|
|
865
|
-
!equal(request.body.correction_ids, state.correction_ids) ||
|
|
866
|
-
!equal(request.body.frozen_rows, state.findings.filter((finding) => state.correction_ids.includes(finding.id))) ||
|
|
867
|
-
request.body.frozen_ledger_hash !== domainHashV1("compact-findings", state.findings) ||
|
|
868
|
-
request.body.correction_paths.some((path) => !state.genesis_paths.includes(path))
|
|
869
|
-
) {
|
|
870
|
-
throw new Error("Compact validator request is not bound to frozen review evidence");
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
if (state.correction) {
|
|
874
|
-
if (!DIGEST.test(state.correction.fix_diff_hash) || state.correction.changed_lines > state.correction_budget || state.correction.candidate_tree !== state.current_candidate_tree || !equal(state.correction.correction_ids, state.correction_ids) || !equal(state.correction.intended_untracked, state.intended_untracked)) {
|
|
875
|
-
throw new Error("Compact correction record is not bound to frozen authority");
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
if (state.validation && (!state.correction || !equal(state.validation.correction_ids, state.correction_ids) || state.validation.original_criteria.evidence.length === 0 || state.validation.correction_regression.evidence.length === 0)) {
|
|
879
|
-
throw new Error("Compact targeted validation is incomplete or unbound");
|
|
880
|
-
}
|
|
881
|
-
if (state.state === COMPACT_REVIEW_STATE.REVIEWING) {
|
|
882
|
-
if (state.findings.length > 0 || state.lens_results.length > 0 || state.correction_ids.length > 0 || state.validator_request !== undefined || state.final_evidence_hash !== undefined) {
|
|
883
|
-
throw new Error("Reviewing compact state contains post-review data");
|
|
884
|
-
}
|
|
885
|
-
} else if (state.lens_results.length !== state.selected_lenses.length) {
|
|
886
|
-
throw new Error("Post-review compact state requires every selected lens result");
|
|
887
|
-
}
|
|
888
|
-
if (state.state === COMPACT_REVIEW_STATE.CORRECTION_REQUIRED && state.correction_ids.length === 0) {
|
|
889
|
-
throw new Error("Correction-required compact state has no correction IDs");
|
|
890
|
-
}
|
|
891
|
-
if (state.state === COMPACT_REVIEW_STATE.VALIDATING && state.correction_ids.length > 0 && (!state.correction || !state.validation || !state.validator_request)) {
|
|
892
|
-
throw new Error("Corrected compact state requires one targeted validation");
|
|
893
|
-
}
|
|
894
|
-
if (state.state === COMPACT_REVIEW_STATE.APPROVED && !DIGEST.test(state.final_evidence_hash ?? "")) {
|
|
895
|
-
throw new Error("Approved compact state requires final verification evidence");
|
|
896
|
-
}
|
|
897
|
-
if (!Object.values(COMPACT_REVIEW_STATE).includes(state.state)) throw new Error("Compact review state is invalid");
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
export function createCompactReceipt(
|
|
901
|
-
state: CompactReviewStateV2,
|
|
902
|
-
authorityRevision: string,
|
|
903
|
-
): CompactReceiptEnvelopeV2 {
|
|
904
|
-
assertCompactReviewState(state);
|
|
905
|
-
if (state.state !== COMPACT_REVIEW_STATE.APPROVED && state.state !== COMPACT_REVIEW_STATE.ESCALATED) {
|
|
906
|
-
throw new Error("Compact receipt requires terminal authority");
|
|
907
|
-
}
|
|
908
|
-
if (!DIGEST.test(authorityRevision)) throw new Error("Compact receipt authority revision is invalid");
|
|
909
|
-
const body: CompactReceiptBodyV2 = {
|
|
910
|
-
schema: "gentle-ai.review-receipt-body/v2",
|
|
911
|
-
lineage_id: state.lineage_id,
|
|
912
|
-
generation: state.generation,
|
|
913
|
-
authority_revision: authorityRevision,
|
|
914
|
-
base_tree: state.initial_snapshot.base_tree,
|
|
915
|
-
initial_review_tree: state.initial_snapshot.initial_review_tree,
|
|
916
|
-
final_candidate_tree: state.current_candidate_tree,
|
|
917
|
-
genesis_paths_hash: domainHashV1("compact-paths", state.genesis_paths),
|
|
918
|
-
intended_untracked_hash: domainHashV1("compact-untracked", state.intended_untracked),
|
|
919
|
-
policy_hash: state.policy_hash,
|
|
920
|
-
risk_tier: state.risk_tier,
|
|
921
|
-
selected_lenses: [...state.selected_lenses],
|
|
922
|
-
original_changed_lines: state.original_changed_lines,
|
|
923
|
-
correction_budget: state.correction_budget,
|
|
924
|
-
correction_ids: [...state.correction_ids],
|
|
925
|
-
fix_diff_hash: state.correction?.fix_diff_hash ?? domainHashV1("compact-empty-fix", null),
|
|
926
|
-
evidence_hash: state.final_evidence_hash ?? domainHashV1("compact-empty-evidence", null),
|
|
927
|
-
terminal_state: state.state,
|
|
928
|
-
};
|
|
929
|
-
return { body, receipt_hash: domainHashV1("compact-receipt", body) };
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
export function assertCompactReceipt(
|
|
933
|
-
receipt: CompactReceiptEnvelopeV2,
|
|
934
|
-
): void {
|
|
935
|
-
const { body } = receipt;
|
|
936
|
-
if (body.schema !== "gentle-ai.review-receipt-body/v2" || body.generation !== 1 || !LINEAGE_ID.test(body.lineage_id)) throw new Error("Compact receipt identity is invalid");
|
|
937
|
-
for (const digest of [body.authority_revision, body.genesis_paths_hash, body.intended_untracked_hash, body.policy_hash, body.fix_diff_hash, body.evidence_hash, receipt.receipt_hash]) {
|
|
938
|
-
if (!DIGEST.test(digest)) throw new Error("Compact receipt contains an invalid digest");
|
|
939
|
-
}
|
|
940
|
-
for (const tree of [body.base_tree, body.initial_review_tree, body.final_candidate_tree]) {
|
|
941
|
-
if (!OBJECT_ID.test(tree)) throw new Error("Compact receipt contains an invalid tree");
|
|
942
|
-
}
|
|
943
|
-
assertSelectedLenses(body.risk_tier, body.selected_lenses);
|
|
944
|
-
if (!equal(canonicalStrings(body.correction_ids, "Compact receipt correction IDs"), body.correction_ids)) throw new Error("Compact receipt correction IDs are not canonical");
|
|
945
|
-
if (body.correction_budget !== correctionBudget(body.original_changed_lines)) throw new Error("Compact receipt budget is invalid");
|
|
946
|
-
if (receipt.receipt_hash !== domainHashV1("compact-receipt", body)) throw new Error("Compact receipt hash mismatch");
|
|
947
|
-
}
|