gentle-pi 1.2.0 → 2.1.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 +32 -10
- 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 +647 -58
- package/lib/gentle-ai-binary.ts +65 -18
- package/lib/native-review-cli.ts +824 -56
- package/lib/review-candidate-view.ts +135 -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 +1567 -0
- package/package.json +15 -11
- package/runtime/gentle-ai-binary.mjs +65 -18
- package/runtime/native-review-cli.mjs +808 -40
- package/runtime/review-integration-v2.mjs +1568 -0
- package/scripts/build-git-commit-transaction-runner.mjs +1 -1
- package/scripts/gentle-ai-installer.mjs +464 -76
- package/scripts/install-gentle-ai.mjs +7 -3
- package/scripts/test-packed-runner.mjs +3 -3
- package/scripts/verify-package-files.mjs +216 -64
- package/skills/_shared/review-ledger-contract.md +3 -1
- package/skills/release/SKILL.md +38 -11
- package/tests/devbinary/native-review-parity.devtest.ts +348 -0
- package/tests/gentle-ai-binary.test.ts +77 -3
- package/tests/gentle-ai-installer.test.ts +561 -22
- package/tests/gentle-ai.test.ts +2 -2
- 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-consent.test.ts +128 -0
- package/tests/native-review-parity-runtime.test.ts +85 -29
- package/tests/native-review-parity.test.ts +782 -0
- package/tests/native-risk-evidence-parity.test.ts +91 -0
- package/tests/package-manifest.test.ts +80 -8
- package/tests/review-actor-tool-deny.test.ts +20 -5
- package/tests/review-candidate-view.test.ts +374 -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 +632 -18
- package/tests/review-controller-workspace-root.test.ts +26 -6
- package/tests/review-controller.test.ts +1 -1
- package/tests/review-correction-lifecycle.test.ts +174 -0
- package/tests/review-integration-v2.test.ts +548 -0
- package/tests/runtime-harness.mjs +211 -12
- 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
|
@@ -8,7 +8,7 @@ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-a
|
|
|
8
8
|
import { createGentleAiExtension } from "../extensions/gentle-ai.ts";
|
|
9
9
|
import type { NativeReviewCli } from "../lib/native-review-cli.ts";
|
|
10
10
|
import { CandidateViewRegistry } from "../lib/review-candidate-view.ts";
|
|
11
|
-
import type {
|
|
11
|
+
import type { AuthorityRepairAssessmentV1, ReviewStatusV3 } from "../lib/review-integration-v2.ts";
|
|
12
12
|
|
|
13
13
|
interface RegisteredTool {
|
|
14
14
|
execute: (
|
|
@@ -90,11 +90,19 @@ function fakeNative(overrides: Partial<NativeReviewCli> = {}): NativeReviewCli {
|
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
const UNSUPPORTED_REPAIR_ASSESSMENT: AuthorityRepairAssessmentV1 = {
|
|
94
|
+
schema: "gentle-ai.review-authority-repair-assessment/v1",
|
|
95
|
+
status: "unsupported",
|
|
96
|
+
counts: { lineages: 0, compactLineages: 0, legacyLineages: 0, events: 0, bytes: 0, eligibleCandidates: 0, unsupportedLineages: 0, conflicts: 0 },
|
|
97
|
+
supportedOperations: ["review/complete-fix", "review/validate-fix"],
|
|
98
|
+
authorizationSchema: "gentle-ai.review-repair-authorization/v1",
|
|
99
|
+
};
|
|
100
|
+
|
|
93
101
|
function targetStatusFixture(options: {
|
|
94
102
|
applicability?: "current_target" | "unrelated";
|
|
95
|
-
action?:
|
|
103
|
+
action?: ReviewStatusV3["action"];
|
|
96
104
|
lineageId?: string;
|
|
97
|
-
} = {}):
|
|
105
|
+
} = {}): ReviewStatusV3 {
|
|
98
106
|
const applicability = options.applicability ?? "current_target";
|
|
99
107
|
const action = options.action ?? (applicability === "current_target" ? "finalize" : "start");
|
|
100
108
|
const lineageId = options.lineageId ?? "native-lineage";
|
|
@@ -114,15 +122,26 @@ function targetStatusFixture(options: {
|
|
|
114
122
|
initialSnapshotIdentity: sha,
|
|
115
123
|
currentSnapshotIdentity: sha,
|
|
116
124
|
};
|
|
125
|
+
const rawRepair = {
|
|
126
|
+
schema: UNSUPPORTED_REPAIR_ASSESSMENT.schema,
|
|
127
|
+
status: UNSUPPORTED_REPAIR_ASSESSMENT.status,
|
|
128
|
+
counts: {
|
|
129
|
+
lineages: 0, compact_lineages: 0, legacy_lineages: 0, events: 0, bytes: 0,
|
|
130
|
+
eligible_candidates: 0, unsupported_lineages: 0, conflicts: 0,
|
|
131
|
+
},
|
|
132
|
+
supported_operations: UNSUPPORTED_REPAIR_ASSESSMENT.supportedOperations,
|
|
133
|
+
authorization_schema: UNSUPPORTED_REPAIR_ASSESSMENT.authorizationSchema,
|
|
134
|
+
};
|
|
117
135
|
const raw: Record<string, unknown> = {
|
|
118
|
-
schema: "gentle-ai.review-integration.status/
|
|
119
|
-
contract: "gentle-ai.review-integration/
|
|
136
|
+
schema: "gentle-ai.review-integration.status/v3",
|
|
137
|
+
contract: "gentle-ai.review-integration/v2",
|
|
120
138
|
operation: "review.status",
|
|
121
139
|
applicability,
|
|
122
140
|
receipt: { status: applicability === "current_target" ? "expected_missing" : "not_applicable" },
|
|
123
141
|
action,
|
|
124
142
|
replayability: "not_replayable",
|
|
125
143
|
target_identity: sha,
|
|
144
|
+
repair: rawRepair,
|
|
126
145
|
projection: {
|
|
127
146
|
schema: projection.schema,
|
|
128
147
|
kind: projection.kind,
|
|
@@ -144,7 +163,7 @@ function targetStatusFixture(options: {
|
|
|
144
163
|
raw.frozen = { tier: "medium", original_changed_lines: 2, correction_budget: 1 };
|
|
145
164
|
}
|
|
146
165
|
return {
|
|
147
|
-
contract: "gentle-ai.review-integration/
|
|
166
|
+
contract: "gentle-ai.review-integration/v2",
|
|
148
167
|
applicability,
|
|
149
168
|
...(applicability === "current_target" ? { authority: { version: "compact-v2" as const, lineageId, state: "reviewing", generation: 1, revision: sha } } : {}),
|
|
150
169
|
receipt: { status: applicability === "current_target" ? "expected_missing" : "not_applicable" },
|
|
@@ -153,6 +172,7 @@ function targetStatusFixture(options: {
|
|
|
153
172
|
...(applicability === "current_target" ? { frozen: { tier: "medium" as const, originalChangedLines: 2, correctionBudget: 1 } } : {}),
|
|
154
173
|
targetIdentity: sha,
|
|
155
174
|
projection,
|
|
175
|
+
repair: UNSUPPORTED_REPAIR_ASSESSMENT,
|
|
156
176
|
candidates: [],
|
|
157
177
|
raw,
|
|
158
178
|
};
|
|
@@ -482,7 +482,7 @@ test("shipped controller and orchestrator contracts specify inspect-first compac
|
|
|
482
482
|
assert.match(toolContract, /RESET.*reclaim.*RECOVER.*recover/s);
|
|
483
483
|
assert.match(toolContract, /native-input-required.*never.*invent/is);
|
|
484
484
|
assert.match(toolContract, /output.*lost|response.*lost|ambiguous.*START/is);
|
|
485
|
-
assert.match(toolContract, /ambiguous START or FINALIZE.*target-scoped native status.*declared action/is);
|
|
485
|
+
assert.match(toolContract, /ambiguous START, answer-consent, or FINALIZE.*target-scoped native status.*declared action/is);
|
|
486
486
|
assert.doesNotMatch(toolContract, /START throws.*lineage does not exist/is);
|
|
487
487
|
|
|
488
488
|
for (const path of ["assets/orchestrator-delegation.md", "skills/gentle-ai/SKILL.md"]) {
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import {
|
|
4
|
+
CORRECTION_OUTCOMES,
|
|
5
|
+
CorrectionEvidenceReplacedError,
|
|
6
|
+
CorrectionOutcomeError,
|
|
7
|
+
assertDistinctCorrectionEvidence,
|
|
8
|
+
resolveCorrectionStep,
|
|
9
|
+
} from "../lib/review-correction-lifecycle.ts";
|
|
10
|
+
|
|
11
|
+
// The provider owns the evidence directory and the budget ledger. Pi's job is
|
|
12
|
+
// narrower and entirely decidable from data: given a status and one captured
|
|
13
|
+
// evidence record, which step comes next, and is it ever allowed to reuse a
|
|
14
|
+
// prior evidence identity. Keeping that a pure function is what makes these
|
|
15
|
+
// branches testable without spawning the binary — the alternative the design
|
|
16
|
+
// rejected was inlining them beside the existing conditionals, where the
|
|
17
|
+
// single-correction invariant would be enforced by control flow instead.
|
|
18
|
+
|
|
19
|
+
const STATUS = Object.freeze({
|
|
20
|
+
lineageId: "review-b39d803b68a90767",
|
|
21
|
+
targetIdentity: "sha256:b39d803b68a90767c822c6bac906c2ff512034df2a2432d4c2d8e48f45c11b0a",
|
|
22
|
+
authorityRevision: "sha256:1298b2f5dd1ab7208bd7dfbf2e5802b21380617050f7ae3d7786bffcf0c6bb29",
|
|
23
|
+
correctionBudget: 56,
|
|
24
|
+
changedLinesCharged: 0,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
function evidence(outcome: string, overrides: Record<string, unknown> = {}) {
|
|
28
|
+
return {
|
|
29
|
+
outcome,
|
|
30
|
+
evidenceIdentity: "sha256:a077148ccb9f1c6e8b65fbfa984309676eee3e35311caaca31d6d61f6351f9fa",
|
|
31
|
+
recordDigest: "sha256:a077148ccb9f1c6e8b65fbfa984309676eee3e35311caaca31d6d61f6351f9fa",
|
|
32
|
+
candidateTree: "0542f90ab7b6af3fe79340509ec6420f0cc9ef68",
|
|
33
|
+
rawPayloadSha256: "sha256:9013edfc64295a20c3f4ed41a7cf61d81a6617bb7180fd95b129cb77ff53fa00",
|
|
34
|
+
...overrides,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
test("the outcome domain is exactly the three closed provider values", () => {
|
|
39
|
+
assert.deepEqual([...CORRECTION_OUTCOMES], ["passed", "verification_failed", "procedural_tooling_failed"]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("an outcome outside the closed domain is rejected with no step and no charge", () => {
|
|
43
|
+
for (const rejected of ["failed", "PASSED", "pass", "ok", "", "verification-failed", "skipped"]) {
|
|
44
|
+
assert.throws(
|
|
45
|
+
() => resolveCorrectionStep(STATUS, evidence(rejected)),
|
|
46
|
+
(error: unknown) => error instanceof CorrectionOutcomeError && /outcome/.test((error as Error).message),
|
|
47
|
+
`outcome ${JSON.stringify(rejected)} must be refused`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("a missing or non-string outcome is refused rather than defaulted", () => {
|
|
53
|
+
for (const rejected of [undefined, null, 1, true, {}, []]) {
|
|
54
|
+
assert.throws(() => resolveCorrectionStep(STATUS, evidence(rejected as unknown as string)), CorrectionOutcomeError);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("passed is the only outcome that unlocks targeted validation", () => {
|
|
59
|
+
const step = resolveCorrectionStep(STATUS, evidence("passed"));
|
|
60
|
+
|
|
61
|
+
assert.equal(step.kind, "run-targeted-validation");
|
|
62
|
+
assert.equal(step.unlocksTargetedValidation, true);
|
|
63
|
+
assert.equal(step.transactionOpen, false);
|
|
64
|
+
assert.equal(step.escalation, undefined);
|
|
65
|
+
// The provider issues the request; Pi must re-query STATUS for it rather
|
|
66
|
+
// than inventing one, which is why the step names the operation instead of
|
|
67
|
+
// carrying a fabricated payload.
|
|
68
|
+
assert.equal(step.expectCaptureOperation, "external.run_targeted_validation");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("verification_failed keeps the transaction open and charges nothing", () => {
|
|
72
|
+
const step = resolveCorrectionStep(STATUS, evidence("verification_failed"));
|
|
73
|
+
|
|
74
|
+
assert.equal(step.kind, "recapture-required");
|
|
75
|
+
assert.equal(step.transactionOpen, true);
|
|
76
|
+
assert.equal(step.unlocksTargetedValidation, false);
|
|
77
|
+
assert.equal(step.attemptConsumed, false);
|
|
78
|
+
assert.equal(step.changedLinesCharged, 0);
|
|
79
|
+
assert.equal(step.budgetConsumed, 0);
|
|
80
|
+
assert.equal(step.autoRetry, false);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("verification_failed carries the prior identity as supersedes and demands a NEW capture", () => {
|
|
84
|
+
const first = evidence("verification_failed");
|
|
85
|
+
const step = resolveCorrectionStep(STATUS, first);
|
|
86
|
+
|
|
87
|
+
assert.equal(step.kind, "recapture-required");
|
|
88
|
+
assert.equal(step.supersedes, first.evidenceIdentity);
|
|
89
|
+
assert.equal(step.requiresNewCapture, true);
|
|
90
|
+
// It must never hand back a step that resubmits the same candidate bytes.
|
|
91
|
+
assert.notEqual(step.supersedes, undefined);
|
|
92
|
+
assert.equal(Object.hasOwn(step, "resubmitCandidate"), false);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("verification_failed never consumes budget even when lines were already charged", () => {
|
|
96
|
+
const charged = { ...STATUS, changedLinesCharged: 40 };
|
|
97
|
+
const step = resolveCorrectionStep(charged, evidence("verification_failed"));
|
|
98
|
+
|
|
99
|
+
// The invariant is about what THIS outcome adds, not about resetting prior
|
|
100
|
+
// accounting: a failed verification must not move the needle at all.
|
|
101
|
+
assert.equal(step.budgetConsumed, 0);
|
|
102
|
+
assert.equal(step.changedLinesCharged, 40);
|
|
103
|
+
assert.equal(step.attemptConsumed, false);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("procedural_tooling_failed escalates terminally before any retry is eligible", () => {
|
|
107
|
+
const step = resolveCorrectionStep(STATUS, evidence("procedural_tooling_failed"));
|
|
108
|
+
|
|
109
|
+
assert.equal(step.kind, "terminal-escalation");
|
|
110
|
+
assert.equal(step.retryEligible, false);
|
|
111
|
+
assert.equal(step.transactionOpen, false);
|
|
112
|
+
assert.equal(step.unlocksTargetedValidation, false);
|
|
113
|
+
assert.equal(typeof step.escalation, "string");
|
|
114
|
+
assert.match(step.escalation as string, /\S/);
|
|
115
|
+
// No further actor may run after a terminal escalation.
|
|
116
|
+
assert.equal(step.launchesReviewer, false);
|
|
117
|
+
assert.equal(step.launchesValidator, false);
|
|
118
|
+
assert.equal(step.launchesCorrection, false);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("every closed outcome resolves to exactly one distinct step kind", () => {
|
|
122
|
+
const kinds = CORRECTION_OUTCOMES.map((outcome) => resolveCorrectionStep(STATUS, evidence(outcome)).kind);
|
|
123
|
+
|
|
124
|
+
assert.equal(new Set(kinds).size, kinds.length, "each outcome must map to its own step kind");
|
|
125
|
+
assert.deepEqual(kinds, ["run-targeted-validation", "recapture-required", "terminal-escalation"]);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("a second capture with a distinct identity and an unchanged prior record is accepted", () => {
|
|
129
|
+
const prior = evidence("verification_failed");
|
|
130
|
+
const next = evidence("passed", { evidenceIdentity: "sha256:" + "b".repeat(64), recordDigest: "sha256:" + "b".repeat(64) });
|
|
131
|
+
|
|
132
|
+
assert.doesNotThrow(() => assertDistinctCorrectionEvidence({ prior, next, priorStillResolvable: true, priorRecordDigestNow: prior.recordDigest }));
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("a second capture reusing the prior evidence identity is correction-evidence-replaced", () => {
|
|
136
|
+
const prior = evidence("verification_failed");
|
|
137
|
+
const next = evidence("passed");
|
|
138
|
+
|
|
139
|
+
assert.throws(
|
|
140
|
+
() => assertDistinctCorrectionEvidence({ prior, next, priorStillResolvable: true, priorRecordDigestNow: prior.recordDigest }),
|
|
141
|
+
(error: unknown) => error instanceof CorrectionEvidenceReplacedError && /correction-evidence-replaced/.test((error as Error).message),
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("a prior evidence record whose bytes changed is correction-evidence-replaced", () => {
|
|
146
|
+
const prior = evidence("verification_failed");
|
|
147
|
+
const next = evidence("passed", { evidenceIdentity: "sha256:" + "c".repeat(64), recordDigest: "sha256:" + "c".repeat(64) });
|
|
148
|
+
|
|
149
|
+
assert.throws(
|
|
150
|
+
() => assertDistinctCorrectionEvidence({ prior, next, priorStillResolvable: true, priorRecordDigestNow: "sha256:" + "d".repeat(64) }),
|
|
151
|
+
CorrectionEvidenceReplacedError,
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("a prior evidence record that no longer resolves is correction-evidence-replaced", () => {
|
|
156
|
+
const prior = evidence("verification_failed");
|
|
157
|
+
const next = evidence("passed", { evidenceIdentity: "sha256:" + "e".repeat(64), recordDigest: "sha256:" + "e".repeat(64) });
|
|
158
|
+
|
|
159
|
+
assert.throws(
|
|
160
|
+
() => assertDistinctCorrectionEvidence({ prior, next, priorStillResolvable: false, priorRecordDigestNow: prior.recordDigest }),
|
|
161
|
+
CorrectionEvidenceReplacedError,
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("the step machine is pure: repeated calls on frozen input return equal results and mutate nothing", () => {
|
|
166
|
+
const input = Object.freeze(evidence("verification_failed"));
|
|
167
|
+
const snapshot = JSON.stringify({ status: STATUS, input });
|
|
168
|
+
|
|
169
|
+
const first = resolveCorrectionStep(STATUS, input);
|
|
170
|
+
const second = resolveCorrectionStep(STATUS, input);
|
|
171
|
+
|
|
172
|
+
assert.deepEqual(first, second);
|
|
173
|
+
assert.equal(JSON.stringify({ status: STATUS, input }), snapshot, "inputs must not be mutated");
|
|
174
|
+
});
|