patchwarden 1.6.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/README.en.md +17 -12
- package/README.md +14 -11
- 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 +166 -12
- package/dist/assessments/securitySnapshot.d.ts +50 -0
- package/dist/assessments/securitySnapshot.js +158 -0
- package/dist/control/routes/status.d.ts +14 -0
- package/dist/control/routes/status.js +22 -0
- package/dist/control/runtime.js +2 -10
- package/dist/control/server.js +1 -0
- package/dist/diagnostics/allowedTestCommandSafety.d.ts +1 -0
- package/dist/diagnostics/allowedTestCommandSafety.js +11 -0
- package/dist/direct/directSessionStore.js +5 -4
- package/dist/doctor.js +3 -5
- package/dist/runner/runTask.d.ts +1 -0
- package/dist/runner/runTask.js +47 -6
- package/dist/runner/taskRuntime.d.ts +2 -0
- package/dist/runner/taskStatusStore.js +12 -1
- package/dist/runner/watch.js +64 -1
- package/dist/smoke-test.js +3 -3
- package/dist/tools/definitions/toolDefs.js +3 -3
- package/dist/tools/diagnostics/auditTask.d.ts +1 -0
- package/dist/tools/diagnostics/auditTask.js +35 -7
- package/dist/tools/diagnostics/healthCheck.d.ts +12 -0
- package/dist/tools/diagnostics/healthCheck.js +29 -1
- package/dist/tools/tasks/cancelTask.js +2 -1
- package/dist/tools/tasks/createTask.d.ts +2 -2
- package/dist/tools/tasks/createTask.js +70 -9
- package/dist/tools/tasks/diagnoseTask.js +4 -8
- package/dist/tools/tasks/getTaskStatus.js +6 -1
- package/dist/tools/tasks/getTaskSummary.js +2 -10
- package/dist/tools/tasks/listTasks.js +2 -1
- package/dist/tools/tasks/reconcileTasks.d.ts +2 -1
- package/dist/tools/tasks/reconcileTasks.js +185 -39
- package/dist/tools/tasks/runTaskLoop.js +5 -11
- package/dist/tools/tasks/taskOutputs.d.ts +2 -2
- package/dist/tools/tasks/taskStates.d.ts +6 -0
- package/dist/tools/tasks/taskStates.js +52 -0
- package/dist/tools/tasks/waitForTask.js +3 -11
- package/dist/tools/workspace/listAgents.d.ts +6 -0
- package/dist/tools/workspace/listAgents.js +12 -2
- package/dist/utils/atomicFile.js +20 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/CODE_WIKI.md +4 -4
- package/docs/open-source-application.md +11 -12
- package/docs/release-evidence.md +33 -47
- package/package.json +1 -1
- package/scripts/checks/control-center-smoke.js +11 -4
- package/scripts/checks/lifecycle-smoke.js +5 -2
- package/scripts/checks/mcp-smoke.js +31 -1
- package/scripts/checks/watcher-supervisor-smoke.js +29 -1
- package/scripts/control/start-patchwarden-tunnel.ps1 +65 -1
- package/scripts/e2e/demo-runtime.mjs +153 -0
- package/scripts/release/desktop-preflight.js +1 -1
- package/src/assessments/assessmentDiagnostics.ts +172 -0
- package/src/assessments/assessmentStore.ts +237 -12
- package/src/assessments/securitySnapshot.ts +231 -0
- package/src/control/routes/status.ts +37 -0
- package/src/control/runtime.ts +2 -10
- package/src/control/server.ts +1 -0
- package/src/diagnostics/allowedTestCommandSafety.ts +12 -0
- package/src/direct/directSessionStore.ts +6 -4
- package/src/doctor.ts +3 -5
- package/src/runner/runTask.ts +59 -6
- package/src/runner/taskRuntime.ts +2 -0
- package/src/runner/taskStatusStore.ts +16 -1
- package/src/runner/watch.ts +67 -1
- package/src/smoke-test.ts +2 -2
- package/src/tools/definitions/toolDefs.ts +3 -3
- package/src/tools/diagnostics/auditTask.ts +37 -7
- package/src/tools/diagnostics/healthCheck.ts +29 -1
- package/src/tools/tasks/cancelTask.ts +2 -1
- package/src/tools/tasks/createTask.ts +113 -8
- package/src/tools/tasks/diagnoseTask.ts +4 -8
- package/src/tools/tasks/getTaskStatus.ts +6 -1
- package/src/tools/tasks/getTaskSummary.ts +2 -11
- package/src/tools/tasks/listTasks.ts +2 -1
- package/src/tools/tasks/reconcileTasks.ts +162 -13
- package/src/tools/tasks/runTaskLoop.ts +4 -12
- package/src/tools/tasks/taskStates.ts +54 -0
- package/src/tools/tasks/waitForTask.ts +3 -12
- package/src/tools/workspace/listAgents.ts +17 -3
- package/src/utils/atomicFile.ts +17 -1
- package/src/version.ts +1 -1
- package/ui/pages/dashboard.html +4 -0
- package/ui/pages/logs.html +29 -0
- package/ui/pages/settings.html +3 -3
- package/ui/settings.js +26 -17
package/README.en.md
CHANGED
|
@@ -19,9 +19,9 @@ PatchWarden stores that plan as a workspace-scoped task, lets a preconfigured
|
|
|
19
19
|
local agent execute it, and returns results, diffs, artifact manifests, and
|
|
20
20
|
independent verification evidence.
|
|
21
21
|
|
|
22
|
-
[Download Windows installer v1.6.
|
|
23
|
-
· [Portable ZIP](https://github.com/jiezeng2004-design/PatchWarden/releases/download/v1.6.
|
|
24
|
-
· [Checksums](https://github.com/jiezeng2004-design/PatchWarden/releases/download/v1.6.
|
|
22
|
+
[Download Windows installer v1.6.2](https://github.com/jiezeng2004-design/PatchWarden/releases/download/v1.6.2/PatchWarden-Setup-1.6.2-x64.exe)
|
|
23
|
+
· [Portable ZIP](https://github.com/jiezeng2004-design/PatchWarden/releases/download/v1.6.2/PatchWarden-Portable-1.6.2-x64.zip)
|
|
24
|
+
· [Checksums](https://github.com/jiezeng2004-design/PatchWarden/releases/download/v1.6.2/PatchWarden-Desktop-SHA256SUMS.txt)
|
|
25
25
|
· [Three-minute quick start](#three-minute-quick-start)
|
|
26
26
|
· [Discussions](https://github.com/jiezeng2004-design/PatchWarden/discussions)
|
|
27
27
|
|
|
@@ -29,12 +29,18 @@ independent verification evidence.
|
|
|
29
29
|
|
|
30
30
|
<sub>Real PatchWarden Desktop first-run UI from a privacy-safe smoke run; no real workspace, account, or credential data is shown.</sub>
|
|
31
31
|
|
|
32
|
-
Current source version
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
Current source version and Windows Release: **v1.6.2**. npm `latest` is
|
|
33
|
+
published separately by the maintainer; use the npm badge above or query the
|
|
34
|
+
registry for its current value. Use the installer above for the first Windows experience. See the
|
|
35
35
|
[CHANGELOG](CHANGELOG.md), [migration guide](docs/migration-from-safe-bifrost.md), and
|
|
36
36
|
[release checklist](docs/release-checklist.md).
|
|
37
37
|
|
|
38
|
+
> [!NOTE]
|
|
39
|
+
> The desktop installer is currently available for Windows x64 only. macOS,
|
|
40
|
+
> Linux, and users who are not installing Desktop yet can continue using the
|
|
41
|
+
> existing stable npm package and its built-in local Dashboard. This change
|
|
42
|
+
> does not run `npm publish` automatically.
|
|
43
|
+
|
|
38
44
|
> [!IMPORTANT]
|
|
39
45
|
> PatchWarden is not a general-purpose remote shell. MCP clients cannot run
|
|
40
46
|
> arbitrary commands: files must remain inside the configured workspace,
|
|
@@ -239,16 +245,15 @@ three minutes. A ChatGPT Tunnel is not required for this first run.
|
|
|
239
245
|
|
|
240
246
|
### Option A: Windows installer (recommended for a first run)
|
|
241
247
|
|
|
242
|
-
1. Download the [Windows installer v1.6.
|
|
243
|
-
and its [SHA256 checksum file](https://github.com/jiezeng2004-design/PatchWarden/releases/download/v1.6.
|
|
248
|
+
1. Download the [Windows installer v1.6.2](https://github.com/jiezeng2004-design/PatchWarden/releases/download/v1.6.2/PatchWarden-Setup-1.6.2-x64.exe)
|
|
249
|
+
and its [SHA256 checksum file](https://github.com/jiezeng2004-design/PatchWarden/releases/download/v1.6.2/PatchWarden-Desktop-SHA256SUMS.txt).
|
|
244
250
|
2. Verify the installer in PowerShell:
|
|
245
251
|
|
|
246
252
|
```powershell
|
|
247
|
-
Get-FileHash .\PatchWarden-Setup-1.6.
|
|
253
|
+
Get-FileHash .\PatchWarden-Setup-1.6.2-x64.exe -Algorithm SHA256
|
|
248
254
|
```
|
|
249
255
|
|
|
250
|
-
|
|
251
|
-
always prefer the checksum file from the same Release. The installer is not
|
|
256
|
+
Always use the checksum file from the same Release. The installer is not
|
|
252
257
|
code-signed yet, so Windows SmartScreen may show an unknown-publisher warning.
|
|
253
258
|
|
|
254
259
|
3. Install and open PatchWarden Desktop, then choose a dedicated workspace that
|
|
@@ -683,7 +688,7 @@ country list.
|
|
|
683
688
|
Use this sequence:
|
|
684
689
|
|
|
685
690
|
1. `health_check` — verify version, workspace, Watcher, and tool catalog.
|
|
686
|
-
2. `list_agents` — confirm the local
|
|
691
|
+
2. `list_agents` — confirm the local agent executable is available. This read-only check does not verify provider balance, login state, or model permissions; `provider_status` is `not_checked` by default.
|
|
687
692
|
3. `list_workspace` — identify the correct `repo_path`.
|
|
688
693
|
4. `save_plan`, or provide an `inline_plan` when creating the task.
|
|
689
694
|
5. `create_task` — specify the agent, repository, and verification commands.
|
package/README.md
CHANGED
|
@@ -15,9 +15,9 @@ ChatGPT、Codex、OpenCode 或其他 MCP 客户端负责规划与验收,
|
|
|
15
15
|
PatchWarden 负责把计划保存成工作区内任务,再由预先配置的本地 Agent
|
|
16
16
|
执行,并返回结果、代码差异和独立测试记录。
|
|
17
17
|
|
|
18
|
-
[下载 Windows 安装版 v1.6.
|
|
19
|
-
· [免安装 ZIP](https://github.com/jiezeng2004-design/PatchWarden/releases/download/v1.6.
|
|
20
|
-
· [校验文件](https://github.com/jiezeng2004-design/PatchWarden/releases/download/v1.6.
|
|
18
|
+
[下载 Windows 安装版 v1.6.2](https://github.com/jiezeng2004-design/PatchWarden/releases/download/v1.6.2/PatchWarden-Setup-1.6.2-x64.exe)
|
|
19
|
+
· [免安装 ZIP](https://github.com/jiezeng2004-design/PatchWarden/releases/download/v1.6.2/PatchWarden-Portable-1.6.2-x64.zip)
|
|
20
|
+
· [校验文件](https://github.com/jiezeng2004-design/PatchWarden/releases/download/v1.6.2/PatchWarden-Desktop-SHA256SUMS.txt)
|
|
21
21
|
· [三分钟快速开始](#三分钟快速开始)
|
|
22
22
|
· [Discussions](https://github.com/jiezeng2004-design/PatchWarden/discussions)
|
|
23
23
|
|
|
@@ -25,11 +25,15 @@ PatchWarden 负责把计划保存成工作区内任务,再由预先配置的
|
|
|
25
25
|
|
|
26
26
|
<sub>PatchWarden Desktop 真实首启界面,来自隐私安全的桌面 smoke 验收;截图未使用真实工作区、账号或凭据。</sub>
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
当前源码版本和 Windows Release:**v1.6.2**。npm `latest` 由维护者独立手动发布,
|
|
29
|
+
请以页面顶部的 npm 徽章和注册表查询结果为准。Windows 首次体验推荐上面的安装版;查看
|
|
30
30
|
[CHANGELOG](CHANGELOG.md)、[迁移指南](docs/migration-from-safe-bifrost.md)和
|
|
31
31
|
[发布检查清单](docs/release-checklist.md)。
|
|
32
32
|
|
|
33
|
+
> [!NOTE]
|
|
34
|
+
> 当前桌面安装包暂时只提供 Windows x64。macOS、Linux 或暂时不安装桌面版的用户,
|
|
35
|
+
> 可以继续使用 npm 上已有的稳定版本及其内置本地 Dashboard;本次 GitHub Release 不自动执行 `npm publish`。
|
|
36
|
+
|
|
33
37
|
> [!IMPORTANT]
|
|
34
38
|
> PatchWarden 不是通用远程 Shell。MCP 客户端不能随意执行命令:
|
|
35
39
|
> 文件必须位于配置的工作区内,Agent 必须预先登记,验证命令必须与白名单
|
|
@@ -148,16 +152,15 @@ CLI,或者先把 OpenCode 配置为执行 Agent。
|
|
|
148
152
|
|
|
149
153
|
### 方案 A:Windows 安装版(首次体验推荐)
|
|
150
154
|
|
|
151
|
-
1. 下载 [Windows 安装版 v1.6.
|
|
152
|
-
和 [SHA256 校验文件](https://github.com/jiezeng2004-design/PatchWarden/releases/download/v1.6.
|
|
155
|
+
1. 下载 [Windows 安装版 v1.6.2](https://github.com/jiezeng2004-design/PatchWarden/releases/download/v1.6.2/PatchWarden-Setup-1.6.2-x64.exe)
|
|
156
|
+
和 [SHA256 校验文件](https://github.com/jiezeng2004-design/PatchWarden/releases/download/v1.6.2/PatchWarden-Desktop-SHA256SUMS.txt)。
|
|
153
157
|
2. 在 PowerShell 中校验安装包:
|
|
154
158
|
|
|
155
159
|
```powershell
|
|
156
|
-
Get-FileHash .\PatchWarden-Setup-1.6.
|
|
160
|
+
Get-FileHash .\PatchWarden-Setup-1.6.2-x64.exe -Algorithm SHA256
|
|
157
161
|
```
|
|
158
162
|
|
|
159
|
-
|
|
160
|
-
仍应以同一 Release 的校验文件为准。安装包尚未代码签名,Windows SmartScreen
|
|
163
|
+
请以同一 Release 的校验文件为准。安装包尚未代码签名,Windows SmartScreen
|
|
161
164
|
可能显示“未知发布者”。
|
|
162
165
|
|
|
163
166
|
3. 安装并打开 PatchWarden Desktop,选择一个只包含项目的专用工作区。
|
|
@@ -565,7 +568,7 @@ Tunnel。支持范围可能变化,因此不要在项目里写死国家列表
|
|
|
565
568
|
推荐顺序:
|
|
566
569
|
|
|
567
570
|
1. `health_check`:确认版本、工作区、Watcher 和工具目录。
|
|
568
|
-
2. `list_agents`:确认本地 Agent
|
|
571
|
+
2. `list_agents`:确认本地 Agent 可执行文件可用;该检查不会验证模型提供商余额、登录态或调用权限,结果中的 `provider_status` 默认为 `not_checked`。
|
|
569
572
|
3. `list_workspace`:确定 `repo_path`。
|
|
570
573
|
4. `save_plan`,或在创建任务时提供 `inline_plan`。
|
|
571
574
|
5. `create_task`:明确 Agent、仓库和验证命令。
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AssessmentValidationResult } from "./assessmentStore.js";
|
|
2
|
+
export interface AssessmentValidationFailureDiagnostic {
|
|
3
|
+
diagnostic_version: string;
|
|
4
|
+
error_code: string;
|
|
5
|
+
reason_code: string;
|
|
6
|
+
assessment_id: string | null;
|
|
7
|
+
assessment_config_sha256: string | null;
|
|
8
|
+
current_config_sha256: string | null;
|
|
9
|
+
assessment_config_hash: string | null;
|
|
10
|
+
current_config_hash: string | null;
|
|
11
|
+
snapshot_version: string | null;
|
|
12
|
+
assessment_fingerprint_version: string | null;
|
|
13
|
+
current_fingerprint_version: string;
|
|
14
|
+
assessment_schema_epoch: string | null;
|
|
15
|
+
current_schema_epoch: string;
|
|
16
|
+
assessment_created_at: string | null;
|
|
17
|
+
assessment_expires_at: string | null;
|
|
18
|
+
config_changed: boolean;
|
|
19
|
+
changed_field_names: string[];
|
|
20
|
+
changed_config_sections: string[];
|
|
21
|
+
validator_module_path: string;
|
|
22
|
+
validator_build_id: string;
|
|
23
|
+
watcher_instance_id: string | null;
|
|
24
|
+
observed_at: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function buildAssessmentValidationFailureDiagnostic(result: AssessmentValidationResult): AssessmentValidationFailureDiagnostic | null;
|
|
27
|
+
/** Persist a bounded value-free failure summary without changing validation behavior. */
|
|
28
|
+
export declare function recordAssessmentValidationFailure(result: AssessmentValidationResult): boolean;
|
|
29
|
+
export declare function readLastAssessmentValidationFailure(): AssessmentValidationFailureDiagnostic | null;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { getAssessmentsDir, getConfig } from "../config.js";
|
|
4
|
+
import { guardPath } from "../security/pathGuard.js";
|
|
5
|
+
import { atomicWriteJsonFileSync } from "../utils/atomicFile.js";
|
|
6
|
+
import { readJsonObjectFileSync } from "../utils/lockedJsonFile.js";
|
|
7
|
+
import { ASSESSMENT_SECURITY_SNAPSHOT_VERSION } from "./securitySnapshot.js";
|
|
8
|
+
import { PATCHWARDEN_VERSION, TOOL_SCHEMA_EPOCH } from "../version.js";
|
|
9
|
+
const DIAGNOSTIC_VERSION = "assessment-validation-diagnostic-v2";
|
|
10
|
+
const DIAGNOSTIC_FILE = "last-validation-failure.json";
|
|
11
|
+
const VALIDATOR_MODULE_PATH = "dist/assessments/assessmentStore.js";
|
|
12
|
+
const VALIDATOR_BUILD_ID = `patchwarden-${PATCHWARDEN_VERSION}:${TOOL_SCHEMA_EPOCH}:${ASSESSMENT_SECURITY_SNAPSHOT_VERSION}`;
|
|
13
|
+
export function buildAssessmentValidationFailureDiagnostic(result) {
|
|
14
|
+
if (result.valid || !result.failure_reason)
|
|
15
|
+
return null;
|
|
16
|
+
const reasonCode = safeCode(result.failure_reason);
|
|
17
|
+
const assessmentHash = safeHash(result.expected_hash);
|
|
18
|
+
const currentHash = safeHash(result.actual_hash);
|
|
19
|
+
const changedSections = [...new Set((result.config_change_categories || [])
|
|
20
|
+
.map((name) => safeCode(name))
|
|
21
|
+
.filter(Boolean))]
|
|
22
|
+
.sort()
|
|
23
|
+
.slice(0, 32);
|
|
24
|
+
const assessmentVersion = safeText(result.assessment?.assessment_security_snapshot_version, 80);
|
|
25
|
+
return {
|
|
26
|
+
diagnostic_version: DIAGNOSTIC_VERSION,
|
|
27
|
+
error_code: reasonCode,
|
|
28
|
+
reason_code: reasonCode,
|
|
29
|
+
assessment_id: safeAssessmentId(result.assessment?.assessment_id),
|
|
30
|
+
assessment_config_sha256: assessmentHash,
|
|
31
|
+
current_config_sha256: currentHash,
|
|
32
|
+
assessment_config_hash: assessmentHash,
|
|
33
|
+
current_config_hash: currentHash,
|
|
34
|
+
snapshot_version: assessmentVersion,
|
|
35
|
+
assessment_fingerprint_version: assessmentVersion,
|
|
36
|
+
current_fingerprint_version: ASSESSMENT_SECURITY_SNAPSHOT_VERSION,
|
|
37
|
+
assessment_schema_epoch: safeText(result.assessment?.assessment_schema_epoch, 80),
|
|
38
|
+
current_schema_epoch: TOOL_SCHEMA_EPOCH,
|
|
39
|
+
assessment_created_at: safeTimestamp(result.assessment?.created_at),
|
|
40
|
+
assessment_expires_at: safeTimestamp(result.assessment?.expires_at),
|
|
41
|
+
config_changed: result.failure_reason === "assessment_stale_config",
|
|
42
|
+
changed_field_names: changedSections,
|
|
43
|
+
changed_config_sections: changedSections,
|
|
44
|
+
validator_module_path: VALIDATOR_MODULE_PATH,
|
|
45
|
+
validator_build_id: VALIDATOR_BUILD_ID,
|
|
46
|
+
watcher_instance_id: safeInstanceId(process.env.PATCHWARDEN_WATCHER_INSTANCE_ID),
|
|
47
|
+
observed_at: new Date().toISOString(),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Persist a bounded value-free failure summary without changing validation behavior. */
|
|
51
|
+
export function recordAssessmentValidationFailure(result) {
|
|
52
|
+
const diagnostic = buildAssessmentValidationFailureDiagnostic(result);
|
|
53
|
+
if (!diagnostic)
|
|
54
|
+
return false;
|
|
55
|
+
try {
|
|
56
|
+
const config = getConfig();
|
|
57
|
+
const assessmentsDir = getAssessmentsDir(config);
|
|
58
|
+
const file = guardPath(join(assessmentsDir, DIAGNOSTIC_FILE), config.workspaceRoot, config.assessmentsDir);
|
|
59
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
60
|
+
atomicWriteJsonFileSync(file, diagnostic);
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// Diagnostics must never replace the original fail-closed validation error.
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export function readLastAssessmentValidationFailure() {
|
|
69
|
+
try {
|
|
70
|
+
const config = getConfig();
|
|
71
|
+
const file = guardPath(join(getAssessmentsDir(config), DIAGNOSTIC_FILE), config.workspaceRoot, config.assessmentsDir);
|
|
72
|
+
if (!existsSync(file))
|
|
73
|
+
return null;
|
|
74
|
+
const raw = readJsonObjectFileSync(file);
|
|
75
|
+
if (raw.diagnostic_version !== DIAGNOSTIC_VERSION)
|
|
76
|
+
return null;
|
|
77
|
+
const changedSections = Array.isArray(raw.changed_config_sections)
|
|
78
|
+
? [...new Set(raw.changed_config_sections.map(safeCode).filter(Boolean))].sort().slice(0, 32)
|
|
79
|
+
: [];
|
|
80
|
+
return {
|
|
81
|
+
diagnostic_version: DIAGNOSTIC_VERSION,
|
|
82
|
+
error_code: safeCode(raw.error_code),
|
|
83
|
+
reason_code: safeCode(raw.reason_code),
|
|
84
|
+
assessment_id: safeAssessmentId(raw.assessment_id),
|
|
85
|
+
assessment_config_sha256: safeHash(raw.assessment_config_sha256),
|
|
86
|
+
current_config_sha256: safeHash(raw.current_config_sha256),
|
|
87
|
+
assessment_config_hash: safeHash(raw.assessment_config_hash),
|
|
88
|
+
current_config_hash: safeHash(raw.current_config_hash),
|
|
89
|
+
snapshot_version: safeText(raw.snapshot_version, 80),
|
|
90
|
+
assessment_fingerprint_version: safeText(raw.assessment_fingerprint_version, 80),
|
|
91
|
+
current_fingerprint_version: safeText(raw.current_fingerprint_version, 80) || ASSESSMENT_SECURITY_SNAPSHOT_VERSION,
|
|
92
|
+
assessment_schema_epoch: safeText(raw.assessment_schema_epoch, 80),
|
|
93
|
+
current_schema_epoch: safeText(raw.current_schema_epoch, 80) || TOOL_SCHEMA_EPOCH,
|
|
94
|
+
assessment_created_at: safeTimestamp(raw.assessment_created_at),
|
|
95
|
+
assessment_expires_at: safeTimestamp(raw.assessment_expires_at),
|
|
96
|
+
config_changed: raw.config_changed === true,
|
|
97
|
+
changed_field_names: Array.isArray(raw.changed_field_names)
|
|
98
|
+
? [...new Set(raw.changed_field_names.map(safeCode).filter(Boolean))].sort().slice(0, 32)
|
|
99
|
+
: [],
|
|
100
|
+
changed_config_sections: changedSections,
|
|
101
|
+
validator_module_path: safeText(raw.validator_module_path, 120) || VALIDATOR_MODULE_PATH,
|
|
102
|
+
validator_build_id: safeText(raw.validator_build_id, 160) || VALIDATOR_BUILD_ID,
|
|
103
|
+
watcher_instance_id: safeInstanceId(raw.watcher_instance_id),
|
|
104
|
+
observed_at: safeTimestamp(raw.observed_at) || new Date(0).toISOString(),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function safeAssessmentId(value) {
|
|
112
|
+
return typeof value === "string" && /^assessment_\d{8}_\d{6}_[0-9a-f]{32}$/.test(value) ? value : null;
|
|
113
|
+
}
|
|
114
|
+
function safeInstanceId(value) {
|
|
115
|
+
return typeof value === "string" && /^[a-zA-Z0-9_-]{1,100}$/.test(value) ? value : null;
|
|
116
|
+
}
|
|
117
|
+
function safeCode(value) {
|
|
118
|
+
return typeof value === "string" && /^[a-z0-9_]{1,80}$/.test(value) ? value : "";
|
|
119
|
+
}
|
|
120
|
+
function safeHash(value) {
|
|
121
|
+
return typeof value === "string" && /^[0-9a-f]{64}$/.test(value) ? value : null;
|
|
122
|
+
}
|
|
123
|
+
function safeText(value, maxLength) {
|
|
124
|
+
return typeof value === "string" && value.length > 0
|
|
125
|
+
? value.replace(/[\r\n]+/g, " ").slice(0, maxLength)
|
|
126
|
+
: null;
|
|
127
|
+
}
|
|
128
|
+
function safeTimestamp(value) {
|
|
129
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value))
|
|
130
|
+
? new Date(value).toISOString()
|
|
131
|
+
: null;
|
|
132
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { PatchWardenConfig } from "../config.js";
|
|
1
2
|
import { type RepoSnapshot } from "../runner/changeCapture.js";
|
|
2
3
|
import type { RiskAssessmentResult } from "../security/riskEngine.js";
|
|
3
4
|
import type { TaskTemplateName, ChangePolicy } from "../tools/taskTemplates.js";
|
|
5
|
+
import { type SecuritySnapshotCategory } from "./securitySnapshot.js";
|
|
4
6
|
export interface AgentAssessmentSummary {
|
|
5
7
|
attempted: boolean;
|
|
6
8
|
status: "completed" | "timed_out" | "non_zero_exit" | "parse_failed" | "read_only_violation" | "spawn_failed" | "not_run";
|
|
@@ -44,6 +46,12 @@ export interface AssessmentRecord {
|
|
|
44
46
|
plan_id: string | null;
|
|
45
47
|
policy_hash: string;
|
|
46
48
|
tool_manifest_sha256: string;
|
|
49
|
+
execution_config_hash?: string;
|
|
50
|
+
execution_config_component_hashes?: Partial<Record<ExecutionConfigChangeCategory, string>>;
|
|
51
|
+
assessment_security_snapshot_version?: string;
|
|
52
|
+
assessment_security_snapshot_sha256?: string;
|
|
53
|
+
assessment_security_snapshot_component_hashes?: Partial<Record<ExecutionConfigChangeCategory, string>>;
|
|
54
|
+
assessment_schema_epoch?: string;
|
|
47
55
|
workspace_fingerprint: string;
|
|
48
56
|
workspace_snapshot_summary: {
|
|
49
57
|
head: string | null;
|
|
@@ -62,10 +70,15 @@ export interface AssessmentRecord {
|
|
|
62
70
|
agent: string;
|
|
63
71
|
timeout_seconds?: number;
|
|
64
72
|
change_policy?: ChangePolicy;
|
|
73
|
+
scope?: string[];
|
|
74
|
+
forbidden?: string[];
|
|
75
|
+
verification?: string[];
|
|
76
|
+
done_evidence?: string[];
|
|
65
77
|
requires_confirm: boolean;
|
|
66
78
|
confirmed: boolean;
|
|
67
79
|
confirmed_at: string | null;
|
|
68
80
|
confirm_code: string | null;
|
|
81
|
+
used_at?: string | null;
|
|
69
82
|
agent_assessment_summary?: AgentAssessmentSummary | null;
|
|
70
83
|
}
|
|
71
84
|
export interface AssessmentCreateInput {
|
|
@@ -85,6 +98,10 @@ export interface AssessmentCreateInput {
|
|
|
85
98
|
agent: string;
|
|
86
99
|
timeout_seconds?: number;
|
|
87
100
|
change_policy?: ChangePolicy;
|
|
101
|
+
scope?: string[];
|
|
102
|
+
forbidden?: string[];
|
|
103
|
+
verification?: string[];
|
|
104
|
+
done_evidence?: string[];
|
|
88
105
|
snapshot: RepoSnapshot;
|
|
89
106
|
assessment_id?: string;
|
|
90
107
|
assessment_dir?: string;
|
|
@@ -94,7 +111,11 @@ export interface AssessmentValidationResult {
|
|
|
94
111
|
valid: boolean;
|
|
95
112
|
failure_reason: string | null;
|
|
96
113
|
assessment: AssessmentRecord | null;
|
|
114
|
+
expected_hash?: string;
|
|
115
|
+
actual_hash?: string;
|
|
116
|
+
config_change_categories?: ExecutionConfigChangeCategory[];
|
|
97
117
|
}
|
|
118
|
+
export type ExecutionConfigChangeCategory = SecuritySnapshotCategory | "allowed_test_commands" | "execution_config";
|
|
98
119
|
export interface AssessmentConfirmationResult {
|
|
99
120
|
assessment_id: string;
|
|
100
121
|
decision: "needs_confirm";
|
|
@@ -111,6 +132,8 @@ export declare function generateAssessmentId(): string;
|
|
|
111
132
|
export declare function createAssessmentDir(assessmentId: string): string;
|
|
112
133
|
export declare function createAssessment(input: AssessmentCreateInput): AssessmentRecord;
|
|
113
134
|
export declare function readAssessment(assessmentId: string): AssessmentRecord;
|
|
135
|
+
/** Atomically consume an Assessment before creating its one allowed task. */
|
|
136
|
+
export declare function markAssessmentUsed(assessmentId: string): AssessmentRecord;
|
|
114
137
|
export declare function validateAssessmentFreshness(assessmentId: string, currentSnapshot: RepoSnapshot, options?: AssessmentValidationOptions): AssessmentValidationResult;
|
|
115
138
|
/**
|
|
116
139
|
* Records an explicit confirmation performed from the local PatchWarden CLI.
|
|
@@ -130,4 +153,20 @@ interface PolicyHashInput {
|
|
|
130
153
|
schema_epoch: string;
|
|
131
154
|
}
|
|
132
155
|
export declare function computePolicyHash(input: PolicyHashInput): string;
|
|
156
|
+
interface ExecutionConfigHashInput {
|
|
157
|
+
agent: string;
|
|
158
|
+
tool_manifest_sha256: string;
|
|
159
|
+
tool_profile: string;
|
|
160
|
+
repo_path: string;
|
|
161
|
+
change_policy?: string | null;
|
|
162
|
+
template?: string | null;
|
|
163
|
+
verify_commands?: string[];
|
|
164
|
+
test_command?: string | null;
|
|
165
|
+
task_timeout_seconds?: number | null;
|
|
166
|
+
scope?: string[];
|
|
167
|
+
forbidden?: string[];
|
|
168
|
+
verification?: string[];
|
|
169
|
+
done_evidence?: string[];
|
|
170
|
+
}
|
|
171
|
+
export declare function computeExecutionConfigHash(config: PatchWardenConfig, input: ExecutionConfigHashInput): string;
|
|
133
172
|
export {};
|
|
@@ -8,8 +8,11 @@ import { redactSensitiveContent } from "../security/contentRedaction.js";
|
|
|
8
8
|
import { atomicWriteJsonFileSync } from "../utils/atomicFile.js";
|
|
9
9
|
import { withFileLockSync } from "../utils/lockedJsonFile.js";
|
|
10
10
|
import { TOOL_SCHEMA_EPOCH } from "../version.js";
|
|
11
|
-
import {
|
|
11
|
+
import { buildToolCatalogSnapshot, resolveToolProfile, } from "../tools/catalog/toolCatalog.js";
|
|
12
|
+
import { getToolDefs } from "../tools/definitions/toolDefs.js";
|
|
12
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";
|
|
13
16
|
const SENSITIVE_PATH_RULES_SIGNATURE = "v1";
|
|
14
17
|
const ARTIFACT_RULES_SIGNATURE = "v1";
|
|
15
18
|
export function generateAssessmentId() {
|
|
@@ -35,7 +38,7 @@ export function createAssessment(input) {
|
|
|
35
38
|
const planHash = input.plan_content
|
|
36
39
|
? createHash("sha256").update(input.plan_content).digest("hex")
|
|
37
40
|
: null;
|
|
38
|
-
const toolManifest =
|
|
41
|
+
const toolManifest = getCurrentToolManifest(config);
|
|
39
42
|
const workspaceFingerprint = computeWorkspaceFingerprint(input.snapshot);
|
|
40
43
|
const snapshotTruncated = input.snapshot.warnings.some((w) => w.includes("snapshot limited"));
|
|
41
44
|
const policyHash = computePolicyHash({
|
|
@@ -48,6 +51,21 @@ export function createAssessment(input) {
|
|
|
48
51
|
artifact_rules: ARTIFACT_RULES_SIGNATURE,
|
|
49
52
|
schema_epoch: TOOL_SCHEMA_EPOCH,
|
|
50
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
|
+
});
|
|
51
69
|
const now = new Date();
|
|
52
70
|
const expiresAt = new Date(now.getTime() + config.assessmentTtlSeconds * 1000).toISOString();
|
|
53
71
|
const record = {
|
|
@@ -62,6 +80,12 @@ export function createAssessment(input) {
|
|
|
62
80
|
plan_id: input.plan_id,
|
|
63
81
|
policy_hash: policyHash,
|
|
64
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,
|
|
65
89
|
workspace_fingerprint: workspaceFingerprint,
|
|
66
90
|
workspace_snapshot_summary: {
|
|
67
91
|
head: input.snapshot.head,
|
|
@@ -80,16 +104,24 @@ export function createAssessment(input) {
|
|
|
80
104
|
agent: input.agent,
|
|
81
105
|
timeout_seconds: input.timeout_seconds,
|
|
82
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 || [],
|
|
83
111
|
requires_confirm: input.decision === "needs_confirm",
|
|
84
112
|
confirmed: false,
|
|
85
113
|
confirmed_at: null,
|
|
86
114
|
confirm_code: null,
|
|
115
|
+
used_at: null,
|
|
87
116
|
agent_assessment_summary: input.agent_assessment_summary || null,
|
|
88
117
|
};
|
|
89
118
|
atomicWriteJsonFileSync(join(dir, "assessment.json"), record);
|
|
90
119
|
return record;
|
|
91
120
|
}
|
|
92
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
|
+
}
|
|
93
125
|
const config = getConfig();
|
|
94
126
|
const assessmentsDir = getAssessmentsDir(config);
|
|
95
127
|
const dir = resolve(assessmentsDir, assessmentId);
|
|
@@ -98,9 +130,36 @@ export function readAssessment(assessmentId) {
|
|
|
98
130
|
if (!existsSync(file)) {
|
|
99
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 });
|
|
100
132
|
}
|
|
101
|
-
|
|
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
|
+
});
|
|
102
158
|
}
|
|
103
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
|
+
}
|
|
104
163
|
let assessment;
|
|
105
164
|
try {
|
|
106
165
|
assessment = readAssessment(assessmentId);
|
|
@@ -124,18 +183,70 @@ export function validateAssessmentFreshness(assessmentId, currentSnapshot, optio
|
|
|
124
183
|
return { valid: false, failure_reason: "assessment_stale_plan", assessment };
|
|
125
184
|
}
|
|
126
185
|
}
|
|
186
|
+
const config = getConfig();
|
|
187
|
+
const currentManifest = getCurrentToolManifest(config);
|
|
127
188
|
if (!options.skip_tool_manifest) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
+
};
|
|
132
201
|
}
|
|
133
202
|
}
|
|
134
203
|
const currentFingerprint = computeWorkspaceFingerprint(currentSnapshot);
|
|
135
204
|
if (currentFingerprint !== assessment.workspace_fingerprint) {
|
|
136
205
|
return { valid: false, failure_reason: "assessment_workspace_changed", assessment };
|
|
137
206
|
}
|
|
138
|
-
|
|
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
|
+
}
|
|
139
250
|
const currentPolicyHash = computePolicyHash({
|
|
140
251
|
change_policy: assessment.change_policy || "repo_scoped_changes",
|
|
141
252
|
template: assessment.template || null,
|
|
@@ -232,10 +343,53 @@ export function computePolicyHash(input) {
|
|
|
232
343
|
function getRepoAllowedCommands(config, repoPath) {
|
|
233
344
|
return getRepoAllowedTestCommands(config, repoPath);
|
|
234
345
|
}
|
|
235
|
-
function
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
+
};
|
|
239
393
|
}
|
|
240
394
|
function extractReason(error) {
|
|
241
395
|
if (error instanceof PatchWardenError)
|