gentle-pi 2.2.0 → 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 +51 -80
- package/assets/agents/review-readability.md +0 -2
- package/assets/agents/review-reliability.md +0 -2
- package/assets/agents/review-resilience.md +0 -2
- package/assets/agents/review-risk.md +0 -2
- package/assets/agents/sdd-apply.md +5 -3
- package/assets/agents/sdd-proposal.md +2 -0
- package/assets/agents/sdd-research.md +54 -0
- package/assets/agents/sdd-status.md +4 -4
- package/assets/agents/sdd-tasks.md +2 -3
- package/assets/agents/sdd-verify.md +21 -1
- package/assets/chains/sdd-full.chain.md +1 -1
- package/assets/chains/sdd-verify.chain.md +1 -1
- package/assets/orchestrator-delegation.md +54 -248
- package/assets/orchestrator-memory.md +2 -0
- package/assets/orchestrator.md +22 -38
- package/assets/sdd-orchestrator-workflow.md +30 -37
- package/assets/support/sdd-status-contract.md +7 -7
- package/contracts/review-integration/v1/schemas/transition-execution.schema.json +42 -0
- 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/docs/native-authority-architecture.md +9 -11
- package/docs/review-integration.md +27 -373
- package/extensions/ask-user-choice.ts +151 -0
- package/extensions/gentle-ai.ts +1778 -3673
- package/extensions/quiet-tools.ts +515 -32
- package/extensions/sdd-init.ts +4 -8
- package/lib/gentle-ai-renderer.ts +70 -0
- package/lib/model-routing-authority.ts +133 -0
- package/lib/native-review-cli.ts +372 -940
- package/lib/opaque-pi-reviewer-adapter.ts +284 -0
- package/lib/review-candidate-view.ts +341 -132
- package/lib/review-host-relay.ts +210 -68
- package/lib/review-integration-v2.ts +839 -307
- package/lib/review-last-event-controller.ts +35 -0
- package/lib/sdd-preflight.ts +177 -66
- package/lib/sdd-status.ts +66 -111
- package/lib/terminal-theme.ts +1 -1
- package/package.json +83 -82
- package/runtime/gentle-ai-binary.mjs +1 -1
- package/runtime/native-review-cli.mjs +322 -890
- package/runtime/review-integration-v2.mjs +789 -257
- package/runtime/review-relay-contract.mjs +1 -1
- package/scripts/{build-git-commit-transaction-runner.mjs → build-runtime-modules.mjs} +4 -5
- package/scripts/gentle-ai-installer.mjs +75 -22
- package/scripts/maintainer/provider-relay-matrix.mjs +195 -11
- package/scripts/test-packed-runner.mjs +4 -7
- package/scripts/verify-package-files.mjs +12 -13
- package/skills/_shared/review-ledger-contract.md +8 -14
- 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/judgment-day/SKILL.md +5 -7
- package/skills/rdd-defect-workflow/SKILL.md +3 -3
- 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/codegraph-tools.test.ts +3 -3
- package/tests/crosslane/cross-lane.mjs +15 -1168
- package/tests/delegated-key-learnings-contract.test.ts +8 -6
- package/tests/devbinary/native-review-parity.devtest.ts +167 -250
- package/tests/devbinary/pi-host-relay.devtest.ts +867 -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/review-acknowledged-v1.captured.json +9 -0
- package/tests/fixtures/devbinary/review-acknowledged.provenance.md +31 -0
- package/tests/fixtures/devbinary/start-v3-zero-lens-closed.captured.json +21 -0
- package/tests/fixtures/native-review-cli/v2.5.0-rc.1/PROVENANCE.txt +15 -0
- package/tests/gentle-ai-binary.test.ts +83 -13
- package/tests/gentle-ai-dev-binary.test.ts +51 -228
- package/tests/gentle-ai-installer.test.ts +137 -47
- package/tests/gentle-ai.test.ts +820 -131
- package/tests/gentle-theme.test.ts +133 -0
- package/tests/maintainer/provider-relay.maintest.ts +340 -4
- package/tests/model-routing-authority.test.ts +257 -0
- package/tests/native-review-capability-contract.test.ts +28 -1
- package/tests/native-review-cli.test.ts +586 -962
- package/tests/native-review-consent.test.ts +150 -66
- package/tests/native-review-parity-runtime.test.ts +80 -359
- package/tests/native-review-parity.test.ts +674 -925
- package/tests/opaque-pi-reviewer-adapter.test.ts +266 -0
- package/tests/orchestrator-budget.test.ts +132 -88
- package/tests/orchestrator-rdd-ownership.test.ts +103 -0
- package/tests/package-manifest.test.ts +37 -35
- package/tests/provider-defect-handoff.test.ts +15 -118
- package/tests/quiet-tool-rendering.test.ts +1055 -28
- package/tests/review-authority-recovery-docs.test.ts +1 -2
- package/tests/review-candidate-view.test.ts +409 -3
- package/tests/review-compact-contract.test.ts +4 -30
- package/tests/review-controller-lock-status.test.ts +2 -2
- package/tests/review-controller-native-recovery.test.ts +363 -1450
- package/tests/review-controller-native-routing.test.ts +1251 -5642
- package/tests/review-controller-retired-ops.test.ts +1 -1
- package/tests/review-controller-workspace-root.test.ts +222 -71
- package/tests/review-controller.test.ts +26 -816
- package/tests/review-corrected-finalize-binding.test.ts +112 -153
- package/tests/review-dispatch-hydration-gap.test.ts +1 -53
- 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 +117 -82
- package/tests/review-host-relay.test.ts +242 -8
- package/tests/review-integration-v2-forward.test.ts +576 -553
- package/tests/review-integration-v2.test.ts +239 -165
- package/tests/review-last-event-closure.test.ts +408 -0
- package/tests/review-ledger-contract.test.ts +97 -35
- package/tests/review-recovered-lineage-routing.test.ts +0 -47
- package/tests/review-relay-transport-agent.test.ts +119 -46
- package/tests/review-snapshot.test.ts +3 -2
- package/tests/runtime-harness.mjs +361 -196
- package/tests/sdd-agent-tools.test.ts +36 -0
- package/tests/sdd-preflight.test.ts +81 -15
- package/tests/sdd-status.test.ts +109 -110
- package/tests/skill-collision-prefixes.test.ts +5 -8
- 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/lib/git-commit-transaction.ts +0 -861
- package/runtime/git-commit-transaction.mjs +0 -862
- package/scripts/run-git-commit-transaction.mjs +0 -35
- 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 -530
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
decodeReviewConsentV2,
|
|
12
12
|
decodeReviewFailureV2,
|
|
13
13
|
decodeReviewNextTransitionV3,
|
|
14
|
-
decodeReviewOperationV2,
|
|
15
14
|
decodeReviewProjectionV1,
|
|
16
15
|
decodeReviewRepairV2,
|
|
17
16
|
decodeReviewStartV3,
|
|
@@ -19,7 +18,9 @@ import {
|
|
|
19
18
|
} from "../lib/review-integration-v2.ts";
|
|
20
19
|
|
|
21
20
|
const fixtureRoot = join(process.cwd(), "contracts", "review-integration", "v2", "fixtures");
|
|
21
|
+
const devFixtureRoot = join(process.cwd(), "tests", "fixtures", "devbinary");
|
|
22
22
|
const fixture = <T = unknown>(name: string): T => JSON.parse(readFileSync(join(fixtureRoot, name), "utf8")) as T;
|
|
23
|
+
const devFixture = <T = unknown>(name: string): T => JSON.parse(readFileSync(join(devFixtureRoot, name), "utf8")) as T;
|
|
23
24
|
const executableDigest = "dcc846103b16d365eaeeb9d7f289c23fc4f2897f23def1cb3fe7f05557b64705";
|
|
24
25
|
const digest = `sha256:${"a".repeat(64)}`;
|
|
25
26
|
|
|
@@ -56,10 +57,9 @@ function assertAdditionalProperty(decoder: Decoder, source: JsonObject, path: re
|
|
|
56
57
|
assert.throws(() => decoder(candidate), /not allowed/, path.length === 0 ? "top-level" : path.join("."));
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
test("
|
|
60
|
+
test("current review integration fixtures decode", () => {
|
|
60
61
|
assert.equal(decodeReviewCapabilitiesV2(fixture("capabilities.fixture.json"), executableDigest).contract, REVIEW_INTEGRATION_CONTRACT);
|
|
61
62
|
assert.equal(decodeReviewStartV3(fixture("start.fixture.json")).riskLevel, "high");
|
|
62
|
-
assert.equal(decodeReviewStatusV3(fixture("status.fixture.json")).contract, REVIEW_INTEGRATION_CONTRACT);
|
|
63
63
|
assert.equal(decodeReviewConsentV2(fixture("consent.fixture.json")).action, "consent_required");
|
|
64
64
|
});
|
|
65
65
|
|
|
@@ -107,10 +107,52 @@ test("START enforces required, exact, and enum-bounded payloads", () => {
|
|
|
107
107
|
}
|
|
108
108
|
});
|
|
109
109
|
|
|
110
|
-
test("
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
test("start/v3 transports the published selected_lenses wire contract without Pi-owned risk policy", () => {
|
|
111
|
+
const captured = devFixture<JsonObject>("start-v3-zero-lens-closed.captured.json");
|
|
112
|
+
const decoded = decodeReviewStartV3(captured);
|
|
113
|
+
assert.equal(decoded.action, "closed");
|
|
114
|
+
assert.equal(decoded.lensesRequired, false);
|
|
115
|
+
assert.deepEqual(decoded.selectedLenses, []);
|
|
116
|
+
|
|
117
|
+
for (const riskLevel of ["medium", "high"] as const) {
|
|
118
|
+
const noLens = clone(captured);
|
|
119
|
+
noLens.risk_level = riskLevel;
|
|
120
|
+
assert.doesNotThrow(() => decodeReviewStartV3(noLens));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const nullLenses = clone(captured);
|
|
124
|
+
nullLenses.selected_lenses = null;
|
|
125
|
+
assert.throws(() => decodeReviewStartV3(nullLenses), /selected_lenses.*invalid length/);
|
|
126
|
+
|
|
127
|
+
const unknownLens = clone(captured);
|
|
128
|
+
unknownLens.selected_lenses = ["review-future"];
|
|
129
|
+
assert.throws(() => decodeReviewStartV3(unknownLens), /selected_lenses.*unsupported/);
|
|
130
|
+
|
|
131
|
+
const tooManyLenses = clone(captured);
|
|
132
|
+
tooManyLenses.selected_lenses = ["review-risk", "review-resilience", "review-readability", "review-reliability", "review-future"];
|
|
133
|
+
assert.throws(() => decodeReviewStartV3(tooManyLenses), /selected_lenses.*invalid length/);
|
|
134
|
+
|
|
135
|
+
const duplicateLenses = clone(captured);
|
|
136
|
+
duplicateLenses.selected_lenses = ["review-risk", "review-risk"];
|
|
137
|
+
assert.throws(() => decodeReviewStartV3(duplicateLenses), /selected_lenses.*duplicates/);
|
|
138
|
+
|
|
139
|
+
const wrongSchema = clone(captured);
|
|
140
|
+
wrongSchema.schema = "gentle-ai.review-integration.start/v2";
|
|
141
|
+
assert.throws(() => decodeReviewStartV3(wrongSchema), /schema/);
|
|
142
|
+
|
|
143
|
+
const invalidAction = clone(captured);
|
|
144
|
+
invalidAction.action = "dispatch-locally";
|
|
145
|
+
assert.throws(() => decodeReviewStartV3(invalidAction), /action.*unsupported/);
|
|
146
|
+
|
|
147
|
+
const invalidLensesRequired = clone(captured);
|
|
148
|
+
invalidLensesRequired.lenses_required = "false";
|
|
149
|
+
assert.throws(() => decodeReviewStartV3(invalidLensesRequired), /lenses_required/);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("historical status fixture with a receipt is rejected after receipt retirement", () => {
|
|
153
|
+
// This archived fixture is pinned for schema-rejection coverage only. Its
|
|
154
|
+
// bytes retain the retired receipt surface and must never regain authority.
|
|
155
|
+
assert.throws(() => decodeReviewStatusV3(fixture("status.fixture.json")), /receipt.*not allowed/);
|
|
114
156
|
});
|
|
115
157
|
|
|
116
158
|
test("projection enforces every required property and rejects additional keys", () => {
|
|
@@ -123,7 +165,7 @@ test("failure enforces exact keys, enums, and identifiers", () => {
|
|
|
123
165
|
const source: JsonObject = {
|
|
124
166
|
schema: "gentle-ai.review-integration.failure/v2",
|
|
125
167
|
contract: REVIEW_INTEGRATION_CONTRACT,
|
|
126
|
-
operation: "review.
|
|
168
|
+
operation: "review.capture-result",
|
|
127
169
|
phase: "pre_native",
|
|
128
170
|
code: "gate_scope_changed",
|
|
129
171
|
message: "the target scope changed since START",
|
|
@@ -142,28 +184,6 @@ test("failure enforces exact keys, enums, and identifiers", () => {
|
|
|
142
184
|
assert.throws(() => decodeReviewFailureV2(badCode), /code/);
|
|
143
185
|
});
|
|
144
186
|
|
|
145
|
-
function finalizeEnvelope(): JsonObject {
|
|
146
|
-
return {
|
|
147
|
-
schema: "gentle-ai.review-integration.operation/v2",
|
|
148
|
-
contract: REVIEW_INTEGRATION_CONTRACT,
|
|
149
|
-
operation: "review.finalize",
|
|
150
|
-
result: {
|
|
151
|
-
operation: "review/finalize",
|
|
152
|
-
lineage_id: "review-fixture",
|
|
153
|
-
state: "approved",
|
|
154
|
-
action: "publish",
|
|
155
|
-
store_revision: digest,
|
|
156
|
-
},
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
test("operation envelopes strictly bind the outer operation to one exact result variant", () => {
|
|
161
|
-
const envelope = finalizeEnvelope();
|
|
162
|
-
assert.equal(decodeReviewOperationV2(envelope).operation, "review.finalize");
|
|
163
|
-
assertAdditionalProperty(decodeReviewOperationV2, envelope);
|
|
164
|
-
assertNestedRequired(decodeReviewOperationV2, envelope, ["result"], ["operation", "lineage_id", "state", "action", "store_revision"]);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
187
|
test("net-new decoders: consent, next-transition, and artifact-subject reject malformed payloads", () => {
|
|
168
188
|
const consentSource = fixture<JsonObject>("consent.fixture.json");
|
|
169
189
|
assertRequired(decodeReviewConsentV2, consentSource, ["schema", "contract", "operation", "action", "blocking", "target_identity", "projection", "risk_level", "changed_files", "changed_lines", "headline", "reason", "value", "risk_evidence", "choices", "off_path"]);
|
|
@@ -315,97 +335,11 @@ test("START independently binds base/candidate tree and the target-mode overlay
|
|
|
315
335
|
assert.throws(() => decodeReviewStartV3(manifestWithoutTrees), /changed_path_manifest/);
|
|
316
336
|
});
|
|
317
337
|
|
|
318
|
-
test("status enforces authority/frozen/receipt conditionals and decodes the required repair field", () => {
|
|
319
|
-
const current = fixture<JsonObject>("status.fixture.json");
|
|
320
|
-
const decoded = decodeReviewStatusV3(current);
|
|
321
|
-
assert.equal(decoded.repair.status, "unsupported");
|
|
322
|
-
assert.equal(decoded.repair.counts.eligibleCandidates, 0);
|
|
323
|
-
|
|
324
|
-
const missingAuthority = clone(current);
|
|
325
|
-
delete missingAuthority.authority;
|
|
326
|
-
assert.throws(() => decodeReviewStatusV3(missingAuthority), /requires authority/);
|
|
327
|
-
|
|
328
|
-
const compactMissingFrozen = clone(current);
|
|
329
|
-
delete compactMissingFrozen.frozen;
|
|
330
|
-
assert.throws(() => decodeReviewStatusV3(compactMissingFrozen), /requires frozen/);
|
|
331
|
-
|
|
332
|
-
const legacyWithFrozen = clone(current);
|
|
333
|
-
(legacyWithFrozen.authority as JsonObject).version = "legacy-v1";
|
|
334
|
-
(legacyWithFrozen.receipt as JsonObject).status = "expected_missing";
|
|
335
|
-
assert.throws(() => decodeReviewStatusV3(legacyWithFrozen), /legacy status cannot expose frozen/);
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
test("status validation_request and retry_final_verification require their exact authority preconditions", () => {
|
|
339
|
-
const current = fixture<JsonObject>("status.fixture.json");
|
|
340
|
-
const withValidationRequest = clone(current);
|
|
341
|
-
withValidationRequest.validation_request = {
|
|
342
|
-
schema: "gentle-ai.review-targeted-validation-request/v1",
|
|
343
|
-
request_hash: digest,
|
|
344
|
-
lineage_id: "review-status-fixture",
|
|
345
|
-
expected_revision: digest,
|
|
346
|
-
target_identity: (current.target_identity as string),
|
|
347
|
-
fix_finding_ids: ["finding-1"],
|
|
348
|
-
projection: "workspace",
|
|
349
|
-
correction_candidate_tree: "a".repeat(40),
|
|
350
|
-
correction_target_identity: digest,
|
|
351
|
-
correction_paths: ["tracked.txt"],
|
|
352
|
-
correction_paths_digest: digest,
|
|
353
|
-
};
|
|
354
|
-
// authority.state is "reviewing" in the fixture, not correction_required.
|
|
355
|
-
assert.throws(() => decodeReviewStatusV3(withValidationRequest), /correction_required/);
|
|
356
|
-
|
|
357
|
-
const retryWithoutIncident = clone(current);
|
|
358
|
-
retryWithoutIncident.action = "retry_final_verification";
|
|
359
|
-
retryWithoutIncident.action_disposition = "final_verification_retry";
|
|
360
|
-
(retryWithoutIncident.authority as JsonObject).state = "escalated";
|
|
361
|
-
assert.throws(() => decodeReviewStatusV3(retryWithoutIncident), /requires final_verification_retry/);
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
test("operation envelopes decode validate, bind_sdd, and retry_final_verification result variants", () => {
|
|
365
|
-
const validateEnvelope: JsonObject = {
|
|
366
|
-
schema: "gentle-ai.review-integration.operation/v2",
|
|
367
|
-
contract: REVIEW_INTEGRATION_CONTRACT,
|
|
368
|
-
operation: "review.validate",
|
|
369
|
-
result: { schema: "gentle-ai.review-gate-result/v1", result: "allow", allowed: true, action: "publish", reason: "receipt matches", context: { gate: "pre-pr" } },
|
|
370
|
-
};
|
|
371
|
-
assert.equal(decodeReviewOperationV2(validateEnvelope).operation, "review.validate");
|
|
372
|
-
|
|
373
|
-
const bindSddEnvelope: JsonObject = {
|
|
374
|
-
schema: "gentle-ai.review-integration.operation/v2",
|
|
375
|
-
contract: REVIEW_INTEGRATION_CONTRACT,
|
|
376
|
-
operation: "review.bind_sdd",
|
|
377
|
-
result: { schema: "gentle-ai.sdd-review-binding/v1", revision: digest, change: "migrate-review-integration-v2", lineage: "review-fixture", authority_revision: digest, receipt_hash: digest, gate_context: { gate: "post-apply" } },
|
|
378
|
-
};
|
|
379
|
-
assert.equal(decodeReviewOperationV2(bindSddEnvelope).operation, "review.bind_sdd");
|
|
380
|
-
|
|
381
|
-
const retryEnvelope: JsonObject = {
|
|
382
|
-
schema: "gentle-ai.review-integration.operation/v2",
|
|
383
|
-
contract: REVIEW_INTEGRATION_CONTRACT,
|
|
384
|
-
operation: "review.retry_final_verification",
|
|
385
|
-
result: {
|
|
386
|
-
operation: "review.retry_final_verification",
|
|
387
|
-
predecessor_lineage_id: "review-predecessor",
|
|
388
|
-
predecessor_revision: digest,
|
|
389
|
-
lineage_id: "review-successor",
|
|
390
|
-
state: "validating",
|
|
391
|
-
store_revision: digest,
|
|
392
|
-
target_identity: digest,
|
|
393
|
-
incident_digest: digest,
|
|
394
|
-
recovery_disposition: "final_verification_retry",
|
|
395
|
-
},
|
|
396
|
-
};
|
|
397
|
-
assert.equal(decodeReviewOperationV2(retryEnvelope).operation, "review.retry_final_verification");
|
|
398
|
-
|
|
399
|
-
const crossedResult = clone(validateEnvelope);
|
|
400
|
-
crossedResult.result = bindSddEnvelope.result;
|
|
401
|
-
assert.throws(() => decodeReviewOperationV2(crossedResult), /does not match|not allowed|required/);
|
|
402
|
-
});
|
|
403
|
-
|
|
404
338
|
test("failure context accepts scope_change or binding_revision but rejects both or neither", () => {
|
|
405
339
|
const base: JsonObject = {
|
|
406
340
|
schema: "gentle-ai.review-integration.failure/v2",
|
|
407
341
|
contract: REVIEW_INTEGRATION_CONTRACT,
|
|
408
|
-
operation: "review.
|
|
342
|
+
operation: "review.capture-result",
|
|
409
343
|
phase: "pre_native",
|
|
410
344
|
code: "gate_scope_changed",
|
|
411
345
|
message: "the target scope changed since START",
|
|
@@ -471,6 +405,44 @@ test("next_transition decodes an execute variant and rejects a stop that carries
|
|
|
471
405
|
assert.throws(() => decodeReviewNextTransitionV3(stopWithExecute), /stop cannot carry/);
|
|
472
406
|
});
|
|
473
407
|
|
|
408
|
+
function approvedAcknowledgementTransition(): JsonObject {
|
|
409
|
+
return {
|
|
410
|
+
kind: "execute",
|
|
411
|
+
reason_code: "approved_acknowledgement_required",
|
|
412
|
+
execute: {
|
|
413
|
+
operation: "review.acknowledge-approved",
|
|
414
|
+
command: "gentle-ai review acknowledge-approved --provider-vector",
|
|
415
|
+
arguments: [
|
|
416
|
+
{ name: "cwd", value: "/provider/repository", token: "--cwd=/provider/repository" },
|
|
417
|
+
{ name: "lineage", value: "review-fixture", token: "--lineage=review-fixture" },
|
|
418
|
+
{ name: "target", value: digest, token: `--target=${digest}` },
|
|
419
|
+
{ name: "expected-revision", value: digest, token: `--expected-revision=${digest}` },
|
|
420
|
+
{ name: "token", value: "provider-issued-once", token: "--token=provider-issued-once" },
|
|
421
|
+
],
|
|
422
|
+
preconditions: [{ name: "state", value: "approved", token: "--state=approved" }],
|
|
423
|
+
binding: { lineage_id: "review-fixture", target_identity: digest, revision: digest },
|
|
424
|
+
},
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
test("acknowledgement execute rejects decoy bindings, closed-vector drift, and malformed approval", () => {
|
|
429
|
+
const valid = approvedAcknowledgementTransition();
|
|
430
|
+
assert.equal(decodeReviewNextTransitionV3(valid).execute?.operation, "review.acknowledge-approved");
|
|
431
|
+
const cases: Array<[string, (candidate: JsonObject) => void]> = [
|
|
432
|
+
["decoy target", (candidate) => { const argument = ((candidate.execute as JsonObject).arguments as JsonObject[])[2]!; argument.value = `sha256:${"b".repeat(64)}`; argument.token = `--target=${argument.value}`; }],
|
|
433
|
+
["mismatched expected revision", (candidate) => { const argument = ((candidate.execute as JsonObject).arguments as JsonObject[])[3]!; argument.value = `sha256:${"c".repeat(64)}`; argument.token = `--expected-revision=${argument.value}`; }],
|
|
434
|
+
["wrong argument order", (candidate) => { ((candidate.execute as JsonObject).arguments as JsonObject[]).reverse(); }],
|
|
435
|
+
["wrong argument name", (candidate) => { const argument = ((candidate.execute as JsonObject).arguments as JsonObject[])[0]!; argument.name = "repository"; argument.token = `--repository=${argument.value}`; }],
|
|
436
|
+
["malformed approved precondition", (candidate) => { ((candidate.execute as JsonObject).preconditions as JsonObject[])[0]!.value = "burned"; }],
|
|
437
|
+
["token value mismatch", (candidate) => { ((candidate.execute as JsonObject).arguments as JsonObject[])[4]!.token = "--token=other"; }],
|
|
438
|
+
];
|
|
439
|
+
for (const [name, mutate] of cases) {
|
|
440
|
+
const candidate = clone(valid);
|
|
441
|
+
mutate(candidate);
|
|
442
|
+
assert.throws(() => decodeReviewNextTransitionV3(candidate), /acknowledgement|arguments|preconditions|binding/, name);
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
|
|
474
446
|
test("repair decodes a committed execute result and rejects a non-eligible preflight carrying provider_inputs", () => {
|
|
475
447
|
const executed: JsonObject = {
|
|
476
448
|
schema: "gentle-ai.review-integration.repair/v2",
|
|
@@ -559,10 +531,10 @@ test("repair eligible preflight with wrong required_inputs order is rejected", (
|
|
|
559
531
|
test("next_transition.execute accepts the optional selector_arguments and artifacts the schema declares", () => {
|
|
560
532
|
const base = {
|
|
561
533
|
kind: "execute" as const,
|
|
562
|
-
reason_code: "
|
|
534
|
+
reason_code: "fresh_target_ready",
|
|
563
535
|
execute: {
|
|
564
|
-
operation: "review.
|
|
565
|
-
command: "gentle-ai review
|
|
536
|
+
operation: "review.start",
|
|
537
|
+
command: "gentle-ai review start --lineage=review-96f29cbd865e77a9",
|
|
566
538
|
arguments: [{ name: "lineage", value: "review-96f29cbd865e77a9", token: "--lineage=review-96f29cbd865e77a9" }],
|
|
567
539
|
preconditions: [{ name: "target_identity", value: "sha256:" + "9".repeat(64) }],
|
|
568
540
|
binding: { target_identity: "sha256:" + "9".repeat(64) },
|
|
@@ -570,47 +542,16 @@ test("next_transition.execute accepts the optional selector_arguments and artifa
|
|
|
570
542
|
};
|
|
571
543
|
|
|
572
544
|
assert.doesNotThrow(() => decodeReviewNextTransitionV3(base));
|
|
573
|
-
assert.doesNotThrow(() => decodeReviewNextTransitionV3({ ...base, execute: { ...base.execute, artifacts: [{ name: "
|
|
545
|
+
assert.doesNotThrow(() => decodeReviewNextTransitionV3({ ...base, execute: { ...base.execute, artifacts: [{ name: "start-record", path: "review-start.json" }] } }));
|
|
574
546
|
assert.doesNotThrow(() => decodeReviewNextTransitionV3({ ...base, execute: { ...base.execute, selector_arguments: [{ name: "projection", value: "workspace", token: "--projection=workspace" }] } }));
|
|
575
547
|
});
|
|
576
548
|
|
|
577
|
-
test("next_transition.execute.binding stays open, as its schema declares no properties", () => {
|
|
578
|
-
const withContext = {
|
|
579
|
-
kind: "execute" as const,
|
|
580
|
-
reason_code: "approved_receipt_ready",
|
|
581
|
-
execute: {
|
|
582
|
-
operation: "review.validate",
|
|
583
|
-
arguments: [{ name: "gate", value: "pre-commit", token: "--gate=pre-commit" }],
|
|
584
|
-
preconditions: [{ name: "target_identity", value: "sha256:" + "8".repeat(64) }],
|
|
585
|
-
// The provider sends this. The schema constrains nothing here, so Pi
|
|
586
|
-
// closing the object was stricter than the contract.
|
|
587
|
-
binding: { target_identity: "sha256:" + "8".repeat(64), repository_context: "rctx1_" + "c".repeat(64) },
|
|
588
|
-
},
|
|
589
|
-
};
|
|
590
|
-
|
|
591
|
-
assert.doesNotThrow(() => decodeReviewNextTransitionV3(withContext));
|
|
592
|
-
});
|
|
593
|
-
|
|
594
549
|
// gentle-pi#311 P4-roles: the two Go-owned non-lens provider role capture
|
|
595
550
|
// operations render SELF-CONTAINED vectors (binding tokens + --agent=pi
|
|
596
|
-
// --execute=true). Their schemas are pinned,
|
|
597
|
-
//
|
|
598
|
-
//
|
|
551
|
+
// --execute=true). Their schemas are pinned, and a submission descriptor on
|
|
552
|
+
// either one is a contract violation because it would hand the caller a way
|
|
553
|
+
// to author the verdict.
|
|
599
554
|
test("next_transition decodes the self-contained provider role capture vectors strictly", () => {
|
|
600
|
-
const tree = "b".repeat(40);
|
|
601
|
-
const validationRequest = {
|
|
602
|
-
schema: "gentle-ai.review-targeted-validation-request/v1",
|
|
603
|
-
request_hash: digest,
|
|
604
|
-
lineage_id: "review-fixture",
|
|
605
|
-
expected_revision: digest,
|
|
606
|
-
target_identity: digest,
|
|
607
|
-
fix_finding_ids: ["RISK-001"],
|
|
608
|
-
projection: "workspace",
|
|
609
|
-
correction_candidate_tree: tree,
|
|
610
|
-
correction_target_identity: digest,
|
|
611
|
-
correction_paths: ["lib/a.ts"],
|
|
612
|
-
correction_paths_digest: digest,
|
|
613
|
-
};
|
|
614
555
|
const roleInput = (name: string, captureOperation: string, schema: string, extra: Record<string, unknown> = {}) => ({
|
|
615
556
|
kind: "collect",
|
|
616
557
|
reason_code: "provider_refuter_required",
|
|
@@ -630,33 +571,166 @@ test("next_transition decodes the self-contained provider role capture vectors s
|
|
|
630
571
|
}] },
|
|
631
572
|
});
|
|
632
573
|
|
|
574
|
+
const decodeRoleTransition = (value: unknown) => decodeReviewNextTransitionV3(value, { v5: true });
|
|
633
575
|
const refuter = roleInput("provider_refuter", "review.capture-refuter", "https://gentle-ai.dev/schema/review/refuter/v1");
|
|
634
|
-
const decodedRefuter =
|
|
576
|
+
const decodedRefuter = decodeRoleTransition(refuter);
|
|
635
577
|
assert.equal(decodedRefuter.collect?.inputs[0]?.captureOperation, "review.capture-refuter");
|
|
636
578
|
assert.equal(decodedRefuter.collect?.inputs[0]?.arguments.at(-1)?.token, "--execute=true");
|
|
637
579
|
|
|
638
580
|
assert.throws(
|
|
639
|
-
() =>
|
|
581
|
+
() => decodeRoleTransition(roleInput("provider_refuter", "review.capture-refuter", "https://gentle-ai.dev/schema/review/reviewer/v1")),
|
|
640
582
|
/schema must be https:\/\/gentle-ai\.dev\/schema\/review\/refuter\/v1/,
|
|
641
583
|
);
|
|
642
584
|
assert.throws(
|
|
643
|
-
() =>
|
|
585
|
+
() => decodeRoleTransition(roleInput("provider_refuter", "review.capture-refuter", "https://gentle-ai.dev/schema/review/refuter/v1", {
|
|
644
586
|
submission: { operation_token: "capture-refuter", argument_tokens: ["--input={{value}}"], values: [{ slot: "{{value}}", domain: "artifact-path", substitution_location: 0 }] },
|
|
645
587
|
})),
|
|
646
588
|
/submission is not allowed on the self-contained/,
|
|
647
589
|
);
|
|
648
590
|
|
|
649
|
-
const validator = roleInput("provider_targeted_validator", "review.capture-validation", "https://gentle-ai.dev/schema/review/validator/v1"
|
|
650
|
-
const decodedValidator = decodeReviewNextTransitionV3(validator);
|
|
651
|
-
assert.equal(decodedValidator.collect?.inputs[0]?.captureOperation, "review.capture-validation");
|
|
652
|
-
assert.equal(decodedValidator.collect?.inputs[0]?.validationRequest?.requestHash, digest);
|
|
653
|
-
|
|
591
|
+
const validator = roleInput("provider_targeted_validator", "review.capture-validation", "https://gentle-ai.dev/schema/review/validator/v1");
|
|
654
592
|
assert.throws(
|
|
655
|
-
() =>
|
|
593
|
+
() => decodeRoleTransition(validator),
|
|
656
594
|
/validation_request is required/,
|
|
657
595
|
);
|
|
596
|
+
|
|
658
597
|
assert.throws(
|
|
659
|
-
() =>
|
|
598
|
+
() => decodeRoleTransition(roleInput("provider_targeted_validator", "review.capture-validation", "https://gentle-ai.dev/schema/review/refuter/v1")),
|
|
660
599
|
/schema must be https:\/\/gentle-ai\.dev\/schema\/review\/validator\/v1/,
|
|
661
600
|
);
|
|
662
601
|
});
|
|
602
|
+
|
|
603
|
+
test("v5 targeted-validator collect inputs carry the exact provider-owned validation request", () => {
|
|
604
|
+
const requestHash = `sha256:${"a".repeat(64)}`;
|
|
605
|
+
const expectedRevision = `sha256:${"b".repeat(64)}`;
|
|
606
|
+
const targetIdentity = `sha256:${"c".repeat(64)}`;
|
|
607
|
+
const correctionTargetIdentity = `sha256:${"d".repeat(64)}`;
|
|
608
|
+
const correctionPathsDigest = `sha256:${"e".repeat(64)}`;
|
|
609
|
+
const validationRequest = {
|
|
610
|
+
schema: "gentle-ai.review-targeted-validation-request/v1",
|
|
611
|
+
request_hash: requestHash,
|
|
612
|
+
lineage_id: "review-fixture",
|
|
613
|
+
expected_revision: expectedRevision,
|
|
614
|
+
target_identity: targetIdentity,
|
|
615
|
+
fix_finding_ids: ["R1-001"],
|
|
616
|
+
policy_content: "Gentle AI native bounded review policy.",
|
|
617
|
+
fix_findings: [{
|
|
618
|
+
id: "R1-001",
|
|
619
|
+
lens: "risk",
|
|
620
|
+
location: "selected.txt:1",
|
|
621
|
+
severity: "BLOCKER",
|
|
622
|
+
claim: "the selected relay input must be corrected before delivery",
|
|
623
|
+
proof_refs: ["selected.txt:1"],
|
|
624
|
+
evidence_class: "deterministic",
|
|
625
|
+
causal_disposition: "introduced",
|
|
626
|
+
}],
|
|
627
|
+
fix_classifications: [{
|
|
628
|
+
finding_id: "R1-001",
|
|
629
|
+
class: "deterministic",
|
|
630
|
+
causal_disposition: "introduced",
|
|
631
|
+
proof: "selected.txt:1",
|
|
632
|
+
}],
|
|
633
|
+
projection: "workspace",
|
|
634
|
+
correction_candidate_tree: "f".repeat(40),
|
|
635
|
+
correction_target_identity: correctionTargetIdentity,
|
|
636
|
+
correction_paths: ["selected.txt"],
|
|
637
|
+
correction_paths_digest: correctionPathsDigest,
|
|
638
|
+
};
|
|
639
|
+
const input = {
|
|
640
|
+
name: "provider_targeted_validator",
|
|
641
|
+
schema: "https://gentle-ai.dev/schema/review/validator/v1",
|
|
642
|
+
capture_operation: "review.capture-validation",
|
|
643
|
+
arguments: [
|
|
644
|
+
{ name: "lineage", value: "review-fixture", token: "--lineage=review-fixture" },
|
|
645
|
+
{ name: "expected-revision", value: expectedRevision, token: `--expected-revision=${expectedRevision}` },
|
|
646
|
+
{ name: "target", value: correctionTargetIdentity, token: `--target=${correctionTargetIdentity}` },
|
|
647
|
+
{ name: "repository-context", value: `rctx1_${"f".repeat(64)}`, token: `--repository-context=rctx1_${"f".repeat(64)}` },
|
|
648
|
+
{ name: "request-hash", value: requestHash, token: `--request-hash=${requestHash}` },
|
|
649
|
+
{ name: "agent", value: "pi", token: "--agent=pi" },
|
|
650
|
+
{ name: "execute", value: "true", token: "--execute=true" },
|
|
651
|
+
],
|
|
652
|
+
validation_request: validationRequest,
|
|
653
|
+
};
|
|
654
|
+
const transition = { kind: "collect" as const, reason_code: "targeted_validation_required", collect: { inputs: [input] } };
|
|
655
|
+
const decoded = decodeReviewNextTransitionV3(transition, { v5: true });
|
|
656
|
+
const decodedRequest = decoded.collect?.inputs[0]?.validationRequest;
|
|
657
|
+
assert.equal(decodedRequest?.requestHash, requestHash);
|
|
658
|
+
assert.equal(decodedRequest?.correctionTargetIdentity, correctionTargetIdentity);
|
|
659
|
+
assert.deepEqual(decodedRequest?.correctionPaths, ["selected.txt"]);
|
|
660
|
+
assert.equal(decodedRequest?.policyContent, validationRequest.policy_content);
|
|
661
|
+
assert.deepEqual(decodedRequest?.fixFindings, [{
|
|
662
|
+
id: "R1-001",
|
|
663
|
+
lens: "risk",
|
|
664
|
+
location: "selected.txt:1",
|
|
665
|
+
severity: "BLOCKER",
|
|
666
|
+
claim: "the selected relay input must be corrected before delivery",
|
|
667
|
+
proofRefs: ["selected.txt:1"],
|
|
668
|
+
evidenceClass: "deterministic",
|
|
669
|
+
causalDisposition: "introduced",
|
|
670
|
+
}]);
|
|
671
|
+
assert.deepEqual(decodedRequest?.fixClassifications, [{
|
|
672
|
+
findingId: "R1-001",
|
|
673
|
+
class: "deterministic",
|
|
674
|
+
causalDisposition: "introduced",
|
|
675
|
+
proof: "selected.txt:1",
|
|
676
|
+
}]);
|
|
677
|
+
assert.deepEqual(decoded.collect?.inputs[0]?.arguments, input.arguments, "provider-rendered arguments must remain unchanged");
|
|
678
|
+
|
|
679
|
+
const missingRequest = clone(transition);
|
|
680
|
+
delete (missingRequest.collect.inputs[0] as JsonObject).validation_request;
|
|
681
|
+
assert.throws(() => decodeReviewNextTransitionV3(missingRequest, { v5: true }), /validation_request is required/);
|
|
682
|
+
|
|
683
|
+
const mismatchedRequestHash = clone(transition);
|
|
684
|
+
((mismatchedRequestHash.collect.inputs[0] as JsonObject).validation_request as JsonObject).request_hash = `sha256:${"0".repeat(64)}`;
|
|
685
|
+
assert.throws(() => decodeReviewNextTransitionV3(mismatchedRequestHash, { v5: true }), /request-hash/);
|
|
686
|
+
|
|
687
|
+
const refuter = clone(transition);
|
|
688
|
+
const refuterInput = refuter.collect.inputs[0] as JsonObject;
|
|
689
|
+
refuterInput.name = "provider_refuter";
|
|
690
|
+
refuterInput.schema = "https://gentle-ai.dev/schema/review/refuter/v1";
|
|
691
|
+
refuterInput.capture_operation = "review.capture-refuter";
|
|
692
|
+
assert.throws(() => decodeReviewNextTransitionV3(refuter, { v5: true }), /validation_request.*targeted-validator/);
|
|
693
|
+
|
|
694
|
+
const malformedRequest = clone(transition);
|
|
695
|
+
((malformedRequest.collect.inputs[0] as JsonObject).validation_request as JsonObject).unadvertised = true;
|
|
696
|
+
assert.throws(() => decodeReviewNextTransitionV3(malformedRequest, { v5: true }), /not allowed/);
|
|
697
|
+
|
|
698
|
+
const weakenedRequest = clone(transition);
|
|
699
|
+
((weakenedRequest.collect.inputs[0] as JsonObject).validation_request as JsonObject).policy_content = "";
|
|
700
|
+
assert.throws(() => decodeReviewNextTransitionV3(weakenedRequest, { v5: true }), /policy_content/);
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
test("status enforces authority/frozen/receipt conditionals and decodes the required repair field", () => {
|
|
704
|
+
const current = devFixture<JsonObject>("status-v5-repository-context.captured.json");
|
|
705
|
+
delete current.receipt;
|
|
706
|
+
if (current.action === "finalize") {
|
|
707
|
+
current.action = "stop";
|
|
708
|
+
delete current.next_transition;
|
|
709
|
+
delete current.forecast;
|
|
710
|
+
}
|
|
711
|
+
const decoded = decodeReviewStatusV3(current);
|
|
712
|
+
assert.equal(decoded.repair.status, "unsupported");
|
|
713
|
+
assert.equal(decoded.authority?.state, "reviewing");
|
|
714
|
+
|
|
715
|
+
const missingAuthority = clone(current);
|
|
716
|
+
delete missingAuthority.authority;
|
|
717
|
+
assert.throws(() => decodeReviewStatusV3(missingAuthority), /requires authority/);
|
|
718
|
+
|
|
719
|
+
const compactMissingFrozen = clone(current);
|
|
720
|
+
delete compactMissingFrozen.frozen;
|
|
721
|
+
assert.throws(() => decodeReviewStatusV3(compactMissingFrozen), /requires frozen/);
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
test("next_transition.execute.binding stays open, as its schema declares no properties", () => {
|
|
725
|
+
const withContext = {
|
|
726
|
+
kind: "execute" as const,
|
|
727
|
+
reason_code: "fresh_target_ready",
|
|
728
|
+
execute: {
|
|
729
|
+
operation: "review.start",
|
|
730
|
+
arguments: [{ name: "lineage", value: "review-fixture", token: "--lineage=review-fixture" }],
|
|
731
|
+
preconditions: [{ name: "target_identity", value: `sha256:${"8".repeat(64)}` }],
|
|
732
|
+
binding: { target_identity: `sha256:${"8".repeat(64)}`, repository_context: `rctx1_${"c".repeat(64)}` },
|
|
733
|
+
},
|
|
734
|
+
};
|
|
735
|
+
assert.doesNotThrow(() => decodeReviewNextTransitionV3(withContext));
|
|
736
|
+
});
|