gentle-pi 4.0.0 → 4.2.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 +157 -18
- package/assets/agents/gentle-ai-explore.md +11 -4
- package/assets/agents/gentle-ai-verify.md +23 -4
- package/assets/agents/gentle-ai-worker.md +6 -6
- package/assets/install-wizard/index.html +47 -0
- package/assets/install-wizard/wizard.css +1279 -0
- package/assets/install-wizard/wizard.js +951 -0
- package/assets/orchestrator-delegation.md +70 -163
- package/assets/orchestrator-memory.md +11 -4
- package/assets/orchestrator-prompts.md +48 -0
- package/assets/orchestrator-tracking.md +53 -0
- package/assets/orchestrator-verification.md +28 -0
- package/assets/orchestrator-writer.md +38 -0
- package/assets/orchestrator.md +29 -25
- package/assets/sounds/LICENSE.md +52 -0
- package/assets/sounds/attention.wav +0 -0
- package/assets/sounds/error.wav +0 -0
- package/assets/sounds/success.wav +0 -0
- package/bin/engram-guard +9 -0
- package/bin/gentle-shell-install.mjs +302 -0
- package/bin/gentle-shell.mjs +708 -22
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +7 -7
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/manifest.json +2 -2
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/orchestration/pi.md +16 -4
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-capabilities.baseline.json +2 -2
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-roles.baseline.json +1 -1
- package/docs/auto-permission-answers.md +25 -0
- package/docs/auto-permission-evidence.md +39 -0
- package/docs/bundled-install.md +399 -0
- package/docs/delegated-verification.md +3 -5
- package/docs/gentle-agents-activity.md +513 -0
- package/docs/gentle-shell-uninstall.md +136 -0
- package/docs/gentle-shell.md +90 -33
- package/docs/install-wizard.md +2040 -0
- package/docs/native-authority-architecture.md +1 -1
- package/docs/permission-modes.md +127 -0
- package/docs/prompt-history.md +1 -1
- package/docs/readme-reference.md +310 -47
- package/docs/render-cache-performance.md +28 -0
- package/docs/session-profile-format.md +39 -0
- package/docs/sound-notifications-proposal.md +191 -0
- package/docs/sound-notifications.md +114 -0
- package/docs/yolo-mode.md +11 -10
- package/extensions/ask-user-choice.ts +4 -0
- package/extensions/ask-user-question.ts +6 -1
- package/extensions/child-context.ts +11 -2
- package/extensions/child-safety.ts +4 -2
- package/extensions/codegraph-tools.ts +18 -5
- package/extensions/gentle-agents.ts +718 -87
- package/extensions/gentle-ai.ts +991 -263
- package/extensions/gentle-herdr-activity.ts +81 -0
- package/extensions/gentle-notifications.ts +104 -0
- package/extensions/gentle-shell.ts +271 -80
- package/extensions/gentle-todo.ts +123 -18
- package/extensions/history/index.ts +11 -6
- package/extensions/pi-pretty.ts +39 -3
- package/extensions/quiet-tools.ts +91 -21
- package/extensions/skill-registry.ts +175 -46
- package/extensions/startup-banner.ts +11 -2
- package/lib/agent-home.ts +10 -1
- package/lib/agent-model-resolution.ts +80 -0
- package/lib/agent-profiles.ts +13 -3
- package/lib/agents-completion-delivery.ts +11 -3
- package/lib/agents-messaging.ts +1 -1
- package/lib/agents-protocol.ts +40 -1
- package/lib/agents-runner.ts +90 -15
- package/lib/auto-permission-answers.ts +133 -0
- package/lib/auto-permission-classifier.ts +459 -0
- package/lib/auto-permission-evidence.ts +88 -0
- package/lib/auto-permission-memory.ts +69 -0
- package/lib/auto-permission-runtime.ts +484 -0
- package/lib/auto-permission-tool-context.ts +61 -0
- package/lib/background-jobs-tools.ts +202 -0
- package/lib/background-jobs.ts +253 -0
- package/lib/background-monitor.ts +154 -0
- package/lib/bounded-writer-admission.ts +83 -10
- package/lib/builtin-codemode-optout.ts +166 -0
- package/lib/card-content-policy.ts +85 -0
- package/lib/child-package-injection.ts +62 -0
- package/lib/codemode-renderer.ts +14 -4
- package/lib/command-palette-catalog.ts +3 -2
- package/lib/context-threshold.ts +26 -0
- package/lib/destructive-command-guard.ts +104 -28
- package/lib/gentle-ai-binary.ts +109 -4
- package/lib/gentle-shell-bundled.ts +535 -0
- package/lib/gentle-shell-launcher.ts +75 -24
- package/lib/gentle-shell-startup-flow.ts +54 -0
- package/lib/gentle-shell-startup-update.ts +92 -0
- package/lib/gentle-shell-uninstall.ts +840 -0
- package/lib/gentle-shell-update-panel.ts +231 -0
- package/lib/herdr-activity.ts +183 -0
- package/lib/inprocess-reviewer.ts +3 -0
- package/lib/jobs-sidebar-state.ts +20 -0
- package/lib/jobs-view.ts +161 -0
- package/lib/model-price-ratio.ts +83 -0
- package/lib/model-routing-authority.ts +6 -1
- package/lib/nan-provider.ts +33 -11
- package/lib/native-review-cli.ts +194 -73
- package/lib/notification-audio-native.ts +162 -0
- package/lib/notification-audio-windows.ts +399 -0
- package/lib/notification-audio.ts +332 -0
- package/lib/notification-customize.ts +241 -0
- package/lib/notification-events.ts +113 -0
- package/lib/notification-policy.ts +148 -0
- package/lib/notification-pulse-client.ts +367 -0
- package/lib/notification-pulse-protocol.ts +305 -0
- package/lib/notification-pulse-stream.ts +213 -0
- package/lib/notification-pulse-worker.ts +66 -0
- package/lib/notification-scheduler.ts +173 -0
- package/lib/notification-service.ts +202 -0
- package/lib/notification-ui.ts +87 -0
- package/lib/odd-phase.ts +1 -1
- package/lib/orchestrator-alias.ts +20 -0
- package/lib/orchestrator-catalog.ts +79 -0
- package/lib/orchestrator-consultation.ts +61 -0
- package/lib/orchestrator-discovery.ts +49 -0
- package/lib/orchestrator-helper-consent.ts +122 -0
- package/lib/orchestrator-helper.ts +167 -0
- package/lib/orchestrator-message-card.ts +105 -0
- package/lib/orchestrator-presence.ts +115 -14
- package/lib/orchestrator-scope.ts +80 -0
- package/lib/orchestrator-state.ts +107 -0
- package/lib/orchestrator-tool-card.ts +280 -0
- package/lib/orchestrator-work-search.ts +218 -0
- package/lib/orchestrator-work.ts +79 -0
- package/lib/permission-mode-policy.ts +59 -0
- package/lib/review-candidate-view-owner.ts +8 -6
- package/lib/review-candidate-view.ts +3 -2
- package/lib/review-compact-contract.ts +2 -2
- package/lib/review-host-relay.ts +7 -2
- package/lib/review-integration-v2.ts +100 -22
- package/lib/review-publication-gate.ts +1 -1
- package/lib/review-risk-assessment.ts +77 -51
- package/lib/review-sidebar-state.ts +2 -1
- package/lib/review-transaction.ts +72 -3
- package/lib/runtime-metrics-children.ts +1 -1
- package/lib/runtime-metrics-native.ts +3 -2
- package/lib/selection-engine.ts +22 -7
- package/lib/session-change-capture.ts +5 -4
- package/lib/session-changes.ts +27 -22
- package/lib/session-profile-binding.ts +83 -0
- package/lib/session-profile-freeze.ts +261 -0
- package/lib/session-profile-persistence.ts +187 -0
- package/lib/session-worktree-registry.ts +74 -8
- package/lib/shell-bar.ts +41 -11
- package/lib/shell-changes-view.ts +40 -13
- package/lib/shell-changes.ts +88 -71
- package/lib/shell-prompt.ts +6 -4
- package/lib/shell-sidebar-layout.ts +5 -1
- package/lib/shell-todo.ts +81 -26
- package/lib/shell-usage.ts +4 -3
- package/lib/status-timing-diagnostics.ts +201 -0
- package/lib/telemetry-trigger.ts +3 -1
- package/lib/vim-editor-adapter.ts +27 -5
- package/lib/visual-customization-policy.ts +20 -9
- package/lib/visual-customize-view.ts +171 -20
- package/lib/writer-surfaces.ts +162 -0
- package/lib/yolo-session-policy.ts +198 -121
- package/package.json +5 -5
- package/runtime/child-package-injection.mjs +63 -0
- package/runtime/gentle-ai-binary.mjs +109 -4
- package/runtime/gentle-shell-bundled.mjs +536 -0
- package/runtime/gentle-shell-launcher.mjs +74 -23
- package/runtime/gentle-shell-startup-flow.mjs +55 -0
- package/runtime/gentle-shell-startup-update.mjs +93 -0
- package/runtime/gentle-shell-uninstall.mjs +841 -0
- package/runtime/gentle-shell-update-panel.mjs +232 -0
- package/runtime/native-review-cli.mjs +191 -70
- package/runtime/review-integration-v2.mjs +100 -22
- package/runtime/review-risk-assessment.mjs +77 -51
- package/runtime/status-timing-diagnostics.mjs +202 -0
- package/runtime/telemetry-trigger.mjs +3 -1
- package/scripts/bootstrap.cmd +255 -0
- package/scripts/bootstrap.sh +168 -0
- package/scripts/build-distribution.mjs +157 -0
- package/scripts/build-installer-bundles.mjs +136 -0
- package/scripts/build-runtime-modules.mjs +7 -0
- package/scripts/bundled-install.mjs +1308 -0
- package/scripts/bundled-wizard.mjs +399 -0
- package/scripts/check-types.mjs +4 -6
- package/scripts/gentle-ai-installer.mjs +79 -32
- package/scripts/install-gentle-ai.mjs +48 -1
- package/scripts/install-tui-mode-setting.mjs +10 -1
- package/scripts/install-wizard-preview.mjs +154 -0
- package/scripts/installer-downloads.mjs +648 -0
- package/scripts/installer-preflight.mjs +387 -0
- package/scripts/installer-probes.mjs +564 -0
- package/scripts/installer-runner.mjs +1102 -0
- package/scripts/installer-server.mjs +881 -0
- package/scripts/installer-windows-artifacts.json +15 -0
- package/scripts/installer-windows.mjs +752 -0
- package/scripts/main-channel.mjs +452 -0
- package/scripts/npm-pack-result.mjs +31 -0
- package/scripts/pi-update.mjs +223 -0
- package/scripts/render-cache-performance-worker.mjs +205 -0
- package/scripts/render-cache-performance.mjs +115 -0
- package/scripts/run-test-suite.mjs +22 -8
- package/scripts/test-installer-acceptance.sh +161 -0
- package/scripts/test-packed-runner.mjs +23 -5
- package/scripts/types-baseline.json +2 -92
- package/scripts/verify-package-files.mjs +82 -18
- package/skills/gentle-ai/SKILL.md +12 -11
- package/tests/agent-home.test.ts +56 -3
- package/tests/agent-model-resolution.test.ts +113 -0
- package/tests/agent-profiles.test.ts +12 -0
- package/tests/agent-risk-escalation.test.ts +123 -0
- package/tests/agents-completion-delivery.test.ts +17 -0
- package/tests/agents-protocol.test.ts +57 -0
- package/tests/agents-queries.test.ts +9 -9
- package/tests/agents-runner-process.test.ts +31 -0
- package/tests/agents-runner.test.ts +89 -4
- package/tests/append-system-prompt-route.test.ts +18 -1
- package/tests/auto-mode-human-answers.test.ts +68 -0
- package/tests/auto-mode-invariants.test.ts +276 -0
- package/tests/auto-mode-sdk.test.ts +84 -0
- package/tests/auto-mode.test.ts +49 -0
- package/tests/auto-permission-answers.test.ts +170 -0
- package/tests/auto-permission-classifier.test.ts +634 -0
- package/tests/auto-permission-compound.test.ts +24 -0
- package/tests/auto-permission-evidence.test.ts +88 -0
- package/tests/auto-permission-memory.test.ts +46 -0
- package/tests/auto-permission-public-regressions.test.ts +111 -0
- package/tests/auto-permission-runtime.test.ts +454 -0
- package/tests/autonomous-guard.test.ts +86 -16
- package/tests/background-cache-warming.test.ts +2 -1
- package/tests/background-jobs.test.ts +227 -0
- package/tests/background-monitor.test.ts +195 -0
- package/tests/background-subagents-default-mode.test.ts +28 -5
- package/tests/bounded-stop-contract.test.ts +85 -0
- package/tests/bounded-writer-admission.test.ts +52 -1
- package/tests/bridge-wake-lifecycle.test.ts +129 -0
- package/tests/bridge-wake-rendering.test.ts +56 -0
- package/tests/build-distribution.test.ts +269 -0
- package/tests/builtin-codemode-optout.test.ts +242 -0
- package/tests/bundled-install.test.ts +1319 -0
- package/tests/bundled-wizard.test.ts +505 -0
- package/tests/card-content-policy.test.ts +68 -0
- package/tests/card-style-policy.test.ts +34 -2
- package/tests/child-codegraph-bootstrap.test.ts +74 -0
- package/tests/child-package-entrypoints.test.ts +65 -0
- package/tests/child-package-injection.test.ts +74 -0
- package/tests/child-safety.test.ts +12 -0
- package/tests/child-web-package.test.ts +224 -0
- package/tests/codegraph-tools.test.ts +35 -0
- package/tests/codemode-rendering.test.ts +107 -2
- package/tests/command-palette.test.ts +5 -5
- package/tests/context-threshold.test.ts +31 -0
- package/tests/delegate-for-reason-contract.test.ts +327 -0
- package/tests/delegated-key-learnings-contract.test.ts +3 -2
- package/tests/devbinary/native-review-parity.devtest.ts +1 -1
- package/tests/devbinary/pi-host-relay.devtest.ts +173 -3
- package/tests/engram-safety.test.ts +316 -0
- package/tests/generic-agent-tools.test.ts +16 -1
- package/tests/gentle-agents.test.ts +1895 -76
- package/tests/gentle-ai-binary-isolation.test.ts +135 -0
- package/tests/gentle-ai-binary.test.ts +19 -2
- package/tests/gentle-ai-child-guards.test.ts +202 -0
- package/tests/gentle-ai-dev-binary-surfacing.test.ts +125 -15
- package/tests/gentle-ai-installer.test.ts +548 -58
- package/tests/gentle-ai-prompt-model-routing.test.ts +123 -0
- package/tests/gentle-ai-renderer.test.ts +6 -6
- package/tests/gentle-ai.test.ts +999 -31
- package/tests/gentle-herdr-activity.test.ts +100 -0
- package/tests/gentle-notifications.test.ts +427 -0
- package/tests/gentle-shell-bin.test.ts +263 -19
- package/tests/gentle-shell-bundled.test.ts +1036 -0
- package/tests/gentle-shell-launcher.test.ts +251 -25
- package/tests/gentle-shell-startup-flow.test.ts +75 -0
- package/tests/gentle-shell-startup-update.test.ts +85 -0
- package/tests/gentle-shell-uninstall.test.ts +978 -0
- package/tests/gentle-shell-update-panel.test.ts +158 -0
- package/tests/gentle-shell.test.ts +611 -100
- package/tests/gentle-todo.test.ts +317 -14
- package/tests/herdr-activity.test.ts +243 -0
- package/tests/history-agent-home.test.ts +81 -0
- package/tests/history-child-guard.test.ts +70 -0
- package/tests/inprocess-reviewer.test.ts +20 -0
- package/tests/install-gentle-ai-failure.test.ts +69 -0
- package/tests/install-tui-mode-setting.test.ts +15 -0
- package/tests/install-wizard.test.ts +1223 -0
- package/tests/installer-bundles.test.ts +122 -0
- package/tests/installer-posix-bootstrap.test.ts +1201 -0
- package/tests/installer-preflight.test.ts +750 -0
- package/tests/installer-probes.test.ts +1106 -0
- package/tests/installer-runner.test.ts +2159 -0
- package/tests/installer-server.test.ts +1567 -0
- package/tests/installer-windows-bootstrap.test.ts +2410 -0
- package/tests/jobs-view.test.ts +160 -0
- package/tests/main-channel-upgrade.test.ts +395 -0
- package/tests/main-channel.test.ts +344 -0
- package/tests/maintainer/provider-relay.maintest.ts +13 -12
- package/tests/model-price-ratio.test.ts +116 -0
- package/tests/model-profiles-preservation.test.ts +153 -0
- package/tests/model-routing-package.test.ts +10 -0
- package/tests/nan-provider-runtime.test.ts +156 -0
- package/tests/nan-provider.test.ts +28 -18
- package/tests/native-binary-gate.test.ts +2 -0
- package/tests/native-review-capability-contract.test.ts +7 -1
- package/tests/native-review-cli.test.ts +236 -1
- package/tests/native-review-consent.test.ts +2 -2
- package/tests/native-review-parity-runtime.test.ts +1 -1
- package/tests/native-review-parity.test.ts +12 -48
- package/tests/native-risk-evidence-parity.test.ts +21 -0
- package/tests/no-self-pause-contract.test.ts +87 -0
- package/tests/notification-audio-native.test.ts +281 -0
- package/tests/notification-audio-windows.test.ts +581 -0
- package/tests/notification-audio.test.ts +548 -0
- package/tests/notification-customize.test.ts +655 -0
- package/tests/notification-events.test.ts +115 -0
- package/tests/notification-policy.test.ts +201 -0
- package/tests/notification-pulse-client.test.ts +356 -0
- package/tests/notification-pulse-protocol.test.ts +213 -0
- package/tests/notification-pulse-stream.test.ts +320 -0
- package/tests/notification-scheduler.test.ts +258 -0
- package/tests/notification-ui.test.ts +157 -0
- package/tests/npm-pack-result.test.ts +41 -0
- package/tests/odd-integration.test.ts +14 -5
- package/tests/odd-routing-canonical-ratchet.test.ts +53 -12
- package/tests/odd-routing-contract.test.ts +80 -23
- package/tests/odd-runtime-delegation-gate.test.ts +4 -1
- package/tests/orchestrator-budget.test.ts +29 -6
- package/tests/orchestrator-catalog.test.ts +146 -0
- package/tests/orchestrator-consultation-sdk.test.ts +614 -0
- package/tests/orchestrator-consultation.test.ts +111 -0
- package/tests/orchestrator-discovery.test.ts +241 -0
- package/tests/orchestrator-helper-consent.test.ts +171 -0
- package/tests/orchestrator-helper.test.ts +297 -0
- package/tests/orchestrator-message-card.test.ts +190 -0
- package/tests/orchestrator-presence.test.ts +23 -0
- package/tests/orchestrator-rdd-ownership.test.ts +6 -5
- package/tests/orchestrator-scope.test.ts +107 -0
- package/tests/orchestrator-state.test.ts +114 -0
- package/tests/orchestrator-status-publication-contract.test.ts +34 -0
- package/tests/orchestrator-tool-card.test.ts +398 -0
- package/tests/orchestrator-work-search.test.ts +318 -0
- package/tests/orchestrator-work.test.ts +147 -0
- package/tests/package-manifest.test.ts +105 -21
- package/tests/packed-runner-owned-path.test.ts +35 -1
- package/tests/parallel-reason-contract.test.ts +21 -0
- package/tests/parallel-writer-admission.test.ts +210 -0
- package/tests/permission-mode-policy.test.ts +194 -0
- package/tests/persona-single-channel.test.ts +4 -12
- package/tests/pi-pretty.test.ts +56 -0
- package/tests/pi-update-launcher.test.ts +87 -0
- package/tests/pi-update.test.ts +186 -0
- package/tests/provider-defect-handoff.test.ts +4 -3
- package/tests/quiet-tool-rendering.test.ts +160 -3
- package/tests/rdd-aware-verification-contract.test.ts +29 -41
- package/tests/rdd-status-line.test.ts +52 -0
- package/tests/render-cache-performance.test.ts +48 -0
- package/tests/review-agent-end-preflight.test.ts +62 -10
- package/tests/review-candidate-view.test.ts +101 -12
- package/tests/review-contract-prompt.test.ts +33 -4
- package/tests/review-controller-native-recovery.test.ts +327 -17
- package/tests/review-controller-native-routing.test.ts +255 -1
- package/tests/review-controller-workspace-root.test.ts +43 -2
- package/tests/review-controller.test.ts +2 -2
- package/tests/review-correction-lifecycle.test.ts +2 -1
- package/tests/review-gate.test.ts +7 -4
- package/tests/review-graph-schema.test.ts +2 -2
- package/tests/review-host-relay-routing.test.ts +5 -3
- package/tests/review-host-relay.test.ts +49 -2
- package/tests/review-integration-v2-forward.test.ts +21 -1
- package/tests/review-json-arguments.test.ts +167 -0
- package/tests/review-last-event-closure.test.ts +157 -8
- package/tests/review-ledger-contract.test.ts +5 -4
- package/tests/review-policy-judgment-day.test.ts +1 -1
- package/tests/review-policy-ordinary.test.ts +2 -2
- package/tests/review-relay-transport-agent.test.ts +66 -1
- package/tests/review-risk-assessment.test.ts +109 -33
- package/tests/review-session-standing-permission-controller.test.ts +16 -17
- package/tests/review-session-standing-permission-ipc.test.ts +6 -5
- package/tests/review-test-fixtures.ts +7 -1
- package/tests/review-transaction.test.ts +18 -1
- package/tests/run-test-suite.test.ts +83 -5
- package/tests/runtime-harness.mjs +42 -15
- package/tests/runtime-metrics-extension.test.ts +1 -1
- package/tests/runtime-metrics-native.test.ts +31 -1
- package/tests/selection-engine.test.ts +65 -5
- package/tests/session-change-capture.test.ts +10 -10
- package/tests/session-changes-shell.test.ts +242 -48
- package/tests/session-changes.test.ts +17 -24
- package/tests/session-profile-binding.test.ts +163 -0
- package/tests/session-profile-freeze.test.ts +394 -0
- package/tests/session-profile-persistence.test.ts +334 -0
- package/tests/session-subject-routing-contract.test.ts +34 -0
- package/tests/session-worktree-registry.test.ts +57 -2
- package/tests/shell-bar.test.ts +40 -0
- package/tests/shell-changes-view.test.ts +85 -34
- package/tests/shell-changes.test.ts +79 -62
- package/tests/shell-render-cache-runtime.test.ts +69 -0
- package/tests/shell-render-cache.test.ts +129 -0
- package/tests/shell-sidebar-layout.test.ts +27 -2
- package/tests/shell-todo.test.ts +132 -11
- package/tests/shell-usage-view.test.ts +1 -1
- package/tests/shell-usage.test.ts +35 -0
- package/tests/short-check-routing-contract.test.ts +49 -0
- package/tests/skill-registry-watcher.test.ts +119 -0
- package/tests/skill-registry.test.ts +311 -5
- package/tests/startup-banner.test.ts +117 -3
- package/tests/startup-home-repo.test.ts +170 -0
- package/tests/status-timing-diagnostics.test.ts +283 -0
- package/tests/support/orchestrator-modules.ts +19 -0
- package/tests/task-size-routing-contract.test.ts +241 -0
- package/tests/telemetry-trigger.test.ts +27 -1
- package/tests/test-strength-contract.test.ts +124 -0
- package/tests/verify-agent-contract.test.ts +90 -0
- package/tests/verify-first-launch-contract.test.ts +139 -0
- package/tests/verify-package-files.test.ts +56 -0
- package/tests/vim-editor-adapter.test.ts +47 -21
- package/tests/visual-customization-policy.test.ts +32 -3
- package/tests/visual-customize-view.test.ts +288 -10
- package/tests/writer-edit-surface-scope.test.ts +59 -2
- package/tests/writer-surfaces.test.ts +199 -0
- package/tests/yolo-customize.test.ts +18 -3
- package/tests/yolo-mode-runtime.test.ts +37 -10
- package/tests/yolo-mode.test.ts +38 -5
- package/tests/yolo-session-policy.test.ts +1 -1
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/README.md +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/lens.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/refuter.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/targeted-validator.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/lens.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/refuter.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/targeted-validator.json +0 -0
package/lib/native-review-cli.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
1
|
+
import { execFile, spawn } from "node:child_process";
|
|
2
2
|
import { chmod, mkdtemp, realpath, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { dirname, isAbsolute, join, posix, win32 } from "node:path";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
|
-
import { PackageLocalGentleAiBinaryMissingError, resolveGentleAiBinary } from "./gentle-ai-binary.ts";
|
|
6
|
+
import { gentleAiIsolationEnv, PackageLocalGentleAiBinaryMissingError, resolveGentleAiBinary } from "./gentle-ai-binary.ts";
|
|
7
7
|
import { GENTLE_PI_REVIEW_RELAY_CONTRACT, GENTLE_PI_REVIEW_RELAY_CONTRACT_ENV } from "./review-relay-contract.ts";
|
|
8
8
|
import { decodeReviewAssessmentV1, type ReviewAssessmentV1 } from "./review-risk-assessment.ts";
|
|
9
9
|
import {
|
|
@@ -31,6 +31,8 @@ import {
|
|
|
31
31
|
type ReviewLastEventClosureV1,
|
|
32
32
|
} from "./review-integration-v2.ts";
|
|
33
33
|
|
|
34
|
+
import { observeNativeStatus, measureStatusSync, measureStatusAsync } from "./status-timing-diagnostics.ts";
|
|
35
|
+
|
|
34
36
|
const execFileAsync = promisify(execFile);
|
|
35
37
|
|
|
36
38
|
// Negotiated review/status responses can carry a complete authority inventory.
|
|
@@ -250,6 +252,8 @@ export interface NativeReviewReclaimRequest {
|
|
|
250
252
|
|
|
251
253
|
export interface NativeReviewRecoverRequest {
|
|
252
254
|
cwd: string;
|
|
255
|
+
baseRef?: string;
|
|
256
|
+
committedOnly?: boolean;
|
|
253
257
|
predecessorLineage: string;
|
|
254
258
|
expectedPredecessorRevision: string;
|
|
255
259
|
successorLineage: string;
|
|
@@ -275,6 +279,13 @@ export const NATIVE_REVIEW_LEGACY_ALIAS_REPAIR = {
|
|
|
275
279
|
} as const;
|
|
276
280
|
export type NativeReviewReconcileAnomalies = (typeof NATIVE_REVIEW_RECONCILE_ANOMALIES)[keyof typeof NATIVE_REVIEW_RECONCILE_ANOMALIES];
|
|
277
281
|
|
|
282
|
+
export const NATIVE_REVIEW_ABANDON_REASONS = ["operator_disposition", "retired_schema"] as const;
|
|
283
|
+
export type NativeReviewAbandonReason = (typeof NATIVE_REVIEW_ABANDON_REASONS)[number];
|
|
284
|
+
|
|
285
|
+
export function isNativeReviewAbandonReason(value: unknown): value is NativeReviewAbandonReason {
|
|
286
|
+
return value === "operator_disposition" || value === "retired_schema";
|
|
287
|
+
}
|
|
288
|
+
|
|
278
289
|
export interface NativeReviewAbandonRequest {
|
|
279
290
|
cwd: string;
|
|
280
291
|
lineage: string;
|
|
@@ -283,7 +294,7 @@ export interface NativeReviewAbandonRequest {
|
|
|
283
294
|
capturedLensResults: readonly string[];
|
|
284
295
|
findingsPresent: boolean;
|
|
285
296
|
actor: string;
|
|
286
|
-
reason:
|
|
297
|
+
reason: NativeReviewAbandonReason;
|
|
287
298
|
maintainerAuthorization: string;
|
|
288
299
|
signal?: AbortSignal;
|
|
289
300
|
}
|
|
@@ -471,6 +482,24 @@ export interface NativeReviewUnachievableLensCaptureArtifact {
|
|
|
471
482
|
// 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.
|
|
472
483
|
const NATIVE_REVIEW_UNKNOWN_UNACHIEVABLE_VERB_REFUSAL = /unknown review command "capture-unachievable"/;
|
|
473
484
|
|
|
485
|
+
// A provider older than START lens selection refuses the STATUS preflight
|
|
486
|
+
// flags with this exact typed preflight cause, before reading the repository.
|
|
487
|
+
const NATIVE_REVIEW_UNKNOWN_LENS_FLAG_CAUSE = /^flag provided but not defined: -lenses(?:-reason)?$/;
|
|
488
|
+
|
|
489
|
+
/** Reports a not-started STATUS refusal caused only by an unknown --lenses flag. */
|
|
490
|
+
export function isNativeReviewLensSelectionUnsupported(error: unknown): boolean {
|
|
491
|
+
if (!(error instanceof NativeReviewIntegrationError)) return false;
|
|
492
|
+
const failure = error.failureEnvelope;
|
|
493
|
+
return failure.code === "invalid_request" && failure.mutationOutcome === "not_started" &&
|
|
494
|
+
typeof failure.cause === "string" && NATIVE_REVIEW_UNKNOWN_LENS_FLAG_CAUSE.test(failure.cause);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function nativeLensSelectionArguments(request: NativeLensSelectionRequest): readonly string[] {
|
|
498
|
+
if (request.lenses === undefined && request.lensesReason === undefined) return [];
|
|
499
|
+
if (request.lenses === undefined || request.lenses.length === 0 || request.lensesReason === undefined) throw new TypeError("Native lens selection requires lenses and lensesReason together");
|
|
500
|
+
return ["--lenses", request.lenses.join(","), "--lenses-reason", request.lensesReason];
|
|
501
|
+
}
|
|
502
|
+
|
|
474
503
|
export function isNativeReviewUnachievableVerbRefused(error: unknown): boolean {
|
|
475
504
|
if (typeof error !== "object" || error === null) return false;
|
|
476
505
|
const stderr = (error as { diagnostics?: { stderr?: unknown } }).diagnostics?.stderr;
|
|
@@ -497,7 +526,17 @@ interface NativeUntrackedSelectionRequest {
|
|
|
497
526
|
intendedUntrackedSelection?: NativeIntendedUntrackedSelectionSubmission;
|
|
498
527
|
}
|
|
499
528
|
|
|
500
|
-
|
|
529
|
+
/**
|
|
530
|
+
* The agent's own review lens choice (gentle-ai START --lenses): canonical
|
|
531
|
+
* lens names in 4R order and one reason. Providers that predate it refuse
|
|
532
|
+
* the flag, and START then falls back to the tier default.
|
|
533
|
+
*/
|
|
534
|
+
export interface NativeLensSelectionRequest {
|
|
535
|
+
lenses?: readonly string[];
|
|
536
|
+
lensesReason?: string;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
export interface NativeStartRequest extends NativeUntrackedSelectionRequest, NativeLensSelectionRequest {
|
|
501
540
|
cwd: string;
|
|
502
541
|
baseRef?: string;
|
|
503
542
|
committedOnly?: boolean;
|
|
@@ -524,7 +563,7 @@ export interface NativeReviewConsentDeclinedResult {
|
|
|
524
563
|
export interface NativeReviewConsentStartedResult { kind: "started"; start: NativeStartResult; }
|
|
525
564
|
export type NativeReviewConsentAnswerResult = NativeReviewConsentStartedResult | NativeReviewConsentDeclinedResult;
|
|
526
565
|
export interface NativeReviewStatusRequest { cwd: string; signal?: AbortSignal; }
|
|
527
|
-
export interface NativeTargetStatusRequest extends NativeUntrackedSelectionRequest {
|
|
566
|
+
export interface NativeTargetStatusRequest extends NativeUntrackedSelectionRequest, NativeLensSelectionRequest {
|
|
528
567
|
cwd: string;
|
|
529
568
|
lineageId?: string;
|
|
530
569
|
baseRef?: string;
|
|
@@ -752,6 +791,8 @@ const REVIEW_RISK_SUBJECT_BY_CODE: Readonly<Record<string, string>> = Object.fre
|
|
|
752
791
|
shell_source: "shell scripting",
|
|
753
792
|
process_boundary: "code that starts other processes",
|
|
754
793
|
process_scan_limit: "code that starts other processes",
|
|
794
|
+
dangerous_sink: "a dangerous code pattern",
|
|
795
|
+
agent_escalation: "the agent that made this change flagged it as high risk",
|
|
755
796
|
executable_mode: "an executable permission change",
|
|
756
797
|
executable_change: "an executable change",
|
|
757
798
|
configuration_change: "a configuration change",
|
|
@@ -1034,6 +1075,13 @@ export const NATIVE_CLI_CONTRACTS = Object.freeze({
|
|
|
1034
1075
|
// to /v4 without a review-integration/v2 change. No new negotiated
|
|
1035
1076
|
// capability is asserted.
|
|
1036
1077
|
"4.0.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 }),
|
|
1078
|
+
// v4.1.1 repeats 4.0.0: the published binary still advertises
|
|
1079
|
+
// capabilities/v2.6 under review-integration/v2. The provider contract moved
|
|
1080
|
+
// from 1.2.0 to 1.3.0 (additive agent lens selection, gentle-ai#5348), with
|
|
1081
|
+
// published tarball SHA-256
|
|
1082
|
+
// b04b6b75d1a446c942a816fbf92932d16f8c3d3001f93159dee2a92bbf2ebe44.
|
|
1083
|
+
// No new negotiated capability identity is asserted.
|
|
1084
|
+
"4.1.1": 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 }),
|
|
1037
1085
|
});
|
|
1038
1086
|
|
|
1039
1087
|
export interface NativeReviewProcessDiagnostics {
|
|
@@ -1075,21 +1123,98 @@ export class NativeReviewCliError extends Error {
|
|
|
1075
1123
|
// makes. It declares the Pi host relay handshake on each spawn: gentle-ai
|
|
1076
1124
|
// refuses pi admission pre-authority without it (gentle-pi#311 P4), and a
|
|
1077
1125
|
// single injection point keeps the declaration impossible to forget on any
|
|
1078
|
-
// individual operation.
|
|
1126
|
+
// individual operation. In a bundled install it also keeps Gentle AI's state
|
|
1127
|
+
// inside the prefix (gentleAiIsolationEnv, S9).
|
|
1079
1128
|
export function gentleAiProcessEnvironment(base: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
|
|
1080
|
-
return { ...base, [GENTLE_PI_REVIEW_RELAY_CONTRACT_ENV]: GENTLE_PI_REVIEW_RELAY_CONTRACT };
|
|
1129
|
+
return { ...gentleAiIsolationEnv(base), [GENTLE_PI_REVIEW_RELAY_CONTRACT_ENV]: GENTLE_PI_REVIEW_RELAY_CONTRACT };
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
export type ProcessTreeKiller = (pid: number) => void;
|
|
1133
|
+
|
|
1134
|
+
type TreeKillSpawn = (file: string, args: readonly string[], options: { cwd: string; stdio: "ignore"; windowsHide: true }) => { on(event: "error", listener: () => void): unknown };
|
|
1135
|
+
|
|
1136
|
+
// A bare "taskkill" resolves from the current directory before PATH on
|
|
1137
|
+
// Windows, so a repository shipping its own taskkill.exe would run on any
|
|
1138
|
+
// abort. Always use the System32 binary, from System32 itself.
|
|
1139
|
+
export function windowsTaskkillPath(env: NodeJS.ProcessEnv = process.env): string {
|
|
1140
|
+
const systemRoot = env.SystemRoot || env.windir;
|
|
1141
|
+
return win32.join(systemRoot && win32.isAbsolute(systemRoot) ? systemRoot : "C:\\Windows", "System32", "taskkill.exe");
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// taskkill walks the tree from a live parent, so it must run before the
|
|
1145
|
+
// parent dies; Node's own abort/timeout kill would orphan the git children.
|
|
1146
|
+
export function createWindowsProcessTreeKiller(env: NodeJS.ProcessEnv = process.env, spawnProcess: TreeKillSpawn = spawn): ProcessTreeKiller {
|
|
1147
|
+
const taskkill = windowsTaskkillPath(env);
|
|
1148
|
+
return (pid) => {
|
|
1149
|
+
try {
|
|
1150
|
+
spawnProcess(taskkill, ["/pid", String(pid), "/T", "/F"], { cwd: win32.dirname(taskkill), stdio: "ignore", windowsHide: true }).on("error", () => {});
|
|
1151
|
+
} catch { /* Best effort: the call still settles through its own child. */ }
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
export const windowsProcessTreeKiller: ProcessTreeKiller = (pid) => createWindowsProcessTreeKiller()(pid);
|
|
1156
|
+
|
|
1157
|
+
const WINDOWS_TREE_KILL_GRACE_MS = 1_000;
|
|
1158
|
+
|
|
1159
|
+
function abortError(signal: AbortSignal): Error {
|
|
1160
|
+
return Object.assign(new Error("The operation was aborted", { cause: signal.reason }), { name: "AbortError", code: "ABORT_ERR" });
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
function execFileResultFromError(error: unknown, timedOut: boolean): ExecFileResult {
|
|
1164
|
+
const detail = error as NodeJS.ErrnoException & { stdout?: string; stderr?: string; code?: string | number; signal?: NodeJS.Signals; killed?: boolean };
|
|
1165
|
+
if (detail.code === "ENOENT" || detail.code === "EACCES" || detail.name === "AbortError") throw error;
|
|
1166
|
+
const outputLimitExceeded = detail.code === "ENOBUFS" || detail.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER";
|
|
1167
|
+
return { stdout: detail.stdout ?? "", stderr: detail.stderr ?? "", exitCode: typeof detail.code === "number" ? detail.code : 1, signal: detail.signal ?? null, timedOut: !outputLimitExceeded && (timedOut || detail.killed === true), outputLimitExceeded };
|
|
1081
1168
|
}
|
|
1082
1169
|
|
|
1083
|
-
|
|
1170
|
+
// Windows: abort and timeout kill the whole process tree instead of only
|
|
1171
|
+
// gentle-ai, with the same observable results as the POSIX path.
|
|
1172
|
+
function windowsExecFile(request: ExecFileRequest, killProcessTree: ProcessTreeKiller): Promise<ExecFileResult> {
|
|
1173
|
+
const { signal } = request;
|
|
1174
|
+
if (signal?.aborted) return Promise.reject(abortError(signal));
|
|
1175
|
+
return new Promise((resolve, reject) => {
|
|
1176
|
+
let timedOut = false, aborted = false, pipesCut = false;
|
|
1177
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
1178
|
+
let fallback: ReturnType<typeof setTimeout> | undefined;
|
|
1179
|
+
const child = execFile(request.file, [...request.arguments], { cwd: request.cwd, encoding: "utf8", shell: false, windowsHide: true, maxBuffer: request.maxBufferBytes, env: gentleAiProcessEnvironment() }, (error, stdout, stderr) => {
|
|
1180
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
1181
|
+
if (fallback !== undefined) clearTimeout(fallback);
|
|
1182
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1183
|
+
if (aborted && signal) { reject(abortError(signal)); return; }
|
|
1184
|
+
if (error === null) { resolve({ stdout, stderr, exitCode: 0, signal: null, timedOut: pipesCut, outputLimitExceeded: false }); return; }
|
|
1185
|
+
try { resolve(execFileResultFromError(Object.assign(error, { stdout, stderr }), timedOut)); } catch (failure) { reject(failure); }
|
|
1186
|
+
});
|
|
1187
|
+
// If taskkill cannot run (AppLocker, a broken PATH), still end gentle-ai
|
|
1188
|
+
// itself so the deadline holds; its children may then outlive it. A
|
|
1189
|
+
// surviving descendant can still hold the inherited stdout/stderr open,
|
|
1190
|
+
// which would keep execFile from calling back, so cut the pipes too.
|
|
1191
|
+
const killTree = () => {
|
|
1192
|
+
if (child.pid === undefined) { child.kill(); return; }
|
|
1193
|
+
killProcessTree(child.pid);
|
|
1194
|
+
fallback = setTimeout(() => {
|
|
1195
|
+
if (child.exitCode === null && child.signalCode === null) child.kill();
|
|
1196
|
+
pipesCut = true;
|
|
1197
|
+
child.stdout?.destroy();
|
|
1198
|
+
child.stderr?.destroy();
|
|
1199
|
+
}, WINDOWS_TREE_KILL_GRACE_MS);
|
|
1200
|
+
};
|
|
1201
|
+
function onAbort(): void { if (aborted || timedOut) return; aborted = true; killTree(); }
|
|
1202
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1203
|
+
if (request.timeoutMs !== undefined && request.timeoutMs > 0) timer = setTimeout(() => { if (aborted) return; timedOut = true; killTree(); }, request.timeoutMs);
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
export function createNodeExecFileAdapter(options: { readonly platform?: NodeJS.Platform; readonly killProcessTree?: ProcessTreeKiller } = {}): ExecFileAdapter {
|
|
1208
|
+
if ((options.platform ?? process.platform) === "win32") {
|
|
1209
|
+
const killProcessTree = options.killProcessTree ?? windowsProcessTreeKiller;
|
|
1210
|
+
return (request) => windowsExecFile(request, killProcessTree);
|
|
1211
|
+
}
|
|
1084
1212
|
return async (request) => {
|
|
1085
1213
|
try {
|
|
1086
1214
|
const output = await execFileAsync(request.file, [...request.arguments], { cwd: request.cwd, encoding: "utf8", shell: false, windowsHide: true, timeout: request.timeoutMs, maxBuffer: request.maxBufferBytes, signal: request.signal, env: gentleAiProcessEnvironment() });
|
|
1087
1215
|
return { stdout: output.stdout, stderr: output.stderr, exitCode: 0, signal: null, timedOut: false, outputLimitExceeded: false };
|
|
1088
1216
|
} catch (error) {
|
|
1089
|
-
|
|
1090
|
-
if (detail.code === "ENOENT" || detail.code === "EACCES" || detail.name === "AbortError") throw error;
|
|
1091
|
-
const outputLimitExceeded = detail.code === "ENOBUFS" || detail.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER";
|
|
1092
|
-
return { stdout: detail.stdout ?? "", stderr: detail.stderr ?? "", exitCode: typeof detail.code === "number" ? detail.code : 1, signal: detail.signal ?? null, timedOut: !outputLimitExceeded && detail.killed === true, outputLimitExceeded };
|
|
1217
|
+
return execFileResultFromError(error, false);
|
|
1093
1218
|
}
|
|
1094
1219
|
};
|
|
1095
1220
|
}
|
|
@@ -1186,8 +1311,10 @@ function nativeProcessDiagnostics(operation: NativeReviewOperation, code: Native
|
|
|
1186
1311
|
}
|
|
1187
1312
|
|
|
1188
1313
|
function parseJson(stdout: string, operation: NativeReviewOperation, mutating: boolean, diagnostics: NativeReviewProcessDiagnostics): Record<string, unknown> {
|
|
1189
|
-
|
|
1190
|
-
|
|
1314
|
+
return measureStatusSync("decode", () => {
|
|
1315
|
+
if (stdout.length === 0) throw new NativeReviewCliError(NATIVE_REVIEW_ERROR_CODE.EMPTY_OUTPUT, operation, true, mutating, "native command returned empty output", { ...diagnostics, error_code: NATIVE_REVIEW_ERROR_CODE.EMPTY_OUTPUT });
|
|
1316
|
+
try { return object(JSON.parse(stdout)); } catch { throw new NativeReviewCliError(NATIVE_REVIEW_ERROR_CODE.MALFORMED_JSON, operation, true, mutating, "native command returned malformed JSON", { ...diagnostics, error_code: NATIVE_REVIEW_ERROR_CODE.MALFORMED_JSON }); }
|
|
1317
|
+
});
|
|
1191
1318
|
}
|
|
1192
1319
|
function decodeNativeMaintenanceResult(value: unknown, expectedOperation: NativeReviewOperation): NativeReviewRecoveryResult {
|
|
1193
1320
|
const body = exactObject(value, ["operation", "record"]);
|
|
@@ -1218,7 +1345,7 @@ function assertSupportedNextTransitionOperation(body: Record<string, unknown>):
|
|
|
1218
1345
|
}
|
|
1219
1346
|
}
|
|
1220
1347
|
function decode<T>(operation: NativeReviewOperation, mutating: boolean, callback: () => T, diagnostics = nativeProcessDiagnostics(operation, NATIVE_REVIEW_ERROR_CODE.SCHEMA_INCOMPATIBLE)): T {
|
|
1221
|
-
try { return callback
|
|
1348
|
+
try { return measureStatusSync("decode", callback); } catch (error) { if (error instanceof NativeReviewCliError) throw error; throw new NativeReviewCliError(NATIVE_REVIEW_ERROR_CODE.SCHEMA_INCOMPATIBLE, operation, true, mutating, "native response is schema incompatible", { ...diagnostics, error_code: NATIVE_REVIEW_ERROR_CODE.SCHEMA_INCOMPATIBLE }); }
|
|
1222
1349
|
}
|
|
1223
1350
|
function decodeReviewStartResponse(value: unknown): ReviewStartV3 | ReviewStartV4 {
|
|
1224
1351
|
const body = object(value);
|
|
@@ -1231,50 +1358,6 @@ function decodeReleaseEvidence(value: unknown): void {
|
|
|
1231
1358
|
for (const field of ["release_tree", "configuration_hash", "generated_artifact_hash", "provenance_hash", "publication_boundary_hash", "evidence_freshness_hash"]) requiredString(release[field]);
|
|
1232
1359
|
if (release.publication_state !== "sealed" || release.evidence_freshness_state !== "current") throw new Error("invalid release evidence");
|
|
1233
1360
|
}
|
|
1234
|
-
function decodeNonDecidingGateContext(value: unknown, expectedGate: string): NativeGateContext {
|
|
1235
|
-
const context = exactObject(value, ["gate"]);
|
|
1236
|
-
const gate = enumString(context.gate, NATIVE_GATE);
|
|
1237
|
-
if (gate !== expectedGate) throw new Error("native non-deciding gate context does not match the requested gate");
|
|
1238
|
-
return { lineageId: "", storeRevision: "", raw: context };
|
|
1239
|
-
}
|
|
1240
|
-
function decodeGateContext(value: unknown): NativeGateContext {
|
|
1241
|
-
const context = exactObject(
|
|
1242
|
-
value,
|
|
1243
|
-
["gate", "lineage_id", "generation", "base_tree", "candidate_tree", "paths_digest", "fix_delta_hash", "policy_hash", "ledger_hash", "evidence_hash", "base_relationship_valid"],
|
|
1244
|
-
["store_revision", "genesis_revision", "chain_identity", "bundle_digest", "external_evidence", "base_advanced_compatible", "release", "pre_pr_boundary", "denial"],
|
|
1245
|
-
);
|
|
1246
|
-
const gate = stringValue(context.gate);
|
|
1247
|
-
if (gate !== "" && !(NATIVE_GATE as readonly string[]).includes(gate)) throw new Error("invalid gate context gate");
|
|
1248
|
-
for (const field of ["lineage_id", "base_tree", "candidate_tree", "paths_digest", "fix_delta_hash", "policy_hash", "ledger_hash", "evidence_hash"]) stringValue(context[field]);
|
|
1249
|
-
for (const field of ["store_revision", "genesis_revision", "chain_identity", "bundle_digest"]) if (context[field] !== undefined) stringValue(context[field]);
|
|
1250
|
-
nonNegativeInteger(context.generation);
|
|
1251
|
-
booleanValue(context.base_relationship_valid);
|
|
1252
|
-
if (context.external_evidence !== undefined) enumString(context.external_evidence, ["invalidating", "escalating"]);
|
|
1253
|
-
let sanitizedContext = context;
|
|
1254
|
-
if (context.denial !== undefined) {
|
|
1255
|
-
const denial = exactObject(context.denial, ["stage", "code"]);
|
|
1256
|
-
const stage = sanitizeNativeDiagnosticText(requiredString(denial.stage), NATIVE_REVIEW_DENIAL_TEXT_LIMIT);
|
|
1257
|
-
const code = sanitizeNativeDiagnosticText(requiredString(denial.code), NATIVE_REVIEW_DENIAL_TEXT_LIMIT);
|
|
1258
|
-
if (!isCanonicalProcessString(stage) || !isCanonicalProcessString(code)) throw new Error("non-canonical denial evidence");
|
|
1259
|
-
sanitizedContext = { ...context, denial: { stage, code } };
|
|
1260
|
-
}
|
|
1261
|
-
if (context.pre_pr_boundary !== undefined) {
|
|
1262
|
-
const boundary = exactObject(context.pre_pr_boundary, ["source", "selector", "commit"], ["remote", "remote_ref", "remote_identity"]);
|
|
1263
|
-
enumString(boundary.source, ["explicit", "publication-default"]); requiredString(boundary.selector); stringValue(boundary.commit);
|
|
1264
|
-
for (const field of ["remote", "remote_ref", "remote_identity"]) if (boundary[field] !== undefined) requiredString(boundary[field]);
|
|
1265
|
-
}
|
|
1266
|
-
if (context.base_advanced_compatible !== undefined) {
|
|
1267
|
-
const proof = exactObject(context.base_advanced_compatible, ["status", "compatible", "old_base_tree", "new_base_tree", "original_patch_identity", "delivered_patch_identity", "delivered_paths_digest", "base_advance_paths_digest", "paths_disjoint", "merged_result_tree", "ci_attestation_artifact_hash", "ci_attestation_issuer", "ci_status"]);
|
|
1268
|
-
for (const field of ["status", "old_base_tree", "new_base_tree", "original_patch_identity", "delivered_patch_identity", "delivered_paths_digest", "base_advance_paths_digest", "merged_result_tree", "ci_attestation_artifact_hash", "ci_attestation_issuer", "ci_status"]) requiredString(proof[field]);
|
|
1269
|
-
booleanValue(proof.compatible); booleanValue(proof.paths_disjoint);
|
|
1270
|
-
}
|
|
1271
|
-
if (context.release !== undefined) decodeReleaseEvidence(context.release);
|
|
1272
|
-
return {
|
|
1273
|
-
lineageId: stringValue(context.lineage_id),
|
|
1274
|
-
storeRevision: context.store_revision === undefined ? "" : stringValue(context.store_revision),
|
|
1275
|
-
raw: sanitizedContext,
|
|
1276
|
-
};
|
|
1277
|
-
}
|
|
1278
1361
|
function decodeNativeReviewRecovery(value: unknown): NativeReviewRecovery {
|
|
1279
1362
|
const recovery = exactObject(value, ["predecessor_lineage_id", "predecessor_revision", "disposition", "reason", "actor", "recovered_at"], ["maintainer_authorization"]);
|
|
1280
1363
|
return {
|
|
@@ -1365,7 +1448,7 @@ function decodeNativeReviewStatus(value: unknown): NativeReviewStatusResult {
|
|
|
1365
1448
|
const complete = booleanValue(body.complete);
|
|
1366
1449
|
const authoritative = booleanValue(body.authoritative);
|
|
1367
1450
|
if (authoritative && !complete) throw new Error("incomplete inventory cannot be authoritative");
|
|
1368
|
-
if (!Array.isArray(body.entries) || !Array.isArray(body.locks)) throw new Error("invalid native status inventory");
|
|
1451
|
+
if (!Array.isArray(body.entries) || !Array.isArray(body.locks) || !Array.isArray(body.diagnostics)) throw new Error("invalid native status inventory");
|
|
1369
1452
|
return {
|
|
1370
1453
|
repository: requiredString(body.repository),
|
|
1371
1454
|
complete,
|
|
@@ -1537,7 +1620,10 @@ class NativeReviewPlainCli {
|
|
|
1537
1620
|
// membership in the frozen set.
|
|
1538
1621
|
private async execute(operation: NativeReviewOperation, cwd: string, arguments_: readonly string[], mutating: boolean, signal?: AbortSignal, toleratedStderr: readonly string[] = []): Promise<NativeJsonExecution> {
|
|
1539
1622
|
let result: ExecFileResult;
|
|
1540
|
-
try {
|
|
1623
|
+
try {
|
|
1624
|
+
const file = measureStatusSync("resolution", () => this.executablePath(operation, mutating));
|
|
1625
|
+
result = await measureStatusAsync("adapter", () => this.adapter({ file, arguments: arguments_, cwd, timeoutMs: mutating ? undefined : this.timeoutMs, maxBufferBytes: this.maxBufferBytes, signal }));
|
|
1626
|
+
}
|
|
1541
1627
|
catch (error) {
|
|
1542
1628
|
if (error instanceof NativeReviewCliError) throw nativeError(error.code, operation, mutating, error.message, undefined, error.launchAttempted);
|
|
1543
1629
|
if (error instanceof Error && error.name === "AbortError") throw nativeError(NATIVE_REVIEW_ERROR_CODE.CANCELLED, operation, mutating, "native process was cancelled");
|
|
@@ -1547,7 +1633,7 @@ class NativeReviewPlainCli {
|
|
|
1547
1633
|
if (result.outputLimitExceeded) throw nativeError(NATIVE_REVIEW_ERROR_CODE.OUTPUT_LIMIT, operation, mutating, "native process output exceeded limit", result, true, undefined, this.maxBufferBytes);
|
|
1548
1634
|
if (result.timedOut) throw nativeError(NATIVE_REVIEW_ERROR_CODE.TIMEOUT, operation, mutating, "native process timed out", result);
|
|
1549
1635
|
if (result.signal) throw nativeError(NATIVE_REVIEW_ERROR_CODE.SIGNAL, operation, mutating, "native process was signalled", result);
|
|
1550
|
-
const maintenancePartialFailure = [NATIVE_REVIEW_OPERATION.ABANDON, NATIVE_REVIEW_OPERATION.QUARANTINE_LEGACY, NATIVE_REVIEW_OPERATION.RECONCILE_AUTHORITY, NATIVE_REVIEW_OPERATION.REPAIR_LEGACY_ALIAS].
|
|
1636
|
+
const maintenancePartialFailure = new Set<NativeReviewOperation>([NATIVE_REVIEW_OPERATION.ABANDON, NATIVE_REVIEW_OPERATION.QUARANTINE_LEGACY, NATIVE_REVIEW_OPERATION.RECONCILE_AUTHORITY, NATIVE_REVIEW_OPERATION.REPAIR_LEGACY_ALIAS]).has(operation) && result.exitCode !== 0;
|
|
1551
1637
|
const toleratedNotice = stderrIsTolerated(result.stderr, toleratedStderr);
|
|
1552
1638
|
if (result.exitCode !== 0 && !maintenancePartialFailure) throw nativeError(NATIVE_REVIEW_ERROR_CODE.NON_ZERO, operation, mutating, "native process failed", result);
|
|
1553
1639
|
if (result.stderr.trim().length > 0 && !maintenancePartialFailure && !toleratedNotice) throw nativeError(NATIVE_REVIEW_ERROR_CODE.UNEXPECTED_STDERR, operation, mutating, "native process wrote stderr", result);
|
|
@@ -1555,6 +1641,10 @@ class NativeReviewPlainCli {
|
|
|
1555
1641
|
}
|
|
1556
1642
|
|
|
1557
1643
|
async reviewStatus(request: NativeReviewStatusRequest): Promise<NativeReviewStatusResult> {
|
|
1644
|
+
return observeNativeStatus(this.timeoutMs, () => this.observedReviewStatus(request));
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
private async observedReviewStatus(request: NativeReviewStatusRequest): Promise<NativeReviewStatusResult> {
|
|
1558
1648
|
const { body: result } = await this.execute(NATIVE_REVIEW_OPERATION.STATUS, request.cwd, ["review", "status", "--cwd", request.cwd], false, request.signal);
|
|
1559
1649
|
const status = decode(NATIVE_REVIEW_OPERATION.STATUS, false, () => decodeNativeReviewStatus(result));
|
|
1560
1650
|
if (!await repositoriesMatch(request.cwd, status.repository)) throw nativeError(NATIVE_REVIEW_ERROR_CODE.IDENTITY_MISMATCH, NATIVE_REVIEW_OPERATION.STATUS, false, "native review status repository mismatch");
|
|
@@ -1602,9 +1692,13 @@ class NativeReviewPlainCli {
|
|
|
1602
1692
|
if (request.maintainerAuthorization !== undefined && (request.maintainerAuthorization.length === 0 || /[\u0000-\u0009\u000b-\u001f\u007f]/.test(request.maintainerAuthorization))) {
|
|
1603
1693
|
throw new TypeError("Native RECOVER maintainerAuthorization must be a non-empty LF-only binding");
|
|
1604
1694
|
}
|
|
1695
|
+
if (request.baseRef !== undefined && !isCanonicalProcessString(request.baseRef)) throw new TypeError("Native RECOVER baseRef must be a non-empty, trimmed, NUL-free string");
|
|
1696
|
+
if (request.baseRef !== undefined && request.committedOnly !== true) throw new TypeError("Native RECOVER baseRef requires explicit committedOnly acknowledgement");
|
|
1697
|
+
if (request.baseRef === undefined && request.committedOnly !== undefined) throw new TypeError("Native RECOVER committedOnly requires an explicit baseRef");
|
|
1605
1698
|
if (!(NATIVE_REVIEW_RECOVER_DISPOSITION as readonly string[]).includes(request.disposition)) throw new TypeError("Native RECOVER disposition must be scope_changed, invalidated, or escalated");
|
|
1606
1699
|
const { body } = await this.execute(NATIVE_REVIEW_OPERATION.RECOVER, request.cwd, [
|
|
1607
1700
|
"review", "recover", "--cwd", request.cwd,
|
|
1701
|
+
...(request.baseRef === undefined ? [] : ["--base-ref", request.baseRef, "--committed-only"]),
|
|
1608
1702
|
"--predecessor-lineage", request.predecessorLineage,
|
|
1609
1703
|
"--expected-predecessor-revision", request.expectedPredecessorRevision,
|
|
1610
1704
|
"--successor-lineage", request.successorLineage,
|
|
@@ -1617,6 +1711,7 @@ class NativeReviewPlainCli {
|
|
|
1617
1711
|
}
|
|
1618
1712
|
|
|
1619
1713
|
async abandon(request: NativeReviewAbandonRequest): Promise<NativeReviewRecoveryResult> {
|
|
1714
|
+
if (!isNativeReviewAbandonReason(request.reason)) throw new TypeError('Native ABANDON reason must be "operator_disposition" or "retired_schema"');
|
|
1620
1715
|
for (const [name, value] of [["lineage", request.lineage], ["expectedRevision", request.expectedRevision], ["snapshotIdentity", request.snapshotIdentity], ["actor", request.actor], ["reason", request.reason]] as const) {
|
|
1621
1716
|
if (!isCanonicalProcessString(value)) throw new TypeError(`Native ABANDON ${name} must be a non-empty, trimmed, NUL-free string`);
|
|
1622
1717
|
}
|
|
@@ -1715,6 +1810,10 @@ export function nativeReviewAbandonAuthorization(request: Pick<NativeReviewAband
|
|
|
1715
1810
|
// so the native v2 gate verifies an exact eight-line binding (schema, lineage,
|
|
1716
1811
|
// revision, snapshot_identity, reason, captured_lens_results, findings_present,
|
|
1717
1812
|
// actor) — there is no evidence_records_present line to derive or relay.
|
|
1813
|
+
// capturedLensResults must arrive verbatim from the native authority
|
|
1814
|
+
// inventory projection (issue #1159): the gate recomputes this line from its
|
|
1815
|
+
// own ordered record, whose entries carry the ordinal prefix, so caller- or
|
|
1816
|
+
// facade-authored lens names cannot reproduce it.
|
|
1718
1817
|
return [
|
|
1719
1818
|
"gentle-ai.review-abandon-authorization/v2",
|
|
1720
1819
|
`lineage=${request.lineage}`,
|
|
@@ -1855,10 +1954,11 @@ function splitNativeConsentInvocation(invocation: string): readonly string[] {
|
|
|
1855
1954
|
continue;
|
|
1856
1955
|
}
|
|
1857
1956
|
// Provider invocations are not shell commands. Keep every path backslash
|
|
1858
|
-
// verbatim, including quoted UNC and drive-root paths. Outside quotes,
|
|
1859
|
-
// only
|
|
1957
|
+
// verbatim, including quoted UNC and drive-root paths. Outside quotes, a
|
|
1958
|
+
// backslash escapes only whitespace or a quote, so a space-containing token
|
|
1959
|
+
// and a POSIX-quoted apostrophe (`'it'\''s'`) split exactly.
|
|
1860
1960
|
const next = source[index + 1];
|
|
1861
|
-
if (character === "\\" && quote === undefined && next !== undefined && /\s/.test(next)) {
|
|
1961
|
+
if (character === "\\" && quote === undefined && next !== undefined && (/\s/.test(next) || next === "'" || next === '"')) {
|
|
1862
1962
|
escaping = true;
|
|
1863
1963
|
started = true;
|
|
1864
1964
|
continue;
|
|
@@ -2101,7 +2201,7 @@ export class NativeReviewCliV216 implements NativeReviewCli {
|
|
|
2101
2201
|
): Promise<NegotiatedExecution> {
|
|
2102
2202
|
let result: ExecFileResult;
|
|
2103
2203
|
try {
|
|
2104
|
-
result = await this.adapter({ file: path, arguments: arguments_, cwd, timeoutMs: mutating ? undefined : this.timeoutMs, maxBufferBytes: this.maxBufferBytes, signal });
|
|
2204
|
+
result = await measureStatusAsync("adapter", () => this.adapter({ file: path, arguments: arguments_, cwd, timeoutMs: mutating ? undefined : this.timeoutMs, maxBufferBytes: this.maxBufferBytes, signal }));
|
|
2105
2205
|
} catch (error) {
|
|
2106
2206
|
if (error instanceof Error && error.name === "AbortError") throw nativeError(NATIVE_REVIEW_ERROR_CODE.CANCELLED, operation, mutating, "native process was cancelled");
|
|
2107
2207
|
throw nativeError(NATIVE_REVIEW_ERROR_CODE.UNAVAILABLE, operation, mutating, "native process could not start");
|
|
@@ -2117,7 +2217,7 @@ export class NativeReviewCliV216 implements NativeReviewCli {
|
|
|
2117
2217
|
const body = parseJson(result.stdout, operation, mutating, diagnostics);
|
|
2118
2218
|
if (result.exitCode !== 0) {
|
|
2119
2219
|
try {
|
|
2120
|
-
throw new NativeReviewIntegrationError(decodeReviewFailureV2(body));
|
|
2220
|
+
throw new NativeReviewIntegrationError(measureStatusSync("decode", () => decodeReviewFailureV2(body)));
|
|
2121
2221
|
} catch (error) {
|
|
2122
2222
|
if (error instanceof NativeReviewIntegrationError) throw error;
|
|
2123
2223
|
throw nativeError(NATIVE_REVIEW_ERROR_CODE.NON_ZERO, operation, mutating, "native negotiated operation failed without a valid failure envelope", result);
|
|
@@ -2136,7 +2236,7 @@ export class NativeReviewCliV216 implements NativeReviewCli {
|
|
|
2136
2236
|
signal?: AbortSignal,
|
|
2137
2237
|
toleratedStderr: readonly string[] = [],
|
|
2138
2238
|
): Promise<NegotiatedExecution> {
|
|
2139
|
-
return this.invoke(operation, cwd, arguments_, mutating, signal, this.executablePath(operation, mutating), toleratedStderr);
|
|
2239
|
+
return this.invoke(operation, cwd, arguments_, mutating, signal, measureStatusSync("resolution", () => this.executablePath(operation, mutating)), toleratedStderr);
|
|
2140
2240
|
}
|
|
2141
2241
|
|
|
2142
2242
|
async start(request: NativeStartRequest): Promise<NativeStartResult> {
|
|
@@ -2149,7 +2249,7 @@ export class NativeReviewCliV216 implements NativeReviewCli {
|
|
|
2149
2249
|
// drift; Pi never rebuilds that vector from request fields.
|
|
2150
2250
|
const projection = request.projection ?? "workspace";
|
|
2151
2251
|
const selection = nativeUntrackedSelection(request);
|
|
2152
|
-
const
|
|
2252
|
+
const statusRequest: NativeTargetStatusRequest = {
|
|
2153
2253
|
cwd: request.cwd,
|
|
2154
2254
|
projection,
|
|
2155
2255
|
...(request.baseRef === undefined ? {} : { baseRef: request.baseRef, committedOnly: true }),
|
|
@@ -2158,7 +2258,19 @@ export class NativeReviewCliV216 implements NativeReviewCli {
|
|
|
2158
2258
|
...(request.intendedUntrackedSelection === undefined ? {} : { intendedUntrackedSelection: request.intendedUntrackedSelection }),
|
|
2159
2259
|
agent: "pi",
|
|
2160
2260
|
...(request.signal === undefined ? {} : { signal: request.signal }),
|
|
2161
|
-
}
|
|
2261
|
+
};
|
|
2262
|
+
const lensSelection = request.lenses === undefined && request.lensesReason === undefined
|
|
2263
|
+
? {}
|
|
2264
|
+
: { lenses: request.lenses, lensesReason: request.lensesReason };
|
|
2265
|
+
let status: ReviewStatusV3;
|
|
2266
|
+
try {
|
|
2267
|
+
status = await this.targetStatus({ ...statusRequest, ...lensSelection });
|
|
2268
|
+
} catch (error) {
|
|
2269
|
+
// Phase A: a provider that predates lens selection still reviews,
|
|
2270
|
+
// with the tier default, instead of refusing the candidate.
|
|
2271
|
+
if (Object.keys(lensSelection).length === 0 || !isNativeReviewLensSelectionUnsupported(error)) throw error;
|
|
2272
|
+
status = await this.targetStatus(statusRequest);
|
|
2273
|
+
}
|
|
2162
2274
|
const transition = status.nextTransition?.kind === "execute" && status.nextTransition.execute?.operation === "review.start"
|
|
2163
2275
|
? status.nextTransition.execute
|
|
2164
2276
|
: undefined;
|
|
@@ -2255,6 +2367,10 @@ export class NativeReviewCliV216 implements NativeReviewCli {
|
|
|
2255
2367
|
}
|
|
2256
2368
|
|
|
2257
2369
|
async targetStatus(request: NativeTargetStatusRequest): Promise<ReviewStatusV3> {
|
|
2370
|
+
return observeNativeStatus(this.timeoutMs, () => this.observedTargetStatus(request));
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
private async observedTargetStatus(request: NativeTargetStatusRequest): Promise<ReviewStatusV3> {
|
|
2258
2374
|
if (request.baseRef !== undefined && !isCanonicalProcessString(request.baseRef)) throw new TypeError("Native STATUS baseRef must be a non-empty, trimmed, NUL-free string");
|
|
2259
2375
|
if (request.baseRef !== undefined && request.committedOnly !== true) throw new TypeError("Native STATUS baseRef requires explicit committedOnly acknowledgement");
|
|
2260
2376
|
if (request.baseRef === undefined && request.committedOnly !== undefined) throw new TypeError("Native STATUS committedOnly requires an explicit baseRef");
|
|
@@ -2293,10 +2409,11 @@ export class NativeReviewCliV216 implements NativeReviewCli {
|
|
|
2293
2409
|
...(request.baseRef === undefined ? [] : ["--base-ref", request.baseRef, "--committed-only"]),
|
|
2294
2410
|
...(request.lineageId === undefined ? [] : ["--lineage", request.lineageId]),
|
|
2295
2411
|
...(request.agent === undefined ? [] : ["--agent", request.agent]),
|
|
2412
|
+
...nativeLensSelectionArguments(request),
|
|
2296
2413
|
"--next-transition",
|
|
2297
|
-
] : ["review", "status", "--cwd", request.cwd, ...submittedTokens, ...forwardedBaseRef, ...forwardedLineage];
|
|
2414
|
+
] : ["review", "status", "--cwd", request.cwd, ...submittedTokens, ...forwardedBaseRef, ...forwardedLineage, ...nativeLensSelectionArguments(request)];
|
|
2298
2415
|
const execution = await this.negotiated(NATIVE_REVIEW_OPERATION.STATUS, request.cwd, statusArguments, false, request.signal);
|
|
2299
|
-
assertSupportedNextTransitionOperation(execution.body);
|
|
2416
|
+
measureStatusSync("decode", () => assertSupportedNextTransitionOperation(execution.body));
|
|
2300
2417
|
return decode(NATIVE_REVIEW_OPERATION.STATUS, false, () => decodeReviewStatusV3(execution.body));
|
|
2301
2418
|
}
|
|
2302
2419
|
|
|
@@ -2485,13 +2602,17 @@ export class NativeReviewCliV216 implements NativeReviewCli {
|
|
|
2485
2602
|
// mutually exclusive with a path.
|
|
2486
2603
|
|
|
2487
2604
|
async reviewStatus(request: NativeReviewStatusRequest): Promise<NativeReviewStatusResult> {
|
|
2605
|
+
return observeNativeStatus(this.timeoutMs, () => this.observedReviewStatus(request));
|
|
2606
|
+
}
|
|
2607
|
+
|
|
2608
|
+
private async observedReviewStatus(request: NativeReviewStatusRequest): Promise<NativeReviewStatusResult> {
|
|
2488
2609
|
const execution = await this.invoke(
|
|
2489
2610
|
NATIVE_REVIEW_OPERATION.STATUS,
|
|
2490
2611
|
request.cwd,
|
|
2491
2612
|
["review", "status", "--cwd", request.cwd],
|
|
2492
2613
|
false,
|
|
2493
2614
|
request.signal,
|
|
2494
|
-
this.executablePath(NATIVE_REVIEW_OPERATION.STATUS, false),
|
|
2615
|
+
measureStatusSync("resolution", () => this.executablePath(NATIVE_REVIEW_OPERATION.STATUS, false)),
|
|
2495
2616
|
);
|
|
2496
2617
|
const status = decode(NATIVE_REVIEW_OPERATION.STATUS, false, () => decodeNativeReviewStatus(execution.body));
|
|
2497
2618
|
if (!await repositoriesMatch(request.cwd, status.repository)) throw nativeError(NATIVE_REVIEW_ERROR_CODE.IDENTITY_MISMATCH, NATIVE_REVIEW_OPERATION.STATUS, false, "native review status repository mismatch");
|