gentle-pi 2.5.0 → 2.6.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 +139 -30
- package/assets/agents/gentle-ai-worker.md +4 -0
- package/assets/agents/jd-fix-agent.md +18 -0
- package/assets/agents/jd-judge-a.md +1 -1
- package/assets/agents/jd-judge-b.md +1 -1
- package/assets/agents/sdd-apply.md +7 -5
- package/assets/agents/sdd-archive.md +5 -3
- package/assets/agents/sdd-design.md +4 -0
- package/assets/agents/sdd-explore.md +4 -0
- package/assets/agents/sdd-init.md +4 -0
- package/assets/agents/sdd-onboard.md +4 -0
- package/assets/agents/sdd-proposal.md +4 -0
- package/assets/agents/sdd-remediate.md +37 -0
- package/assets/agents/sdd-research.md +26 -3
- package/assets/agents/sdd-spec.md +4 -0
- package/assets/agents/sdd-status.md +9 -75
- package/assets/agents/sdd-sync.md +4 -0
- package/assets/agents/sdd-tasks.md +4 -0
- package/assets/agents/sdd-verify.md +5 -3
- package/assets/chains/sdd-full.chain.md +4 -0
- package/assets/chains/sdd-plan.chain.md +4 -0
- package/assets/chains/sdd-verify.chain.md +4 -0
- package/assets/migrations/managed-assets-v2.5.0.json +7 -0
- package/assets/orchestrator-delegation.md +21 -3
- package/assets/sdd-orchestrator-workflow.md +54 -21
- package/assets/support/sdd-status-contract.md +34 -90
- package/contracts/telemetry/runtime-aggregate-v1.schema.json +65 -0
- package/docs/telemetry.md +57 -1
- package/docs/windows-startup-console-visibility.md +18 -0
- package/extensions/ask-user-choice.ts +143 -15
- package/extensions/codegraph-tools.ts +1 -0
- package/extensions/gentle-agents.ts +799 -50
- package/extensions/gentle-ai.ts +2033 -322
- package/extensions/gentle-shell.ts +145 -42
- package/extensions/gentle-todo.ts +47 -12
- package/extensions/quiet-tools.ts +1 -0
- package/extensions/runtime-metrics.ts +130 -0
- package/extensions/sdd-init.ts +2 -2
- package/extensions/startup-banner.ts +52 -75
- package/lib/agent-profiles.ts +550 -0
- package/lib/agents-completion-delivery.ts +72 -0
- package/lib/agents-config.ts +7 -10
- package/lib/agents-history.ts +9 -1
- package/lib/agents-messaging.ts +187 -0
- package/lib/agents-protocol.ts +77 -5
- package/lib/agents-runner.ts +548 -26
- package/lib/agents-thread-view.ts +57 -0
- package/lib/agents-view-layout.ts +40 -0
- package/lib/agents-view.ts +548 -191
- package/lib/agents-widget.ts +33 -14
- package/lib/gentle-ai-binary.ts +3 -1
- package/lib/gentle-ai-renderer.ts +8 -6
- package/lib/native-review-cli.ts +283 -1
- package/lib/orchestrator-presence.ts +337 -0
- package/lib/profiles-orchestrator.ts +203 -0
- package/lib/review-candidate-view-owner.ts +296 -46
- package/lib/review-candidate-view.ts +30 -20
- package/lib/review-consent-component.ts +247 -0
- package/lib/review-consent-ui.ts +53 -8
- package/lib/review-host-relay.ts +28 -0
- package/lib/review-integration-v2.ts +187 -5
- package/lib/review-last-event-controller.ts +7 -4
- package/lib/review-reminder-receipt.ts +74 -0
- package/lib/review-session-standing-permission.ts +27 -6
- package/lib/runtime-metrics-children.ts +199 -0
- package/lib/runtime-metrics-delivery.ts +68 -0
- package/lib/runtime-metrics-native.ts +166 -0
- package/lib/runtime-metrics-pi-identity.ts +113 -0
- package/lib/runtime-metrics-policy.ts +51 -0
- package/lib/runtime-metrics.ts +255 -0
- package/lib/sdd-preflight.ts +362 -81
- package/lib/sdd-research-capabilities.ts +228 -0
- package/lib/sdd-status.ts +29 -7
- package/lib/session-worktree-registry.ts +118 -0
- package/lib/shell-bar.ts +47 -1
- package/lib/shell-card.ts +1 -4
- package/lib/shell-changes-view.ts +362 -37
- package/lib/shell-changes.ts +81 -1
- package/lib/shell-prompt.ts +11 -15
- package/lib/shell-sidebar-banner.ts +11 -0
- package/lib/shell-sidebar-layout.ts +213 -0
- package/lib/shell-sidebar.ts +41 -0
- package/lib/shell-todo.ts +28 -11
- package/lib/telemetry-trigger.ts +2 -0
- package/package.json +6 -3
- package/runtime/gentle-ai-binary.mjs +3 -1
- package/runtime/native-review-cli.mjs +283 -1
- package/runtime/review-integration-v2.mjs +187 -5
- package/runtime/telemetry-trigger.mjs +2 -0
- package/scripts/build-runtime-modules.mjs +9 -1
- package/scripts/check-types.mjs +125 -0
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/install-gentle-ai.mjs +12 -0
- package/scripts/install-tui-mode-setting.mjs +114 -0
- package/scripts/test-packed-runner.mjs +16 -2
- package/scripts/types-baseline.json +99 -0
- package/scripts/verify-package-files.mjs +4 -2
- package/skills/_shared/review-ledger-contract.md +17 -1
- package/skills/issue-creation/SKILL.md +3 -3
- package/skills/judgment-day/SKILL.md +17 -3
- package/skills/judgment-day/references/prompts-and-formats.md +14 -3
- package/tests/agent-profiles.test.ts +722 -0
- package/tests/agents-completion-delivery.test.ts +94 -0
- package/tests/agents-config.test.ts +62 -0
- package/tests/agents-fake-child.ts +15 -1
- package/tests/agents-grouping.test.ts +179 -0
- package/tests/agents-integration.test.ts +100 -0
- package/tests/agents-messaging.test.ts +94 -0
- package/tests/agents-protocol.test.ts +45 -0
- package/tests/agents-queries.test.ts +190 -0
- package/tests/agents-responsive.test.ts +43 -0
- package/tests/agents-runner.test.ts +571 -14
- package/tests/agents-thread-view.test.ts +45 -0
- package/tests/agents-view.test.ts +476 -65
- package/tests/agents-widget.test.ts +31 -1
- package/tests/artifact-language.test.ts +25 -2
- package/tests/ask-user-choice.test.ts +169 -3
- package/tests/asset-installation-runtime.test.ts +108 -0
- package/tests/autonomous-guard.test.ts +116 -1
- package/tests/codegraph-tools.test.ts +2 -1
- package/tests/delegated-key-learnings-contract.test.ts +1 -1
- package/tests/devbinary/native-review-parity.devtest.ts +2 -0
- package/tests/feature-request-form.test.ts +67 -0
- package/tests/fixtures/agents-messaging-child.mjs +5 -0
- package/tests/fixtures/runtime-metrics-native-batches.json +6 -0
- package/tests/gentle-agents.test.ts +1460 -33
- package/tests/gentle-ai-binary.test.ts +7 -2
- package/tests/gentle-ai-installer.test.ts +47 -47
- package/tests/gentle-ai-renderer.test.ts +38 -0
- package/tests/gentle-ai.test.ts +944 -4
- package/tests/gentle-shell.test.ts +303 -12
- package/tests/gentle-todo.test.ts +54 -10
- package/tests/install-tui-mode-setting.test.ts +324 -0
- package/tests/issue-creation-skill.test.ts +22 -0
- package/tests/model-routing-authority.test.ts +12 -0
- package/tests/native-review-capability-contract.test.ts +12 -1
- package/tests/native-review-cli.test.ts +277 -3
- package/tests/native-review-parity.test.ts +14 -7
- package/tests/native-sdd-attempt-authority.test.ts +7 -2
- package/tests/orchestrator-presence.test.ts +389 -0
- package/tests/package-manifest.test.ts +232 -7
- package/tests/profiles-orchestrator.test.ts +208 -0
- package/tests/quiet-tool-rendering.test.ts +1 -0
- package/tests/rdd-aware-verification-contract.test.ts +10 -0
- package/tests/review-agent-end-preflight.test.ts +332 -24
- package/tests/review-candidate-view.test.ts +304 -6
- package/tests/review-consent-ui.test.ts +352 -0
- package/tests/review-contract-prompt.test.ts +14 -0
- package/tests/review-controller-native-routing.test.ts +563 -3
- package/tests/review-controller.test.ts +1 -1
- package/tests/review-host-relay-restart-parity.test.ts +142 -1
- package/tests/review-host-relay-routing.test.ts +364 -4
- package/tests/review-host-relay.test.ts +29 -0
- package/tests/review-integration-v2-forward.test.ts +44 -0
- package/tests/review-integration-v2.test.ts +164 -0
- package/tests/review-last-event-closure.test.ts +105 -1
- package/tests/review-ledger-contract.test.ts +61 -6
- package/tests/review-reminder-receipt.test.ts +62 -0
- package/tests/review-session-standing-permission-controller.test.ts +52 -4
- package/tests/review-session-standing-permission.test.ts +30 -0
- package/tests/runtime-harness.mjs +447 -39
- package/tests/runtime-metrics-children.test.ts +206 -0
- package/tests/runtime-metrics-delivery.test.ts +85 -0
- package/tests/runtime-metrics-extension.test.ts +187 -0
- package/tests/runtime-metrics-native.test.ts +209 -0
- package/tests/runtime-metrics-pi-identity.test.ts +113 -0
- package/tests/runtime-metrics-policy.test.ts +62 -0
- package/tests/runtime-metrics.test.ts +184 -0
- package/tests/sdd-agent-tools.test.ts +10 -1
- package/tests/sdd-execution-routing-contract.test.ts +28 -0
- package/tests/sdd-managed-runtime-settlement.test.ts +331 -0
- package/tests/sdd-native-managed-uptake.test.ts +253 -0
- package/tests/sdd-planning-routing-contract.test.ts +45 -0
- package/tests/sdd-preflight.test.ts +252 -8
- package/tests/sdd-research-capabilities.test.ts +256 -0
- package/tests/sdd-research-live.test.ts +241 -0
- package/tests/sdd-selection-transport.test.ts +504 -0
- package/tests/sdd-status.test.ts +51 -0
- package/tests/session-worktree-registry.test.ts +135 -0
- package/tests/shell-card.test.ts +24 -3
- package/tests/shell-changes-view.test.ts +471 -8
- package/tests/shell-changes.test.ts +168 -0
- package/tests/shell-prompt.test.ts +28 -6
- package/tests/shell-sidebar-banner.test.ts +23 -0
- package/tests/shell-sidebar-layout.test.ts +387 -0
- package/tests/shell-sidebar.test.ts +50 -0
- package/tests/shell-todo.test.ts +100 -11
- package/tests/startup-banner.test.ts +126 -0
- package/tests/telemetry-trigger.test.ts +3 -1
|
@@ -66,7 +66,11 @@ export const NATIVE_REVIEW_OPERATION = {
|
|
|
66
66
|
CAPTURE_RESULT: "review/capture-result",
|
|
67
67
|
CAPTURE_CORRECTION_PLAN: "review/capture-correction-plan",
|
|
68
68
|
CAPTURE_PROVIDER_ROLE: "review/capture-provider-role",
|
|
69
|
+
CAPTURE_UNACHIEVABLE: "review/capture-unachievable",
|
|
69
70
|
ACKNOWLEDGE_APPROVED: "review/acknowledge-approved",
|
|
71
|
+
SDD_STATUS: "sdd-status",
|
|
72
|
+
SDD_ATTEMPT: "sdd-attempt",
|
|
73
|
+
SDD_CONTINUE: "sdd-continue",
|
|
70
74
|
} ;
|
|
71
75
|
|
|
72
76
|
|
|
@@ -106,6 +110,8 @@ export const NATIVE_REVIEW_ERROR_CODE = {
|
|
|
106
110
|
|
|
107
111
|
|
|
108
112
|
|
|
113
|
+
|
|
114
|
+
|
|
109
115
|
|
|
110
116
|
|
|
111
117
|
|
|
@@ -119,6 +125,44 @@ export const NATIVE_REVIEW_ERROR_CODE = {
|
|
|
119
125
|
|
|
120
126
|
|
|
121
127
|
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
export const SDD_ATTEMPT_OUTCOME = { PASSED: "passed", FAILED: "failed", INTERRUPTED: "interrupted" } ;
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
|
|
122
166
|
export const NATIVE_REVIEW_MODE_OPERATION = {
|
|
123
167
|
STATUS: "status",
|
|
124
168
|
ENABLE: "enable",
|
|
@@ -158,6 +202,35 @@ export const NATIVE_REVIEW_MODE_SCOPE = {
|
|
|
158
202
|
|
|
159
203
|
|
|
160
204
|
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* The native CLI owns this complete v2 record. The decoder validates the fields
|
|
211
|
+
* Pi relies on and returns the original object without adding, omitting, or
|
|
212
|
+
* reconciling local SDD state.
|
|
213
|
+
*/
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
|
|
161
234
|
|
|
162
235
|
// Read-only risk assessment request (gentle-pi#662). `baseRef` requires
|
|
163
236
|
// explicit `committedOnly` acknowledgement, exactly like Native START's
|
|
@@ -432,6 +505,49 @@ function isNativeReviewProviderRoleCaptureOperation(operation )
|
|
|
432
505
|
|
|
433
506
|
|
|
434
507
|
|
|
508
|
+
// gentle-pi#638: the typed declaration that one bound selected lens slot cannot be completed under current conditions. Mirrored from Go's reviewUnachievableLensCaptureArtifact (internal/cli/review_capture_unachievable.go): same schema identity, same closed field set, and the same 512-byte detail bound Go enforces, so a declaration this client refuses locally can never reach a binary that would accept it, and vice versa.
|
|
509
|
+
export const NATIVE_REVIEW_UNACHIEVABLE_LENS_CAPTURE_SCHEMA = "gentle-ai.review-capture-unachievable/v1";
|
|
510
|
+
export const NATIVE_REVIEW_UNACHIEVABLE_LENS_DETAIL_LIMIT = 512;
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
// gentle-pi#638 fail-open capability gate: `review capture-unachievable` is younger than every released binary pinned in NATIVE_CLI_CONTRACTS, so the verb is gated invocation-adjacent instead of by a capability row. An older binary renders Go's exact `unknown review command "capture-unachievable"` refusal (internal/cli/review_facade.go) on stderr with no stdout, so the invocation rejects before any decode and the captured diagnostics are the only place that text survives. Every other failure -- a typed binding-mismatch refusal, a timeout, a decode failure -- is a real outcome the caller must surface, never a capability signal. Duck-typed on purpose: the classifier must survive a duplicated module instance exactly like the error it inspects.
|
|
543
|
+
const NATIVE_REVIEW_UNKNOWN_UNACHIEVABLE_VERB_REFUSAL = /unknown review command "capture-unachievable"/;
|
|
544
|
+
|
|
545
|
+
export function isNativeReviewUnachievableVerbRefused(error ) {
|
|
546
|
+
if (typeof error !== "object" || error === null) return false;
|
|
547
|
+
const stderr = (error ).diagnostics?.stderr;
|
|
548
|
+
return typeof stderr === "string" && NATIVE_REVIEW_UNKNOWN_UNACHIEVABLE_VERB_REFUSAL.test(stderr);
|
|
549
|
+
}
|
|
550
|
+
|
|
435
551
|
/** Refuter and validator captures close only when they are the native last event. */
|
|
436
552
|
|
|
437
553
|
|
|
@@ -844,6 +960,13 @@ export const NATIVE_CLI_CONTRACTS = Object.freeze({
|
|
|
844
960
|
// negotiated START path Pi reads. The closed fields Pi consumes did not
|
|
845
961
|
// change between 2.6.0 and 2.7.0, so the columns match the 2.6.0 row.
|
|
846
962
|
"2.7.0": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
|
|
963
|
+
// Ground-truthed against the published v2.8.0 linux/amd64 binary from its
|
|
964
|
+
// signed release archive. The v2 lane still advertises capabilities/v2.5
|
|
965
|
+
// (protocol minor 5) with status/v7, consent/v3, and start/v4 schemas.
|
|
966
|
+
// The closed fields Pi consumes did not change between 2.7.0 and 2.8.0,
|
|
967
|
+
// so this row repeats 2.7.0. riskEvidence and hint remain dark because
|
|
968
|
+
// neither is proven to reach the negotiated START path Pi consumes.
|
|
969
|
+
"2.8.0": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
|
|
847
970
|
});
|
|
848
971
|
|
|
849
972
|
|
|
@@ -875,7 +998,7 @@ export class NativeReviewCliError extends Error {
|
|
|
875
998
|
this.launchAttempted = launchAttempted;
|
|
876
999
|
this.mutating = mutating;
|
|
877
1000
|
this.mutationOutcome = launchAttempted && mutating ? "unknown" : "none";
|
|
878
|
-
this.nextAction = this.mutationOutcome === "unknown" ? "review.status" : undefined;
|
|
1001
|
+
this.nextAction = this.mutationOutcome === "unknown" && operation !== NATIVE_REVIEW_OPERATION.SDD_ATTEMPT ? "review.status" : undefined;
|
|
879
1002
|
this.diagnostics = diagnostics ?? { operation, error_code: code, timed_out: false, output_limit_exceeded: false };
|
|
880
1003
|
this.auditRecord = auditRecord;
|
|
881
1004
|
}
|
|
@@ -1300,6 +1423,46 @@ function nativeError(code , operation
|
|
|
1300
1423
|
|
|
1301
1424
|
|
|
1302
1425
|
|
|
1426
|
+
const NATIVE_SDD_DEPENDENCIES = ["proposal", "specs", "design", "tasks", "apply", "verify", "archive"] ;
|
|
1427
|
+
const NATIVE_SDD_INSTRUCTION_PHASES = ["apply", "verify", "remediate", "archive"] ;
|
|
1428
|
+
const NATIVE_SDD_NEXT_RECOMMENDATIONS = ["apply", "verify", "remediate", "archive", "archived", "resolve-blockers", "sdd-new", "select-change", "propose", "spec", "design", "tasks"] ;
|
|
1429
|
+
const NATIVE_SDD_DEPENDENCY_STATES = ["blocked", "ready", "all_done"] ;
|
|
1430
|
+
|
|
1431
|
+
/** Strictly validates the native v2 contract while preserving its whole record. */
|
|
1432
|
+
export function decodeNativeSddStatusV2(value , request ) {
|
|
1433
|
+
const status = object(value);
|
|
1434
|
+
if (status.schemaName !== "gentle-ai.sdd-status" || status.schemaVersion !== 2) throw new Error("wrong native SDD status schema");
|
|
1435
|
+
if ((request.changeName !== undefined && status.changeName !== request.changeName) || (status.changeName !== null && !isCanonicalProcessString(status.changeName))) throw new Error("native SDD status change identity mismatch");
|
|
1436
|
+
const artifactStore = enumString(status.artifactStore, ["openspec", "engram", "hybrid", "none"]);
|
|
1437
|
+
const planningHome = object(status.planningHome);
|
|
1438
|
+
if (planningHome.mode !== "repo-local" || !isCanonicalProcessString(planningHome.path)) throw new Error("invalid native SDD planning home");
|
|
1439
|
+
const expectedOpenSpecHome = join(request.workspaceRoot, "openspec");
|
|
1440
|
+
if (planningHome.path !== expectedOpenSpecHome && !((artifactStore === "engram" || artifactStore === "hybrid") && planningHome.path === "engram:sdd")) throw new Error("native SDD planning home escaped its workspace");
|
|
1441
|
+
if (status.changeRoot !== null && !isCanonicalProcessString(status.changeRoot)) throw new Error("invalid native SDD change root");
|
|
1442
|
+
const actionContext = object(status.actionContext);
|
|
1443
|
+
if (actionContext.mode !== "repo-local" || actionContext.workspaceRoot !== request.workspaceRoot || !isCanonicalProcessString(actionContext.workspaceRoot)) throw new Error("native SDD status workspace root mismatch");
|
|
1444
|
+
const allowedEditRoots = stringArray(actionContext.allowedEditRoots);
|
|
1445
|
+
if (!allowedEditRoots.includes(request.workspaceRoot) || allowedEditRoots.some((root) => !isAbsolute(root) || root !== join(root))) throw new Error("invalid native SDD allowed edit roots");
|
|
1446
|
+
const dependencies = object(status.dependencies);
|
|
1447
|
+
for (const phase of NATIVE_SDD_DEPENDENCIES) {
|
|
1448
|
+
if (enumString(dependencies[phase], NATIVE_SDD_DEPENDENCY_STATES) !== dependencies[phase]) throw new Error("invalid native SDD dependency");
|
|
1449
|
+
}
|
|
1450
|
+
if (Object.keys(dependencies).length !== NATIVE_SDD_DEPENDENCIES.length) throw new Error("native SDD dependencies have an unsupported shape");
|
|
1451
|
+
if (status.instructions !== undefined) throw new Error("native SDD status uses phaseInstructions, not instructions");
|
|
1452
|
+
if (status.phaseInstructions !== undefined) {
|
|
1453
|
+
const instructions = object(status.phaseInstructions);
|
|
1454
|
+
for (const phase of NATIVE_SDD_INSTRUCTION_PHASES) stringArray(instructions[phase]);
|
|
1455
|
+
if (Object.keys(instructions).length !== NATIVE_SDD_INSTRUCTION_PHASES.length) throw new Error("native SDD instructions have an unsupported shape");
|
|
1456
|
+
}
|
|
1457
|
+
if (status.nextRecommended === "remediate" || status.remediationState !== undefined) {
|
|
1458
|
+
const remediation = object(status.remediationState);
|
|
1459
|
+
if (typeof remediation.required !== "boolean" || typeof remediation.complete !== "boolean" || typeof remediation.failedEvidenceRevision !== "string" || (remediation.failedEvidenceRevision !== "" && !/^sha256:[0-9a-f]{64}$/.test(remediation.failedEvidenceRevision)) || (status.nextRecommended === "remediate" && (!remediation.required || remediation.complete || !remediation.failedEvidenceRevision))) throw new Error("Invalid native remediation state");
|
|
1460
|
+
}
|
|
1461
|
+
stringArray(status.blockedReasons);
|
|
1462
|
+
enumString(status.nextRecommended, NATIVE_SDD_NEXT_RECOMMENDATIONS);
|
|
1463
|
+
return status ;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1303
1466
|
class NativeReviewPlainCli {
|
|
1304
1467
|
adapter ;
|
|
1305
1468
|
executable ;
|
|
@@ -1830,6 +1993,23 @@ function decodeNativeProviderRoleCaptureArtifact(value )
|
|
|
1830
1993
|
});
|
|
1831
1994
|
}
|
|
1832
1995
|
|
|
1996
|
+
// gentle-pi#638: the recorded declaration Go prints for one unachievable slot. Closed key set, exact schema identity, and the lens Go itself resolved from the request hash -- the host never asserts which lens failed, only reads back the one the provider bound.
|
|
1997
|
+
function decodeNativeUnachievableLensCaptureArtifact(value ) {
|
|
1998
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("native unachievable lens capture artifact must be an object");
|
|
1999
|
+
const body = value ;
|
|
2000
|
+
const allowed = new Set(["schema", "lineage_id", "target_identity", "lens", "selected_order", "reason", "recorded"]);
|
|
2001
|
+
for (const key of Object.keys(body)) if (!allowed.has(key)) throw new TypeError(`native unachievable lens capture artifact carries unexpected key ${key}`);
|
|
2002
|
+
const text = (key ) => {
|
|
2003
|
+
const found = body[key];
|
|
2004
|
+
if (typeof found !== "string" || found.trim() !== found || found.length === 0) throw new TypeError(`native unachievable lens capture artifact ${key} must be a non-empty trimmed string`);
|
|
2005
|
+
return found;
|
|
2006
|
+
};
|
|
2007
|
+
if (text("schema") !== NATIVE_REVIEW_UNACHIEVABLE_LENS_CAPTURE_SCHEMA) throw new TypeError(`native unachievable lens capture artifact schema must be ${NATIVE_REVIEW_UNACHIEVABLE_LENS_CAPTURE_SCHEMA}`);
|
|
2008
|
+
if (typeof body.selected_order !== "number" || !Number.isSafeInteger(body.selected_order) || body.selected_order < 0) throw new TypeError("native unachievable lens capture artifact selected_order must be a non-negative integer");
|
|
2009
|
+
if (body.recorded !== true) throw new TypeError("native unachievable lens capture artifact must report recorded: true");
|
|
2010
|
+
return Object.freeze({ schema: NATIVE_REVIEW_UNACHIEVABLE_LENS_CAPTURE_SCHEMA, lineageId: text("lineage_id"), targetIdentity: text("target_identity"), lens: text("lens"), selectedOrder: body.selected_order, reason: text("reason"), recorded: true });
|
|
2011
|
+
}
|
|
2012
|
+
|
|
1833
2013
|
export class NativeReviewCliV216 {
|
|
1834
2014
|
plain ;
|
|
1835
2015
|
adapter ;
|
|
@@ -1921,6 +2101,89 @@ export class NativeReviewCliV216 {
|
|
|
1921
2101
|
return this.invoke(operation, cwd, arguments_, mutating, signal, this.executablePath(operation, mutating), toleratedStderr);
|
|
1922
2102
|
}
|
|
1923
2103
|
|
|
2104
|
+
async sddAttemptAcquire(request ) {
|
|
2105
|
+
return this.sddAttempt("acquire", request);
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
async sddAttemptSettle(request ) {
|
|
2109
|
+
return this.sddAttempt("settle", request);
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
async sddAttempt(verb , request ) {
|
|
2113
|
+
const args = ["sdd-attempt", verb, "--cwd", request.workspaceRoot, "--change", request.changeName, "--request-id", request.requestId];
|
|
2114
|
+
if (!isAbsolute(request.workspaceRoot) || !isCanonicalProcessString(request.workspaceRoot) || !isCanonicalProcessString(request.changeName) || !/^[a-z0-9][a-z0-9._-]{0,127}$/.test(request.requestId)) throw new TypeError("Invalid SDD attempt identity");
|
|
2115
|
+
const text = (flag , value , max ) => {
|
|
2116
|
+
if (!isCanonicalProcessString(value) || /[\r\n]/.test(value) || Buffer.byteLength(value) > max) throw new TypeError(`Invalid ${flag}`);
|
|
2117
|
+
args.push(flag, value);
|
|
2118
|
+
};
|
|
2119
|
+
const revision = (flag , value ) => {
|
|
2120
|
+
if (value === undefined) return;
|
|
2121
|
+
if (!/^sha256:[0-9a-f]{64}$/.test(value)) throw new TypeError(`Invalid ${flag}`);
|
|
2122
|
+
args.push(flag, value);
|
|
2123
|
+
};
|
|
2124
|
+
if (verb === "acquire") {
|
|
2125
|
+
const acquire = request ;
|
|
2126
|
+
text("--work-unit", acquire.workUnit, 160);
|
|
2127
|
+
text("--evidence-goal", acquire.evidenceGoal, 240);
|
|
2128
|
+
for (const [flag, value, max] of [["--max-attempts", acquire.maxAttempts, 100], ["--max-changed-lines", acquire.maxChangedLines, 1_000_000]] ) {
|
|
2129
|
+
if (value === undefined) continue;
|
|
2130
|
+
if (!Number.isInteger(value) || value < 1 || value > max) throw new TypeError(`Invalid ${flag}`);
|
|
2131
|
+
args.push(flag, String(value));
|
|
2132
|
+
}
|
|
2133
|
+
if (acquire.expectedRevision === "") args.push("--expected-revision", "");
|
|
2134
|
+
else revision("--expected-revision", acquire.expectedRevision);
|
|
2135
|
+
if (acquire.token !== undefined) text("--token", acquire.token, 500);
|
|
2136
|
+
} else {
|
|
2137
|
+
const settle = request ;
|
|
2138
|
+
text("--token", settle.token, 500);
|
|
2139
|
+
if (!Object.values(SDD_ATTEMPT_OUTCOME).includes(settle.outcome)) throw new TypeError("Invalid outcome");
|
|
2140
|
+
args.push("--outcome", settle.outcome);
|
|
2141
|
+
if (settle.outcome === "interrupted" ? settle.evidenceRevision !== undefined || settle.remediationEvidence !== undefined : !settle.evidenceRevision && !(settle.outcome === "passed" && settle.remediationEvidence)) throw new TypeError("Invalid terminal evidence");
|
|
2142
|
+
revision("--evidence-revision", settle.evidenceRevision);
|
|
2143
|
+
text("--diagnosis", settle.diagnosis, 500);
|
|
2144
|
+
if (!["reused", "invalidated"].includes(settle.harnessDisposition)) throw new TypeError("Invalid harness disposition");
|
|
2145
|
+
args.push("--harness-disposition", settle.harnessDisposition);
|
|
2146
|
+
text("--cleanup-evidence", settle.cleanupEvidence, 500);
|
|
2147
|
+
text("--process-evidence", settle.processEvidence, 500);
|
|
2148
|
+
if (settle.remediationEvidence !== undefined) args.push("--remediation-evidence", settle.remediationEvidence);
|
|
2149
|
+
}
|
|
2150
|
+
revision("--remediates-evidence-revision", request.remediatesEvidenceRevision);
|
|
2151
|
+
args.push(...nativeUntrackedSelectionArguments(nativeUntrackedSelection(request)));
|
|
2152
|
+
const operation = NATIVE_REVIEW_OPERATION.SDD_ATTEMPT;
|
|
2153
|
+
const { body } = await this.negotiated(operation, request.workspaceRoot, args, true);
|
|
2154
|
+
return decode(operation, true, () => {
|
|
2155
|
+
const result = body ;
|
|
2156
|
+
if (!result || !["proceed", "blocked", "complete"].includes(result.state)) throw new TypeError("Invalid compact state");
|
|
2157
|
+
if (verb === "acquire" && result.state === "proceed" && !isCanonicalProcessString(result.token)) throw new TypeError("Missing compact token");
|
|
2158
|
+
if (result.reason !== undefined && typeof result.reason !== "string") throw new TypeError("Invalid compact reason");
|
|
2159
|
+
return { state: result.state, ...(result.token === undefined ? {} : { token: result.token }), ...(result.reason === undefined ? {} : { reason: result.reason }) };
|
|
2160
|
+
});
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
async sddStatus(request ) {
|
|
2164
|
+
return this.sddProjection(request, false);
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
async sddContinue(request ) {
|
|
2168
|
+
if (!isCanonicalProcessString(request.changeName)) throw new TypeError("Native SDD continuation requires an exact selected change");
|
|
2169
|
+
return this.sddProjection(request, true);
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
async sddProjection(request , mutating ) {
|
|
2173
|
+
if ((request.changeName !== undefined && !isCanonicalProcessString(request.changeName)) || !isCanonicalProcessString(request.workspaceRoot) || !isAbsolute(request.workspaceRoot)) {
|
|
2174
|
+
throw new TypeError("Native SDD status requires a canonical change and absolute workspace root");
|
|
2175
|
+
}
|
|
2176
|
+
const operation = mutating ? NATIVE_REVIEW_OPERATION.SDD_CONTINUE : NATIVE_REVIEW_OPERATION.SDD_STATUS;
|
|
2177
|
+
const execution = await this.negotiated(
|
|
2178
|
+
operation,
|
|
2179
|
+
request.workspaceRoot,
|
|
2180
|
+
[operation, ...(request.changeName === undefined ? [] : [request.changeName]), "--cwd", request.workspaceRoot, "--json", "--instructions"],
|
|
2181
|
+
mutating,
|
|
2182
|
+
request.signal,
|
|
2183
|
+
);
|
|
2184
|
+
return decode(operation, mutating, () => decodeNativeSddStatusV2(execution.body, request));
|
|
2185
|
+
}
|
|
2186
|
+
|
|
1924
2187
|
async start(request ) {
|
|
1925
2188
|
if (request.baseRef !== undefined && !isCanonicalProcessString(request.baseRef)) throw new TypeError("Native START baseRef must be a non-empty, trimmed, NUL-free string");
|
|
1926
2189
|
if (request.baseRef !== undefined && request.committedOnly !== true) throw new TypeError("Native START baseRef requires explicit committedOnly acknowledgement");
|
|
@@ -2195,6 +2458,25 @@ export class NativeReviewCliV216 {
|
|
|
2195
2458
|
});
|
|
2196
2459
|
}
|
|
2197
2460
|
|
|
2461
|
+
// gentle-pi#638: `gentle-ai review capture-unachievable` records one bound declaration that the exact slot the collect transition offered cannot be completed under current conditions. Unlike every other capture verb the host runs, this one reports a failure instead of a result, so it never takes the provider-issued tokens verbatim: the declaration's binding values are named fields re-derived from the slot, and Go verifies them against the frozen lineage, revision, target, and subject hash before recording anything (internal/cli/review_capture_unachievable.go). A binding that no longer matches is a typed refusal the caller surfaces, never a host-side reconstruction.
|
|
2462
|
+
async captureUnachievableLens(request ) {
|
|
2463
|
+
for (const [field, value] of (["lineageId", "reason"] ).map((field) => [field, request[field]] )) {
|
|
2464
|
+
if (!isCanonicalProcessString(value)) throw new TypeError(`Native CAPTURE_UNACHIEVABLE ${field} must be a non-empty, trimmed, NUL-free string`);
|
|
2465
|
+
}
|
|
2466
|
+
// The three identity fields are digests on every slot the provider offers, so a non-sha value means the declaration binding was parsed wrong; refuse it locally instead of invoking a doomed process.
|
|
2467
|
+
for (const [field, value] of (["targetIdentity", "expectedRevision", "requestHash"] ).map((field) => [field, request[field]] )) {
|
|
2468
|
+
if (!/^sha256:[0-9a-f]{64}$/.test(value)) throw new TypeError(`Native CAPTURE_UNACHIEVABLE ${field} must be a canonical SHA-256 identity`);
|
|
2469
|
+
}
|
|
2470
|
+
const detail = request.detail === undefined ? "" : request.detail.trim();
|
|
2471
|
+
// gentle-pi#822: the native limit is 512 UTF-8 bytes, not 512 UTF-16 code units — the Go side measures the encoded payload, so a multibyte detail needs Buffer.byteLength; a .length check would let 300 two-byte characters through.
|
|
2472
|
+
if (Buffer.byteLength(detail, "utf8") > NATIVE_REVIEW_UNACHIEVABLE_LENS_DETAIL_LIMIT) throw new TypeError(`Native CAPTURE_UNACHIEVABLE detail exceeds ${NATIVE_REVIEW_UNACHIEVABLE_LENS_DETAIL_LIMIT} bytes`);
|
|
2473
|
+
if (request.repositoryContext !== undefined && !isCanonicalProcessString(request.repositoryContext)) throw new TypeError("Native CAPTURE_UNACHIEVABLE repositoryContext must be a non-empty, trimmed, NUL-free string");
|
|
2474
|
+
const executable = this.executablePath(NATIVE_REVIEW_OPERATION.CAPTURE_UNACHIEVABLE, true);
|
|
2475
|
+
// gentle-pi#822: --repository-context is authoritative and mutually exclusive with a path (the captureProviderRole discipline), so --cwd rides the invocation only when no repository context names it; the process working directory stays request.cwd either way.
|
|
2476
|
+
const execution = await this.invoke(NATIVE_REVIEW_OPERATION.CAPTURE_UNACHIEVABLE, request.cwd, ["review", "capture-unachievable", "--lineage", request.lineageId, "--target", request.targetIdentity, "--expected-revision", request.expectedRevision, "--request-hash", request.requestHash, "--reason", request.reason, ...(detail === "" ? [] : ["--detail", detail]), ...(request.repositoryContext === undefined ? ["--cwd", request.cwd] : ["--repository-context", request.repositoryContext])], true, request.signal, executable);
|
|
2477
|
+
return decode(NATIVE_REVIEW_OPERATION.CAPTURE_UNACHIEVABLE, true, () => decodeNativeUnachievableLensCaptureArtifact(execution.body));
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2198
2480
|
// gentle-pi#311 P5: executes one provider-rendered `review.finalize`
|
|
2199
2481
|
// execute transition exactly as rendered. The tokens come verbatim from
|
|
2200
2482
|
// the negotiated next_transition (e.g. `--lineage=<id>
|
|
@@ -561,6 +561,46 @@ export const REVIEW_PROVIDER_ROLE_CAPTURE_OPERATIONS = Object.freeze(Object.valu
|
|
|
561
561
|
|
|
562
562
|
|
|
563
563
|
|
|
564
|
+
// gentle-pi#638: one selected lens slot the host declared unachievable
|
|
565
|
+
// through the native capture-unachievable verb, mirrored from Go's
|
|
566
|
+
// ReviewUnachievableLensSlot (internal/cli/review_next_transition.go). The
|
|
567
|
+
// withdraw form is deliberately narrower than a full execute transition:
|
|
568
|
+
// it is the literally runnable retraction command for exactly this slot, so
|
|
569
|
+
// a restart that never saw the collect offer can still take the declaration
|
|
570
|
+
// back from the stop alone.
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
const ESCALATION_CAUSES = ["unknown_causality", "insufficient_evidence", "missing_refuter_outcome", "targeted_validator_rejected", "correction_budget_exceeded", "unresolved_severe_findings"] ;
|
|
601
|
+
const ESCALATION_REFUTER_OUTCOMES = ["corroborated", "refuted", "inconclusive"] ;
|
|
602
|
+
|
|
603
|
+
|
|
564
604
|
|
|
565
605
|
|
|
566
606
|
|
|
@@ -1713,7 +1753,7 @@ export function decodeReviewManagedAssetsContinuationV1(value , label
|
|
|
1713
1753
|
export function decodeReviewNextTransitionV3(value , options = {}) {
|
|
1714
1754
|
const v6 = options.v6 === true;
|
|
1715
1755
|
const v5 = options.v5 === true || v6;
|
|
1716
|
-
const transition = exactRecord(value, "next_transition", ["kind", "reason_code"], ["execute", "collect", ...(v5 ? ["correction_request"] : []), "continuation"]);
|
|
1756
|
+
const transition = exactRecord(value, "next_transition", ["kind", "reason_code"], ["execute", "collect", ...(v5 ? ["correction_request"] : []), "continuation", "unachievable_lens_slots"]);
|
|
1717
1757
|
const kind = enumeration(transition.kind, ["execute", "collect", "stop"] , "next_transition.kind");
|
|
1718
1758
|
const reasonCode = text(transition.reason_code, "next_transition.reason_code", { minimum: 1, pattern: /^[a-z0-9_]+$/ });
|
|
1719
1759
|
const continuation = transition.continuation === undefined ? undefined : decodeReviewManagedAssetsContinuationV1(transition.continuation, "next_transition.continuation");
|
|
@@ -1723,6 +1763,13 @@ export function decodeReviewNextTransitionV3(value , options
|
|
|
1723
1763
|
// it is only valid on a stop with this exact reason_code.
|
|
1724
1764
|
if (continuation !== undefined && !(kind === "stop" && reasonCode === "managed_assets_outdated")) throw new TypeError("next_transition.continuation is only valid for a stop transition with reason_code managed_assets_outdated");
|
|
1725
1765
|
|
|
1766
|
+
// gentle-pi#638: the unachievable stop names one entry per declared slot, each carrying its complete withdraw command (schemas/status-v7.schema.json, the stop variant). Like the continuation, the field is only valid on its own exact stop reason code, and an entry is refused rather than silently dropped when the provider drifts.
|
|
1767
|
+
let unachievableLensSlots ;
|
|
1768
|
+
if (transition.unachievable_lens_slots !== undefined) {
|
|
1769
|
+
if (!(kind === "stop" && reasonCode === "unachievable_lens_slot")) throw new TypeError("next_transition.unachievable_lens_slots is only valid for a stop transition with reason_code unachievable_lens_slot");
|
|
1770
|
+
unachievableLensSlots = array(transition.unachievable_lens_slots, "next_transition.unachievable_lens_slots", decodeUnachievableLensSlot, { minimum: 1 });
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1726
1773
|
// status/v5: the bounded correction plan request rides exactly its two
|
|
1727
1774
|
// reason codes and never any other (vendored status-v5.schema.json).
|
|
1728
1775
|
let correctionRequest ;
|
|
@@ -1771,7 +1818,64 @@ export function decodeReviewNextTransitionV3(value , options
|
|
|
1771
1818
|
return { kind, reasonCode, collect: { inputs }, ...(correctionRequest === undefined ? {} : { correctionRequest }) };
|
|
1772
1819
|
}
|
|
1773
1820
|
if (transition.execute !== undefined || transition.collect !== undefined) throw new TypeError("next_transition stop cannot carry a transition");
|
|
1774
|
-
return { kind, reasonCode, ...(correctionRequest === undefined ? {} : { correctionRequest }), ...(continuation === undefined ? {} : { continuation }) };
|
|
1821
|
+
return { kind, reasonCode, ...(correctionRequest === undefined ? {} : { correctionRequest }), ...(continuation === undefined ? {} : { continuation }), ...(unachievableLensSlots === undefined ? {} : { unachievableLensSlots }) };
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
// Mirrors NATIVE_REVIEW_UNACHIEVABLE_LENS_DETAIL_LIMIT (lib/native-review-cli.ts) and Go's 512-byte CAPTURE_UNACHIEVABLE detail bound.
|
|
1825
|
+
export const REVIEW_INTEGRATION_UNACHIEVABLE_LENS_DETAIL_LIMIT = 512;
|
|
1826
|
+
|
|
1827
|
+
function decodeUnachievableLensSlot(value , label ) {
|
|
1828
|
+
const body = exactRecord(value, label, ["lens", "selected_order", "subject_hash", "reason", "withdraw"], ["detail"]);
|
|
1829
|
+
const lens = nonempty(body.lens, `${label}.lens`);
|
|
1830
|
+
const selectedOrder = integer(body.selected_order, `${label}.selected_order`);
|
|
1831
|
+
const subjectHash = sha256(body.subject_hash, `${label}.subject_hash`);
|
|
1832
|
+
const reason = nonempty(body.reason, `${label}.reason`);
|
|
1833
|
+
const detail = body.detail === undefined ? undefined : nonempty(body.detail, `${label}.detail`);
|
|
1834
|
+
// gentle-pi#822: Go refuses a CAPTURE_UNACHIEVABLE detail above 512 UTF-8 bytes, so the stop decoder mirrors the same bound — measured in bytes, not UTF-16 code units — and a STATUS stop can never carry a detail this client would have refused to declare.
|
|
1835
|
+
if (detail !== undefined && Buffer.byteLength(detail, "utf8") > REVIEW_INTEGRATION_UNACHIEVABLE_LENS_DETAIL_LIMIT) throw new TypeError(`${label}.detail exceeds ${REVIEW_INTEGRATION_UNACHIEVABLE_LENS_DETAIL_LIMIT} bytes`);
|
|
1836
|
+
const withdraw = exactRecord(body.withdraw, `${label}.withdraw`, ["operation", "command", "arguments", "binding"]);
|
|
1837
|
+
const operation = enumeration(withdraw.operation, ["review.capture-unachievable"] , `${label}.withdraw.operation`);
|
|
1838
|
+
const command = nonempty(withdraw.command, `${label}.withdraw.command`);
|
|
1839
|
+
const arguments_ = decodeTransitionArguments(withdraw.arguments, `${label}.withdraw.arguments`);
|
|
1840
|
+
// Withdrawal is a single affirmative native action, never an optional or
|
|
1841
|
+
// negatable provider argument. The general token and command checks below
|
|
1842
|
+
// then prove its exact --withdraw=true rendering is the complete vector.
|
|
1843
|
+
const withdrawArguments = arguments_.filter((argument) => argument.name === "withdraw");
|
|
1844
|
+
if (withdrawArguments.length !== 1) throw new TypeError(`${label}.withdraw.arguments withdraw must appear exactly once`);
|
|
1845
|
+
if (withdrawArguments[0] .value !== "true") throw new TypeError(`${label}.withdraw.arguments withdraw must be true`);
|
|
1846
|
+
// The binding keeps the execute branch's open-record discipline: the Go shape is target_identity plus optional lineage_id, revision, and repository_context, and closing it here would make Pi stricter than the contract it implements.
|
|
1847
|
+
const binding = exactRecord(withdraw.binding, `${label}.withdraw.binding`, ["target_identity"], ["lineage_id", "revision", "repository_context"], true);
|
|
1848
|
+
const targetIdentity = sha256(binding.target_identity, `${label}.withdraw.binding.target_identity`);
|
|
1849
|
+
const lineageId = binding.lineage_id === undefined ? undefined : lineage(binding.lineage_id, `${label}.withdraw.binding.lineage_id`);
|
|
1850
|
+
const revision = binding.revision === undefined ? undefined : sha256(binding.revision, `${label}.withdraw.binding.revision`);
|
|
1851
|
+
// gentle-pi#822: the withdraw form names the slot identity twice — as named arguments and as the binding object — and the two renderings must agree before the slot decodes. Strict equality also enforces both-or-neither on the optional lineage and revision fields, so a partially rendered binding never slips through and restart cannot withdraw a different slot. Each identity argument must appear EXACTLY once: a first-match lookup let a duplicate {name} entry smuggle a second value past the identity checks.
|
|
1852
|
+
const withdrawIdentityArgument = (name ) => {
|
|
1853
|
+
const matches = arguments_.filter((argument) => argument.name === name);
|
|
1854
|
+
if (matches.length > 1) throw new TypeError(`${label}.withdraw.arguments ${name} must appear exactly once`);
|
|
1855
|
+
return matches[0];
|
|
1856
|
+
};
|
|
1857
|
+
const withdrawRequestHash = withdrawIdentityArgument("request-hash");
|
|
1858
|
+
if (withdrawRequestHash?.value !== subjectHash) throw new TypeError(`${label}.withdraw.arguments request-hash does not match the slot subject_hash`);
|
|
1859
|
+
const withdrawTarget = withdrawIdentityArgument("target");
|
|
1860
|
+
if (withdrawTarget?.value !== targetIdentity) throw new TypeError(`${label}.withdraw.arguments target does not match the withdraw binding target_identity`);
|
|
1861
|
+
const withdrawLineage = withdrawIdentityArgument("lineage");
|
|
1862
|
+
if (withdrawLineage?.value !== lineageId) throw new TypeError(`${label}.withdraw.arguments lineage does not match the withdraw binding lineage_id`);
|
|
1863
|
+
const withdrawExpectedRevision = withdrawIdentityArgument("expected-revision");
|
|
1864
|
+
if (withdrawExpectedRevision?.value !== revision) throw new TypeError(`${label}.withdraw.arguments expected-revision does not match the withdraw binding revision`);
|
|
1865
|
+
// gentle-pi#822: command is executable authority, not a display hint. Every
|
|
1866
|
+
// provider-issued argument token must be the canonical --name=value rendering,
|
|
1867
|
+
// and command must render the canonical operation plus those tokens exactly in
|
|
1868
|
+
// order. This rejects omitted tokens, a second command, suffixes, and shell
|
|
1869
|
+
// payloads rather than merely finding identity-token substrings.
|
|
1870
|
+
const tokens = arguments_.map((argument, index) => {
|
|
1871
|
+
if (argument.token === undefined) throw new TypeError(`${label}.withdraw.arguments[${index}].token is required`);
|
|
1872
|
+
const expectedToken = `--${argument.name}=${argument.value}`;
|
|
1873
|
+
if (argument.token !== expectedToken) throw new TypeError(`${label}.withdraw.arguments[${index}].token must exactly render its name and value`);
|
|
1874
|
+
return argument.token;
|
|
1875
|
+
});
|
|
1876
|
+
const expectedCommand = `gentle-ai review capture-unachievable ${tokens.join(" ")}`;
|
|
1877
|
+
if (command !== expectedCommand) throw new TypeError(`${label}.withdraw.command must exactly render the canonical operation and withdraw arguments`);
|
|
1878
|
+
return { lens, selectedOrder, subjectHash, reason, ...(detail === undefined ? {} : { detail }), withdraw: { operation, command, arguments: arguments_, binding: { targetIdentity, ...(lineageId === undefined ? {} : { lineageId }), ...(revision === undefined ? {} : { revision }) } } };
|
|
1775
1879
|
}
|
|
1776
1880
|
|
|
1777
1881
|
// ---------------------------------------------------------------------------
|
|
@@ -1821,7 +1925,7 @@ export function decodeReviewStatusV3(value ) {
|
|
|
1821
1925
|
// key set plus the optional forecast and the v5-only next_transition
|
|
1822
1926
|
// surfaces. status/v6 adds the intended-untracked selection; status/v7
|
|
1823
1927
|
// (gentle-ai v2.6.0, advertised through capabilities/v2.5 alongside v6)
|
|
1824
|
-
// adds
|
|
1928
|
+
// adds optional `eligible_untracked_inventory` and escalation metadata,
|
|
1825
1929
|
// so it is decoded on the v6 surface. v3 keeps rejecting every v5/v6/v7-only
|
|
1826
1930
|
// field.
|
|
1827
1931
|
const schema = typeof value === "object" && value !== null ? (value ).schema : undefined;
|
|
@@ -1830,7 +1934,7 @@ export function decodeReviewStatusV3(value ) {
|
|
|
1830
1934
|
const v5 = v6 || schema === "gentle-ai.review-integration.status/v5";
|
|
1831
1935
|
const body = exactRecord(value, "status", [
|
|
1832
1936
|
"schema", "contract", "operation", "applicability", "action", "replayability", "target_identity", "projection", "repair", "candidates",
|
|
1833
|
-
], ["authority", "frozen", "action_disposition", "eligibility", "next_transition", "authority_target_identity", ...(v5 ? ["receipt", "forecast", "repository_context", "validation_request"] : []), ...(v7 ? ["eligible_untracked_inventory"] : [])]);
|
|
1937
|
+
], ["authority", "frozen", "action_disposition", "eligibility", "next_transition", "authority_target_identity", ...(v5 ? ["receipt", "forecast", "repository_context", "validation_request"] : []), ...(v7 ? ["eligible_untracked_inventory", "escalation"] : [])]);
|
|
1834
1938
|
requireIdentity(body, v7 ? "gentle-ai.review-integration.status/v7" : v6 ? "gentle-ai.review-integration.status/v6" : v5 ? "gentle-ai.review-integration.status/v5" : "gentle-ai.review-integration.status/v3", REVIEW_INTEGRATION_OPERATION.STATUS);
|
|
1835
1939
|
|
|
1836
1940
|
const applicability = enumeration(body.applicability, ["current_target", "unrelated", "ambiguous", "corrupted"] , "status.applicability");
|
|
@@ -1914,6 +2018,24 @@ export function decodeReviewStatusV3(value ) {
|
|
|
1914
2018
|
};
|
|
1915
2019
|
}
|
|
1916
2020
|
|
|
2021
|
+
let escalation ;
|
|
2022
|
+
if (Object.hasOwn(body, "escalation")) {
|
|
2023
|
+
const label = "status.escalation";
|
|
2024
|
+
const source = exactRecord(body.escalation, label, ["cause", "finding_ids"], ["refuter_outcomes"]);
|
|
2025
|
+
escalation = {
|
|
2026
|
+
cause: enumeration(source.cause, ESCALATION_CAUSES, `${label}.cause`),
|
|
2027
|
+
findingIds: stringArray(source.finding_ids, `${label}.finding_ids`),
|
|
2028
|
+
};
|
|
2029
|
+
if (Object.hasOwn(source, "refuter_outcomes")) escalation.refuterOutcomes = array(source.refuter_outcomes, `${label}.refuter_outcomes`, (entry, itemLabel) => {
|
|
2030
|
+
const row = exactRecord(entry, itemLabel, ["finding_id", "outcome", "proof"]);
|
|
2031
|
+
return {
|
|
2032
|
+
findingId: nonempty(row.finding_id, `${itemLabel}.finding_id`),
|
|
2033
|
+
outcome: enumeration(row.outcome, ESCALATION_REFUTER_OUTCOMES, `${itemLabel}.outcome`),
|
|
2034
|
+
proof: nonempty(row.proof, `${itemLabel}.proof`),
|
|
2035
|
+
};
|
|
2036
|
+
});
|
|
2037
|
+
}
|
|
2038
|
+
|
|
1917
2039
|
// status/v7 top-level optional digest (gentle-ai v2.6.0): resolves #4066's
|
|
1918
2040
|
// closed loop where `sdd-attempt finish` named a digest status never
|
|
1919
2041
|
// published. Absent on the `staged` projection, which never resolves an
|
|
@@ -1941,6 +2063,7 @@ export function decodeReviewStatusV3(value ) {
|
|
|
1941
2063
|
...(forecast === undefined ? {} : { forecast }),
|
|
1942
2064
|
...(repositoryContext === undefined ? {} : { repositoryContext }),
|
|
1943
2065
|
...(validationRequest === undefined ? {} : { validationRequest }),
|
|
2066
|
+
...(escalation === undefined ? {} : { escalation }),
|
|
1944
2067
|
...(eligibleUntrackedInventory === undefined ? {} : { eligibleUntrackedInventory }),
|
|
1945
2068
|
raw: body,
|
|
1946
2069
|
};
|
|
@@ -2451,6 +2574,29 @@ export function decodeReviewAcknowledgedV1(value , expected
|
|
|
2451
2574
|
return decoded;
|
|
2452
2575
|
}
|
|
2453
2576
|
|
|
2577
|
+
const REVIEW_LAST_EVENT_FINDING_SEVERITIES = ["BLOCKER", "CRITICAL", "WARNING", "SUGGESTION"] ;
|
|
2578
|
+
const REVIEW_LAST_EVENT_EVIDENCE_CLASSES = ["deterministic", "inferential", "insufficient"] ;
|
|
2579
|
+
const REVIEW_LAST_EVENT_CAUSAL_DISPOSITIONS = ["introduced", "behavior-activated", "worsened", "pre-existing", "base-only", "unknown"] ;
|
|
2580
|
+
|
|
2581
|
+
|
|
2582
|
+
|
|
2583
|
+
|
|
2584
|
+
|
|
2585
|
+
|
|
2586
|
+
|
|
2587
|
+
|
|
2588
|
+
|
|
2589
|
+
|
|
2590
|
+
|
|
2591
|
+
|
|
2592
|
+
|
|
2593
|
+
|
|
2594
|
+
|
|
2595
|
+
|
|
2596
|
+
|
|
2597
|
+
|
|
2598
|
+
|
|
2599
|
+
|
|
2454
2600
|
|
|
2455
2601
|
|
|
2456
2602
|
|
|
@@ -2573,8 +2719,38 @@ function decodeReviewStatusContinuationV1(value )
|
|
|
2573
2719
|
};
|
|
2574
2720
|
}
|
|
2575
2721
|
|
|
2722
|
+
function decodeReviewLastEventReviewerFindingV1(value , label ) {
|
|
2723
|
+
const finding = exactRecord(value, label, ["id", "lens", "location", "severity", "claim", "proof_refs"], ["evidence_class", "causal_disposition"]);
|
|
2724
|
+
const evidenceClass = finding.evidence_class === undefined
|
|
2725
|
+
? undefined
|
|
2726
|
+
: enumeration(finding.evidence_class, REVIEW_LAST_EVENT_EVIDENCE_CLASSES, `${label}.evidence_class`);
|
|
2727
|
+
const causalDisposition = finding.causal_disposition === undefined
|
|
2728
|
+
? undefined
|
|
2729
|
+
: enumeration(finding.causal_disposition, REVIEW_LAST_EVENT_CAUSAL_DISPOSITIONS, `${label}.causal_disposition`);
|
|
2730
|
+
return {
|
|
2731
|
+
id: nonempty(finding.id, `${label}.id`),
|
|
2732
|
+
lens: nonempty(finding.lens, `${label}.lens`),
|
|
2733
|
+
location: nonempty(finding.location, `${label}.location`),
|
|
2734
|
+
severity: enumeration(finding.severity, REVIEW_LAST_EVENT_FINDING_SEVERITIES, `${label}.severity`),
|
|
2735
|
+
claim: nonempty(finding.claim, `${label}.claim`),
|
|
2736
|
+
proofRefs: stringArray(finding.proof_refs, `${label}.proof_refs`, { minimum: 1 }),
|
|
2737
|
+
...(evidenceClass === undefined ? {} : { evidenceClass }),
|
|
2738
|
+
...(causalDisposition === undefined ? {} : { causalDisposition }),
|
|
2739
|
+
};
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2742
|
+
function decodeReviewLastEventReviewerResultV1(value , label ) {
|
|
2743
|
+
const result = exactRecord(value, label, ["lens", "findings", "evidence", "result_hash"]);
|
|
2744
|
+
return {
|
|
2745
|
+
lens: nonempty(result.lens, `${label}.lens`),
|
|
2746
|
+
findings: array(result.findings, `${label}.findings`, decodeReviewLastEventReviewerFindingV1),
|
|
2747
|
+
evidence: stringArray(result.evidence, `${label}.evidence`),
|
|
2748
|
+
resultHash: sha256(result.result_hash, `${label}.result_hash`),
|
|
2749
|
+
};
|
|
2750
|
+
}
|
|
2751
|
+
|
|
2576
2752
|
export function decodeReviewLastEventClosureV1(value ) {
|
|
2577
|
-
const body = exactRecord(value, "last_event_closure", ["schema", "operation", "lineage_id", "state", "store_revision"], ["target_identity", "request_hash", "correction_lines", "action", "advisory_findings", "status_continuation", "acknowledgement"]);
|
|
2753
|
+
const body = exactRecord(value, "last_event_closure", ["schema", "operation", "lineage_id", "state", "store_revision"], ["target_identity", "request_hash", "correction_lines", "action", "advisory_findings", "reviewer_results", "status_continuation", "acknowledgement"]);
|
|
2578
2754
|
if (body.schema !== REVIEW_LAST_EVENT_CLOSURE_SCHEMA) throw new TypeError(`last_event_closure.schema must be ${REVIEW_LAST_EVENT_CLOSURE_SCHEMA}`);
|
|
2579
2755
|
const operation = enumeration(body.operation, Object.values(REVIEW_LAST_EVENT_CLOSURE_OPERATION), "last_event_closure.operation") ;
|
|
2580
2756
|
const state = enumeration(body.state, REVIEW_LAST_EVENT_TERMINAL_STATES, "last_event_closure.state") ;
|
|
@@ -2586,6 +2762,7 @@ export function decodeReviewLastEventClosureV1(value )
|
|
|
2586
2762
|
storeRevision: sha256(body.store_revision, "last_event_closure.store_revision"),
|
|
2587
2763
|
};
|
|
2588
2764
|
if (operation === REVIEW_LAST_EVENT_CLOSURE_OPERATION.CAPTURE_CORRECTION_PLAN) {
|
|
2765
|
+
if (body.reviewer_results !== undefined) throw new TypeError("last_event_closure reviewer_results requires approved state");
|
|
2589
2766
|
if (body.action !== undefined || body.advisory_findings !== undefined || body.status_continuation !== undefined) throw new TypeError("last_event_closure correction-plan cannot carry action, advisory_findings, or status_continuation");
|
|
2590
2767
|
if (state !== "correction_required") throw new TypeError("last_event_closure correction-plan requires correction_required state");
|
|
2591
2768
|
return {
|
|
@@ -2653,10 +2830,15 @@ export function decodeReviewLastEventClosureV1(value )
|
|
|
2653
2830
|
? undefined
|
|
2654
2831
|
: decodeReviewAdvisoryFindingsV1(body.advisory_findings, "last_event_closure.advisory_findings");
|
|
2655
2832
|
if (advisoryFindings !== undefined && state !== "approved") throw new TypeError("last_event_closure advisory_findings requires approved state");
|
|
2833
|
+
const reviewerResults = body.reviewer_results === undefined
|
|
2834
|
+
? undefined
|
|
2835
|
+
: array(body.reviewer_results, "last_event_closure.reviewer_results", decodeReviewLastEventReviewerResultV1);
|
|
2836
|
+
if (reviewerResults !== undefined && state !== "approved") throw new TypeError("last_event_closure reviewer_results requires approved state");
|
|
2656
2837
|
return {
|
|
2657
2838
|
...shared,
|
|
2658
2839
|
action,
|
|
2659
2840
|
...(advisoryFindings === undefined ? {} : { advisoryFindings }),
|
|
2841
|
+
...(reviewerResults === undefined ? {} : { reviewerResults }),
|
|
2660
2842
|
...(statusContinuation === undefined ? {} : { statusContinuation }),
|
|
2661
2843
|
...(acknowledgement === undefined ? {} : { acknowledgement }),
|
|
2662
2844
|
...(acknowledgementUndecodable ? { acknowledgementUndecodable: true } : {}),
|
|
@@ -111,6 +111,7 @@ export function shouldTriggerTelemetry(env
|
|
|
111
111
|
|
|
112
112
|
|
|
113
113
|
|
|
114
|
+
|
|
114
115
|
|
|
115
116
|
|
|
116
117
|
/**
|
|
@@ -130,6 +131,7 @@ export function spawnTelemetryTrigger(options )
|
|
|
130
131
|
cwd: options.cwd,
|
|
131
132
|
env: options.env,
|
|
132
133
|
detached: true,
|
|
134
|
+
windowsHide: true,
|
|
133
135
|
stdio: "ignore",
|
|
134
136
|
});
|
|
135
137
|
child.on("error", () => {
|