gentle-pi 1.2.0 → 2.0.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 +40 -9
- package/assets/agents/review-readability.md +0 -1
- package/assets/agents/review-reliability.md +0 -1
- package/assets/agents/review-resilience.md +0 -1
- package/assets/agents/review-risk.md +0 -1
- package/contracts/review-integration/v1/fixtures/binding-revision-conflict.fixture.json +25 -0
- package/contracts/review-integration/v1/fixtures/capabilities-v1.2.fixture.json +263 -0
- package/contracts/review-integration/v1/fixtures/capabilities-v1.3.fixture.json +301 -0
- package/contracts/review-integration/v1/fixtures/capabilities-v1.4.fixture.json +312 -0
- package/contracts/review-integration/v1/fixtures/capabilities-v1.5.fixture.json +321 -0
- package/contracts/review-integration/v1/fixtures/consent.fixture.json +36 -0
- package/contracts/review-integration/v1/fixtures/final-verification-incident.fixture.json +1 -0
- package/contracts/review-integration/v1/fixtures/operation.fixture.json +4 -0
- package/contracts/review-integration/v1/fixtures/repair-preflight.fixture.json +54 -0
- package/contracts/review-integration/v1/fixtures/start-v2.fixture.json +97 -0
- package/contracts/review-integration/v1/fixtures/start.fixture.json +25 -1
- package/contracts/review-integration/v1/fixtures/status-ambiguous.fixture.json +4 -0
- package/contracts/review-integration/v1/fixtures/status-corrupted.fixture.json +4 -0
- package/contracts/review-integration/v1/fixtures/status-recover.fixture.json +4 -0
- package/contracts/review-integration/v1/fixtures/status-unrelated.fixture.json +4 -0
- package/contracts/review-integration/v1/fixtures/status-v2-ambiguous.fixture.json +110 -0
- package/contracts/review-integration/v1/fixtures/status-v2-corrupted.fixture.json +107 -0
- package/contracts/review-integration/v1/fixtures/status-v2-final-verification-retry.fixture.json +192 -0
- package/contracts/review-integration/v1/fixtures/status-v2-recover.fixture.json +130 -0
- package/contracts/review-integration/v1/fixtures/status-v2-repair.fixture.json +161 -0
- package/contracts/review-integration/v1/fixtures/status-v2-unrelated.fixture.json +103 -0
- package/contracts/review-integration/v1/fixtures/status-v2.fixture.json +193 -0
- package/contracts/review-integration/v1/fixtures/status.fixture.json +19 -5
- package/contracts/review-integration/v1/fixtures/verification-evidence.fixture.json +1 -0
- package/contracts/review-integration/v1/schemas/admitted-result.schema.json +35 -0
- package/contracts/review-integration/v1/schemas/artifact-subject.schema.json +33 -0
- package/contracts/review-integration/v1/schemas/authority-repair-assessment.schema.json +247 -0
- package/contracts/review-integration/v1/schemas/capabilities-v1.2.schema.json +249 -0
- package/contracts/review-integration/v1/schemas/capabilities-v1.3.schema.json +429 -0
- package/contracts/review-integration/v1/schemas/capabilities-v1.4.schema.json +432 -0
- package/contracts/review-integration/v1/schemas/capabilities-v1.5.schema.json +154 -0
- package/contracts/review-integration/v1/schemas/consent.schema.json +86 -0
- package/contracts/review-integration/v1/schemas/failure.schema.json +54 -8
- package/contracts/review-integration/v1/schemas/final-verification-incident.schema.json +51 -0
- package/contracts/review-integration/v1/schemas/operation.schema.json +204 -31
- package/contracts/review-integration/v1/schemas/repair.schema.json +251 -0
- package/contracts/review-integration/v1/schemas/result-artifact-v2.schema.json +38 -0
- package/contracts/review-integration/v1/schemas/start-v2.schema.json +216 -0
- package/contracts/review-integration/v1/schemas/start.schema.json +79 -3
- package/contracts/review-integration/v1/schemas/status-v2.schema.json +313 -0
- package/contracts/review-integration/v1/schemas/status.schema.json +37 -10
- package/contracts/review-integration/v1/schemas/targeted-validation-request.schema.json +46 -0
- package/contracts/review-integration/v1/schemas/verification-evidence.schema.json +64 -0
- package/contracts/review-integration/v2/fixtures/capabilities.fixture.json +111 -0
- package/contracts/review-integration/v2/fixtures/consent.fixture.json +36 -0
- package/contracts/review-integration/v2/fixtures/start.fixture.json +93 -0
- package/contracts/review-integration/v2/fixtures/status.fixture.json +113 -0
- package/contracts/review-integration/v2/schemas/admitted-result.schema.json +14 -0
- package/contracts/review-integration/v2/schemas/artifact-subject.schema.json +36 -0
- package/contracts/review-integration/v2/schemas/capabilities.schema.json +111 -0
- package/contracts/review-integration/v2/schemas/consent.schema.json +70 -0
- package/contracts/review-integration/v2/schemas/failure.schema.json +35 -0
- package/contracts/review-integration/v2/schemas/operation.schema.json +46 -0
- package/contracts/review-integration/v2/schemas/repair.schema.json +22 -0
- package/contracts/review-integration/v2/schemas/start.schema.json +106 -0
- package/contracts/review-integration/v2/schemas/status.schema.json +104 -0
- package/docs/native-authority-architecture.md +18 -3
- package/docs/review-integration.md +175 -27
- package/extensions/gentle-ai.ts +414 -19
- package/lib/gentle-ai-binary.ts +1 -1
- package/lib/native-review-cli.ts +679 -57
- package/lib/review-candidate-view.ts +131 -1
- package/lib/review-compact-contract.ts +12 -3
- package/lib/review-consent-latch.ts +51 -0
- package/lib/review-correction-lifecycle.ts +170 -0
- package/lib/review-integration-v2.ts +1563 -0
- package/package.json +15 -11
- package/runtime/gentle-ai-binary.mjs +1 -1
- package/runtime/native-review-cli.mjs +663 -41
- package/runtime/review-integration-v2.mjs +1564 -0
- package/scripts/build-git-commit-transaction-runner.mjs +1 -1
- package/scripts/gentle-ai-installer.mjs +16 -11
- package/scripts/install-gentle-ai.mjs +7 -3
- package/scripts/test-packed-runner.mjs +2 -2
- package/scripts/verify-package-files.mjs +216 -64
- package/skills/release/SKILL.md +38 -11
- package/tests/devbinary/native-review-parity.devtest.ts +348 -0
- package/tests/gentle-ai-binary.test.ts +8 -1
- package/tests/gentle-ai-installer.test.ts +24 -21
- package/tests/native-binary-gate.test.ts +145 -0
- package/tests/native-review-capability-contract.test.ts +87 -0
- package/tests/native-review-cli.test.ts +44 -1
- package/tests/native-review-parity-runtime.test.ts +70 -23
- package/tests/native-review-parity.test.ts +673 -0
- package/tests/native-risk-evidence-parity.test.ts +91 -0
- package/tests/package-manifest.test.ts +72 -7
- package/tests/review-actor-tool-deny.test.ts +20 -5
- package/tests/review-candidate-view.test.ts +331 -1
- package/tests/review-compact-contract.test.ts +5 -0
- package/tests/review-consent-latch.test.ts +67 -0
- package/tests/review-controller-native-recovery.test.ts +180 -5
- package/tests/review-controller-native-routing.test.ts +385 -12
- package/tests/review-controller-workspace-root.test.ts +26 -6
- package/tests/review-correction-lifecycle.test.ts +174 -0
- package/tests/review-integration-v2.test.ts +539 -0
- package/tests/runtime-harness.mjs +187 -0
- package/tests/support/native-binary-gate.ts +86 -0
- package/tests/verify-package-files.test.ts +119 -0
- package/lib/review-integration-v1.ts +0 -795
- package/runtime/review-integration-v1.mjs +0 -796
- package/tests/native-review-integration-v1.test.ts +0 -102
- package/tests/review-integration-v1.test.ts +0 -530
|
@@ -1,795 +0,0 @@
|
|
|
1
|
-
export const REVIEW_INTEGRATION_CONTRACT = "gentle-ai.review-integration/v1";
|
|
2
|
-
|
|
3
|
-
export const REVIEW_INTEGRATION_OPERATION = {
|
|
4
|
-
BIND_SDD: "review.bind_sdd",
|
|
5
|
-
CAPABILITIES: "review.capabilities",
|
|
6
|
-
FINALIZE: "review.finalize",
|
|
7
|
-
START: "review.start",
|
|
8
|
-
STATUS: "review.status",
|
|
9
|
-
VALIDATE: "review.validate",
|
|
10
|
-
} as const;
|
|
11
|
-
export type ReviewIntegrationOperation = (typeof REVIEW_INTEGRATION_OPERATION)[keyof typeof REVIEW_INTEGRATION_OPERATION];
|
|
12
|
-
|
|
13
|
-
export const REVIEW_AUTHORITY_APPLICABILITY = {
|
|
14
|
-
CURRENT_TARGET: "current_target",
|
|
15
|
-
UNRELATED: "unrelated",
|
|
16
|
-
AMBIGUOUS: "ambiguous",
|
|
17
|
-
CORRUPTED: "corrupted",
|
|
18
|
-
NOT_EVALUATED: "not_evaluated",
|
|
19
|
-
} as const;
|
|
20
|
-
export type ReviewAuthorityApplicability = (typeof REVIEW_AUTHORITY_APPLICABILITY)[keyof typeof REVIEW_AUTHORITY_APPLICABILITY];
|
|
21
|
-
|
|
22
|
-
export const REVIEW_REPLAYABILITY = {
|
|
23
|
-
NOT_REPLAYABLE: "not_replayable",
|
|
24
|
-
EXACT_REPLAY_SAFE: "exact_replay_safe",
|
|
25
|
-
STATUS_REQUIRED: "status_required",
|
|
26
|
-
MANUAL_ACTION_REQUIRED: "manual_action_required",
|
|
27
|
-
} as const;
|
|
28
|
-
export type ReviewReplayability = (typeof REVIEW_REPLAYABILITY)[keyof typeof REVIEW_REPLAYABILITY];
|
|
29
|
-
|
|
30
|
-
export const REVIEW_MUTATION_OUTCOME = {
|
|
31
|
-
NOT_STARTED: "not_started",
|
|
32
|
-
UNKNOWN: "unknown",
|
|
33
|
-
COMMITTED: "committed",
|
|
34
|
-
} as const;
|
|
35
|
-
export type ReviewMutationOutcome = (typeof REVIEW_MUTATION_OUTCOME)[keyof typeof REVIEW_MUTATION_OUTCOME];
|
|
36
|
-
|
|
37
|
-
export const REVIEW_PROJECTION = {
|
|
38
|
-
STAGED: "staged",
|
|
39
|
-
WORKSPACE: "workspace",
|
|
40
|
-
} as const;
|
|
41
|
-
export type ReviewProjection = (typeof REVIEW_PROJECTION)[keyof typeof REVIEW_PROJECTION];
|
|
42
|
-
|
|
43
|
-
export const REVIEW_PROJECTION_KIND = {
|
|
44
|
-
CURRENT_CHANGES: "current-changes",
|
|
45
|
-
BASE_DIFF: "base-diff",
|
|
46
|
-
BASE_WORKSPACE_OVERLAY: "base-workspace-overlay",
|
|
47
|
-
EXACT_REVISION: "exact-revision",
|
|
48
|
-
FIX_DIFF: "fix-diff",
|
|
49
|
-
} as const;
|
|
50
|
-
export type ReviewProjectionKind = (typeof REVIEW_PROJECTION_KIND)[keyof typeof REVIEW_PROJECTION_KIND];
|
|
51
|
-
|
|
52
|
-
export const REVIEW_AUTHORITY_VERSION = {
|
|
53
|
-
COMPACT_V2: "compact-v2",
|
|
54
|
-
LEGACY_V1: "legacy-v1",
|
|
55
|
-
} as const;
|
|
56
|
-
export type ReviewAuthorityVersion = (typeof REVIEW_AUTHORITY_VERSION)[keyof typeof REVIEW_AUTHORITY_VERSION];
|
|
57
|
-
|
|
58
|
-
export const REVIEW_START_STATE = {
|
|
59
|
-
UNREVIEWED: "unreviewed",
|
|
60
|
-
REVIEWING: "reviewing",
|
|
61
|
-
JUDGES_CONFIRMED: "judges_confirmed",
|
|
62
|
-
FINDINGS_FROZEN: "findings_frozen",
|
|
63
|
-
EVIDENCE_CLASSIFIED: "evidence_classified",
|
|
64
|
-
FIX_REQUIRED: "fix_required",
|
|
65
|
-
FIXING: "fixing",
|
|
66
|
-
FIX_VALIDATING: "fix_validating",
|
|
67
|
-
READY_FINAL_VERIFICATION: "ready_final_verification",
|
|
68
|
-
FINAL_VERIFYING: "final_verifying",
|
|
69
|
-
APPROVED: "approved",
|
|
70
|
-
ESCALATED: "escalated",
|
|
71
|
-
INVALIDATED: "invalidated",
|
|
72
|
-
} as const;
|
|
73
|
-
export type ReviewStartState = (typeof REVIEW_START_STATE)[keyof typeof REVIEW_START_STATE];
|
|
74
|
-
|
|
75
|
-
const START_ACTIONS = ["created", "resumed", "reuse-receipt", "blocked-scope-action"] as const;
|
|
76
|
-
const RISK_LEVELS = ["low", "medium", "high"] as const;
|
|
77
|
-
const REVIEW_LENSES = ["review-risk", "review-resilience", "review-readability", "review-reliability"] as const;
|
|
78
|
-
const RISK_REASON_CODES = ["configuration_change", "executable_change", "executable_mode", "hot_path", "large_change", "non_executable_only", "process_boundary", "process_scan_limit", "service_token", "shell_source"] as const;
|
|
79
|
-
const RISK_SIGNALS = ["auth", "update", "security", "payments", "permissions", "shell_process"] as const;
|
|
80
|
-
const STATUS_ACTIONS = ["start", "finalize", "validate", "recover", "maintainer_action", "select_lineage", "repair_authority", "reconcile_finalize", "stop"] as const;
|
|
81
|
-
export const REVIEW_STATUS_ACTION_DISPOSITION = {
|
|
82
|
-
SCOPE_CHANGED: "scope_changed",
|
|
83
|
-
INVALIDATED: "invalidated",
|
|
84
|
-
ESCALATED: "escalated",
|
|
85
|
-
} as const;
|
|
86
|
-
export type ReviewStatusActionDisposition = (typeof REVIEW_STATUS_ACTION_DISPOSITION)[keyof typeof REVIEW_STATUS_ACTION_DISPOSITION];
|
|
87
|
-
const RECEIPT_STATUSES = ["expected_missing", "present", "publication_pending", "not_applicable"] as const;
|
|
88
|
-
const REQUIRED_OPERATIONS = Object.freeze(Object.values(REVIEW_INTEGRATION_OPERATION));
|
|
89
|
-
const REQUIRED_GATES = Object.freeze(["post-apply", "pre-commit", "pre-push", "pre-pr", "release"] as const);
|
|
90
|
-
const REQUIRED_PROJECTIONS = Object.freeze(Object.values(REVIEW_PROJECTION));
|
|
91
|
-
const REQUIRED_SCHEMAS = Object.freeze([
|
|
92
|
-
"gentle-ai.review-authority-status/v1",
|
|
93
|
-
"gentle-ai.review-gate-request/v1",
|
|
94
|
-
"gentle-ai.review-integration.capabilities/v1",
|
|
95
|
-
"gentle-ai.review-integration.failure/v1",
|
|
96
|
-
"gentle-ai.review-integration.operation/v1",
|
|
97
|
-
"gentle-ai.review-integration.projection/v1",
|
|
98
|
-
"gentle-ai.review-integration.start/v1",
|
|
99
|
-
"gentle-ai.review-integration.status/v1",
|
|
100
|
-
"gentle-ai.review-receipt/v1",
|
|
101
|
-
"gentle-ai.review-receipt/v2",
|
|
102
|
-
"gentle-ai.review-result-artifact/v1",
|
|
103
|
-
"https://gentle-ai.dev/schema/review/refuter/v1",
|
|
104
|
-
"https://gentle-ai.dev/schema/review/reviewer/v1",
|
|
105
|
-
"https://gentle-ai.dev/schema/review/validator/v1",
|
|
106
|
-
] as const);
|
|
107
|
-
const OPTIONAL_FEATURE_NAMES = Object.freeze([
|
|
108
|
-
"bounded_process_waits",
|
|
109
|
-
"exact_gate_receipt_discovery",
|
|
110
|
-
"native_low_risk_verification",
|
|
111
|
-
"risk_reasons",
|
|
112
|
-
"scope_change_diagnostics",
|
|
113
|
-
] as const);
|
|
114
|
-
const FEATURE_NAMES = Object.freeze([
|
|
115
|
-
"bounded_process_waits",
|
|
116
|
-
"compact_v2_authority",
|
|
117
|
-
"exact_gate_receipt_discovery",
|
|
118
|
-
"exact_receipt_replay",
|
|
119
|
-
"five_delivery_gates",
|
|
120
|
-
"immutable_snapshot",
|
|
121
|
-
"legacy_v1_target_scoped_read_only",
|
|
122
|
-
"native_low_risk_verification",
|
|
123
|
-
"repository_independent_capabilities",
|
|
124
|
-
"restart_safe_projection",
|
|
125
|
-
"risk_reasons",
|
|
126
|
-
"scope_change_diagnostics",
|
|
127
|
-
"sdd_receipt_binding",
|
|
128
|
-
"target_scoped_status",
|
|
129
|
-
"uniform_failure_envelope",
|
|
130
|
-
] as const);
|
|
131
|
-
const REQUIRED_MANDATORY_FEATURES = Object.freeze(FEATURE_NAMES.filter((name) => !(OPTIONAL_FEATURE_NAMES as readonly string[]).includes(name)));
|
|
132
|
-
|
|
133
|
-
type StartAction = (typeof START_ACTIONS)[number];
|
|
134
|
-
type RiskLevel = (typeof RISK_LEVELS)[number];
|
|
135
|
-
type ReviewLens = (typeof REVIEW_LENSES)[number];
|
|
136
|
-
type RiskReasonCode = (typeof RISK_REASON_CODES)[number];
|
|
137
|
-
type RiskSignal = (typeof RISK_SIGNALS)[number];
|
|
138
|
-
type ReviewStatusAction = (typeof STATUS_ACTIONS)[number];
|
|
139
|
-
type ReviewReceiptStatus = (typeof RECEIPT_STATUSES)[number];
|
|
140
|
-
|
|
141
|
-
export interface ReviewFeatureV1 {
|
|
142
|
-
name: (typeof FEATURE_NAMES)[number];
|
|
143
|
-
supported: boolean;
|
|
144
|
-
requires: readonly string[];
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export interface ReviewCapabilitiesV1 {
|
|
148
|
-
contract: typeof REVIEW_INTEGRATION_CONTRACT;
|
|
149
|
-
packageVersion: string;
|
|
150
|
-
buildId: string;
|
|
151
|
-
executableDigest: string;
|
|
152
|
-
operations: ReadonlySet<string>;
|
|
153
|
-
gates: ReadonlySet<string>;
|
|
154
|
-
projections: ReadonlySet<string>;
|
|
155
|
-
schemas: ReadonlySet<string>;
|
|
156
|
-
mandatoryFeatures: ReadonlySet<string>;
|
|
157
|
-
optionalFeatures: ReadonlySet<string>;
|
|
158
|
-
raw: Readonly<Record<string, unknown>>;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export interface ReviewRiskReasonV1 {
|
|
162
|
-
code: RiskReasonCode;
|
|
163
|
-
signal?: RiskSignal;
|
|
164
|
-
path?: string;
|
|
165
|
-
oldMode?: string;
|
|
166
|
-
newMode?: string;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export interface ReviewStartV1 {
|
|
170
|
-
contract: typeof REVIEW_INTEGRATION_CONTRACT;
|
|
171
|
-
action: StartAction;
|
|
172
|
-
lensesRequired: boolean;
|
|
173
|
-
lineageId: string;
|
|
174
|
-
state: ReviewStartState;
|
|
175
|
-
riskLevel: RiskLevel;
|
|
176
|
-
selectedLenses: readonly ReviewLens[];
|
|
177
|
-
projection: ReviewProjection;
|
|
178
|
-
changedFiles: number;
|
|
179
|
-
changedLines: number;
|
|
180
|
-
correctionBudget: number;
|
|
181
|
-
riskReasons: readonly ReviewRiskReasonV1[];
|
|
182
|
-
targetMode?: "base-workspace-overlay";
|
|
183
|
-
targetIdentity?: string;
|
|
184
|
-
baseTree?: string;
|
|
185
|
-
candidateTree?: string;
|
|
186
|
-
raw: Readonly<Record<string, unknown>>;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
export interface ReviewProjectionDescriptorV1 {
|
|
190
|
-
schema: "gentle-ai.review-integration.projection/v1";
|
|
191
|
-
kind: ReviewProjectionKind;
|
|
192
|
-
projection: ReviewProjection;
|
|
193
|
-
baseTree: string;
|
|
194
|
-
initialReviewTree: string;
|
|
195
|
-
currentCandidateTree: string;
|
|
196
|
-
pathsDigest: string;
|
|
197
|
-
paths: readonly string[];
|
|
198
|
-
intendedUntracked: readonly string[];
|
|
199
|
-
intendedUntrackedProof: string;
|
|
200
|
-
initialSnapshotIdentity: string;
|
|
201
|
-
currentSnapshotIdentity: string;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export interface ReviewStatusAuthorityV1 {
|
|
205
|
-
version: ReviewAuthorityVersion;
|
|
206
|
-
lineageId: string;
|
|
207
|
-
state: string;
|
|
208
|
-
generation: number;
|
|
209
|
-
revision: string;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
export interface ReviewStatusReceiptV1 {
|
|
213
|
-
status: ReviewReceiptStatus;
|
|
214
|
-
identity?: string;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
export interface ReviewStatusFrozenV1 {
|
|
218
|
-
tier: RiskLevel;
|
|
219
|
-
originalChangedLines: number;
|
|
220
|
-
correctionBudget: number;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
export interface ReviewStatusReconciliationV1 {
|
|
224
|
-
required: true;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
export interface ReviewStatusV1 {
|
|
228
|
-
contract: typeof REVIEW_INTEGRATION_CONTRACT;
|
|
229
|
-
applicability: Exclude<ReviewAuthorityApplicability, "not_evaluated">;
|
|
230
|
-
authority?: ReviewStatusAuthorityV1;
|
|
231
|
-
receipt: ReviewStatusReceiptV1;
|
|
232
|
-
action: ReviewStatusAction;
|
|
233
|
-
actionDisposition?: ReviewStatusActionDisposition;
|
|
234
|
-
replayability: ReviewReplayability;
|
|
235
|
-
frozen?: ReviewStatusFrozenV1;
|
|
236
|
-
reconciliation?: ReviewStatusReconciliationV1;
|
|
237
|
-
targetIdentity: string;
|
|
238
|
-
projection: ReviewProjectionDescriptorV1;
|
|
239
|
-
candidates: readonly string[];
|
|
240
|
-
raw: Readonly<Record<string, unknown>>;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const ELIGIBLE_ACTIONS = ["stop", "review.start", "review.finalize", "review.validate", "review.recover"] as const;
|
|
244
|
-
const FORBIDDEN_ACTIONS = ["review.abandon", "review.finalize", "review.invalidate", "review.quarantine-legacy", "review.reclaim", "review.reconcile-authority", "review.recover", "review.start", "review.validate"] as const;
|
|
245
|
-
const NEXT_TRANSITION_OPERATIONS = ["review.start", "review.finalize", "review.recover", "review.validate"] as const;
|
|
246
|
-
|
|
247
|
-
function decodeTransitionArguments(value: unknown, label: string): void {
|
|
248
|
-
array(value, label, (entry, entryLabel) => {
|
|
249
|
-
const argument = exactRecord(entry, entryLabel, ["name", "value"]);
|
|
250
|
-
text(argument.name, `${entryLabel}.name`, { minimum: 1, pattern: /^[a-z0-9_-]+$/ });
|
|
251
|
-
text(argument.value, `${entryLabel}.value`, { minimum: 1 });
|
|
252
|
-
return argument;
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function decodeEligibility(value: unknown, label: string): void {
|
|
257
|
-
const eligibility = exactRecord(value, label, ["allowed_actions", "forbidden_actions"]);
|
|
258
|
-
array(eligibility.allowed_actions, `${label}.allowed_actions`, (entry, entryLabel) => {
|
|
259
|
-
const action = exactRecord(entry, entryLabel, ["action", "reason_code", "required_inputs"], ["disposition", "binding"]);
|
|
260
|
-
const selected = enumeration(action.action, ELIGIBLE_ACTIONS, `${entryLabel}.action`);
|
|
261
|
-
text(action.reason_code, `${entryLabel}.reason_code`, { minimum: 1, pattern: /^[a-z0-9_]+$/ });
|
|
262
|
-
array(action.required_inputs, `${entryLabel}.required_inputs`, (input, inputLabel) => text(input, inputLabel, { minimum: 1, pattern: /^[a-z0-9_]+$/ }), { unique: true });
|
|
263
|
-
if (selected === "review.recover") {
|
|
264
|
-
const binding = exactRecord(action.binding, `${entryLabel}.binding`, ["lineage_id", "revision", "target_identity"]);
|
|
265
|
-
enumeration(action.disposition, Object.values(REVIEW_STATUS_ACTION_DISPOSITION), `${entryLabel}.disposition`);
|
|
266
|
-
lineage(binding.lineage_id, `${entryLabel}.binding.lineage_id`);
|
|
267
|
-
sha256(binding.revision, `${entryLabel}.binding.revision`);
|
|
268
|
-
sha256(binding.target_identity, `${entryLabel}.binding.target_identity`);
|
|
269
|
-
} else if (action.disposition !== undefined || action.binding !== undefined) throw new TypeError(`${entryLabel} recovery fields require review.recover`);
|
|
270
|
-
return action;
|
|
271
|
-
}, { minimum: 1, maximum: 1 });
|
|
272
|
-
array(eligibility.forbidden_actions, `${label}.forbidden_actions`, (entry, entryLabel) => {
|
|
273
|
-
const action = exactRecord(entry, entryLabel, ["action", "reason_code"]);
|
|
274
|
-
enumeration(action.action, FORBIDDEN_ACTIONS, `${entryLabel}.action`);
|
|
275
|
-
text(action.reason_code, `${entryLabel}.reason_code`, { minimum: 1, pattern: /^[a-z0-9_]+$/ });
|
|
276
|
-
return action;
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function decodeNextTransition(value: unknown, label: string): void {
|
|
281
|
-
const transition = exactRecord(value, label, ["kind", "reason_code"], ["execute", "collect"]);
|
|
282
|
-
const kind = enumeration(transition.kind, ["execute", "collect", "stop"] as const, `${label}.kind`);
|
|
283
|
-
text(transition.reason_code, `${label}.reason_code`, { minimum: 1, pattern: /^[a-z0-9_]+$/ });
|
|
284
|
-
if (kind === "execute") {
|
|
285
|
-
const execute = exactRecord(transition.execute, `${label}.execute`, ["operation", "arguments", "preconditions", "binding"], ["artifacts"]);
|
|
286
|
-
enumeration(execute.operation, NEXT_TRANSITION_OPERATIONS, `${label}.execute.operation`);
|
|
287
|
-
decodeTransitionArguments(execute.arguments, `${label}.execute.arguments`);
|
|
288
|
-
decodeTransitionArguments(execute.preconditions, `${label}.execute.preconditions`);
|
|
289
|
-
const binding = exactRecord(execute.binding, `${label}.execute.binding`, ["target_identity"], ["lineage_id", "revision"]);
|
|
290
|
-
sha256(binding.target_identity, `${label}.execute.binding.target_identity`);
|
|
291
|
-
if (binding.lineage_id !== undefined) lineage(binding.lineage_id, `${label}.execute.binding.lineage_id`);
|
|
292
|
-
if (binding.revision !== undefined) sha256(binding.revision, `${label}.execute.binding.revision`);
|
|
293
|
-
if (transition.collect !== undefined) throw new TypeError(`${label}.collect is incompatible with execute`);
|
|
294
|
-
} else if (kind === "collect") {
|
|
295
|
-
const collect = exactRecord(transition.collect, `${label}.collect`, ["inputs"]);
|
|
296
|
-
array(collect.inputs, `${label}.collect.inputs`, (entry, entryLabel) => {
|
|
297
|
-
const input = exactRecord(entry, entryLabel, ["name", "schema", "capture_operation", "arguments"]);
|
|
298
|
-
text(input.name, `${entryLabel}.name`, { minimum: 1, pattern: /^[a-z0-9_]+$/ });
|
|
299
|
-
text(input.schema, `${entryLabel}.schema`, { minimum: 1 });
|
|
300
|
-
text(input.capture_operation, `${entryLabel}.capture_operation`, { minimum: 1 });
|
|
301
|
-
decodeTransitionArguments(input.arguments, `${entryLabel}.arguments`);
|
|
302
|
-
return input;
|
|
303
|
-
}, { minimum: 1 });
|
|
304
|
-
if (transition.execute !== undefined) throw new TypeError(`${label}.execute is incompatible with collect`);
|
|
305
|
-
} else if (transition.execute !== undefined || transition.collect !== undefined) throw new TypeError(`${label} stop cannot carry a transition`);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
const FAILURE_REQUIRED_INPUTS = [
|
|
309
|
-
"lineage_id",
|
|
310
|
-
"change",
|
|
311
|
-
"expected_binding_revision",
|
|
312
|
-
"predecessor_lineage_id",
|
|
313
|
-
"expected_predecessor_revision",
|
|
314
|
-
"successor_lineage_id",
|
|
315
|
-
"disposition",
|
|
316
|
-
"reason",
|
|
317
|
-
"actor",
|
|
318
|
-
] as const;
|
|
319
|
-
const FAILURE_NEXT_ACTIONS = ["correct_request", "retry", "retry_with_bounded_backoff", "review.status", "review.finalize", "review.bind_sdd", "explicit-maintainer-action", "stop"] as const;
|
|
320
|
-
// Known cause_category values: the vendored failure.schema.json enum plus
|
|
321
|
-
// "incomplete_store_entry", which the v2.1.8 emitter produces beyond that enum.
|
|
322
|
-
// cause_category is diagnostic metadata (nothing routes on it), so unknown
|
|
323
|
-
// snake_case values are tolerated for forward compatibility.
|
|
324
|
-
const FAILURE_CAUSE_CATEGORIES = ["inventory_io_or_layout", "lock_ambiguous", "reset_residue", "record_or_graph_invalid", "inventory_incomplete", "incomplete_store_entry"] as const;
|
|
325
|
-
export type ReviewFailureRequiredInputV1 = (typeof FAILURE_REQUIRED_INPUTS)[number];
|
|
326
|
-
export type ReviewFailureNextActionV1 = (typeof FAILURE_NEXT_ACTIONS)[number];
|
|
327
|
-
export type ReviewFailureCauseCategoryV1 = (typeof FAILURE_CAUSE_CATEGORIES)[number] | (string & {});
|
|
328
|
-
|
|
329
|
-
export interface ReviewFailureTargetEvidenceV1 {
|
|
330
|
-
candidateTree: string;
|
|
331
|
-
pathsDigest: string;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
export interface ReviewFailureScopeChangeV1 {
|
|
335
|
-
expected: ReviewFailureTargetEvidenceV1;
|
|
336
|
-
actual: ReviewFailureTargetEvidenceV1;
|
|
337
|
-
differingPathCount: number;
|
|
338
|
-
differingPathsDigest: string;
|
|
339
|
-
predecessorLineageId: string;
|
|
340
|
-
predecessorRevision: string;
|
|
341
|
-
recoveryOperation: "review.recover";
|
|
342
|
-
recoveryRequiredInputs: readonly string[];
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
export interface ReviewFailureContextV1 {
|
|
346
|
-
scopeChange: ReviewFailureScopeChangeV1;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
export interface ReviewFailureV1 {
|
|
350
|
-
schema: "gentle-ai.review-integration.failure/v1";
|
|
351
|
-
contract: typeof REVIEW_INTEGRATION_CONTRACT;
|
|
352
|
-
operation: ReviewIntegrationOperation;
|
|
353
|
-
phase: "preflight" | "pre_native" | "native_running" | "native_committed" | "reconciliation";
|
|
354
|
-
code: string;
|
|
355
|
-
message: string;
|
|
356
|
-
mutationOutcome: ReviewMutationOutcome;
|
|
357
|
-
authorityApplicability: ReviewAuthorityApplicability;
|
|
358
|
-
retrySafe: boolean;
|
|
359
|
-
replayability: ReviewReplayability;
|
|
360
|
-
lineageId?: string;
|
|
361
|
-
requestDigest?: string;
|
|
362
|
-
requiredInputs: readonly ReviewFailureRequiredInputV1[];
|
|
363
|
-
nextAction: ReviewFailureNextActionV1;
|
|
364
|
-
causeCategory?: ReviewFailureCauseCategoryV1;
|
|
365
|
-
context?: ReviewFailureContextV1;
|
|
366
|
-
raw: Readonly<Record<string, unknown>>;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
export interface ReviewOperationV1 {
|
|
370
|
-
contract: typeof REVIEW_INTEGRATION_CONTRACT;
|
|
371
|
-
operation: Exclude<ReviewIntegrationOperation, "review.capabilities" | "review.start" | "review.status">;
|
|
372
|
-
result: Readonly<Record<string, unknown>>;
|
|
373
|
-
raw: Readonly<Record<string, unknown>>;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
function record(value: unknown, label: string): Record<string, unknown> {
|
|
377
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError(`${label} must be an object`);
|
|
378
|
-
return value as Record<string, unknown>;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function exactRecord(value: unknown, label: string, required: readonly string[], optional: readonly string[] = [], allowAdditional = false): Record<string, unknown> {
|
|
382
|
-
const body = record(value, label);
|
|
383
|
-
for (const key of required) {
|
|
384
|
-
if (!Object.hasOwn(body, key)) throw new TypeError(`${label}.${key} is required`);
|
|
385
|
-
}
|
|
386
|
-
const allowed = new Set([...required, ...optional]);
|
|
387
|
-
if (!allowAdditional) for (const key of Object.keys(body)) if (!allowed.has(key)) throw new TypeError(`${label}.${key} is not allowed`);
|
|
388
|
-
return body;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
function text(value: unknown, label: string, options: { minimum?: number; maximum?: number; pattern?: RegExp } = {}): string {
|
|
392
|
-
const minimum = options.minimum ?? 0;
|
|
393
|
-
if (typeof value !== "string" || value.length < minimum || (options.maximum !== undefined && value.length > options.maximum) || (options.pattern !== undefined && !options.pattern.test(value))) {
|
|
394
|
-
throw new TypeError(`${label} is invalid`);
|
|
395
|
-
}
|
|
396
|
-
return value;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
function nonempty(value: unknown, label: string): string {
|
|
400
|
-
return text(value, label, { minimum: 1 });
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
function boolean(value: unknown, label: string): boolean {
|
|
404
|
-
if (typeof value !== "boolean") throw new TypeError(`${label} must be a boolean`);
|
|
405
|
-
return value;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
function integer(value: unknown, label: string, minimum = 0, maximum?: number): number {
|
|
409
|
-
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < minimum || (maximum !== undefined && value > maximum)) {
|
|
410
|
-
throw new TypeError(`${label} must be an integer in range`);
|
|
411
|
-
}
|
|
412
|
-
return value;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
function enumeration<T extends string>(value: unknown, values: readonly T[], label: string): T {
|
|
416
|
-
if (typeof value !== "string" || !values.includes(value as T)) throw new TypeError(`${label} is unsupported`);
|
|
417
|
-
return value as T;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
function canonicalJson(value: unknown): string {
|
|
421
|
-
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
422
|
-
if (typeof value === "object" && value !== null) {
|
|
423
|
-
const body = value as Record<string, unknown>;
|
|
424
|
-
return `{${Object.keys(body).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(body[key])}`).join(",")}}`;
|
|
425
|
-
}
|
|
426
|
-
return JSON.stringify(value);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
function array<T>(value: unknown, label: string, decodeItem: (entry: unknown, label: string) => T, options: { minimum?: number; maximum?: number; unique?: boolean } = {}): readonly T[] {
|
|
430
|
-
if (!Array.isArray(value) || value.length < (options.minimum ?? 0) || (options.maximum !== undefined && value.length > options.maximum)) {
|
|
431
|
-
throw new TypeError(`${label} has an invalid length`);
|
|
432
|
-
}
|
|
433
|
-
const decoded = value.map((entry, index) => decodeItem(entry, `${label}[${index}]`));
|
|
434
|
-
if (options.unique && new Set(decoded.map(canonicalJson)).size !== decoded.length) throw new TypeError(`${label} must not contain duplicates`);
|
|
435
|
-
return decoded;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
function stringArray(value: unknown, label: string, options: { minimum?: number; maximum?: number; unique?: boolean; pattern?: RegExp } = {}): readonly string[] {
|
|
439
|
-
return array(value, label, (entry, itemLabel) => text(entry, itemLabel, { minimum: 1, pattern: options.pattern }), options);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
function enumArray<T extends string>(value: unknown, values: readonly T[], label: string, options: { minimum?: number; maximum?: number; unique?: boolean } = {}): readonly T[] {
|
|
443
|
-
return array(value, label, (entry, itemLabel) => enumeration(entry, values, itemLabel), options);
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
function sha256(value: unknown, label: string): string {
|
|
447
|
-
return text(value, label, { pattern: /^sha256:[0-9a-f]{64}$/ });
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
function gitTree(value: unknown, label: string): string {
|
|
451
|
-
return text(value, label, { pattern: /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/ });
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
function lineage(value: unknown, label: string): string {
|
|
455
|
-
return text(value, label, { pattern: /^[a-z0-9]+(?:-[a-z0-9]+)*$/ });
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
function safePath(value: unknown, label: string): string {
|
|
459
|
-
return text(value, label, { minimum: 1, pattern: /^(?!\/)(?!.*(?:^|\/)\.\.(?:\/|$)).+$/ });
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
function requireIdentity(value: Record<string, unknown>, schema: string, operation?: string): void {
|
|
463
|
-
if (value.schema !== schema) throw new TypeError(`schema must be ${schema}`);
|
|
464
|
-
if (value.contract !== REVIEW_INTEGRATION_CONTRACT) throw new TypeError(`contract must be ${REVIEW_INTEGRATION_CONTRACT}`);
|
|
465
|
-
if (operation !== undefined && value.operation !== operation) throw new TypeError(`operation must be ${operation}`);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
function assertExactSet(actual: readonly string[], expected: readonly string[], label: string): void {
|
|
469
|
-
if (actual.length !== expected.length || expected.some((value) => !actual.includes(value))) throw new TypeError(`${label} does not match the required integration surface`);
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
function decodeFeature(value: unknown, label: string, allowAdditional = false): ReviewFeatureV1 {
|
|
473
|
-
const feature = exactRecord(value, label, ["name", "supported", "requires"], [], allowAdditional);
|
|
474
|
-
return {
|
|
475
|
-
name: enumeration(feature.name, FEATURE_NAMES, `${label}.name`),
|
|
476
|
-
supported: boolean(feature.supported, `${label}.supported`),
|
|
477
|
-
requires: stringArray(feature.requires, `${label}.requires`, { unique: true }),
|
|
478
|
-
};
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
function decodeOptionalFeature(value: unknown, label: string, allowUnknown: boolean): { name: string; supported: boolean; requires: readonly string[] } {
|
|
482
|
-
const feature = exactRecord(value, label, ["name", "supported", "requires"], [], allowUnknown);
|
|
483
|
-
const name = nonempty(feature.name, `${label}.name`);
|
|
484
|
-
if (!allowUnknown && !(FEATURE_NAMES as readonly string[]).includes(name)) throw new TypeError(`${label}.name is unsupported`);
|
|
485
|
-
return {
|
|
486
|
-
name,
|
|
487
|
-
supported: boolean(feature.supported, `${label}.supported`),
|
|
488
|
-
requires: stringArray(feature.requires, `${label}.requires`, { unique: true }),
|
|
489
|
-
};
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
export function decodeReviewCapabilitiesV1(value: unknown, verifiedExecutableDigest: string): ReviewCapabilitiesV1 {
|
|
493
|
-
const requiredFields = ["schema", "contract", "protocol", "package", "build", "executable", "operations", "gates", "projections", "schemas", "features", "compatibility"] as const;
|
|
494
|
-
const candidate = exactRecord(value, "capabilities", requiredFields, [], true);
|
|
495
|
-
const candidateProtocol = exactRecord(candidate.protocol, "capabilities.protocol", ["major", "minor"], [], true);
|
|
496
|
-
const protocolMajor = integer(candidateProtocol.major, "capabilities.protocol.major");
|
|
497
|
-
const protocolMinor = integer(candidateProtocol.minor, "capabilities.protocol.minor");
|
|
498
|
-
if (protocolMajor !== 1) throw new TypeError("incompatible review integration protocol");
|
|
499
|
-
const allowAdditions = protocolMinor > 0;
|
|
500
|
-
const body = exactRecord(value, "capabilities", requiredFields, [], allowAdditions);
|
|
501
|
-
const capabilitiesSchema = protocolMinor === 0 ? "gentle-ai.review-integration.capabilities/v1" : `gentle-ai.review-integration.capabilities/v1.${protocolMinor}`;
|
|
502
|
-
requireIdentity(body, capabilitiesSchema);
|
|
503
|
-
const requiredSchemas = REQUIRED_SCHEMAS.map((schema) => (schema === "gentle-ai.review-integration.capabilities/v1" ? capabilitiesSchema : schema));
|
|
504
|
-
|
|
505
|
-
const protocol = exactRecord(body.protocol, "capabilities.protocol", ["major", "minor"], [], allowAdditions);
|
|
506
|
-
if (protocol.major !== protocolMajor || protocol.minor !== protocolMinor) throw new TypeError("incompatible review integration protocol");
|
|
507
|
-
|
|
508
|
-
const packageIdentity = exactRecord(body.package, "capabilities.package", ["name", "version", "release_channel"], [], allowAdditions);
|
|
509
|
-
if (packageIdentity.name !== "gentle-ai") throw new TypeError("capabilities package identity mismatch");
|
|
510
|
-
const packageVersion = nonempty(packageIdentity.version, "capabilities.package.version");
|
|
511
|
-
enumeration(packageIdentity.release_channel, ["development", "prerelease", "stable"] as const, "capabilities.package.release_channel");
|
|
512
|
-
|
|
513
|
-
const build = exactRecord(body.build, "capabilities.build", ["id", "go_version", "module_version", "vcs", "vcs_revision", "vcs_time", "vcs_modified"], [], allowAdditions);
|
|
514
|
-
const buildId = sha256(build.id, "capabilities.build.id");
|
|
515
|
-
nonempty(build.go_version, "capabilities.build.go_version");
|
|
516
|
-
for (const field of ["module_version", "vcs", "vcs_revision", "vcs_time"] as const) text(build[field], `capabilities.build.${field}`);
|
|
517
|
-
enumeration(build.vcs_modified, ["true", "false", "unknown"] as const, "capabilities.build.vcs_modified");
|
|
518
|
-
|
|
519
|
-
const executable = exactRecord(body.executable, "capabilities.executable", ["sha256", "evidence", "verification"], [], allowAdditions);
|
|
520
|
-
const selfReportedDigest = sha256(executable.sha256, "capabilities.executable.sha256");
|
|
521
|
-
if (executable.evidence !== "self-reported" || executable.verification !== "compare-with-published-manifest") throw new TypeError("capabilities executable evidence is incompatible");
|
|
522
|
-
const normalizedVerifiedDigest = sha256(verifiedExecutableDigest.startsWith("sha256:") ? verifiedExecutableDigest : `sha256:${verifiedExecutableDigest}`, "verified executable digest");
|
|
523
|
-
if (selfReportedDigest !== normalizedVerifiedDigest) throw new TypeError("review provider executable digest mismatch");
|
|
524
|
-
|
|
525
|
-
const operations = enumArray(body.operations, REQUIRED_OPERATIONS, "capabilities.operations", { minimum: 6, maximum: 6, unique: true });
|
|
526
|
-
const gates = enumArray(body.gates, REQUIRED_GATES, "capabilities.gates", { minimum: 5, maximum: 5, unique: true });
|
|
527
|
-
const projections = enumArray(body.projections, REQUIRED_PROJECTIONS, "capabilities.projections", { minimum: 2, maximum: 2, unique: true });
|
|
528
|
-
const schemas = enumArray(body.schemas, requiredSchemas, "capabilities.schemas", { minimum: 14, maximum: 14, unique: true });
|
|
529
|
-
assertExactSet(operations, REQUIRED_OPERATIONS, "capabilities operations");
|
|
530
|
-
assertExactSet(gates, REQUIRED_GATES, "capabilities gates");
|
|
531
|
-
assertExactSet(projections, REQUIRED_PROJECTIONS, "capabilities projections");
|
|
532
|
-
assertExactSet(schemas, requiredSchemas, "capabilities schemas");
|
|
533
|
-
|
|
534
|
-
const features = exactRecord(body.features, "capabilities.features", ["mandatory", "optional"], [], allowAdditions);
|
|
535
|
-
const mandatory = array(features.mandatory, "capabilities.features.mandatory", (entry, label) => decodeFeature(entry, label, allowAdditions), { minimum: 10, ...(allowAdditions ? {} : { maximum: 10 }) });
|
|
536
|
-
const optional = array(features.optional, "capabilities.features.optional", (entry, label) => decodeOptionalFeature(entry, label, allowAdditions), { minimum: 5, ...(allowAdditions ? {} : { maximum: 5 }), unique: true });
|
|
537
|
-
const mandatoryNames = mandatory.map((feature) => feature.name);
|
|
538
|
-
const optionalNames = optional.map((feature) => feature.name);
|
|
539
|
-
assertExactSet(mandatoryNames, REQUIRED_MANDATORY_FEATURES, "mandatory capabilities");
|
|
540
|
-
if (new Set(optionalNames).size !== optionalNames.length) throw new TypeError("optional capabilities contain duplicate names");
|
|
541
|
-
if (optionalNames.some((name) => mandatoryNames.includes(name as ReviewFeatureV1["name"]))) throw new TypeError("mandatory and optional capabilities overlap");
|
|
542
|
-
if (mandatory.some((feature) => !feature.supported)) throw new TypeError("mandatory capability is unsupported");
|
|
543
|
-
|
|
544
|
-
const compatibility = exactRecord(body.compatibility, "capabilities.compatibility", ["minimum_protocol_major", "maximum_protocol_major", "additive_minor_policy", "unknown_mandatory", "unknown_optional", "modes", "legacy_window"], [], allowAdditions);
|
|
545
|
-
if (compatibility.minimum_protocol_major !== 1 || compatibility.maximum_protocol_major !== 1 || compatibility.additive_minor_policy !== "optional-fields-only" || compatibility.unknown_mandatory !== "reject" || compatibility.unknown_optional !== "ignore") {
|
|
546
|
-
throw new TypeError("incompatible capability evolution policy");
|
|
547
|
-
}
|
|
548
|
-
const modes = enumArray(compatibility.modes, Object.values(REVIEW_AUTHORITY_VERSION), "capabilities.compatibility.modes", { minimum: 2, maximum: 2 });
|
|
549
|
-
if (modes[0] !== REVIEW_AUTHORITY_VERSION.COMPACT_V2 || modes[1] !== REVIEW_AUTHORITY_VERSION.LEGACY_V1) throw new TypeError("capabilities compatibility modes are out of order");
|
|
550
|
-
const legacyWindow = exactRecord(compatibility.legacy_window, "capabilities.compatibility.legacy_window", ["mode", "state", "read_only", "deprecation_started", "removal", "minimum_compatibility_releases"], [], allowAdditions);
|
|
551
|
-
if (legacyWindow.mode !== REVIEW_AUTHORITY_VERSION.LEGACY_V1) throw new TypeError("capabilities legacy window mode is incompatible");
|
|
552
|
-
enumeration(legacyWindow.state, ["pre-fence", "active", "deprecated", "expired"] as const, "capabilities.compatibility.legacy_window.state");
|
|
553
|
-
boolean(legacyWindow.read_only, "capabilities.compatibility.legacy_window.read_only");
|
|
554
|
-
boolean(legacyWindow.deprecation_started, "capabilities.compatibility.legacy_window.deprecation_started");
|
|
555
|
-
nonempty(legacyWindow.removal, "capabilities.compatibility.legacy_window.removal");
|
|
556
|
-
integer(legacyWindow.minimum_compatibility_releases, "capabilities.compatibility.legacy_window.minimum_compatibility_releases", 1);
|
|
557
|
-
|
|
558
|
-
return {
|
|
559
|
-
contract: REVIEW_INTEGRATION_CONTRACT,
|
|
560
|
-
packageVersion,
|
|
561
|
-
buildId,
|
|
562
|
-
executableDigest: selfReportedDigest,
|
|
563
|
-
operations: new Set(operations),
|
|
564
|
-
gates: new Set(gates),
|
|
565
|
-
projections: new Set(projections),
|
|
566
|
-
schemas: new Set(schemas),
|
|
567
|
-
mandatoryFeatures: new Set(mandatoryNames),
|
|
568
|
-
optionalFeatures: new Set(optional.filter((feature) => feature.supported && (FEATURE_NAMES as readonly string[]).includes(feature.name)).map((feature) => feature.name)),
|
|
569
|
-
raw: body,
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
export function decodeReviewStartV1(value: unknown): ReviewStartV1 {
|
|
574
|
-
const overlayFields = ["target_mode", "target_identity", "base_tree", "candidate_tree"] as const;
|
|
575
|
-
const body = exactRecord(value, "start", ["schema", "contract", "operation", "action", "lenses_required", "lineage_id", "state", "risk_level", "selected_lenses", "projection", "changed_files", "changed_lines", "correction_budget", "risk_reasons"], [...overlayFields]);
|
|
576
|
-
requireIdentity(body, "gentle-ai.review-integration.start/v1", REVIEW_INTEGRATION_OPERATION.START);
|
|
577
|
-
const overlayPresent = overlayFields.filter((field) => body[field] !== undefined);
|
|
578
|
-
if (overlayPresent.length > 0 && overlayPresent.length !== overlayFields.length) throw new TypeError("start workspace-overlay target binding requires target_mode, target_identity, base_tree, and candidate_tree together");
|
|
579
|
-
let overlay: Pick<ReviewStartV1, "targetMode" | "targetIdentity" | "baseTree" | "candidateTree"> = {};
|
|
580
|
-
if (overlayPresent.length === overlayFields.length) {
|
|
581
|
-
overlay = {
|
|
582
|
-
targetMode: enumeration(body.target_mode, ["base-workspace-overlay"] as const, "start.target_mode"),
|
|
583
|
-
targetIdentity: sha256(body.target_identity, "start.target_identity"),
|
|
584
|
-
baseTree: gitTree(body.base_tree, "start.base_tree"),
|
|
585
|
-
candidateTree: gitTree(body.candidate_tree, "start.candidate_tree"),
|
|
586
|
-
};
|
|
587
|
-
}
|
|
588
|
-
const riskReasons = array(body.risk_reasons, "start.risk_reasons", (entry, label): ReviewRiskReasonV1 => {
|
|
589
|
-
const reason = exactRecord(entry, label, ["code"], ["signal", "path", "old_mode", "new_mode"]);
|
|
590
|
-
return {
|
|
591
|
-
code: enumeration(reason.code, RISK_REASON_CODES, `${label}.code`),
|
|
592
|
-
...(reason.signal === undefined ? {} : { signal: enumeration(reason.signal, RISK_SIGNALS, `${label}.signal`) }),
|
|
593
|
-
...(reason.path === undefined ? {} : { path: nonempty(reason.path, `${label}.path`) }),
|
|
594
|
-
...(reason.old_mode === undefined ? {} : { oldMode: text(reason.old_mode, `${label}.old_mode`, { pattern: /^[0-7]{6}$/ }) }),
|
|
595
|
-
...(reason.new_mode === undefined ? {} : { newMode: text(reason.new_mode, `${label}.new_mode`, { pattern: /^[0-7]{6}$/ }) }),
|
|
596
|
-
};
|
|
597
|
-
}, { minimum: 1, unique: true });
|
|
598
|
-
return {
|
|
599
|
-
contract: REVIEW_INTEGRATION_CONTRACT,
|
|
600
|
-
action: enumeration(body.action, START_ACTIONS, "start.action"),
|
|
601
|
-
lensesRequired: boolean(body.lenses_required, "start.lenses_required"),
|
|
602
|
-
lineageId: nonempty(body.lineage_id, "start.lineage_id"),
|
|
603
|
-
state: enumeration(body.state, Object.values(REVIEW_START_STATE), "start.state"),
|
|
604
|
-
riskLevel: enumeration(body.risk_level, RISK_LEVELS, "start.risk_level"),
|
|
605
|
-
selectedLenses: enumArray(body.selected_lenses, REVIEW_LENSES, "start.selected_lenses", { maximum: 4, unique: true }),
|
|
606
|
-
projection: enumeration(body.projection, REQUIRED_PROJECTIONS, "start.projection"),
|
|
607
|
-
changedFiles: integer(body.changed_files, "start.changed_files"),
|
|
608
|
-
changedLines: integer(body.changed_lines, "start.changed_lines"),
|
|
609
|
-
correctionBudget: integer(body.correction_budget, "start.correction_budget", 0, 200),
|
|
610
|
-
riskReasons,
|
|
611
|
-
...overlay,
|
|
612
|
-
raw: body,
|
|
613
|
-
};
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
export function decodeReviewProjectionV1(value: unknown): ReviewProjectionDescriptorV1 {
|
|
617
|
-
const projection = exactRecord(value, "status.projection", ["schema", "kind", "projection", "base_tree", "initial_review_tree", "current_candidate_tree", "paths_digest", "paths", "intended_untracked", "intended_untracked_proof", "initial_snapshot_identity", "current_snapshot_identity"]);
|
|
618
|
-
if (projection.schema !== "gentle-ai.review-integration.projection/v1") throw new TypeError("status.projection schema is incompatible");
|
|
619
|
-
return {
|
|
620
|
-
schema: "gentle-ai.review-integration.projection/v1",
|
|
621
|
-
kind: enumeration(projection.kind, Object.values(REVIEW_PROJECTION_KIND), "status.projection.kind"),
|
|
622
|
-
projection: enumeration(projection.projection, REQUIRED_PROJECTIONS, "status.projection.projection"),
|
|
623
|
-
baseTree: gitTree(projection.base_tree, "status.projection.base_tree"),
|
|
624
|
-
initialReviewTree: gitTree(projection.initial_review_tree, "status.projection.initial_review_tree"),
|
|
625
|
-
currentCandidateTree: gitTree(projection.current_candidate_tree, "status.projection.current_candidate_tree"),
|
|
626
|
-
pathsDigest: sha256(projection.paths_digest, "status.projection.paths_digest"),
|
|
627
|
-
paths: array(projection.paths, "status.projection.paths", safePath, { unique: true }),
|
|
628
|
-
intendedUntracked: array(projection.intended_untracked, "status.projection.intended_untracked", safePath, { unique: true }),
|
|
629
|
-
intendedUntrackedProof: sha256(projection.intended_untracked_proof, "status.projection.intended_untracked_proof"),
|
|
630
|
-
initialSnapshotIdentity: sha256(projection.initial_snapshot_identity, "status.projection.initial_snapshot_identity"),
|
|
631
|
-
currentSnapshotIdentity: sha256(projection.current_snapshot_identity, "status.projection.current_snapshot_identity"),
|
|
632
|
-
};
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
export function decodeReviewStatusV1(value: unknown): ReviewStatusV1 {
|
|
636
|
-
const body = exactRecord(value, "status", ["schema", "contract", "operation", "applicability", "receipt", "action", "replayability", "target_identity", "projection", "candidates"], ["authority", "frozen", "reconciliation", "action_disposition", "eligibility", "next_transition"]);
|
|
637
|
-
requireIdentity(body, "gentle-ai.review-integration.status/v1", REVIEW_INTEGRATION_OPERATION.STATUS);
|
|
638
|
-
const applicability = enumeration(body.applicability, ["current_target", "unrelated", "ambiguous", "corrupted"] as const, "status.applicability");
|
|
639
|
-
const receiptBody = exactRecord(body.receipt, "status.receipt", ["status"], ["identity"]);
|
|
640
|
-
const receiptStatus = enumeration(receiptBody.status, RECEIPT_STATUSES, "status.receipt.status");
|
|
641
|
-
const receipt: ReviewStatusReceiptV1 = { status: receiptStatus, ...(receiptBody.identity === undefined ? {} : { identity: sha256(receiptBody.identity, "status.receipt.identity") }) };
|
|
642
|
-
|
|
643
|
-
let authority: ReviewStatusAuthorityV1 | undefined;
|
|
644
|
-
if (body.authority !== undefined) {
|
|
645
|
-
const source = exactRecord(body.authority, "status.authority", ["version", "lineage_id", "state", "generation", "revision"]);
|
|
646
|
-
authority = {
|
|
647
|
-
version: enumeration(source.version, Object.values(REVIEW_AUTHORITY_VERSION), "status.authority.version"),
|
|
648
|
-
lineageId: lineage(source.lineage_id, "status.authority.lineage_id"),
|
|
649
|
-
state: nonempty(source.state, "status.authority.state"),
|
|
650
|
-
generation: integer(source.generation, "status.authority.generation", 1),
|
|
651
|
-
revision: sha256(source.revision, "status.authority.revision"),
|
|
652
|
-
};
|
|
653
|
-
}
|
|
654
|
-
if (applicability === REVIEW_AUTHORITY_APPLICABILITY.CURRENT_TARGET && authority === undefined) throw new TypeError("current_target status requires authority");
|
|
655
|
-
if (applicability !== REVIEW_AUTHORITY_APPLICABILITY.CURRENT_TARGET && authority !== undefined) throw new TypeError("non-current status cannot expose authority");
|
|
656
|
-
|
|
657
|
-
let frozen: ReviewStatusFrozenV1 | undefined;
|
|
658
|
-
if (body.frozen !== undefined) {
|
|
659
|
-
const source = exactRecord(body.frozen, "status.frozen", ["tier", "original_changed_lines", "correction_budget"]);
|
|
660
|
-
frozen = {
|
|
661
|
-
tier: enumeration(source.tier, RISK_LEVELS, "status.frozen.tier"),
|
|
662
|
-
originalChangedLines: integer(source.original_changed_lines, "status.frozen.original_changed_lines"),
|
|
663
|
-
correctionBudget: integer(source.correction_budget, "status.frozen.correction_budget", 0, 200),
|
|
664
|
-
};
|
|
665
|
-
}
|
|
666
|
-
if (authority?.version === REVIEW_AUTHORITY_VERSION.COMPACT_V2 && frozen === undefined) throw new TypeError("compact-v2 status requires frozen metadata");
|
|
667
|
-
if ((authority?.version === REVIEW_AUTHORITY_VERSION.LEGACY_V1 || applicability !== REVIEW_AUTHORITY_APPLICABILITY.CURRENT_TARGET) && frozen !== undefined) throw new TypeError("legacy/non-current status cannot expose frozen metadata");
|
|
668
|
-
if (authority?.version === REVIEW_AUTHORITY_VERSION.LEGACY_V1 && receiptStatus !== "expected_missing" && receiptStatus !== "present") throw new TypeError("legacy status receipt is incompatible");
|
|
669
|
-
|
|
670
|
-
const action = enumeration(body.action, STATUS_ACTIONS, "status.action");
|
|
671
|
-
const actionDisposition = body.action_disposition === undefined
|
|
672
|
-
? undefined
|
|
673
|
-
: enumeration(body.action_disposition, Object.values(REVIEW_STATUS_ACTION_DISPOSITION), "status.action_disposition");
|
|
674
|
-
if (action === "recover" && actionDisposition === undefined) throw new TypeError("recover status requires action_disposition");
|
|
675
|
-
if (action !== "recover" && actionDisposition !== undefined) throw new TypeError("status.action_disposition is only valid for the recover action");
|
|
676
|
-
if (body.eligibility !== undefined) decodeEligibility(body.eligibility, "status.eligibility");
|
|
677
|
-
if (body.next_transition !== undefined) decodeNextTransition(body.next_transition, "status.next_transition");
|
|
678
|
-
const replayability = enumeration(body.replayability, Object.values(REVIEW_REPLAYABILITY), "status.replayability");
|
|
679
|
-
let reconciliation: ReviewStatusReconciliationV1 | undefined;
|
|
680
|
-
if (action === "reconcile_finalize") {
|
|
681
|
-
if (body.reconciliation === undefined) throw new TypeError("reconcile_finalize status requires reconciliation");
|
|
682
|
-
const source = exactRecord(body.reconciliation, "status.reconciliation", ["required"]);
|
|
683
|
-
if (source.required !== true) throw new TypeError("status.reconciliation.required must be true");
|
|
684
|
-
if (applicability !== REVIEW_AUTHORITY_APPLICABILITY.CURRENT_TARGET) throw new TypeError("reconcile_finalize status requires current_target applicability");
|
|
685
|
-
if (replayability !== REVIEW_REPLAYABILITY.STATUS_REQUIRED) throw new TypeError("reconcile_finalize status requires status_required replayability");
|
|
686
|
-
reconciliation = { required: true };
|
|
687
|
-
} else if (body.reconciliation !== undefined) {
|
|
688
|
-
throw new TypeError("status.reconciliation is only valid for the reconcile_finalize action");
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
return {
|
|
692
|
-
contract: REVIEW_INTEGRATION_CONTRACT,
|
|
693
|
-
applicability,
|
|
694
|
-
...(authority === undefined ? {} : { authority }),
|
|
695
|
-
receipt,
|
|
696
|
-
action,
|
|
697
|
-
...(actionDisposition === undefined ? {} : { actionDisposition }),
|
|
698
|
-
replayability,
|
|
699
|
-
...(frozen === undefined ? {} : { frozen }),
|
|
700
|
-
...(reconciliation === undefined ? {} : { reconciliation }),
|
|
701
|
-
targetIdentity: sha256(body.target_identity, "status.target_identity"),
|
|
702
|
-
projection: decodeReviewProjectionV1(body.projection),
|
|
703
|
-
candidates: stringArray(body.candidates, "status.candidates", { unique: true, pattern: /^[a-z0-9]+(?:-[a-z0-9]+)*$/ }),
|
|
704
|
-
raw: body,
|
|
705
|
-
};
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
function decodeFailureTargetEvidence(value: unknown, label: string): ReviewFailureTargetEvidenceV1 {
|
|
709
|
-
const evidence = exactRecord(value, label, ["candidate_tree", "paths_digest"]);
|
|
710
|
-
return {
|
|
711
|
-
candidateTree: gitTree(evidence.candidate_tree, `${label}.candidate_tree`),
|
|
712
|
-
pathsDigest: sha256(evidence.paths_digest, `${label}.paths_digest`),
|
|
713
|
-
};
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
function decodeFailureContext(value: unknown, label: string): ReviewFailureContextV1 {
|
|
717
|
-
const context = exactRecord(value, label, ["scope_change"]);
|
|
718
|
-
const scopeLabel = `${label}.scope_change`;
|
|
719
|
-
const scope = exactRecord(context.scope_change, scopeLabel, ["expected", "actual", "differing_path_count", "differing_paths_digest", "predecessor_lineage_id", "predecessor_revision", "recovery_operation", "recovery_required_inputs"]);
|
|
720
|
-
if (scope.recovery_operation !== "review.recover") throw new TypeError(`${scopeLabel}.recovery_operation is unsupported`);
|
|
721
|
-
const recoveryInputs = stringArray(scope.recovery_required_inputs, `${scopeLabel}.recovery_required_inputs`, { minimum: 6, maximum: 6 });
|
|
722
|
-
const expectedRecoveryInputs = ["predecessor_lineage_id", "expected_predecessor_revision", "successor_lineage_id", "disposition", "reason", "actor"];
|
|
723
|
-
if (recoveryInputs.some((input, index) => input !== expectedRecoveryInputs[index])) throw new TypeError(`${scopeLabel}.recovery_required_inputs is unsupported`);
|
|
724
|
-
return {
|
|
725
|
-
scopeChange: {
|
|
726
|
-
expected: decodeFailureTargetEvidence(scope.expected, `${scopeLabel}.expected`),
|
|
727
|
-
actual: decodeFailureTargetEvidence(scope.actual, `${scopeLabel}.actual`),
|
|
728
|
-
differingPathCount: integer(scope.differing_path_count, `${scopeLabel}.differing_path_count`, 0, 1_000_000),
|
|
729
|
-
differingPathsDigest: sha256(scope.differing_paths_digest, `${scopeLabel}.differing_paths_digest`),
|
|
730
|
-
predecessorLineageId: lineage(scope.predecessor_lineage_id, `${scopeLabel}.predecessor_lineage_id`),
|
|
731
|
-
predecessorRevision: sha256(scope.predecessor_revision, `${scopeLabel}.predecessor_revision`),
|
|
732
|
-
recoveryOperation: "review.recover",
|
|
733
|
-
recoveryRequiredInputs: recoveryInputs,
|
|
734
|
-
},
|
|
735
|
-
};
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
export function decodeReviewFailureV1(value: unknown): ReviewFailureV1 {
|
|
739
|
-
const body = exactRecord(value, "failure", ["schema", "contract", "operation", "phase", "code", "message", "mutation_outcome", "authority_applicability", "retry_safe", "replayability", "required_inputs", "next_action"], ["lineage_id", "request_digest", "cause_category", "context"]);
|
|
740
|
-
requireIdentity(body, "gentle-ai.review-integration.failure/v1");
|
|
741
|
-
return {
|
|
742
|
-
schema: "gentle-ai.review-integration.failure/v1",
|
|
743
|
-
contract: REVIEW_INTEGRATION_CONTRACT,
|
|
744
|
-
operation: enumeration(body.operation, REQUIRED_OPERATIONS, "failure.operation"),
|
|
745
|
-
phase: enumeration(body.phase, ["preflight", "pre_native", "native_running", "native_committed", "reconciliation"] as const, "failure.phase"),
|
|
746
|
-
code: text(body.code, "failure.code", { pattern: /^[a-z0-9]+(?:_[a-z0-9]+)*$/ }),
|
|
747
|
-
message: text(body.message, "failure.message", { minimum: 1, maximum: 240, pattern: /^[^\r\n]+$/ }),
|
|
748
|
-
mutationOutcome: enumeration(body.mutation_outcome, Object.values(REVIEW_MUTATION_OUTCOME), "failure.mutation_outcome"),
|
|
749
|
-
authorityApplicability: enumeration(body.authority_applicability, Object.values(REVIEW_AUTHORITY_APPLICABILITY), "failure.authority_applicability"),
|
|
750
|
-
retrySafe: boolean(body.retry_safe, "failure.retry_safe"),
|
|
751
|
-
replayability: enumeration(body.replayability, Object.values(REVIEW_REPLAYABILITY), "failure.replayability"),
|
|
752
|
-
...(body.lineage_id === undefined ? {} : { lineageId: lineage(body.lineage_id, "failure.lineage_id") }),
|
|
753
|
-
...(body.request_digest === undefined ? {} : { requestDigest: sha256(body.request_digest, "failure.request_digest") }),
|
|
754
|
-
requiredInputs: enumArray(body.required_inputs, FAILURE_REQUIRED_INPUTS, "failure.required_inputs", { unique: true }),
|
|
755
|
-
nextAction: enumeration(body.next_action, FAILURE_NEXT_ACTIONS, "failure.next_action"),
|
|
756
|
-
...(body.cause_category === undefined ? {} : { causeCategory: text(body.cause_category, "failure.cause_category", { minimum: 1, pattern: /^[a-z0-9]+(?:_[a-z0-9]+)*$/ }) }),
|
|
757
|
-
...(body.context === undefined ? {} : { context: decodeFailureContext(body.context, "failure.context") }),
|
|
758
|
-
raw: body,
|
|
759
|
-
};
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
export function decodeReviewOperationV1(value: unknown): ReviewOperationV1 {
|
|
763
|
-
const body = exactRecord(value, "operation", ["schema", "contract", "operation", "result"]);
|
|
764
|
-
requireIdentity(body, "gentle-ai.review-integration.operation/v1");
|
|
765
|
-
const operation = enumeration(body.operation, ["review.finalize", "review.validate", "review.bind_sdd"] as const, "operation.operation");
|
|
766
|
-
let result: Record<string, unknown>;
|
|
767
|
-
if (operation === REVIEW_INTEGRATION_OPERATION.FINALIZE) {
|
|
768
|
-
result = exactRecord(body.result, "operation.result", ["operation", "lineage_id", "state", "action", "store_revision"], ["eligibility", "next_transition"]);
|
|
769
|
-
if (result.operation !== "review/finalize") throw new TypeError("operation.result does not match review.finalize");
|
|
770
|
-
nonempty(result.lineage_id, "operation.result.lineage_id");
|
|
771
|
-
nonempty(result.state, "operation.result.state");
|
|
772
|
-
nonempty(result.action, "operation.result.action");
|
|
773
|
-
sha256(result.store_revision, "operation.result.store_revision");
|
|
774
|
-
if (result.eligibility !== undefined) decodeEligibility(result.eligibility, "operation.result.eligibility");
|
|
775
|
-
if (result.next_transition !== undefined) decodeNextTransition(result.next_transition, "operation.result.next_transition");
|
|
776
|
-
} else if (operation === REVIEW_INTEGRATION_OPERATION.VALIDATE) {
|
|
777
|
-
result = exactRecord(body.result, "operation.result", ["schema", "result", "allowed", "action", "reason", "context"]);
|
|
778
|
-
if (result.schema !== "gentle-ai.review-gate-result/v1") throw new TypeError("operation.result does not match review.validate");
|
|
779
|
-
enumeration(result.result, ["allow", "scope-changed", "invalidated", "escalated"] as const, "operation.result.result");
|
|
780
|
-
boolean(result.allowed, "operation.result.allowed");
|
|
781
|
-
nonempty(result.action, "operation.result.action");
|
|
782
|
-
nonempty(result.reason, "operation.result.reason");
|
|
783
|
-
record(result.context, "operation.result.context");
|
|
784
|
-
} else {
|
|
785
|
-
result = exactRecord(body.result, "operation.result", ["schema", "revision", "change", "lineage", "authority_revision", "receipt_hash", "gate_context"]);
|
|
786
|
-
if (result.schema !== "gentle-ai.sdd-review-binding/v1") throw new TypeError("operation.result does not match review.bind_sdd");
|
|
787
|
-
sha256(result.revision, "operation.result.revision");
|
|
788
|
-
nonempty(result.change, "operation.result.change");
|
|
789
|
-
nonempty(result.lineage, "operation.result.lineage");
|
|
790
|
-
sha256(result.authority_revision, "operation.result.authority_revision");
|
|
791
|
-
sha256(result.receipt_hash, "operation.result.receipt_hash");
|
|
792
|
-
record(result.gate_context, "operation.result.gate_context");
|
|
793
|
-
}
|
|
794
|
-
return { contract: REVIEW_INTEGRATION_CONTRACT, operation, result, raw: body };
|
|
795
|
-
}
|