blun-king-cli 9.1.566 → 9.1.569
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/agent-spine-plugin/.claude-plugin/marketplace.json +1 -1
- package/agent-spine-plugin/.claude-plugin/plugin.json +1 -1
- package/agent-spine-plugin/.codex-plugin/plugin.json +2 -1
- package/agent-spine-plugin/CHANGELOG.md +1581 -0
- package/agent-spine-plugin/README.md +30 -4
- package/agent-spine-plugin/blun.plugin.json +3 -3
- package/agent-spine-plugin/docs/acceptance.md +61 -0
- package/agent-spine-plugin/docs/assignment-continuation.md +48 -0
- package/agent-spine-plugin/docs/host-integration.md +178 -0
- package/agent-spine-plugin/docs/preflight-recall.md +69 -0
- package/agent-spine-plugin/docs/preservation-contract.md +53 -0
- package/agent-spine-plugin/docs/quality-gates.md +50 -0
- package/agent-spine-plugin/docs/releasing.md +85 -0
- package/agent-spine-plugin/docs/session-timeline.md +251 -0
- package/agent-spine-plugin/docs/source-roots.md +113 -0
- package/agent-spine-plugin/docs/structured-completion.md +67 -0
- package/agent-spine-plugin/docs/world-model.md +94 -0
- package/agent-spine-plugin/hooks/codex.json +2 -2
- package/agent-spine-plugin/hooks/hooks.json +1 -1
- package/agent-spine-plugin/hooks/version.json +1 -1
- package/agent-spine-plugin/package.json +13 -3
- package/agent-spine-plugin/scripts/check-codex-install.js +226 -0
- package/agent-spine-plugin/scripts/check-hosts.js +14 -5
- package/agent-spine-plugin/scripts/check-install-hook.js +226 -0
- package/agent-spine-plugin/scripts/check-install-selfstarter.js +154 -0
- package/agent-spine-plugin/scripts/check-install.js +478 -0
- package/agent-spine-plugin/scripts/check-line-budget.js +58 -0
- package/agent-spine-plugin/scripts/check-syntax.js +29 -0
- package/agent-spine-plugin/scripts/github-actions.js +11 -0
- package/agent-spine-plugin/scripts/hermetic-process.js +183 -0
- package/agent-spine-plugin/scripts/release-check.js +145 -0
- package/agent-spine-plugin/scripts/run-acceptance.js +19 -0
- package/agent-spine-plugin/scripts/run-checks.js +47 -0
- package/agent-spine-plugin/scripts/run-tests-hermetic.js +89 -0
- package/agent-spine-plugin/skills/agent-spine/SKILL.md +16 -2
- package/agent-spine-plugin/spine-example/1-identity.md +12 -0
- package/agent-spine-plugin/spine-example/2-voice.md +6 -0
- package/agent-spine-plugin/spine-example/3-conduct.md +8 -0
- package/agent-spine-plugin/spine-example/4-history.md +4 -0
- package/agent-spine-plugin/src/cli-agent.js +296 -0
- package/agent-spine-plugin/src/cli-attention.js +95 -0
- package/agent-spine-plugin/src/cli-autonomy.js +36 -0
- package/agent-spine-plugin/src/cli-common.js +71 -0
- package/agent-spine-plugin/src/cli-continuity.js +116 -0
- package/agent-spine-plugin/src/cli-core.js +128 -0
- package/agent-spine-plugin/src/cli-diagnostics.js +291 -0
- package/agent-spine-plugin/src/cli-host.js +21 -0
- package/agent-spine-plugin/src/cli-learning.js +305 -0
- package/agent-spine-plugin/src/cli-premortem.js +16 -0
- package/agent-spine-plugin/src/cli-sharing.js +230 -0
- package/agent-spine-plugin/src/cli.js +40 -1350
- package/agent-spine-plugin/src/codex-reader-launcher.js +182 -0
- package/agent-spine-plugin/src/hook.js +185 -567
- package/agent-spine-plugin/src/index.js +16 -2
- package/agent-spine-plugin/src/lib/acceptance.js +113 -11
- package/agent-spine-plugin/src/lib/action-lesson-recall.js +53 -0
- package/agent-spine-plugin/src/lib/attention-context.js +167 -0
- package/agent-spine-plugin/src/lib/attention-events.js +113 -0
- package/agent-spine-plugin/src/lib/attention-privacy.js +72 -0
- package/agent-spine-plugin/src/lib/attention-schema.js +164 -0
- package/agent-spine-plugin/src/lib/attention-storage.js +93 -0
- package/agent-spine-plugin/src/lib/attention.js +9 -600
- package/agent-spine-plugin/src/lib/audit-premortem.js +223 -0
- package/agent-spine-plugin/src/lib/audit.js +56 -6
- package/agent-spine-plugin/src/lib/autonomy-policy.js +110 -0
- package/agent-spine-plugin/src/lib/autonomy-store.js +202 -0
- package/agent-spine-plugin/src/lib/autonomy.js +8 -0
- package/agent-spine-plugin/src/lib/briefing.js +67 -4
- package/agent-spine-plugin/src/lib/catalog-document-read.js +51 -0
- package/agent-spine-plugin/src/lib/catalog.js +1 -1
- package/agent-spine-plugin/src/lib/codex-installation.js +231 -0
- package/agent-spine-plugin/src/lib/codex-skill-installation.js +211 -0
- package/agent-spine-plugin/src/lib/context.js +3 -1
- package/agent-spine-plugin/src/lib/delivery-agent-usage.js +224 -0
- package/agent-spine-plugin/src/lib/delivery-assignment.js +220 -0
- package/agent-spine-plugin/src/lib/delivery-command-actions.js +453 -0
- package/agent-spine-plugin/src/lib/delivery-knowledge.js +78 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-binding.js +107 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-closure.js +171 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-codec.js +45 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-correction.js +101 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-file.js +21 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-index.js +493 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-inspection.js +37 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-recovery.js +120 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-rejection.js +65 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-results.js +28 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-session-guard.js +46 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-write-ledger.js +285 -0
- package/agent-spine-plugin/src/lib/delivery-premortem.js +499 -0
- package/agent-spine-plugin/src/lib/delivery-shell-heredoc.js +115 -0
- package/agent-spine-plugin/src/lib/delivery-shell-substitutions.js +111 -0
- package/agent-spine-plugin/src/lib/delivery-shell-wrapper.js +64 -0
- package/agent-spine-plugin/src/lib/delivery-target.js +73 -0
- package/agent-spine-plugin/src/lib/delivery-verification.js +445 -0
- package/agent-spine-plugin/src/lib/documents.js +27 -5
- package/agent-spine-plugin/src/lib/filesystem-retry.js +2 -0
- package/agent-spine-plugin/src/lib/gateway-common.js +68 -0
- package/agent-spine-plugin/src/lib/gateway-control.js +302 -0
- package/agent-spine-plugin/src/lib/gateway-delivery.js +103 -0
- package/agent-spine-plugin/src/lib/gateway-execution.js +350 -0
- package/agent-spine-plugin/src/lib/gateway-host-lifecycle.js +185 -0
- package/agent-spine-plugin/src/lib/gateway-inspection.js +85 -0
- package/agent-spine-plugin/src/lib/gateway-knowledge.js +129 -0
- package/agent-spine-plugin/src/lib/gateway-policy-provenance.js +197 -0
- package/agent-spine-plugin/src/lib/gateway-premortem-disposition.js +82 -0
- package/agent-spine-plugin/src/lib/gateway-premortem.js +363 -0
- package/agent-spine-plugin/src/lib/gateway-runs.js +300 -0
- package/agent-spine-plugin/src/lib/gateway-runtime-identity.js +31 -0
- package/agent-spine-plugin/src/lib/gateway-runtime-records.js +21 -0
- package/agent-spine-plugin/src/lib/gateway-runtime.js +18 -1623
- package/agent-spine-plugin/src/lib/gateway-state-transaction.js +356 -0
- package/agent-spine-plugin/src/lib/gateway-state.js +342 -0
- package/agent-spine-plugin/src/lib/hook-artifact-guards.js +356 -0
- package/agent-spine-plugin/src/lib/hook-audit.js +16 -2
- package/agent-spine-plugin/src/lib/hook-briefing-use.js +68 -0
- package/agent-spine-plugin/src/lib/hook-context.js +424 -0
- package/agent-spine-plugin/src/lib/hook-final-message.js +26 -0
- package/agent-spine-plugin/src/lib/hook-input.js +27 -0
- package/agent-spine-plugin/src/lib/hook-output.js +140 -0
- package/agent-spine-plugin/src/lib/hook-premortem.js +257 -0
- package/agent-spine-plugin/src/lib/hook-process-advisory.js +37 -0
- package/agent-spine-plugin/src/lib/hook-protection.js +95 -0
- package/agent-spine-plugin/src/lib/hook-stop-verification.js +84 -0
- package/agent-spine-plugin/src/lib/hook-timeline.js +91 -0
- package/agent-spine-plugin/src/lib/identifier-analysis.js +446 -0
- package/agent-spine-plugin/src/lib/indexed-memory.js +23 -6
- package/agent-spine-plugin/src/lib/knowledge-evidence.js +431 -0
- package/agent-spine-plugin/src/lib/learning-applications.js +441 -0
- package/agent-spine-plugin/src/lib/learning-candidates.js +274 -0
- package/agent-spine-plugin/src/lib/learning-context.js +130 -0
- package/agent-spine-plugin/src/lib/learning-delivery-contracts.js +310 -0
- package/agent-spine-plugin/src/lib/learning-evaluation-contracts.js +350 -0
- package/agent-spine-plugin/src/lib/learning-evaluation-registration.js +372 -0
- package/agent-spine-plugin/src/lib/learning-evaluation-revocation.js +292 -0
- package/agent-spine-plugin/src/lib/learning-evidence-contracts.js +321 -0
- package/agent-spine-plugin/src/lib/learning-findings.js +458 -0
- package/agent-spine-plugin/src/lib/learning-measurement-contracts.js +285 -0
- package/agent-spine-plugin/src/lib/learning-measurements.js +265 -0
- package/agent-spine-plugin/src/lib/learning-outcome-contracts.js +165 -0
- package/agent-spine-plugin/src/lib/learning-outcomes.js +343 -0
- package/agent-spine-plugin/src/lib/learning-reconciliation.js +290 -0
- package/agent-spine-plugin/src/lib/learning-retry-contracts.js +188 -0
- package/agent-spine-plugin/src/lib/learning-schema.js +229 -0
- package/agent-spine-plugin/src/lib/learning-scope-targets.js +424 -0
- package/agent-spine-plugin/src/lib/learning-state-upgrade.js +477 -0
- package/agent-spine-plugin/src/lib/learning-status-configuration.js +476 -0
- package/agent-spine-plugin/src/lib/learning-storage.js +203 -0
- package/agent-spine-plugin/src/lib/learning-trial-recovery.js +218 -0
- package/agent-spine-plugin/src/lib/learning-validation-contracts.js +375 -0
- package/agent-spine-plugin/src/lib/learning-validation-renewal.js +298 -0
- package/agent-spine-plugin/src/lib/learning-validation-runtime.js +234 -0
- package/agent-spine-plugin/src/lib/learning.js +36 -6923
- package/agent-spine-plugin/src/lib/lesson-recall-session.js +172 -0
- package/agent-spine-plugin/src/lib/mcp-autonomy-tools.js +37 -0
- package/agent-spine-plugin/src/lib/mcp-delivery-completion.js +124 -0
- package/agent-spine-plugin/src/lib/mcp-delivery-tools.js +45 -0
- package/agent-spine-plugin/src/lib/mcp-premortem.js +68 -0
- package/agent-spine-plugin/src/lib/mcp-runtime.js +269 -0
- package/agent-spine-plugin/src/lib/mcp-source-context.js +51 -0
- package/agent-spine-plugin/src/lib/mcp-timeline-tools.js +164 -0
- package/agent-spine-plugin/src/lib/mcp-world-tools.js +52 -0
- package/agent-spine-plugin/src/lib/owned-file-lock.js +30 -10
- package/agent-spine-plugin/src/lib/project-portfolio.js +176 -0
- package/agent-spine-plugin/src/lib/selfstarter-core.js +382 -0
- package/agent-spine-plugin/src/lib/selfstarter-jobs.js +149 -0
- package/agent-spine-plugin/src/lib/selfstarter-lease.js +204 -0
- package/agent-spine-plugin/src/lib/selfstarter-policy.js +90 -0
- package/agent-spine-plugin/src/lib/selfstarter-workspace.js +111 -0
- package/agent-spine-plugin/src/lib/selfstarter.js +11 -911
- package/agent-spine-plugin/src/lib/session-timeline-auth.js +316 -0
- package/agent-spine-plugin/src/lib/session-timeline-codex.js +58 -0
- package/agent-spine-plugin/src/lib/session-timeline-contract.js +48 -0
- package/agent-spine-plugin/src/lib/session-timeline-enrollment-source.js +41 -0
- package/agent-spine-plugin/src/lib/session-timeline-enrollment-storage.js +132 -0
- package/agent-spine-plugin/src/lib/session-timeline-enrollment-transport.js +17 -0
- package/agent-spine-plugin/src/lib/session-timeline-enrollment.js +500 -0
- package/agent-spine-plugin/src/lib/session-timeline-event-extract.js +157 -0
- package/agent-spine-plugin/src/lib/session-timeline-host-origin.js +74 -0
- package/agent-spine-plugin/src/lib/session-timeline-host-receipt.js +117 -0
- package/agent-spine-plugin/src/lib/session-timeline-invocation.js +201 -0
- package/agent-spine-plugin/src/lib/session-timeline-king.js +79 -0
- package/agent-spine-plugin/src/lib/session-timeline-prior.js +59 -0
- package/agent-spine-plugin/src/lib/session-timeline-provider.js +34 -0
- package/agent-spine-plugin/src/lib/session-timeline-query.js +55 -0
- package/agent-spine-plugin/src/lib/session-timeline-results.js +31 -0
- package/agent-spine-plugin/src/lib/session-timeline-root.js +11 -0
- package/agent-spine-plugin/src/lib/session-timeline-search.js +82 -0
- package/agent-spine-plugin/src/lib/session-timeline-sid-acl.js +217 -0
- package/agent-spine-plugin/src/lib/session-timeline-source.js +83 -0
- package/agent-spine-plugin/src/lib/session-timeline-state.js +45 -0
- package/agent-spine-plugin/src/lib/session-timeline-transport.js +50 -0
- package/agent-spine-plugin/src/lib/session-timeline-windows-acl.js +148 -0
- package/agent-spine-plugin/src/lib/session-timeline.js +453 -0
- package/agent-spine-plugin/src/lib/source-roots.js +69 -136
- package/agent-spine-plugin/src/lib/source-tree-scan.js +178 -0
- package/agent-spine-plugin/src/lib/task-knowledge-context.js +78 -0
- package/agent-spine-plugin/src/lib/timeline-tool-guard.js +202 -0
- package/agent-spine-plugin/src/lib/world-knowledge.js +249 -0
- package/agent-spine-plugin/src/lib/world-model.js +278 -0
- package/agent-spine-plugin/src/mcp.js +20 -161
- package/agent-spine-plugin/src/version.js +1 -1
- package/agent-spine-plugin/src/worker.js +22 -5
- package/bin/agent-resume-snapshot.cjs +2 -2
- package/bin/agentspine-king-goal-inbox.mjs +111 -0
- package/bin/agentspine-king-goal-intake.mjs +106 -0
- package/bin/baseline-skill-performance-policy.cjs +1 -16
- package/bin/core-bootstrap.js +2 -0
- package/bin/curiosity-scout-policy.cjs +5 -1
- package/bin/input-draft-persistence.cjs +2 -2
- package/bin/king-tui-function-contract.json +33 -0
- package/bin/launcher-restart-policy.cjs +150 -0
- package/bin/launcher-runtime.js +56 -44
- package/bin/managed-context-startup-policy.cjs +27 -0
- package/bin/managed-plugin-selection.cjs +116 -0
- package/bin/mistake-relevance-policy.cjs +1 -1
- package/bin/observer-hooks.cjs +14 -0
- package/bin/oversized-context-offload-policy.cjs +86 -0
- package/bin/plugin-bootstrap.js +7 -40
- package/bin/proactive-compaction-policy.cjs +1 -1
- package/bin/provider-model-refresh-deadline.cjs +53 -0
- package/bin/provider-model-refresh-policy.cjs +107 -0
- package/bin/release-artifact-freeze-policy.cjs +30 -0
- package/bin/repeated-user-message-projection.cjs +3 -126
- package/bin/research-page-result.cjs +74 -0
- package/bin/runtime-exit-ledger.cjs +1 -0
- package/bin/session-compaction-policy.cjs +84 -0
- package/bin/skill-listing-performance-policy.cjs +2 -2
- package/bin/standard-tools-bootstrap.js +0 -37
- package/bin/subagent-skill-policy.cjs +3 -1
- package/bin/telegram-approval-relay.cjs +12 -7
- package/bin/telegram-private-conversation-policy.cjs +3 -2
- package/bin/telegram-queue-handoff-policy.cjs +24 -0
- package/bin/thinking-activity-status-policy.cjs +1 -1
- package/bin/thinking-only-guard.cjs +16 -12
- package/bin/tool-call-loop-policy.cjs +0 -2
- package/bin/tool-result-offload-policy.cjs +11 -2
- package/bin/tui-functional-contract.cjs +55 -0
- package/bin/update-notice.js +18 -14
- package/bin/user-message-offload-policy.cjs +1 -1
- package/bin/user-prompt-hook-origin-policy.cjs +34 -0
- package/bin/windows-node-crash-dump.cjs +110 -0
- package/blun.mjs +2517 -1048
- package/codebase-index/codebase_index.py +4 -3
- package/package.json +3 -17
- package/standard-skills/research-evidence/SKILL.md +39 -0
- package/standard-skills/research-evidence/references/evidence-format.md +82 -0
- package/standard-skills/research-evidence/scripts/evidence-collection.cjs +254 -0
- package/standard-skills/research-evidence/scripts/score-report.cjs +112 -0
- package/standard-skills/web-lesen/SKILL.md +37 -22
- package/standard-skills/web-lesen/scripts/crawl_public.py +376 -0
- package/telegram-plugin/DELIVERY.md +36 -0
- package/telegram-plugin/bin/telegram-approval-relay.cjs +13 -7
- package/telegram-plugin/bin/telegram-launcher-status-queue.cjs +122 -0
- package/telegram-plugin/bin/telegram-private-conversation-policy.cjs +3 -2
- package/telegram-plugin/bin/telegram-reply-parts.cjs +149 -0
- package/telegram-plugin/dist/bridge.mjs +11 -57
- package/telegram-plugin/dist/mcp-server.mjs +33 -4
- package/agent-spine-plugin/skill/SKILL.md +0 -76
- package/bin/mnemo-connect-heartbeat.cjs +0 -204
- package/bin/mnemo-tool-agent-policy.cjs +0 -22
- package/telegram-plugin/bin/telegram-mnemo-capture.cjs +0 -297
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { createReadStream } from "node:fs";
|
|
4
|
+
import { lstat, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
5
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
6
|
+
import { canonicalPath, stateRoot } from "./paths.js";
|
|
7
|
+
import { undeclaredCalls } from "./identifier-analysis.js";
|
|
8
|
+
import { resolveFinalAssistantMessage } from "./hook-final-message.js";
|
|
9
|
+
import { auditGuard } from "./hook-protection.js";
|
|
10
|
+
|
|
11
|
+
const STAND_SCHEMA = "blun.snapshot-stand/v1";
|
|
12
|
+
const ALLOWLIST_SCHEMA = "agentspine.identifier-allowlist/v1";
|
|
13
|
+
const HEX_RE = /^[a-f0-9]{64}$/i;
|
|
14
|
+
const MAX_STAND_BYTES = 64 * 1024;
|
|
15
|
+
const MAX_ASSIGNMENT_BYTES = 128 * 1024;
|
|
16
|
+
const MAX_SOURCE_BYTES = 200 * 1024;
|
|
17
|
+
const DIRECT_WRITE = /(^|__)(write|edit)(_|$)/i;
|
|
18
|
+
const JAVASCRIPT_FILE = /\.(?:js|mjs|cjs)$/i;
|
|
19
|
+
const IDENTIFIER_STATE_SCHEMA = "agentspine.identifier-state/v1";
|
|
20
|
+
|
|
21
|
+
function configuredExchange(input, cwd) {
|
|
22
|
+
const value = input.agent_spine_exchange_directory ?? input.exchange_directory
|
|
23
|
+
?? process.env.AGENTSPINE_EXCHANGE_DIR;
|
|
24
|
+
return typeof value === "string" && value.trim() ? resolve(cwd, value.trim()) : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function assignmentText(input) {
|
|
28
|
+
const values = [];
|
|
29
|
+
const collect = (value) => {
|
|
30
|
+
if (typeof value === "string") values.push(value);
|
|
31
|
+
else if (Array.isArray(value)) value.forEach(collect);
|
|
32
|
+
else if (value && typeof value === "object") Object.values(value).forEach(collect);
|
|
33
|
+
};
|
|
34
|
+
for (const key of ["agent_spine_assignment", "current_assignment", "assignment"]) collect(input[key]);
|
|
35
|
+
return values.join("\n");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function baselineDigests(content) {
|
|
39
|
+
const values = [];
|
|
40
|
+
for (const match of content.matchAll(/\bBasis\s*:[^\r\n]*?\bsha256\s*[:=]?\s*([a-f0-9]{64}|[a-f0-9]{16})\b/gi)) {
|
|
41
|
+
values.push(match[1].toLowerCase());
|
|
42
|
+
}
|
|
43
|
+
return values;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function boundedText(path, maxBytes) {
|
|
47
|
+
const metadata = await stat(path);
|
|
48
|
+
if (!metadata.isFile() || metadata.size > maxBytes) return null;
|
|
49
|
+
return readFile(path, "utf8");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function assignmentBaselines(input, cwd) {
|
|
53
|
+
const direct = baselineDigests(assignmentText(input));
|
|
54
|
+
if (direct.length) return direct;
|
|
55
|
+
const exchange = configuredExchange(input, cwd);
|
|
56
|
+
if (!exchange) return [];
|
|
57
|
+
let entries;
|
|
58
|
+
try {
|
|
59
|
+
entries = await readdir(exchange, { withFileTypes: true });
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (error.code === "ENOENT") return [];
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
const digests = [];
|
|
65
|
+
for (const entry of entries.filter((item) => item.isFile() && /^AUFTRAG-.*\.md$/i.test(item.name))
|
|
66
|
+
.sort((left, right) => left.name.localeCompare(right.name)).slice(0, 32)) {
|
|
67
|
+
const content = await boundedText(join(exchange, entry.name), MAX_ASSIGNMENT_BYTES);
|
|
68
|
+
if (content !== null) digests.push(...baselineDigests(content));
|
|
69
|
+
}
|
|
70
|
+
return digests;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function verifyBaselineBeforeWrite({ input, cwd }) {
|
|
74
|
+
if ((input.hook_event_name || input.event_name) !== "PreToolUse" || !DIRECT_WRITE.test(input.tool_name || "")) {
|
|
75
|
+
return { status: "not-applicable", blocked: false };
|
|
76
|
+
}
|
|
77
|
+
const standPath = join(cwd, ".blun-snapshot-stand.json");
|
|
78
|
+
let content;
|
|
79
|
+
try {
|
|
80
|
+
content = await boundedText(standPath, MAX_STAND_BYTES);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (error.code === "ENOENT") return { status: "no-stand", blocked: false, path: standPath };
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
if (content === null) return { status: "invalid-stand", blocked: false, path: standPath };
|
|
86
|
+
let stand;
|
|
87
|
+
try {
|
|
88
|
+
stand = JSON.parse(content);
|
|
89
|
+
} catch {
|
|
90
|
+
return { status: "invalid-stand", blocked: false, path: standPath };
|
|
91
|
+
}
|
|
92
|
+
if (!(stand && stand.schema === STAND_SCHEMA && typeof stand.projectId === "string"
|
|
93
|
+
&& stand.projectId && HEX_RE.test(stand.snapshotSha256 || ""))) {
|
|
94
|
+
return { status: "invalid-stand", blocked: false, path: standPath };
|
|
95
|
+
}
|
|
96
|
+
const baselines = [...new Set(await assignmentBaselines(input, cwd))];
|
|
97
|
+
if (!baselines.length) return { status: "no-baseline", blocked: false, path: standPath };
|
|
98
|
+
if (baselines.length !== 1) return { status: "ambiguous-baseline", blocked: false, path: standPath };
|
|
99
|
+
const required = baselines[0];
|
|
100
|
+
const actual = stand.snapshotSha256.toLowerCase();
|
|
101
|
+
if (!actual.startsWith(required)) {
|
|
102
|
+
return {
|
|
103
|
+
status: "mismatch", blocked: true, path: standPath, requiredDigest: required, snapshotDigest: actual,
|
|
104
|
+
reason: `AgentSpine baseline mismatch: required sha256 ${required}; snapshot stand sha256 ${actual}.`
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return { status: "verified", blocked: false, path: standPath, requiredDigest: required, snapshotDigest: actual };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function writtenPath(input, cwd) {
|
|
111
|
+
const value = input.tool_input ?? input.tool_args;
|
|
112
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
113
|
+
const path = value.file_path ?? value.path ?? value.target_file ?? value.filename;
|
|
114
|
+
return typeof path === "string" && path ? resolve(cwd, path) : null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function toolDeliveryId(input) {
|
|
118
|
+
const value = input.tool_use_id ?? input.toolUseId ?? input.event_id ?? input.hook_event_id;
|
|
119
|
+
return typeof value === "string" && value.length > 0 && value.length <= 512 ? value : null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function identifierStatePath(root, path, kind, deliveryId = null) {
|
|
123
|
+
const project = createHash("sha256").update(resolve(root)).digest("hex").slice(0, 32);
|
|
124
|
+
const file = createHash("sha256").update(resolve(path)).digest("hex");
|
|
125
|
+
const suffix = kind === "pending"
|
|
126
|
+
? `${file}.${createHash("sha256").update(deliveryId || "unbound").digest("hex")}.json`
|
|
127
|
+
: `${file}.json`;
|
|
128
|
+
return join(stateRoot(), "identifier-guard", project, kind, suffix);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function writeIdentifierState(root, path, kind, deliveryId, state) {
|
|
132
|
+
const target = identifierStatePath(root, path, kind, deliveryId);
|
|
133
|
+
await mkdir(dirname(target), { recursive: true, mode: 0o700 });
|
|
134
|
+
const temporary = `${target}.${process.pid}.${Date.now()}.tmp`;
|
|
135
|
+
await writeFile(temporary, `${JSON.stringify(state)}\n`, { mode: 0o600 });
|
|
136
|
+
await rename(temporary, target);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function readIdentifierState(root, path, kind, deliveryId) {
|
|
140
|
+
const target = identifierStatePath(root, path, kind, deliveryId);
|
|
141
|
+
let content;
|
|
142
|
+
try {
|
|
143
|
+
content = await boundedText(target, MAX_STAND_BYTES);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
if (error.code === "ENOENT") return null;
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
if (content === null) return null;
|
|
149
|
+
try {
|
|
150
|
+
const state = JSON.parse(content);
|
|
151
|
+
if (!(state?.schema === IDENTIFIER_STATE_SCHEMA && state.path === resolve(path)
|
|
152
|
+
&& Array.isArray(state.names) && state.names.every((name) => /^[A-Za-z_$][\w$]*$/.test(name))
|
|
153
|
+
&& typeof state.existed === "boolean")) return null;
|
|
154
|
+
return state;
|
|
155
|
+
} catch {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function stateFor(path, existed, findings, source) {
|
|
161
|
+
return {
|
|
162
|
+
schema: IDENTIFIER_STATE_SCHEMA, path: resolve(path), existed,
|
|
163
|
+
names: [...new Set(findings.map((item) => item.name))].sort(),
|
|
164
|
+
sourceDigest: source === null ? null : createHash("sha256").update(source).digest("hex"),
|
|
165
|
+
authority: "diagnostic-only"
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function originalSource(input, current) {
|
|
170
|
+
const value = input.tool_input ?? input.tool_args;
|
|
171
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
172
|
+
for (const key of ["original_content", "previous_content", "before_content"]) {
|
|
173
|
+
if (typeof value[key] === "string" && Buffer.byteLength(value[key]) <= MAX_SOURCE_BYTES) return value[key];
|
|
174
|
+
}
|
|
175
|
+
if (typeof value.old_string !== "string" || typeof value.new_string !== "string") return null;
|
|
176
|
+
if (value.new_string.length === 0) return null;
|
|
177
|
+
if (value.replace_all === true) return current.split(value.new_string).join(value.old_string);
|
|
178
|
+
const index = current.indexOf(value.new_string);
|
|
179
|
+
return index < 0 ? null : current.slice(0, index) + value.old_string + current.slice(index + value.new_string.length);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function parsesJavaScript(source) {
|
|
183
|
+
return spawnSync(process.execPath, ["--check", "-"], {
|
|
184
|
+
input: source, encoding: "utf8", timeout: 2000
|
|
185
|
+
}).status === 0;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export async function captureJavaScriptBeforeWrite({ input, cwd, root }) {
|
|
189
|
+
if ((input.hook_event_name || input.event_name) !== "PreToolUse" || !DIRECT_WRITE.test(input.tool_name || "")) {
|
|
190
|
+
return { status: "not-applicable", blocked: false };
|
|
191
|
+
}
|
|
192
|
+
const path = writtenPath(input, cwd);
|
|
193
|
+
const deliveryId = toolDeliveryId(input);
|
|
194
|
+
if (!path || !JAVASCRIPT_FILE.test(path) || !deliveryId) return { status: "not-applicable", blocked: false, path };
|
|
195
|
+
let source = null;
|
|
196
|
+
try {
|
|
197
|
+
const metadata = await lstat(path);
|
|
198
|
+
if (!metadata.isFile() || metadata.size > MAX_SOURCE_BYTES) {
|
|
199
|
+
return { status: metadata.isFile() ? "skipped-large" : "skipped-non-file", blocked: false, path };
|
|
200
|
+
}
|
|
201
|
+
source = await readFile(path, "utf8");
|
|
202
|
+
} catch (error) {
|
|
203
|
+
if (error.code !== "ENOENT") throw error;
|
|
204
|
+
}
|
|
205
|
+
let findings = [];
|
|
206
|
+
if (source !== null) {
|
|
207
|
+
if (!parsesJavaScript(source)) return { status: "parse-warning", blocked: false, path };
|
|
208
|
+
findings = undeclaredCalls(source, { allowlist: await identifierAllowlist(root) });
|
|
209
|
+
}
|
|
210
|
+
await writeIdentifierState(root, path, "pending", deliveryId, stateFor(path, source !== null, findings, source));
|
|
211
|
+
return { status: "captured", blocked: false, path, findings };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function identifierAllowlist(root) {
|
|
215
|
+
const path = join(root, ".agentspine-identifier-allowlist.json");
|
|
216
|
+
let content;
|
|
217
|
+
try {
|
|
218
|
+
content = await boundedText(path, MAX_STAND_BYTES);
|
|
219
|
+
} catch (error) {
|
|
220
|
+
if (error.code === "ENOENT") return [];
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
if (content === null) return [];
|
|
224
|
+
try {
|
|
225
|
+
const value = JSON.parse(content);
|
|
226
|
+
if (!(value && value.schema === ALLOWLIST_SCHEMA && Array.isArray(value.identifiers)
|
|
227
|
+
&& value.identifiers.every((item) => typeof item === "string" && /^[A-Za-z_$][\w$]*$/.test(item)))) return [];
|
|
228
|
+
return [...new Set(value.identifiers)];
|
|
229
|
+
} catch {
|
|
230
|
+
return [];
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export async function inspectWrittenJavaScript({ input, cwd, root }) {
|
|
235
|
+
if ((input.hook_event_name || input.event_name) !== "PostToolUse" || !DIRECT_WRITE.test(input.tool_name || "")) {
|
|
236
|
+
return { status: "not-applicable", blocked: false, findings: [] };
|
|
237
|
+
}
|
|
238
|
+
const path = writtenPath(input, cwd);
|
|
239
|
+
if (!path || !JAVASCRIPT_FILE.test(path)) return { status: "not-applicable", blocked: false, findings: [] };
|
|
240
|
+
const metadata = await lstat(path);
|
|
241
|
+
if (!metadata.isFile() || metadata.size > MAX_SOURCE_BYTES) {
|
|
242
|
+
return { status: metadata.isFile() ? "skipped-large" : "skipped-non-file", blocked: false, path, findings: [] };
|
|
243
|
+
}
|
|
244
|
+
const syntax = spawnSync(process.execPath, ["--check", path], { encoding: "utf8", timeout: 2000 });
|
|
245
|
+
if (syntax.status !== 0) {
|
|
246
|
+
return { status: "parse-warning", blocked: false, path, findings: [],
|
|
247
|
+
reason: String(syntax.stderr || syntax.error?.message || "JavaScript parse failed").slice(0, 2048) };
|
|
248
|
+
}
|
|
249
|
+
const source = await readFile(path, "utf8");
|
|
250
|
+
let findings;
|
|
251
|
+
const allowlist = await identifierAllowlist(root);
|
|
252
|
+
try {
|
|
253
|
+
findings = undeclaredCalls(source, { allowlist })
|
|
254
|
+
.map((item) => ({ ...item, path }));
|
|
255
|
+
} catch (error) {
|
|
256
|
+
return { status: "parse-warning", blocked: false, path, findings: [], reason: error.message };
|
|
257
|
+
}
|
|
258
|
+
const deliveryId = toolDeliveryId(input);
|
|
259
|
+
const previousSource = originalSource(input, source);
|
|
260
|
+
let previous = null;
|
|
261
|
+
if (previousSource !== null && parsesJavaScript(previousSource)) {
|
|
262
|
+
previous = stateFor(path, true, undeclaredCalls(previousSource, { allowlist }), previousSource);
|
|
263
|
+
} else if (deliveryId) {
|
|
264
|
+
previous = await readIdentifierState(root, path, "pending", deliveryId);
|
|
265
|
+
}
|
|
266
|
+
previous ||= await readIdentifierState(root, path, "last", null);
|
|
267
|
+
const previousNames = previous ? new Set(previous.names) : null;
|
|
268
|
+
const newFindings = previousNames ? findings.filter((item) => !previousNames.has(item.name)) : [];
|
|
269
|
+
const existingFindings = previousNames ? findings.filter((item) => previousNames.has(item.name)) : findings;
|
|
270
|
+
await writeIdentifierState(root, path, "last", null, stateFor(path, true, findings, source));
|
|
271
|
+
if (deliveryId) await rm(identifierStatePath(root, path, "pending", deliveryId), { force: true });
|
|
272
|
+
const warning = existingFindings.map((item) => `${item.path}:${item.line}: ${item.name}`).join("\n");
|
|
273
|
+
if (newFindings.length) {
|
|
274
|
+
const added = newFindings.map((item) => `${item.path}:${item.line}: ${item.name}`).join("\n");
|
|
275
|
+
return {
|
|
276
|
+
status: "new-undeclared-identifiers", blocked: true, path, findings, newFindings, existingFindings,
|
|
277
|
+
reason: `AgentSpine new undeclared calls:\n${added}${warning ? `\nPre-existing warnings:\n${warning}` : ""}`
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
if (!findings.length) return { status: "clean", blocked: false, path, findings, newFindings, existingFindings };
|
|
281
|
+
return {
|
|
282
|
+
status: previousNames ? "pre-existing-undeclared-identifiers" : "unverified-undeclared-identifiers",
|
|
283
|
+
blocked: false, path, findings, newFindings, existingFindings,
|
|
284
|
+
reason: `AgentSpine pre-existing undeclared-call warning (non-blocking):\n${warning}`
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function claimedArtifacts(message) {
|
|
289
|
+
const claims = [];
|
|
290
|
+
for (const line of message.split(/\r?\n/)) {
|
|
291
|
+
const digestMatch = line.match(/\b(?:sha256\s*[:=]?\s*)?([a-f0-9]{64}|[a-f0-9]{16})\b/i);
|
|
292
|
+
if (!digestMatch) continue;
|
|
293
|
+
const before = line.slice(0, digestMatch.index);
|
|
294
|
+
const quoted = [...before.matchAll(/[`'"]([^`'"]+)[`'"]/g)].at(-1)?.[1];
|
|
295
|
+
const plain = [...before.matchAll(/(?:^|\s)([A-Za-z0-9_.-]+(?:[\\/][A-Za-z0-9_. -]+)*\.[A-Za-z0-9_.-]+)/g)].at(-1)?.[1];
|
|
296
|
+
const path = (quoted || plain || "").trim();
|
|
297
|
+
if (path) claims.push({ path, digest: digestMatch[1].toLowerCase() });
|
|
298
|
+
}
|
|
299
|
+
return claims;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function under(directory, path) {
|
|
303
|
+
const child = relative(directory, path);
|
|
304
|
+
return child === "" || (!child.startsWith("..") && !isAbsolute(child));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async function fileDigest(path) {
|
|
308
|
+
const hash = createHash("sha256");
|
|
309
|
+
for await (const chunk of createReadStream(path)) hash.update(chunk);
|
|
310
|
+
return hash.digest("hex");
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export async function verifyDeliveredArtifacts({ input, cwd }) {
|
|
314
|
+
if (!["Stop", "SubagentStop"].includes(input.hook_event_name || input.event_name)) {
|
|
315
|
+
return { status: "not-applicable", blocked: false, mismatches: [] };
|
|
316
|
+
}
|
|
317
|
+
const message = resolveFinalAssistantMessage(input);
|
|
318
|
+
if (["conflict", "malformed"].includes(message.status)) {
|
|
319
|
+
const result = { status: "degraded", blocked: false, mismatches: [], path: cwd,
|
|
320
|
+
reason: `AgentSpine delivery ${message.reason}` };
|
|
321
|
+
await auditGuard(input, "delivery-artifact-guard", result);
|
|
322
|
+
return result;
|
|
323
|
+
}
|
|
324
|
+
const claims = claimedArtifacts(message.text);
|
|
325
|
+
if (!claims.length) return { status: "no-claims", blocked: false, mismatches: [] };
|
|
326
|
+
const configured = configuredExchange(input, cwd);
|
|
327
|
+
if (!configured) return { status: "unconfigured", blocked: false, mismatches: [] };
|
|
328
|
+
const exchange = await canonicalPath(configured);
|
|
329
|
+
const mismatches = [];
|
|
330
|
+
for (const claim of claims) {
|
|
331
|
+
const fromCwd = resolve(cwd, claim.path);
|
|
332
|
+
const target = under(exchange, fromCwd) ? fromCwd : resolve(exchange, claim.path);
|
|
333
|
+
if (!under(exchange, target)) {
|
|
334
|
+
mismatches.push({ path: claim.path, expected: claim.digest, actual: "outside-exchange" });
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
try {
|
|
338
|
+
const canonical = await canonicalPath(target);
|
|
339
|
+
if (!under(exchange, canonical) || !(await lstat(canonical)).isFile()) {
|
|
340
|
+
mismatches.push({ path: claim.path, expected: claim.digest, actual: "missing" });
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
const actual = await fileDigest(canonical);
|
|
344
|
+
if (!actual.startsWith(claim.digest)) mismatches.push({ path: claim.path, expected: claim.digest, actual });
|
|
345
|
+
} catch (error) {
|
|
346
|
+
if (error.code === "ENOENT") mismatches.push({ path: claim.path, expected: claim.digest, actual: "missing" });
|
|
347
|
+
else throw error;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
if (!mismatches.length) return { status: "verified", blocked: false, mismatches: [] };
|
|
351
|
+
return {
|
|
352
|
+
status: "mismatch", blocked: true, mismatches,
|
|
353
|
+
reason: `AgentSpine delivery artifact verification failed:\n${mismatches
|
|
354
|
+
.map((item) => `${item.path}: expected ${item.expected}; actual ${item.actual}`).join("\n")}`
|
|
355
|
+
};
|
|
356
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { appendFile, mkdir, writeFile } from "node:fs/promises";
|
|
2
3
|
import { dirname, join } from "node:path";
|
|
3
4
|
import { stateRoot } from "./paths.js";
|
|
4
5
|
|
|
@@ -7,10 +8,22 @@ export function hookScanAuditPath(env = process.env) {
|
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export async function recordHookScanAudit({ event = "PreToolUse", toolName = null, phase, error = null,
|
|
10
|
-
path = null, operation = null, now = new Date(), env = process.env }) {
|
|
11
|
+
path = null, operation = null, onceKey = null, now = new Date(), env = process.env }) {
|
|
11
12
|
try {
|
|
12
13
|
const target = hookScanAuditPath(env);
|
|
13
14
|
await mkdir(dirname(target), { recursive: true, mode: 0o700 });
|
|
15
|
+
let onceDigest = null;
|
|
16
|
+
if (onceKey) {
|
|
17
|
+
onceDigest = createHash("sha256").update(String(onceKey)).digest("hex");
|
|
18
|
+
const onceDirectory = join(dirname(target), "hook-audit-once");
|
|
19
|
+
await mkdir(onceDirectory, { recursive: true, mode: 0o700 });
|
|
20
|
+
try {
|
|
21
|
+
await writeFile(join(onceDirectory, onceDigest), "", { flag: "wx", mode: 0o600 });
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (error.code === "EEXIST") return false;
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
14
27
|
const record = {
|
|
15
28
|
schema: "agentspine.hook-scan-audit/v1",
|
|
16
29
|
at: new Date(now).toISOString(), event, toolName, phase,
|
|
@@ -18,6 +31,7 @@ export async function recordHookScanAudit({ event = "PreToolUse", toolName = nul
|
|
|
18
31
|
error: String(error?.message || error || "filesystem scan skipped").slice(0, 2048),
|
|
19
32
|
path: String(path || error?.path || "unknown").slice(0, 4096),
|
|
20
33
|
operation: operation || error?.syscall || null,
|
|
34
|
+
onceDigest,
|
|
21
35
|
decision: "allow",
|
|
22
36
|
authority: "diagnostic-only"
|
|
23
37
|
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { verifyPreflightReceipt } from "./preflight.js";
|
|
2
|
+
import { inspectPremortemState } from "./delivery-premortem.js";
|
|
3
|
+
import { recordDeliveryBriefingUse } from "./delivery-agent-usage.js";
|
|
4
|
+
import { canonicalPath, comparablePath, projectId } from "./paths.js";
|
|
5
|
+
|
|
6
|
+
const verified = new WeakMap();
|
|
7
|
+
|
|
8
|
+
// Process-local capability, issued only by the native pre-answer verifier.
|
|
9
|
+
// JSON, MCP arguments and model claims cannot reconstruct this capability.
|
|
10
|
+
export async function consumeHookBriefingOrigin({
|
|
11
|
+
event, input, scope, resolvedSources, preflight, prompt,
|
|
12
|
+
environment = process.env, now = new Date()
|
|
13
|
+
}) {
|
|
14
|
+
if (event !== "UserPromptSubmit" || !input || !scope || !resolvedSources
|
|
15
|
+
|| typeof prompt !== "string") return null;
|
|
16
|
+
try {
|
|
17
|
+
if (!await verifyPreflightReceipt({ receipt: preflight?.receipt, input, scope,
|
|
18
|
+
resolvedSources, prompt, now, env: environment, consume: true })) return null;
|
|
19
|
+
const origin = Object.freeze({});
|
|
20
|
+
verified.set(origin, { receipt: structuredClone(preflight.receipt),
|
|
21
|
+
scope: structuredClone(scope), root: resolvedSources.projectRoot,
|
|
22
|
+
requirementId: preflight.premortem?.requirementId || null });
|
|
23
|
+
return origin;
|
|
24
|
+
} catch { return null; }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function unverified(reason) {
|
|
28
|
+
return { status: "unverified", blocked: false, verified: false, reason,
|
|
29
|
+
automaticRetry: false, completionVerified: false, authority: "context-only" };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function recordHookBriefingUse({ origin, root, now = new Date() }) {
|
|
33
|
+
const proof = verified.get(origin);
|
|
34
|
+
if (!verified.delete(origin)) return unverified("host-briefing-origin-unavailable");
|
|
35
|
+
try {
|
|
36
|
+
if (await canonicalPath(root) !== proof.root || !proof.requirementId) {
|
|
37
|
+
return unverified("host-briefing-requirement-unavailable");
|
|
38
|
+
}
|
|
39
|
+
const { receipt, scope, requirementId } = proof;
|
|
40
|
+
if (Date.parse(receipt.expiresAt) <= new Date(now).getTime()) return unverified("host-briefing-expired");
|
|
41
|
+
const current = await inspectPremortemState({ root, requirementId });
|
|
42
|
+
const binding = current.binding;
|
|
43
|
+
const expected = { host: receipt.host, sessionId: receipt.sessionId,
|
|
44
|
+
entityId: scope.entityId, projectId: scope.projectId || `project:${projectId(comparablePath(root))}`,
|
|
45
|
+
groupId: receipt.groupId,
|
|
46
|
+
taskId: scope.goalId || scope.queueId ? null : receipt.taskId,
|
|
47
|
+
goalId: scope.goalId, goalStepId: scope.goalStepId, queueId: scope.queueId,
|
|
48
|
+
gatewayAttempt: scope.gatewayAttempt, planDefinitionsDigest: scope.planDefinitionsDigest };
|
|
49
|
+
if (current.blocked || current.status === "degraded" || !binding
|
|
50
|
+
|| Object.entries(expected).some(([key, value]) => (binding[key] ?? null) !== (value ?? null))) {
|
|
51
|
+
return unverified("host-briefing-binding-mismatch");
|
|
52
|
+
}
|
|
53
|
+
const usage = await recordDeliveryBriefingUse({ root, requirementId,
|
|
54
|
+
preserveConsumed: true, verifiedHostBriefing: true,
|
|
55
|
+
input: { origin: "verified-host-preflight", receiptId: receipt.id,
|
|
56
|
+
promptDigest: receipt.promptDigest, requirementId },
|
|
57
|
+
result: { briefingDigest: receipt.briefingDigest, sourceSnapshotDigest: receipt.bodyDigest }, now });
|
|
58
|
+
return { ...usage, verified: ["recorded", "duplicate"].includes(usage.status),
|
|
59
|
+
automaticRetry: false, completionVerified: false };
|
|
60
|
+
} catch { return unverified("host-briefing-service-unavailable"); }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Process diagnostics do not change the validity of evidence or native access.
|
|
64
|
+
export function advisoryDeliveryUse(value) {
|
|
65
|
+
return { ...value, blocked: false, verificationBlocked: Boolean(value.blocked),
|
|
66
|
+
verified: ["recorded", "duplicate", "verified", "satisfied-by-host"].includes(value.status),
|
|
67
|
+
completionVerified: false, automaticRetry: false, authority: "context-only" };
|
|
68
|
+
}
|