patchwarden 1.5.1 → 1.6.2
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/PatchWarden.cmd +32 -2
- package/README.en.md +158 -26
- package/README.md +124 -24
- package/dist/assessments/agentAssessor.js +21 -12
- package/dist/assessments/assessmentDiagnostics.d.ts +29 -0
- package/dist/assessments/assessmentDiagnostics.js +132 -0
- package/dist/assessments/assessmentStore.d.ts +39 -0
- package/dist/assessments/assessmentStore.js +207 -45
- package/dist/assessments/securitySnapshot.d.ts +50 -0
- package/dist/assessments/securitySnapshot.js +158 -0
- package/dist/config.d.ts +15 -0
- package/dist/config.js +66 -13
- package/dist/control/fileManager.d.ts +2 -0
- package/dist/control/fileManager.js +39 -0
- package/dist/control/middleware/auth.d.ts +6 -0
- package/dist/control/middleware/auth.js +14 -5
- package/dist/control/middleware/static.js +16 -3
- package/dist/control/routeTable.d.ts +24 -0
- package/dist/control/routeTable.js +263 -0
- package/dist/control/routes/audit.js +17 -7
- package/dist/control/routes/evidence.js +1 -1
- package/dist/control/routes/lineage.js +1 -1
- package/dist/control/routes/process.d.ts +7 -0
- package/dist/control/routes/process.js +329 -42
- package/dist/control/routes/sessions.d.ts +9 -1
- package/dist/control/routes/sessions.js +84 -21
- package/dist/control/routes/status.d.ts +60 -1
- package/dist/control/routes/status.js +94 -15
- package/dist/control/routes/taskActions.js +24 -60
- package/dist/control/routes/tasks.d.ts +4 -0
- package/dist/control/routes/tasks.js +61 -62
- package/dist/control/routes/workspace.js +50 -23
- package/dist/control/runtime.d.ts +3 -2
- package/dist/control/runtime.js +65 -40
- package/dist/control/server.js +37 -365
- package/dist/control/shared.d.ts +25 -1
- package/dist/control/shared.js +64 -11
- package/dist/diagnostics/allowedTestCommandSafety.d.ts +1 -0
- package/dist/diagnostics/allowedTestCommandSafety.js +11 -0
- package/dist/direct/directAudit.js +234 -225
- package/dist/direct/directGuards.d.ts +2 -1
- package/dist/direct/directGuards.js +42 -29
- package/dist/direct/directPatch.d.ts +6 -1
- package/dist/direct/directPatch.js +55 -8
- package/dist/direct/directSessionStore.d.ts +16 -6
- package/dist/direct/directSessionStore.js +128 -35
- package/dist/direct/directVerification.js +8 -16
- package/dist/doctor.js +65 -57
- package/dist/goal/acceptanceEngine.d.ts +1 -1
- package/dist/goal/goalProgress.js +75 -69
- package/dist/goal/goalReport.js +5 -12
- package/dist/goal/goalStatus.d.ts +2 -0
- package/dist/goal/goalStatus.js +10 -3
- package/dist/goal/goalStore.d.ts +9 -2
- package/dist/goal/goalStore.js +152 -45
- package/dist/goal/handoffExport.js +6 -6
- package/dist/goal/specKitImport.js +97 -96
- package/dist/goal/subgoalSync.js +11 -9
- package/dist/goal/worktreeManager.d.ts +2 -0
- package/dist/goal/worktreeManager.js +209 -136
- package/dist/httpServer.js +111 -32
- package/dist/logging.js +5 -3
- package/dist/policy/projectPolicy.js +5 -2
- package/dist/release/releaseGate.d.ts +1 -1
- package/dist/release/releaseGate.js +78 -25
- package/dist/runner/agentInvocation.d.ts +14 -0
- package/dist/runner/agentInvocation.js +96 -4
- package/dist/runner/changeCapture.d.ts +13 -5
- package/dist/runner/changeCapture.js +243 -61
- package/dist/runner/postTaskCleanup.js +48 -19
- package/dist/runner/processSecurity.d.ts +56 -0
- package/dist/runner/processSecurity.js +373 -0
- package/dist/runner/runTask.d.ts +2 -1
- package/dist/runner/runTask.js +162 -93
- package/dist/runner/simpleProcess.d.ts +3 -0
- package/dist/runner/simpleProcess.js +50 -36
- package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
- package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
- package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +3 -1
- package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
- package/dist/runner/taskStatusStore.d.ts +11 -0
- package/dist/runner/taskStatusStore.js +35 -0
- package/dist/runner/watch.d.ts +14 -1
- package/dist/runner/watch.js +320 -49
- package/dist/security/contentRedaction.js +2 -2
- package/dist/security/discoveryTokenStore.d.ts +3 -2
- package/dist/security/discoveryTokenStore.js +17 -2
- package/dist/security/loopbackHost.d.ts +2 -0
- package/dist/security/loopbackHost.js +16 -0
- package/dist/security/pathGuard.js +32 -17
- package/dist/security/planGuard.js +2 -1
- package/dist/security/runtimeGuard.js +9 -6
- package/dist/security/secretComparison.d.ts +3 -0
- package/dist/security/secretComparison.js +11 -0
- package/dist/security/sensitiveGuard.d.ts +5 -0
- package/dist/security/sensitiveGuard.js +27 -8
- package/dist/security/toolInvocationGuard.d.ts +2 -2
- package/dist/security/toolInvocationGuard.js +1 -1
- package/dist/security/workspaceRootGuard.d.ts +8 -0
- package/dist/security/workspaceRootGuard.js +47 -0
- package/dist/smoke-test.js +27 -26
- package/dist/tools/catalog/index.d.ts +4 -0
- package/dist/tools/catalog/index.js +4 -0
- package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
- package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
- package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +6 -15
- package/dist/tools/definitions/toolDefs.d.ts +18 -0
- package/dist/tools/definitions/toolDefs.js +1177 -0
- package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
- package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
- package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +2 -1
- package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +134 -36
- package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +18 -6
- package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +36 -8
- package/dist/tools/diagnostics/index.d.ts +6 -0
- package/dist/tools/diagnostics/index.js +6 -0
- package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
- package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
- package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +12 -8
- package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +23 -13
- package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
- package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
- package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +6 -6
- package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +1 -1
- package/dist/tools/direct/finalizeDirectSession.js +86 -0
- package/dist/tools/direct/index.d.ts +3 -0
- package/dist/tools/direct/index.js +3 -0
- package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
- package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
- package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
- package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
- package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
- package/dist/tools/discovery/index.d.ts +3 -0
- package/dist/tools/discovery/index.js +3 -0
- package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
- package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
- package/dist/tools/dispatch/coreDispatch.js +32 -31
- package/dist/tools/dispatch/diagnosticDispatch.d.ts +3 -5
- package/dist/tools/dispatch/diagnosticDispatch.js +70 -68
- package/dist/tools/dispatch/directDispatch.js +11 -10
- package/dist/tools/dispatch/goalDispatch.js +5 -4
- package/dist/tools/dispatch/releaseDispatch.js +3 -2
- package/dist/tools/dispatch/validation.d.ts +7 -0
- package/dist/tools/dispatch/validation.js +71 -0
- package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
- package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +1 -1
- package/dist/tools/goals/goalSubgoalTask.js +118 -0
- package/dist/tools/goals/index.d.ts +3 -0
- package/dist/tools/goals/index.js +3 -0
- package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
- package/dist/tools/registry.d.ts +7 -11
- package/dist/tools/registry.js +17 -1178
- package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
- package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
- package/dist/tools/release/index.d.ts +2 -0
- package/dist/tools/release/index.js +2 -0
- package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
- package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
- package/dist/tools/tasks/cancelTask.d.ts +11 -0
- package/dist/tools/tasks/cancelTask.js +86 -0
- package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +12 -4
- package/dist/tools/{createTask.js → tasks/createTask.js} +110 -34
- package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
- package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +12 -16
- package/dist/tools/{evidencePack.js → tasks/evidencePack.js} +34 -22
- package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
- package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
- package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +11 -6
- package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
- package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +35 -36
- package/dist/tools/tasks/index.d.ts +18 -0
- package/dist/tools/tasks/index.js +18 -0
- package/dist/tools/tasks/killTask.d.ts +1 -0
- package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
- package/dist/tools/{listTasks.js → tasks/listTasks.js} +5 -4
- package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +3 -2
- package/dist/tools/tasks/reconcileTasks.js +496 -0
- package/dist/tools/tasks/retryTask.js +60 -0
- package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
- package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +63 -41
- package/dist/tools/tasks/runVerification.js +38 -0
- package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
- package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +2 -2
- package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
- package/dist/tools/tasks/taskStates.d.ts +6 -0
- package/dist/tools/tasks/taskStates.js +52 -0
- package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +3 -11
- package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
- package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
- package/dist/tools/workspace/applyPatch.js +53 -0
- package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
- package/dist/tools/workspace/index.d.ts +10 -0
- package/dist/tools/workspace/index.js +10 -0
- package/dist/tools/workspace/listAgents.d.ts +23 -0
- package/dist/tools/workspace/listAgents.js +56 -0
- package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
- package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
- package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
- package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
- package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
- package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
- package/dist/tools/workspace/syncFile.js +134 -0
- package/dist/utils/atomicFile.d.ts +7 -0
- package/dist/utils/atomicFile.js +67 -0
- package/dist/utils/boundedFile.d.ts +8 -0
- package/dist/utils/boundedFile.js +93 -0
- package/dist/utils/lockedJsonFile.d.ts +15 -0
- package/dist/utils/lockedJsonFile.js +224 -0
- package/dist/utils/platform.d.ts +11 -0
- package/dist/utils/platform.js +26 -0
- package/dist/utils/stableJson.d.ts +2 -0
- package/dist/utils/stableJson.js +12 -0
- package/dist/version.d.ts +3 -2
- package/dist/version.js +3 -2
- package/dist/watcherStatus.d.ts +2 -2
- package/dist/watcherStatus.js +18 -0
- package/docs/CODE_WIKI.md +808 -143
- package/docs/architecture.md +15 -55
- package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
- package/docs/control-center/README.md +4 -4
- package/docs/dashboard-overview.md +4 -4
- package/docs/desktop-app.md +175 -0
- package/docs/evidence-pack-schema.md +3 -3
- package/docs/open-source-application.md +16 -11
- package/docs/release-checklist.md +1 -1
- package/docs/release-evidence.md +35 -39
- package/docs/task-safe-review-workflow.md +5 -5
- package/docs/threat-model.md +19 -1
- package/examples/config.example.json +4 -2
- package/package.json +27 -10
- package/scripts/README.md +15 -6
- package/scripts/build.js +11 -0
- package/scripts/checks/brand-check.js +3 -0
- package/scripts/checks/build-output-check.js +37 -0
- package/scripts/checks/control-center-smoke.js +102 -10
- package/scripts/checks/control-smoke.js +19 -8
- package/scripts/checks/http-mcp-smoke.js +82 -4
- package/scripts/checks/lifecycle-smoke.js +15 -12
- package/scripts/checks/mcp-manifest-check.js +1 -1
- package/scripts/checks/mcp-smoke.js +58 -4
- package/scripts/checks/package-manifest-check.js +20 -7
- package/scripts/checks/unit-tests.js +3 -5
- package/scripts/checks/watcher-supervisor-smoke.js +38 -1
- package/scripts/control/get-patchwarden-health.ps1 +1 -1
- package/scripts/control/manage-patchwarden.ps1 +69 -4
- package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
- package/scripts/control/run-background-supervisor.ps1 +41 -0
- package/scripts/control/start-patchwarden-tunnel.ps1 +191 -33
- package/scripts/e2e/demo-runtime.mjs +153 -0
- package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
- package/scripts/launchers/PatchWarden-Control.cmd +6 -0
- package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
- package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
- package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
- package/scripts/lib/clean-generated-output.js +11 -0
- package/scripts/lib/file-discovery.js +12 -0
- package/scripts/release/desktop-preflight.js +211 -0
- package/scripts/release/pack-clean.js +59 -14
- package/src/assessments/agentAssessor.ts +26 -12
- package/src/assessments/assessmentDiagnostics.ts +172 -0
- package/src/assessments/assessmentStore.ts +288 -51
- package/src/assessments/securitySnapshot.ts +231 -0
- package/src/config.ts +70 -13
- package/src/control/fileManager.ts +40 -0
- package/src/control/middleware/auth.ts +22 -4
- package/src/control/middleware/static.ts +16 -3
- package/src/control/routeTable.ts +301 -0
- package/src/control/routes/audit.ts +14 -6
- package/src/control/routes/evidence.ts +1 -1
- package/src/control/routes/lineage.ts +1 -1
- package/src/control/routes/process.ts +341 -40
- package/src/control/routes/sessions.ts +88 -25
- package/src/control/routes/status.ts +134 -17
- package/src/control/routes/taskActions.ts +24 -58
- package/src/control/routes/tasks.ts +66 -58
- package/src/control/routes/workspace.ts +56 -25
- package/src/control/runtime.ts +71 -43
- package/src/control/server.ts +37 -342
- package/src/control/shared.ts +77 -11
- package/src/diagnostics/allowedTestCommandSafety.ts +12 -0
- package/src/direct/directAudit.ts +17 -9
- package/src/direct/directGuards.ts +72 -44
- package/src/direct/directPatch.ts +95 -8
- package/src/direct/directSessionStore.ts +213 -55
- package/src/direct/directVerification.ts +9 -23
- package/src/doctor.ts +76 -64
- package/src/goal/acceptanceEngine.ts +1 -1
- package/src/goal/goalProgress.ts +17 -13
- package/src/goal/goalReport.ts +5 -13
- package/src/goal/goalStatus.ts +19 -3
- package/src/goal/goalStore.ts +241 -54
- package/src/goal/handoffExport.ts +6 -6
- package/src/goal/specKitImport.ts +12 -12
- package/src/goal/subgoalSync.ts +11 -8
- package/src/goal/worktreeManager.ts +139 -22
- package/src/httpServer.ts +113 -30
- package/src/logging.ts +10 -3
- package/src/policy/projectPolicy.ts +5 -2
- package/src/release/releaseGate.ts +74 -27
- package/src/runner/agentInvocation.ts +126 -4
- package/src/runner/changeCapture.ts +291 -57
- package/src/runner/postTaskCleanup.ts +41 -17
- package/src/runner/processSecurity.ts +433 -0
- package/src/runner/runTask.ts +197 -97
- package/src/runner/simpleProcess.ts +66 -31
- package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
- package/src/{taskRuntime.ts → runner/taskRuntime.ts} +6 -3
- package/src/runner/taskStatusStore.ts +69 -0
- package/src/runner/watch.ts +348 -52
- package/src/security/contentRedaction.ts +2 -2
- package/src/security/discoveryTokenStore.ts +18 -4
- package/src/security/loopbackHost.ts +19 -0
- package/src/security/pathGuard.ts +35 -16
- package/src/security/planGuard.ts +2 -1
- package/src/security/runtimeGuard.ts +9 -6
- package/src/security/secretComparison.ts +13 -0
- package/src/security/sensitiveGuard.ts +27 -8
- package/src/security/toolInvocationGuard.ts +2 -2
- package/src/security/workspaceRootGuard.ts +53 -0
- package/src/smoke-test.ts +27 -26
- package/src/tools/catalog/index.ts +4 -0
- package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
- package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +7 -16
- package/src/tools/definitions/toolDefs.ts +1264 -0
- package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
- package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +136 -39
- package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +37 -9
- package/src/tools/diagnostics/index.ts +6 -0
- package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
- package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +27 -17
- package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
- package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +6 -6
- package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +7 -5
- package/src/tools/direct/index.ts +3 -0
- package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
- package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
- package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
- package/src/tools/discovery/index.ts +3 -0
- package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
- package/src/tools/dispatch/coreDispatch.ts +36 -31
- package/src/tools/dispatch/diagnosticDispatch.ts +26 -21
- package/src/tools/dispatch/directDispatch.ts +11 -10
- package/src/tools/dispatch/goalDispatch.ts +5 -4
- package/src/tools/dispatch/releaseDispatch.ts +3 -2
- package/src/tools/dispatch/validation.ts +78 -0
- package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
- package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +41 -26
- package/src/tools/goals/index.ts +3 -0
- package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
- package/src/tools/registry.ts +17 -1261
- package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
- package/src/tools/release/index.ts +2 -0
- package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
- package/src/tools/tasks/cancelTask.ts +110 -0
- package/src/tools/{createTask.ts → tasks/createTask.ts} +161 -33
- package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +12 -16
- package/src/tools/{evidencePack.ts → tasks/evidencePack.ts} +36 -22
- package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
- package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +13 -6
- package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
- package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +46 -44
- package/src/tools/tasks/index.ts +18 -0
- package/src/tools/{listTasks.ts → tasks/listTasks.ts} +6 -5
- package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +228 -77
- package/src/tools/tasks/retryTask.ts +69 -0
- package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +73 -46
- package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
- package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
- package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
- package/src/tools/tasks/taskStates.ts +54 -0
- package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +3 -12
- package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
- package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
- package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
- package/src/tools/workspace/index.ts +10 -0
- package/src/tools/workspace/listAgents.ts +75 -0
- package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
- package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
- package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
- package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
- package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
- package/src/tools/workspace/syncFile.ts +292 -0
- package/src/utils/atomicFile.ts +74 -0
- package/src/utils/boundedFile.ts +116 -0
- package/src/utils/lockedJsonFile.ts +251 -0
- package/src/utils/platform.ts +31 -0
- package/src/utils/stableJson.ts +12 -0
- package/src/version.ts +3 -2
- package/src/watcherStatus.ts +19 -1
- package/ui/desktop-bootstrap.js +11 -0
- package/ui/desktop-bridge.js +36 -0
- package/ui/desktop.css +160 -0
- package/ui/getting-started.js +93 -0
- package/ui/i18n.js +484 -0
- package/ui/log-parser.js +30 -0
- package/ui/pages/audit.html +12 -0
- package/ui/pages/dashboard.html +94 -147
- package/ui/pages/direct-sessions.html +123 -26
- package/ui/pages/getting-started.html +43 -0
- package/ui/pages/logs.html +134 -22
- package/ui/pages/settings.html +101 -0
- package/ui/pages/task-detail.html +12 -0
- package/ui/pages/tasks.html +54 -8
- package/ui/pages/workspace.html +38 -1
- package/ui/settings.js +254 -0
- package/PatchWarden-Control.cmd +0 -6
- package/Restart-PatchWarden-Control.cmd +0 -6
- package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
- package/dist/test/unit/acceptance-engine.test.js +0 -228
- package/dist/test/unit/agent-router.test.d.ts +0 -1
- package/dist/test/unit/agent-router.test.js +0 -287
- package/dist/test/unit/android-doctor.test.d.ts +0 -1
- package/dist/test/unit/android-doctor.test.js +0 -118
- package/dist/test/unit/apply-patch.test.d.ts +0 -1
- package/dist/test/unit/apply-patch.test.js +0 -225
- package/dist/test/unit/audit-checks.test.d.ts +0 -1
- package/dist/test/unit/audit-checks.test.js +0 -350
- package/dist/test/unit/chinese-path.test.d.ts +0 -1
- package/dist/test/unit/chinese-path.test.js +0 -91
- package/dist/test/unit/command-guard.test.d.ts +0 -1
- package/dist/test/unit/command-guard.test.js +0 -160
- package/dist/test/unit/create-task.test.d.ts +0 -1
- package/dist/test/unit/create-task.test.js +0 -197
- package/dist/test/unit/diagnose-task.test.d.ts +0 -1
- package/dist/test/unit/diagnose-task.test.js +0 -457
- package/dist/test/unit/direct-guards.test.d.ts +0 -1
- package/dist/test/unit/direct-guards.test.js +0 -328
- package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
- package/dist/test/unit/discovery-token-store.test.js +0 -139
- package/dist/test/unit/evidence-pack.test.d.ts +0 -1
- package/dist/test/unit/evidence-pack.test.js +0 -224
- package/dist/test/unit/get-task-status.test.d.ts +0 -1
- package/dist/test/unit/get-task-status.test.js +0 -174
- package/dist/test/unit/get-task-summary.test.d.ts +0 -1
- package/dist/test/unit/get-task-summary.test.js +0 -146
- package/dist/test/unit/goal-graph.test.d.ts +0 -1
- package/dist/test/unit/goal-graph.test.js +0 -298
- package/dist/test/unit/goal-progress.test.d.ts +0 -1
- package/dist/test/unit/goal-progress.test.js +0 -381
- package/dist/test/unit/goal-report.test.d.ts +0 -1
- package/dist/test/unit/goal-report.test.js +0 -159
- package/dist/test/unit/goal-status.test.d.ts +0 -1
- package/dist/test/unit/goal-status.test.js +0 -215
- package/dist/test/unit/goal-store.test.d.ts +0 -1
- package/dist/test/unit/goal-store.test.js +0 -253
- package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
- package/dist/test/unit/goal-subgoal-task.test.js +0 -55
- package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
- package/dist/test/unit/goal-tools-registry.test.js +0 -192
- package/dist/test/unit/handoff-export.test.d.ts +0 -1
- package/dist/test/unit/handoff-export.test.js +0 -263
- package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
- package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
- package/dist/test/unit/logging.test.d.ts +0 -1
- package/dist/test/unit/logging.test.js +0 -397
- package/dist/test/unit/path-guard.test.d.ts +0 -1
- package/dist/test/unit/path-guard.test.js +0 -133
- package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
- package/dist/test/unit/post-task-cleanup.test.js +0 -48
- package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
- package/dist/test/unit/project-policy-release-mode.test.js +0 -125
- package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
- package/dist/test/unit/reconcile-tasks.test.js +0 -456
- package/dist/test/unit/release-gate.test.d.ts +0 -1
- package/dist/test/unit/release-gate.test.js +0 -242
- package/dist/test/unit/run-task-loop.test.d.ts +0 -1
- package/dist/test/unit/run-task-loop.test.js +0 -380
- package/dist/test/unit/safe-status.test.d.ts +0 -1
- package/dist/test/unit/safe-status.test.js +0 -165
- package/dist/test/unit/safe-views.test.d.ts +0 -1
- package/dist/test/unit/safe-views.test.js +0 -171
- package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
- package/dist/test/unit/schema-drift-check.test.js +0 -176
- package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
- package/dist/test/unit/sensitive-guard.test.js +0 -104
- package/dist/test/unit/spec-kit-import.test.d.ts +0 -1
- package/dist/test/unit/spec-kit-import.test.js +0 -341
- package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
- package/dist/test/unit/subgoal-sync.test.js +0 -183
- package/dist/test/unit/sync-file.test.d.ts +0 -1
- package/dist/test/unit/sync-file.test.js +0 -154
- package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
- package/dist/test/unit/tool-invocation-guard.test.js +0 -432
- package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
- package/dist/test/unit/tool-usage-stats.test.js +0 -300
- package/dist/test/unit/toolSearch.test.d.ts +0 -1
- package/dist/test/unit/toolSearch.test.js +0 -571
- package/dist/test/unit/wait-for-task.test.d.ts +0 -1
- package/dist/test/unit/wait-for-task.test.js +0 -144
- package/dist/test/unit/watcher-status.test.d.ts +0 -1
- package/dist/test/unit/watcher-status.test.js +0 -169
- package/dist/test/unit/worktree-manager.test.d.ts +0 -1
- package/dist/test/unit/worktree-manager.test.js +0 -176
- package/dist/tools/applyPatch.js +0 -41
- package/dist/tools/cancelTask.d.ts +0 -30
- package/dist/tools/cancelTask.js +0 -64
- package/dist/tools/finalizeDirectSession.js +0 -84
- package/dist/tools/goalSubgoalTask.js +0 -110
- package/dist/tools/killTask.d.ts +0 -15
- package/dist/tools/listAgents.d.ts +0 -12
- package/dist/tools/listAgents.js +0 -42
- package/dist/tools/reconcileTasks.js +0 -352
- package/dist/tools/retryTask.js +0 -45
- package/dist/tools/runVerification.js +0 -36
- package/dist/tools/syncFile.js +0 -65
- package/docs/control-center/control-center-daily-driver.md +0 -211
- package/docs/control-center/control-center-mvp.md +0 -205
- package/docs/control-center/control-center-phase2.md +0 -159
- package/docs/execution-plan-2026-07-09.md +0 -315
- package/docs/release-v0.6.0.md +0 -71
- package/docs/release-v0.6.1.md +0 -75
- package/docs/release-v0.6.4.md +0 -45
- package/docs/roadmap-execution-and-acceptance.md +0 -365
- package/src/test/unit/acceptance-engine.test.ts +0 -261
- package/src/test/unit/agent-router.test.ts +0 -342
- package/src/test/unit/android-doctor.test.ts +0 -158
- package/src/test/unit/apply-patch.test.ts +0 -293
- package/src/test/unit/audit-checks.test.ts +0 -567
- package/src/test/unit/chinese-path.test.ts +0 -106
- package/src/test/unit/command-guard.test.ts +0 -221
- package/src/test/unit/create-task.test.ts +0 -255
- package/src/test/unit/diagnose-task.test.ts +0 -544
- package/src/test/unit/direct-guards.test.ts +0 -467
- package/src/test/unit/discovery-token-store.test.ts +0 -181
- package/src/test/unit/evidence-pack.test.ts +0 -251
- package/src/test/unit/get-task-status.test.ts +0 -203
- package/src/test/unit/get-task-summary.test.ts +0 -173
- package/src/test/unit/goal-graph.test.ts +0 -347
- package/src/test/unit/goal-progress.test.ts +0 -538
- package/src/test/unit/goal-report.test.ts +0 -189
- package/src/test/unit/goal-status.test.ts +0 -270
- package/src/test/unit/goal-store.test.ts +0 -318
- package/src/test/unit/goal-subgoal-task.test.ts +0 -72
- package/src/test/unit/goal-tools-registry.test.ts +0 -245
- package/src/test/unit/handoff-export.test.ts +0 -295
- package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
- package/src/test/unit/logging.test.ts +0 -497
- package/src/test/unit/path-guard.test.ts +0 -180
- package/src/test/unit/post-task-cleanup.test.ts +0 -53
- package/src/test/unit/project-policy-release-mode.test.ts +0 -156
- package/src/test/unit/reconcile-tasks.test.ts +0 -551
- package/src/test/unit/release-gate.test.ts +0 -314
- package/src/test/unit/run-task-loop.test.ts +0 -425
- package/src/test/unit/safe-status.test.ts +0 -187
- package/src/test/unit/safe-views.test.ts +0 -184
- package/src/test/unit/schema-drift-check.test.ts +0 -260
- package/src/test/unit/sensitive-guard.test.ts +0 -124
- package/src/test/unit/spec-kit-import.test.ts +0 -429
- package/src/test/unit/subgoal-sync.test.ts +0 -236
- package/src/test/unit/sync-file.test.ts +0 -231
- package/src/test/unit/tool-invocation-guard.test.ts +0 -542
- package/src/test/unit/tool-usage-stats.test.ts +0 -384
- package/src/test/unit/toolSearch.test.ts +0 -756
- package/src/test/unit/wait-for-task.test.ts +0 -176
- package/src/test/unit/watcher-status.test.ts +0 -190
- package/src/test/unit/worktree-manager.test.ts +0 -247
- package/src/tools/cancelTask.ts +0 -70
- package/src/tools/listAgents.ts +0 -54
- package/src/tools/retryTask.ts +0 -53
- package/src/tools/syncFile.ts +0 -122
- /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
- /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
- /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
- /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
- /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
- /package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +0 -0
- /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
- /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
- /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
- /package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +0 -0
- /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
- /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
- /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
- /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
- /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
- /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
- /package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +0 -0
- /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
- /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
- /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
- /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
- /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
- /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
- /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
- /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
- /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
- /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
- /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
- /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
- /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
- /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import { mkdirSync,
|
|
1
|
+
import { mkdirSync, existsSync, readFileSync, } from "node:fs";
|
|
2
2
|
import { createHash, randomBytes } from "node:crypto";
|
|
3
3
|
import { resolve, join } from "node:path";
|
|
4
4
|
import { getAssessmentsDir, getConfig, getRepoAllowedTestCommands } from "../config.js";
|
|
5
5
|
import { guardPath } from "../security/pathGuard.js";
|
|
6
6
|
import { PatchWardenError } from "../errors.js";
|
|
7
|
+
import { redactSensitiveContent } from "../security/contentRedaction.js";
|
|
8
|
+
import { atomicWriteJsonFileSync } from "../utils/atomicFile.js";
|
|
9
|
+
import { withFileLockSync } from "../utils/lockedJsonFile.js";
|
|
7
10
|
import { TOOL_SCHEMA_EPOCH } from "../version.js";
|
|
8
|
-
import {
|
|
11
|
+
import { buildToolCatalogSnapshot, resolveToolProfile, } from "../tools/catalog/toolCatalog.js";
|
|
12
|
+
import { getToolDefs } from "../tools/definitions/toolDefs.js";
|
|
9
13
|
import { captureRepoSnapshot } from "../runner/changeCapture.js";
|
|
14
|
+
import { getProjectPolicySummary } from "../policy/projectPolicy.js";
|
|
15
|
+
import { ASSESSMENT_SECURITY_SNAPSHOT_VERSION, buildAssessmentSecuritySnapshot, getAssessmentSecuritySnapshotComponentHashes, hashAssessmentSecuritySnapshot, } from "./securitySnapshot.js";
|
|
10
16
|
const SENSITIVE_PATH_RULES_SIGNATURE = "v1";
|
|
11
17
|
const ARTIFACT_RULES_SIGNATURE = "v1";
|
|
12
18
|
export function generateAssessmentId() {
|
|
@@ -32,7 +38,7 @@ export function createAssessment(input) {
|
|
|
32
38
|
const planHash = input.plan_content
|
|
33
39
|
? createHash("sha256").update(input.plan_content).digest("hex")
|
|
34
40
|
: null;
|
|
35
|
-
const toolManifest =
|
|
41
|
+
const toolManifest = getCurrentToolManifest(config);
|
|
36
42
|
const workspaceFingerprint = computeWorkspaceFingerprint(input.snapshot);
|
|
37
43
|
const snapshotTruncated = input.snapshot.warnings.some((w) => w.includes("snapshot limited"));
|
|
38
44
|
const policyHash = computePolicyHash({
|
|
@@ -45,6 +51,21 @@ export function createAssessment(input) {
|
|
|
45
51
|
artifact_rules: ARTIFACT_RULES_SIGNATURE,
|
|
46
52
|
schema_epoch: TOOL_SCHEMA_EPOCH,
|
|
47
53
|
});
|
|
54
|
+
const executionConfig = computeExecutionConfigFingerprint(config, {
|
|
55
|
+
agent: input.agent,
|
|
56
|
+
tool_manifest_sha256: toolManifest,
|
|
57
|
+
tool_profile: resolveToolProfile(config.toolProfile),
|
|
58
|
+
repo_path: input.resolved_repo_path,
|
|
59
|
+
change_policy: input.change_policy || "repo_scoped_changes",
|
|
60
|
+
template: input.template || null,
|
|
61
|
+
verify_commands: input.verify_commands || [],
|
|
62
|
+
test_command: input.test_command || null,
|
|
63
|
+
task_timeout_seconds: input.timeout_seconds,
|
|
64
|
+
scope: input.scope,
|
|
65
|
+
forbidden: input.forbidden,
|
|
66
|
+
verification: input.verification,
|
|
67
|
+
done_evidence: input.done_evidence,
|
|
68
|
+
});
|
|
48
69
|
const now = new Date();
|
|
49
70
|
const expiresAt = new Date(now.getTime() + config.assessmentTtlSeconds * 1000).toISOString();
|
|
50
71
|
const record = {
|
|
@@ -59,6 +80,12 @@ export function createAssessment(input) {
|
|
|
59
80
|
plan_id: input.plan_id,
|
|
60
81
|
policy_hash: policyHash,
|
|
61
82
|
tool_manifest_sha256: toolManifest,
|
|
83
|
+
execution_config_hash: executionConfig.hash,
|
|
84
|
+
execution_config_component_hashes: executionConfig.components,
|
|
85
|
+
assessment_security_snapshot_version: ASSESSMENT_SECURITY_SNAPSHOT_VERSION,
|
|
86
|
+
assessment_security_snapshot_sha256: executionConfig.hash,
|
|
87
|
+
assessment_security_snapshot_component_hashes: executionConfig.components,
|
|
88
|
+
assessment_schema_epoch: TOOL_SCHEMA_EPOCH,
|
|
62
89
|
workspace_fingerprint: workspaceFingerprint,
|
|
63
90
|
workspace_snapshot_summary: {
|
|
64
91
|
head: input.snapshot.head,
|
|
@@ -71,22 +98,30 @@ export function createAssessment(input) {
|
|
|
71
98
|
repo_path: input.repo_path,
|
|
72
99
|
resolved_repo_path: input.resolved_repo_path,
|
|
73
100
|
template: input.template || null,
|
|
74
|
-
goal: input.goal
|
|
101
|
+
goal: input.goal ? redactSensitiveContent(input.goal).content : null,
|
|
75
102
|
test_command: input.test_command || null,
|
|
76
103
|
verify_commands: input.verify_commands || [],
|
|
77
104
|
agent: input.agent,
|
|
78
105
|
timeout_seconds: input.timeout_seconds,
|
|
79
106
|
change_policy: input.change_policy || "repo_scoped_changes",
|
|
107
|
+
scope: input.scope || [],
|
|
108
|
+
forbidden: input.forbidden || [],
|
|
109
|
+
verification: input.verification || [],
|
|
110
|
+
done_evidence: input.done_evidence || [],
|
|
80
111
|
requires_confirm: input.decision === "needs_confirm",
|
|
81
112
|
confirmed: false,
|
|
82
113
|
confirmed_at: null,
|
|
83
114
|
confirm_code: null,
|
|
115
|
+
used_at: null,
|
|
84
116
|
agent_assessment_summary: input.agent_assessment_summary || null,
|
|
85
117
|
};
|
|
86
|
-
|
|
118
|
+
atomicWriteJsonFileSync(join(dir, "assessment.json"), record);
|
|
87
119
|
return record;
|
|
88
120
|
}
|
|
89
121
|
export function readAssessment(assessmentId) {
|
|
122
|
+
if (!/^assessment_\d{8}_\d{6}_[0-9a-f]{32}$/.test(assessmentId)) {
|
|
123
|
+
throw new PatchWardenError("assessment_id_invalid", "A full assessment_id with 32 hexadecimal random characters is required.", "Copy the complete assessment_id from the assess_only response; assessment_short_id is display-only.");
|
|
124
|
+
}
|
|
90
125
|
const config = getConfig();
|
|
91
126
|
const assessmentsDir = getAssessmentsDir(config);
|
|
92
127
|
const dir = resolve(assessmentsDir, assessmentId);
|
|
@@ -95,9 +130,36 @@ export function readAssessment(assessmentId) {
|
|
|
95
130
|
if (!existsSync(file)) {
|
|
96
131
|
throw new PatchWardenError("assessment_not_found", `Assessment "${assessmentId}" not found.`, "Call create_task with execution_mode=assess_only first to get a valid assessment_id.", true, { assessment_id: assessmentId });
|
|
97
132
|
}
|
|
98
|
-
|
|
133
|
+
try {
|
|
134
|
+
const parsed = JSON.parse(readFileSync(file, "utf-8"));
|
|
135
|
+
if (!parsed || typeof parsed !== "object" || parsed.assessment_id !== assessmentId) {
|
|
136
|
+
throw new Error("record identity mismatch");
|
|
137
|
+
}
|
|
138
|
+
return parsed;
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
throw new PatchWardenError("assessment_corrupted", `Assessment "${assessmentId}" cannot be read as a valid record.`, "Run create_task with execution_mode=assess_only again.", true, { assessment_id: assessmentId, failure_category: "invalid_assessment_record" });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/** Atomically consume an Assessment before creating its one allowed task. */
|
|
145
|
+
export function markAssessmentUsed(assessmentId) {
|
|
146
|
+
const config = getConfig();
|
|
147
|
+
const assessmentFile = resolve(getAssessmentsDir(config), assessmentId, "assessment.json");
|
|
148
|
+
guardPath(assessmentFile, config.workspaceRoot, config.assessmentsDir);
|
|
149
|
+
return withFileLockSync(assessmentFile, () => {
|
|
150
|
+
const assessment = readAssessment(assessmentId);
|
|
151
|
+
if (assessment.used_at) {
|
|
152
|
+
throw new PatchWardenError("assessment_used", `Assessment "${assessmentId}" has already been used.`, "Run create_task with execution_mode=assess_only again to obtain a new assessment_id.", true, { assessment_id: assessmentId, used_at: assessment.used_at });
|
|
153
|
+
}
|
|
154
|
+
const consumed = { ...assessment, used_at: new Date().toISOString() };
|
|
155
|
+
atomicWriteJsonFileSync(assessmentFile, consumed);
|
|
156
|
+
return consumed;
|
|
157
|
+
});
|
|
99
158
|
}
|
|
100
159
|
export function validateAssessmentFreshness(assessmentId, currentSnapshot, options = {}) {
|
|
160
|
+
if (!/^assessment_\d{8}_\d{6}_[0-9a-f]{32}$/.test(assessmentId)) {
|
|
161
|
+
return { valid: false, failure_reason: "assessment_id_invalid", assessment: null };
|
|
162
|
+
}
|
|
101
163
|
let assessment;
|
|
102
164
|
try {
|
|
103
165
|
assessment = readAssessment(assessmentId);
|
|
@@ -121,18 +183,70 @@ export function validateAssessmentFreshness(assessmentId, currentSnapshot, optio
|
|
|
121
183
|
return { valid: false, failure_reason: "assessment_stale_plan", assessment };
|
|
122
184
|
}
|
|
123
185
|
}
|
|
186
|
+
const config = getConfig();
|
|
187
|
+
const currentManifest = getCurrentToolManifest(config);
|
|
124
188
|
if (!options.skip_tool_manifest) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
189
|
+
// Legacy records need the standalone manifest check. Versioned security
|
|
190
|
+
// snapshots compare the manifest together with the source config, so the
|
|
191
|
+
// diagnostic can report both "tool_manifest" and e.g. "allowed_commands".
|
|
192
|
+
if (currentManifest !== assessment.tool_manifest_sha256 && !assessment.assessment_security_snapshot_sha256) {
|
|
193
|
+
return {
|
|
194
|
+
valid: false,
|
|
195
|
+
failure_reason: "assessment_stale_config",
|
|
196
|
+
assessment,
|
|
197
|
+
expected_hash: assessment.tool_manifest_sha256,
|
|
198
|
+
actual_hash: currentManifest,
|
|
199
|
+
config_change_categories: ["tool_manifest"],
|
|
200
|
+
};
|
|
129
201
|
}
|
|
130
202
|
}
|
|
131
203
|
const currentFingerprint = computeWorkspaceFingerprint(currentSnapshot);
|
|
132
204
|
if (currentFingerprint !== assessment.workspace_fingerprint) {
|
|
133
205
|
return { valid: false, failure_reason: "assessment_workspace_changed", assessment };
|
|
134
206
|
}
|
|
135
|
-
|
|
207
|
+
if (assessment.execution_config_hash) {
|
|
208
|
+
const currentExecutionConfig = computeExecutionConfigFingerprint(config, {
|
|
209
|
+
agent: assessment.agent,
|
|
210
|
+
tool_manifest_sha256: currentManifest,
|
|
211
|
+
tool_profile: resolveToolProfile(config.toolProfile),
|
|
212
|
+
repo_path: assessment.resolved_repo_path,
|
|
213
|
+
change_policy: assessment.change_policy || "repo_scoped_changes",
|
|
214
|
+
template: assessment.template || null,
|
|
215
|
+
verify_commands: assessment.verify_commands || [],
|
|
216
|
+
test_command: assessment.test_command || null,
|
|
217
|
+
task_timeout_seconds: assessment.timeout_seconds,
|
|
218
|
+
scope: assessment.scope,
|
|
219
|
+
forbidden: assessment.forbidden,
|
|
220
|
+
verification: assessment.verification,
|
|
221
|
+
done_evidence: assessment.done_evidence,
|
|
222
|
+
});
|
|
223
|
+
if (assessment.assessment_security_snapshot_version
|
|
224
|
+
&& assessment.assessment_security_snapshot_version !== ASSESSMENT_SECURITY_SNAPSHOT_VERSION) {
|
|
225
|
+
return {
|
|
226
|
+
valid: false,
|
|
227
|
+
failure_reason: "assessment_snapshot_version_incompatible",
|
|
228
|
+
assessment,
|
|
229
|
+
expected_hash: assessment.execution_config_hash,
|
|
230
|
+
actual_hash: currentExecutionConfig.hash,
|
|
231
|
+
config_change_categories: ["schema_epoch"],
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
if (currentExecutionConfig.hash !== assessment.execution_config_hash) {
|
|
235
|
+
const previousComponents = assessment.execution_config_component_hashes;
|
|
236
|
+
const changedCategories = previousComponents
|
|
237
|
+
? Object.keys(currentExecutionConfig.components)
|
|
238
|
+
.filter((category) => previousComponents[category] !== currentExecutionConfig.components[category])
|
|
239
|
+
: ["execution_config"];
|
|
240
|
+
return {
|
|
241
|
+
valid: false,
|
|
242
|
+
failure_reason: "assessment_stale_config",
|
|
243
|
+
assessment,
|
|
244
|
+
expected_hash: assessment.execution_config_hash,
|
|
245
|
+
actual_hash: currentExecutionConfig.hash,
|
|
246
|
+
config_change_categories: changedCategories.length > 0 ? changedCategories : ["execution_config"],
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
}
|
|
136
250
|
const currentPolicyHash = computePolicyHash({
|
|
137
251
|
change_policy: assessment.change_policy || "repo_scoped_changes",
|
|
138
252
|
template: assessment.template || null,
|
|
@@ -160,6 +274,9 @@ export async function confirmAssessment(assessmentId) {
|
|
|
160
274
|
if (!/^assessment_\d{8}_\d{6}_[0-9a-f]{32}$/.test(assessmentId)) {
|
|
161
275
|
throw new PatchWardenError("assessment_id_invalid", "Local confirmation requires the full assessment_id (32 hexadecimal random characters).", "Copy the complete assessment_id from the assess_only response; assessment_short_id is display-only.");
|
|
162
276
|
}
|
|
277
|
+
const config = getConfig();
|
|
278
|
+
const assessmentFile = resolve(getAssessmentsDir(config), assessmentId, "assessment.json");
|
|
279
|
+
guardPath(assessmentFile, config.workspaceRoot, config.assessmentsDir);
|
|
163
280
|
const assessment = readAssessment(assessmentId);
|
|
164
281
|
if (assessment.decision !== "needs_confirm" || !assessment.requires_confirm) {
|
|
165
282
|
throw new PatchWardenError("assessment_confirmation_not_allowed", `Assessment "${assessmentId}" has decision "${assessment.decision}" and cannot be locally confirmed.`, assessment.decision === "blocked"
|
|
@@ -167,37 +284,39 @@ export async function confirmAssessment(assessmentId) {
|
|
|
167
284
|
: "This assessment does not require confirmation; execute it with the minimal assessment_id call.");
|
|
168
285
|
}
|
|
169
286
|
const snapshot = await captureRepoSnapshot(assessment.resolved_repo_path);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
287
|
+
// Re-read and write while holding one cross-process lock. Concurrent local
|
|
288
|
+
// confirmers may compute snapshots in parallel, but they cannot replace one
|
|
289
|
+
// another's confirmation metadata from a stale assessment record.
|
|
290
|
+
return withFileLockSync(assessmentFile, () => {
|
|
291
|
+
const validation = validateAssessmentFreshness(assessmentId, snapshot, {
|
|
292
|
+
allow_unconfirmed: true,
|
|
293
|
+
// A standalone CLI has no active MCP profile snapshot. The execute path
|
|
294
|
+
// still performs the full tool-manifest check before creating the task.
|
|
295
|
+
skip_tool_manifest: true,
|
|
296
|
+
});
|
|
297
|
+
if (!validation.valid || !validation.assessment) {
|
|
298
|
+
throw new PatchWardenError(validation.failure_reason || "assessment_validation_failed", `Assessment "${assessmentId}" cannot be confirmed: ${validation.failure_reason || "validation_failed"}.`, "Run create_task with execution_mode=assess_only again, then confirm the new assessment locally.");
|
|
299
|
+
}
|
|
300
|
+
if (validation.assessment.decision !== "needs_confirm" || !validation.assessment.requires_confirm) {
|
|
301
|
+
throw new PatchWardenError("assessment_confirmation_not_allowed", `Assessment "${assessmentId}" is no longer eligible for local confirmation.`, "Run create_task with execution_mode=assess_only again.");
|
|
302
|
+
}
|
|
303
|
+
const confirmedAt = validation.assessment.confirmed_at || new Date().toISOString();
|
|
304
|
+
const confirmedRecord = {
|
|
305
|
+
...validation.assessment,
|
|
306
|
+
confirmed: true,
|
|
307
|
+
confirmed_at: confirmedAt,
|
|
308
|
+
confirm_code: validation.assessment.confirm_code || randomBytes(8).toString("hex"),
|
|
309
|
+
};
|
|
310
|
+
atomicWriteJsonFileSync(assessmentFile, confirmedRecord);
|
|
311
|
+
return {
|
|
312
|
+
assessment_id: assessmentId,
|
|
313
|
+
decision: "needs_confirm",
|
|
314
|
+
confirmed: true,
|
|
315
|
+
confirmed_at: confirmedAt,
|
|
316
|
+
expires_at: confirmedRecord.expires_at,
|
|
317
|
+
next_action: "Call create_task with only execution_mode=execute and this full assessment_id.",
|
|
318
|
+
};
|
|
175
319
|
});
|
|
176
|
-
if (!validation.valid || !validation.assessment) {
|
|
177
|
-
throw new PatchWardenError(validation.failure_reason || "assessment_validation_failed", `Assessment "${assessmentId}" cannot be confirmed: ${validation.failure_reason || "validation_failed"}.`, "Run create_task with execution_mode=assess_only again, then confirm the new assessment locally.");
|
|
178
|
-
}
|
|
179
|
-
const confirmedAt = validation.assessment.confirmed_at || new Date().toISOString();
|
|
180
|
-
const confirmedRecord = {
|
|
181
|
-
...validation.assessment,
|
|
182
|
-
confirmed: true,
|
|
183
|
-
confirmed_at: confirmedAt,
|
|
184
|
-
confirm_code: validation.assessment.confirm_code || randomBytes(8).toString("hex"),
|
|
185
|
-
};
|
|
186
|
-
const config = getConfig();
|
|
187
|
-
const assessmentFile = resolve(getAssessmentsDir(config), assessmentId, "assessment.json");
|
|
188
|
-
guardPath(assessmentFile, config.workspaceRoot, config.assessmentsDir);
|
|
189
|
-
const temporaryFile = `${assessmentFile}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
|
|
190
|
-
guardPath(temporaryFile, config.workspaceRoot, config.assessmentsDir);
|
|
191
|
-
writeFileSync(temporaryFile, JSON.stringify(confirmedRecord, null, 2), "utf-8");
|
|
192
|
-
renameSync(temporaryFile, assessmentFile);
|
|
193
|
-
return {
|
|
194
|
-
assessment_id: assessmentId,
|
|
195
|
-
decision: "needs_confirm",
|
|
196
|
-
confirmed: true,
|
|
197
|
-
confirmed_at: confirmedAt,
|
|
198
|
-
expires_at: confirmedRecord.expires_at,
|
|
199
|
-
next_action: "Call create_task with only execution_mode=execute and this full assessment_id.",
|
|
200
|
-
};
|
|
201
320
|
}
|
|
202
321
|
export function computeWorkspaceFingerprint(snapshot) {
|
|
203
322
|
const fileHashes = Object.entries(snapshot.files)
|
|
@@ -224,10 +343,53 @@ export function computePolicyHash(input) {
|
|
|
224
343
|
function getRepoAllowedCommands(config, repoPath) {
|
|
225
344
|
return getRepoAllowedTestCommands(config, repoPath);
|
|
226
345
|
}
|
|
227
|
-
function
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
346
|
+
function getCurrentToolManifest(config) {
|
|
347
|
+
// The catalog cache is process-local. Rebuild the canonical catalog here so
|
|
348
|
+
// MCP and the independent Watcher derive the same manifest across processes.
|
|
349
|
+
const profile = resolveToolProfile(config.toolProfile);
|
|
350
|
+
return buildToolCatalogSnapshot(getToolDefs(), profile).tool_manifest_sha256;
|
|
351
|
+
}
|
|
352
|
+
export function computeExecutionConfigHash(config, input) {
|
|
353
|
+
return computeExecutionConfigFingerprint(config, input).hash;
|
|
354
|
+
}
|
|
355
|
+
function computeExecutionConfigFingerprint(config, input) {
|
|
356
|
+
let projectPolicy = null;
|
|
357
|
+
let projectPolicyValid = null;
|
|
358
|
+
let projectPolicyIssues = [];
|
|
359
|
+
try {
|
|
360
|
+
const summary = getProjectPolicySummary(input.repo_path);
|
|
361
|
+
projectPolicy = summary.effective_policy;
|
|
362
|
+
projectPolicyValid = summary.valid;
|
|
363
|
+
projectPolicyIssues = summary.issues.map(({ code, severity, field }) => ({ code, severity, field }));
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
// A workspace-root change can make the old repository inaccessible. The
|
|
367
|
+
// workspace_root component still fails closed without exposing the path.
|
|
368
|
+
}
|
|
369
|
+
const snapshot = buildAssessmentSecuritySnapshot({
|
|
370
|
+
config,
|
|
371
|
+
schemaEpoch: TOOL_SCHEMA_EPOCH,
|
|
372
|
+
toolProfile: input.tool_profile,
|
|
373
|
+
toolManifestSha256: input.tool_manifest_sha256,
|
|
374
|
+
agent: input.agent,
|
|
375
|
+
repoPath: input.repo_path,
|
|
376
|
+
changePolicy: input.change_policy,
|
|
377
|
+
template: input.template,
|
|
378
|
+
verifyCommands: input.verify_commands,
|
|
379
|
+
testCommand: input.test_command,
|
|
380
|
+
taskTimeoutSeconds: input.task_timeout_seconds,
|
|
381
|
+
scope: input.scope,
|
|
382
|
+
forbidden: input.forbidden,
|
|
383
|
+
verification: input.verification,
|
|
384
|
+
doneEvidence: input.done_evidence,
|
|
385
|
+
projectPolicy,
|
|
386
|
+
projectPolicyValid,
|
|
387
|
+
projectPolicyIssues,
|
|
388
|
+
});
|
|
389
|
+
return {
|
|
390
|
+
hash: hashAssessmentSecuritySnapshot(snapshot),
|
|
391
|
+
components: getAssessmentSecuritySnapshotComponentHashes(snapshot),
|
|
392
|
+
};
|
|
231
393
|
}
|
|
232
394
|
function extractReason(error) {
|
|
233
395
|
if (error instanceof PatchWardenError)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { PatchWardenConfig } from "../config.js";
|
|
2
|
+
import type { ProjectPolicy } from "../policy/projectPolicy.js";
|
|
3
|
+
/**
|
|
4
|
+
* Version the security snapshot independently from the server/schema version.
|
|
5
|
+
* Changing the fields or their canonicalization should invalidate old records.
|
|
6
|
+
*/
|
|
7
|
+
export declare const ASSESSMENT_SECURITY_SNAPSHOT_VERSION = "assessment-security-v2";
|
|
8
|
+
export type SecuritySnapshotCategory = "schema_epoch" | "tool_profile" | "tool_manifest" | "workspace_root" | "repo_boundary" | "agent_launch" | "allowed_commands" | "repo_allowed_test_commands" | "direct_allowed_commands" | "repo_direct_allowed_commands" | "sensitive_path_rules" | "protected_paths" | "project_policy" | "risk_rules" | "confirmation_policy" | "task_parameters" | "release_protection" | "direct_profile" | "assessment_ttl" | "timeout_policy";
|
|
9
|
+
export interface AssessmentSecuritySnapshotInput {
|
|
10
|
+
config: PatchWardenConfig;
|
|
11
|
+
schemaEpoch: string;
|
|
12
|
+
toolProfile: string;
|
|
13
|
+
toolManifestSha256: string;
|
|
14
|
+
agent: string;
|
|
15
|
+
repoPath: string;
|
|
16
|
+
changePolicy?: string | null;
|
|
17
|
+
template?: string | null;
|
|
18
|
+
verifyCommands?: string[];
|
|
19
|
+
testCommand?: string | null;
|
|
20
|
+
taskTimeoutSeconds?: number | null;
|
|
21
|
+
scope?: string[];
|
|
22
|
+
forbidden?: string[];
|
|
23
|
+
verification?: string[];
|
|
24
|
+
doneEvidence?: string[];
|
|
25
|
+
riskRulesVersion?: string;
|
|
26
|
+
projectPolicy?: ProjectPolicy | null;
|
|
27
|
+
projectPolicyValid?: boolean | null;
|
|
28
|
+
projectPolicyIssues?: Array<{
|
|
29
|
+
code: string;
|
|
30
|
+
severity: string;
|
|
31
|
+
field: string;
|
|
32
|
+
}>;
|
|
33
|
+
}
|
|
34
|
+
export interface AssessmentSecuritySnapshot {
|
|
35
|
+
assessment_security_snapshot_version: string;
|
|
36
|
+
components: Record<SecuritySnapshotCategory, unknown>;
|
|
37
|
+
}
|
|
38
|
+
export interface AssessmentSecuritySnapshotComparison {
|
|
39
|
+
equal: boolean;
|
|
40
|
+
expected_hash: string;
|
|
41
|
+
actual_hash: string;
|
|
42
|
+
changed_field_names: SecuritySnapshotCategory[];
|
|
43
|
+
}
|
|
44
|
+
/** Build only security-relevant configuration; runtime state is deliberately absent. */
|
|
45
|
+
export declare function buildAssessmentSecuritySnapshot(input: AssessmentSecuritySnapshotInput): AssessmentSecuritySnapshot;
|
|
46
|
+
/** Recursively sort object keys while preserving array order where it matters. */
|
|
47
|
+
export declare function canonicalizeAssessmentSecuritySnapshot(snapshot: AssessmentSecuritySnapshot): AssessmentSecuritySnapshot;
|
|
48
|
+
export declare function hashAssessmentSecuritySnapshot(snapshot: AssessmentSecuritySnapshot): string;
|
|
49
|
+
export declare function getAssessmentSecuritySnapshotComponentHashes(snapshot: AssessmentSecuritySnapshot): Record<SecuritySnapshotCategory, string>;
|
|
50
|
+
export declare function compareAssessmentSecuritySnapshots(expected: AssessmentSecuritySnapshot, actual: AssessmentSecuritySnapshot): AssessmentSecuritySnapshotComparison;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { resolve, win32 } from "node:path";
|
|
3
|
+
import { getRepoAllowedTestCommands, getRepoDirectAllowedCommands } from "../config.js";
|
|
4
|
+
import { stableJsonStringify } from "../utils/stableJson.js";
|
|
5
|
+
/**
|
|
6
|
+
* Version the security snapshot independently from the server/schema version.
|
|
7
|
+
* Changing the fields or their canonicalization should invalidate old records.
|
|
8
|
+
*/
|
|
9
|
+
export const ASSESSMENT_SECURITY_SNAPSHOT_VERSION = "assessment-security-v2";
|
|
10
|
+
function canonicalPath(value) {
|
|
11
|
+
const rawValue = String(value);
|
|
12
|
+
if (/^[a-z]:[\\/]/i.test(rawValue)) {
|
|
13
|
+
return win32.normalize(rawValue).replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
14
|
+
}
|
|
15
|
+
const normalized = resolve(rawValue).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
16
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
17
|
+
}
|
|
18
|
+
function sortedUnique(values) {
|
|
19
|
+
return [...new Set((values || []).map((value) => String(value).trim()))]
|
|
20
|
+
.filter(Boolean)
|
|
21
|
+
.sort((left, right) => left.localeCompare(right));
|
|
22
|
+
}
|
|
23
|
+
function sortedUniqueTaskPaths(values) {
|
|
24
|
+
return sortedUnique(values?.map((value) => {
|
|
25
|
+
const normalized = String(value).replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/{2,}/g, "/");
|
|
26
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
function sortedAgentConfig(config) {
|
|
30
|
+
return Object.fromEntries(Object.entries(config.agents)
|
|
31
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
32
|
+
.map(([name, agent]) => [name, {
|
|
33
|
+
command: agent.command,
|
|
34
|
+
args: [...agent.args],
|
|
35
|
+
adapter: agent.adapter || null,
|
|
36
|
+
model: agent.model || null,
|
|
37
|
+
envAllowlist: sortedUnique(agent.envAllowlist),
|
|
38
|
+
}]));
|
|
39
|
+
}
|
|
40
|
+
/** Build only security-relevant configuration; runtime state is deliberately absent. */
|
|
41
|
+
export function buildAssessmentSecuritySnapshot(input) {
|
|
42
|
+
const { config } = input;
|
|
43
|
+
const repoPath = canonicalPath(input.repoPath);
|
|
44
|
+
const components = {
|
|
45
|
+
schema_epoch: input.schemaEpoch,
|
|
46
|
+
tool_profile: input.toolProfile,
|
|
47
|
+
tool_manifest: input.toolManifestSha256,
|
|
48
|
+
workspace_root: canonicalPath(config.workspaceRoot),
|
|
49
|
+
repo_boundary: {
|
|
50
|
+
workspace_root: canonicalPath(config.workspaceRoot),
|
|
51
|
+
repo_path: repoPath,
|
|
52
|
+
repo_aliases: Object.fromEntries(Object.entries(config.repoAliases || {})
|
|
53
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
54
|
+
.map(([key, value]) => [key, canonicalPath(resolve(config.workspaceRoot, value))])),
|
|
55
|
+
},
|
|
56
|
+
agent_launch: {
|
|
57
|
+
selected_agent: input.agent,
|
|
58
|
+
configured_agents: sortedAgentConfig(config),
|
|
59
|
+
},
|
|
60
|
+
allowed_commands: {
|
|
61
|
+
configured: sortedUnique(config.allowedTestCommands),
|
|
62
|
+
project: sortedUnique(input.projectPolicy?.allowed_commands),
|
|
63
|
+
},
|
|
64
|
+
repo_allowed_test_commands: sortedUnique(getRepoAllowedTestCommands(config, repoPath)),
|
|
65
|
+
direct_allowed_commands: sortedUnique(config.directAllowedCommands),
|
|
66
|
+
repo_direct_allowed_commands: sortedUnique(getRepoDirectAllowedCommands(config, repoPath)),
|
|
67
|
+
sensitive_path_rules: "sensitive-path-rules-v1",
|
|
68
|
+
protected_paths: sortedUnique(input.projectPolicy?.protected_paths || [
|
|
69
|
+
".env", ".env.*", ".ssh", ".npmrc", ".pypirc", "patchwarden.config.json",
|
|
70
|
+
]),
|
|
71
|
+
project_policy: {
|
|
72
|
+
valid: input.projectPolicyValid ?? null,
|
|
73
|
+
effective_policy: input.projectPolicy || null,
|
|
74
|
+
issues: [...(input.projectPolicyIssues || [])]
|
|
75
|
+
.map((issue) => ({ code: issue.code, severity: issue.severity, field: issue.field }))
|
|
76
|
+
.sort((left, right) => stableJsonStringify(left).localeCompare(stableJsonStringify(right))),
|
|
77
|
+
},
|
|
78
|
+
risk_rules: {
|
|
79
|
+
implementation: input.riskRulesVersion || "risk-engine-v1",
|
|
80
|
+
project_high_risk_commands: sortedUnique(input.projectPolicy?.high_risk_commands),
|
|
81
|
+
},
|
|
82
|
+
confirmation_policy: {
|
|
83
|
+
change_policy: input.changePolicy || "repo_scoped_changes",
|
|
84
|
+
template: input.template || null,
|
|
85
|
+
},
|
|
86
|
+
task_parameters: {
|
|
87
|
+
test_command: input.testCommand || null,
|
|
88
|
+
verify_commands: sortedUnique(input.verifyCommands),
|
|
89
|
+
timeout_seconds: input.taskTimeoutSeconds ?? config.defaultTaskTimeoutSeconds,
|
|
90
|
+
scope: sortedUniqueTaskPaths(input.scope),
|
|
91
|
+
forbidden: sortedUniqueTaskPaths(input.forbidden),
|
|
92
|
+
verification: sortedUnique(input.verification),
|
|
93
|
+
done_evidence: sortedUniqueTaskPaths(input.doneEvidence),
|
|
94
|
+
},
|
|
95
|
+
release_protection: {
|
|
96
|
+
high_risk_commands: sortedUnique(input.projectPolicy?.high_risk_commands || [
|
|
97
|
+
"npm publish", "git push", "git tag", "gh release create",
|
|
98
|
+
]),
|
|
99
|
+
release_mode: input.projectPolicy?.release_mode || {
|
|
100
|
+
version_source: "package.json",
|
|
101
|
+
required_commands: ["npm run build", "npm test"],
|
|
102
|
+
},
|
|
103
|
+
artifact_rules: "artifact-rules-v1",
|
|
104
|
+
},
|
|
105
|
+
direct_profile: Boolean(config.enableDirectProfile),
|
|
106
|
+
assessment_ttl: config.assessmentTtlSeconds,
|
|
107
|
+
timeout_policy: {
|
|
108
|
+
default_task_timeout_seconds: config.defaultTaskTimeoutSeconds,
|
|
109
|
+
max_task_timeout_seconds: config.maxTaskTimeoutSeconds,
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
return {
|
|
113
|
+
assessment_security_snapshot_version: ASSESSMENT_SECURITY_SNAPSHOT_VERSION,
|
|
114
|
+
components,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/** Recursively sort object keys while preserving array order where it matters. */
|
|
118
|
+
export function canonicalizeAssessmentSecuritySnapshot(snapshot) {
|
|
119
|
+
const canonicalize = (value) => {
|
|
120
|
+
if (Array.isArray(value))
|
|
121
|
+
return value.map(canonicalize);
|
|
122
|
+
if (value !== null && typeof value === "object") {
|
|
123
|
+
return Object.fromEntries(Object.entries(value)
|
|
124
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
125
|
+
.map(([key, entry]) => [key, canonicalize(entry)]));
|
|
126
|
+
}
|
|
127
|
+
return value;
|
|
128
|
+
};
|
|
129
|
+
return canonicalize(snapshot);
|
|
130
|
+
}
|
|
131
|
+
export function hashAssessmentSecuritySnapshot(snapshot) {
|
|
132
|
+
return createHash("sha256")
|
|
133
|
+
.update(stableJsonStringify(canonicalizeAssessmentSecuritySnapshot(snapshot)))
|
|
134
|
+
.digest("hex");
|
|
135
|
+
}
|
|
136
|
+
export function getAssessmentSecuritySnapshotComponentHashes(snapshot) {
|
|
137
|
+
return Object.fromEntries(Object.entries(snapshot.components).map(([name, value]) => [
|
|
138
|
+
name,
|
|
139
|
+
createHash("sha256").update(stableJsonStringify(value)).digest("hex"),
|
|
140
|
+
]));
|
|
141
|
+
}
|
|
142
|
+
export function compareAssessmentSecuritySnapshots(expected, actual) {
|
|
143
|
+
const expectedHash = hashAssessmentSecuritySnapshot(expected);
|
|
144
|
+
const actualHash = hashAssessmentSecuritySnapshot(actual);
|
|
145
|
+
const expectedComponents = getAssessmentSecuritySnapshotComponentHashes(expected);
|
|
146
|
+
const actualComponents = getAssessmentSecuritySnapshotComponentHashes(actual);
|
|
147
|
+
const changed = Object.keys(actualComponents)
|
|
148
|
+
.filter((name) => expectedComponents[name] !== actualComponents[name]);
|
|
149
|
+
if (expected.assessment_security_snapshot_version !== actual.assessment_security_snapshot_version) {
|
|
150
|
+
changed.unshift("schema_epoch");
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
equal: expectedHash === actualHash,
|
|
154
|
+
expected_hash: expectedHash,
|
|
155
|
+
actual_hash: actualHash,
|
|
156
|
+
changed_field_names: [...new Set(changed)],
|
|
157
|
+
};
|
|
158
|
+
}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export interface AgentConfig {
|
|
2
2
|
command: string;
|
|
3
3
|
args: string[];
|
|
4
|
+
envAllowlist?: string[];
|
|
5
|
+
adapter?: string;
|
|
6
|
+
model?: string;
|
|
4
7
|
}
|
|
5
8
|
export interface PatchWardenConfig {
|
|
6
9
|
workspaceRoot: string;
|
|
@@ -29,6 +32,18 @@ export interface PatchWardenConfig {
|
|
|
29
32
|
agentAssessmentMaxOutputBytes?: number;
|
|
30
33
|
agentAssessmentAgentName?: string;
|
|
31
34
|
enableDirectProfile?: boolean;
|
|
35
|
+
tunnelClientPath?: string;
|
|
36
|
+
tunnelProxy?: {
|
|
37
|
+
scope: "shared" | "separate";
|
|
38
|
+
core: {
|
|
39
|
+
mode: "environment" | "none" | "manual";
|
|
40
|
+
url?: string;
|
|
41
|
+
};
|
|
42
|
+
direct: {
|
|
43
|
+
mode: "environment" | "none" | "manual";
|
|
44
|
+
url?: string;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
32
47
|
directSessionsDir: string;
|
|
33
48
|
directSessionTtlSeconds: number;
|
|
34
49
|
directMaxPatchBytes: number;
|