blun-king-cli 9.1.562 → 9.1.564
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/LIESMICH.txt +2 -2
- package/README.md +1 -1
- 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 +1 -1
- package/agent-spine-plugin/blun.plugin.json +3 -3
- 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 +1 -1
- package/agent-spine-plugin/scripts/check-hosts.js +199 -196
- package/agent-spine-plugin/skills/agent-spine/SKILL.md +1 -1
- package/agent-spine-plugin/src/cli.js +390 -7
- package/agent-spine-plugin/src/hook.js +130 -11
- package/agent-spine-plugin/src/index.js +4 -2
- package/agent-spine-plugin/src/lib/audit.js +20 -2
- package/agent-spine-plugin/src/lib/filesystem-retry.js +5 -0
- package/agent-spine-plugin/src/lib/hook-audit.js +29 -0
- package/agent-spine-plugin/src/lib/learning.js +5575 -128
- package/agent-spine-plugin/src/lib/owned-file-lock.js +143 -0
- package/agent-spine-plugin/src/lib/selfstarter.js +52 -5
- package/agent-spine-plugin/src/lib/source-roots.js +36 -20
- package/agent-spine-plugin/src/version.js +1 -1
- package/blun.mjs +8 -1
- package/package.json +1 -1
|
@@ -5,9 +5,10 @@ import { recordAttentionEvent } from "./lib/attention.js";
|
|
|
5
5
|
import { catalogForStateRoot, saveCatalog } from "./lib/catalog.js";
|
|
6
6
|
import { loadGraph } from "./lib/graph.js";
|
|
7
7
|
import { canonicalPath } from "./lib/paths.js";
|
|
8
|
-
import { resolveHostSourceCatalog } from "./lib/source-roots.js";
|
|
8
|
+
import { isInaccessibleScanError, resolveHostSourceCatalog } from "./lib/source-roots.js";
|
|
9
9
|
import { sessionBriefing } from "./lib/briefing.js";
|
|
10
10
|
import { captureContinuityPrompt, loadContinuity } from "./lib/continuity.js";
|
|
11
|
+
import { recordLearningApplications, recordLearningDeliveries } from "./lib/learning.js";
|
|
11
12
|
import {
|
|
12
13
|
authorizeJobEffect, checkpointJobEffect, closeJobLease, resolveSessionJob, startOrResumeJob
|
|
13
14
|
} from "./lib/selfstarter.js";
|
|
@@ -15,6 +16,7 @@ import { claimChannelEvent } from "./lib/channel-runtime.js";
|
|
|
15
16
|
import { syncPersonaRosterFromEnvironment } from "./lib/persona-runtime.js";
|
|
16
17
|
import { captureMustRememberPrompt, recordPreflightFailure, runPreflight, verifyPreflightReceipt } from "./lib/preflight.js";
|
|
17
18
|
import { isMainModule } from "./lib/runtime.js";
|
|
19
|
+
import { recordHookScanAudit } from "./lib/hook-audit.js";
|
|
18
20
|
|
|
19
21
|
const MAX_STDIN_BYTES = 64 * 1024;
|
|
20
22
|
const STANDARD_HOST_CONTEXT_BYTES = 9500;
|
|
@@ -26,15 +28,25 @@ const KNOWN_EVENTS = new Set([
|
|
|
26
28
|
const ID_RE = /^[A-Za-z0-9][A-Za-z0-9:_.@/-]{0,127}$/;
|
|
27
29
|
const ATTENTION_WRITE_EVENTS = new Set(["UserPromptSubmit", "PostToolUse", "Stop", "SubagentStop"]);
|
|
28
30
|
const SELFSTART_EVENTS = new Set(["SessionStart", "PostCompact"]);
|
|
31
|
+
const SILENT_OVERSIZE_POST_TOOL_USE = Symbol("silent-oversize-post-tool-use");
|
|
32
|
+
const SILENT_OVERSIZE_POST_TOOL_USE_ARG = "--silent-oversize-post-tool-use";
|
|
29
33
|
|
|
30
|
-
async function readStdin() {
|
|
31
|
-
|
|
34
|
+
async function readStdin({ silentOversizePostToolUse = false } = {}) {
|
|
35
|
+
const chunks = [];
|
|
32
36
|
let bytes = 0;
|
|
37
|
+
let oversized = false;
|
|
33
38
|
for await (const chunk of process.stdin) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
40
|
+
const remaining = Math.max(0, MAX_STDIN_BYTES - bytes);
|
|
41
|
+
if (remaining) chunks.push(buffer.subarray(0, remaining));
|
|
42
|
+
bytes += buffer.length;
|
|
43
|
+
if (bytes > MAX_STDIN_BYTES) oversized = true;
|
|
37
44
|
}
|
|
45
|
+
if (oversized) {
|
|
46
|
+
if (silentOversizePostToolUse) return SILENT_OVERSIZE_POST_TOOL_USE;
|
|
47
|
+
throw new Error("hook input exceeds the 64 KiB limit");
|
|
48
|
+
}
|
|
49
|
+
const value = Buffer.concat(chunks, bytes).toString("utf8");
|
|
38
50
|
const parsed = value.trim() ? JSON.parse(value) : {};
|
|
39
51
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("hook input must be one JSON object");
|
|
40
52
|
return parsed;
|
|
@@ -196,6 +208,7 @@ function renderContext(event, catalog, briefing, signal = null, attentionEvent =
|
|
|
196
208
|
createdAt: preflight.receipt.createdAt,
|
|
197
209
|
expiresAt: preflight.receipt.expiresAt,
|
|
198
210
|
policy: preflight.policy,
|
|
211
|
+
learningApplications: preflight.learningApplications || null,
|
|
199
212
|
pendingMustRemember: preflight.pendingMustRemember ? {
|
|
200
213
|
id: preflight.pendingMustRemember.candidate?.id || null,
|
|
201
214
|
status: preflight.pendingMustRemember.candidate?.status || (preflight.pendingMustRemember.rejected ? "rejected" : null),
|
|
@@ -459,6 +472,30 @@ function isMutationTool(name = "") {
|
|
|
459
472
|
return /(^|__)(apply_patch|edit|write|delete|move|rename|bash|exec_command|shell)(_|$)/i.test(name);
|
|
460
473
|
}
|
|
461
474
|
|
|
475
|
+
function filesystemScanError(error) {
|
|
476
|
+
return Boolean(error && (isInaccessibleScanError(error)
|
|
477
|
+
|| error.code === "AGENTSPINE_SCAN_INCOMPLETE" || error.agentSpineScan === true));
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async function auditSkippedScans(input, phase, skipped = []) {
|
|
481
|
+
for (const item of skipped) {
|
|
482
|
+
await recordHookScanAudit({
|
|
483
|
+
event: "PreToolUse", toolName: input.tool_name || null, phase,
|
|
484
|
+
error: { code: item.code, message: `${item.code}: ${item.kind || item.operation || "scan"} skipped ${item.relativePath || item.path}` },
|
|
485
|
+
path: item.path || item.relativePath, operation: item.operation || item.kind,
|
|
486
|
+
now: input.timestamp || new Date()
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
async function allowScanFailure(input, phase, error) {
|
|
492
|
+
await recordHookScanAudit({
|
|
493
|
+
event: "PreToolUse", toolName: input.tool_name || null, phase, error,
|
|
494
|
+
path: error?.path || input.cwd || process.cwd(), now: input.timestamp || new Date()
|
|
495
|
+
});
|
|
496
|
+
return { blocked: false, degraded: true, scanFailedOpen: true, phase, error: error.message };
|
|
497
|
+
}
|
|
498
|
+
|
|
462
499
|
function stringValues(value, output = []) {
|
|
463
500
|
if (typeof value === "string") output.push(value);
|
|
464
501
|
else if (Array.isArray(value)) value.forEach((item) => stringValues(item, output));
|
|
@@ -501,8 +538,9 @@ function blockPrompt(reason) {
|
|
|
501
538
|
})}\n`);
|
|
502
539
|
}
|
|
503
540
|
|
|
504
|
-
export async function runHook(payload = null) {
|
|
505
|
-
const input = payload || await readStdin();
|
|
541
|
+
export async function runHook(payload = null, options = {}) {
|
|
542
|
+
const input = payload || await readStdin(options);
|
|
543
|
+
if (input === SILENT_OVERSIZE_POST_TOOL_USE) return;
|
|
506
544
|
if (!input || typeof input !== "object" || Array.isArray(input)) throw new Error("hook input must be one JSON object");
|
|
507
545
|
const event = input.hook_event_name || input.event_name || "";
|
|
508
546
|
if (!KNOWN_EVENTS.has(event)) throw new Error(`unsupported hook event: ${event || "missing"}`);
|
|
@@ -533,6 +571,19 @@ export async function runHook(payload = null) {
|
|
|
533
571
|
resolvedSources = await resolveHostSourceCatalog({ host: instructionHost, cwd, input });
|
|
534
572
|
} catch (error) {
|
|
535
573
|
const reason = `AgentSpine source resolution failed closed: ${error.message}`;
|
|
574
|
+
if (event === "PreToolUse" && isMutationTool(input.tool_name) && filesystemScanError(error)) {
|
|
575
|
+
const allowed = await allowScanFailure(input, "source-resolution", error);
|
|
576
|
+
const context = JSON.stringify({
|
|
577
|
+
schema: "agentspine.hook-context/v1", event, loaded: false, failedClosed: false,
|
|
578
|
+
indexedSources: 0,
|
|
579
|
+
sourceResolution: { status: "degraded", reason: error.message, auditFinding: "inaccessible-source-scan" },
|
|
580
|
+
instruction: "The filesystem scan was incomplete. The requested tool remains governed by native host permissions.",
|
|
581
|
+
authority: "context-only"
|
|
582
|
+
});
|
|
583
|
+
if (payload) return { ...allowed, context };
|
|
584
|
+
process.stdout.write("{}\n");
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
536
587
|
if (event === "PreToolUse" && isMutationTool(input.tool_name)) {
|
|
537
588
|
if (payload) return { blocked: true, failedClosed: true, reason };
|
|
538
589
|
deny(reason);
|
|
@@ -560,6 +611,12 @@ export async function runHook(payload = null) {
|
|
|
560
611
|
let scope = null;
|
|
561
612
|
let selfstarter = null;
|
|
562
613
|
let channelEvent = null;
|
|
614
|
+
let learningDelivery = null;
|
|
615
|
+
|
|
616
|
+
if (event === "PreToolUse" && isMutationTool(input.tool_name)
|
|
617
|
+
&& resolvedSources.diagnostics.skippedInaccessibleDirectories?.length) {
|
|
618
|
+
await auditSkippedScans(input, "source-resolution", resolvedSources.diagnostics.skippedInaccessibleDirectories);
|
|
619
|
+
}
|
|
563
620
|
|
|
564
621
|
if (event === "PreToolUse" && isMutationTool(input.tool_name)) {
|
|
565
622
|
const { graph } = await loadGraph(root, catalog);
|
|
@@ -610,6 +667,12 @@ export async function runHook(payload = null) {
|
|
|
610
667
|
});
|
|
611
668
|
}
|
|
612
669
|
} catch (error) {
|
|
670
|
+
if (isMutationTool(input.tool_name) && filesystemScanError(error)) {
|
|
671
|
+
const allowed = await allowScanFailure(input, "self-starter", error);
|
|
672
|
+
if (payload) return allowed;
|
|
673
|
+
process.stdout.write("{}\n");
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
613
676
|
const reason = `AgentSpine self-starter denied this effect: ${error.message}`;
|
|
614
677
|
if (payload) return { blocked: true, reason, selfstarter: { allowed: false, reason: error.message } };
|
|
615
678
|
deny(reason);
|
|
@@ -637,6 +700,19 @@ export async function runHook(payload = null) {
|
|
|
637
700
|
if (["Stop", "SubagentStop"].includes(event)) {
|
|
638
701
|
scope ||= await runtimeScope(input, root, resolvedSources.userStateRoot, catalog);
|
|
639
702
|
const exact = await selfstarterScope(input, scope, root, "resume");
|
|
703
|
+
if (exact && !scope.currentTaskId) scope.currentTaskId = exact.taskId;
|
|
704
|
+
try {
|
|
705
|
+
learningDelivery = await recordLearningDeliveries({
|
|
706
|
+
root, sessionId: sessionId(input), hookEvent: event,
|
|
707
|
+
scope: {
|
|
708
|
+
personaId: scope.entityId, userId: scope.userId, tenantId: scope.tenantId,
|
|
709
|
+
projectId: scope.projectId, groupId: scope.groupId, taskId: scope.currentTaskId
|
|
710
|
+
},
|
|
711
|
+
completedAt: input.timestamp || new Date()
|
|
712
|
+
});
|
|
713
|
+
} catch (error) {
|
|
714
|
+
learningDelivery = { status: "degraded", receipts: [], reason: error.message, authority: "context-only" };
|
|
715
|
+
}
|
|
640
716
|
const requested = selfstarterInput(input);
|
|
641
717
|
if (exact) {
|
|
642
718
|
selfstarter = await closeJobLease({
|
|
@@ -694,7 +770,7 @@ export async function runHook(payload = null) {
|
|
|
694
770
|
catalog, userStateRoot: resolvedSources.userStateRoot, sourceDiagnostics: resolvedSources.diagnostics,
|
|
695
771
|
prompt: event === "UserPromptSubmit" ? promptFromInput(input) : null
|
|
696
772
|
});
|
|
697
|
-
|
|
773
|
+
let context = renderContext(event, catalog, briefing, signal, attentionEvent, selfstarter, channelEvent, resolvedSources.diagnostics, preflight);
|
|
698
774
|
if (event === "UserPromptSubmit" && Buffer.byteLength(context) > hostContextLimit(preflight)) {
|
|
699
775
|
throw new Error("mandatory preflight context exceeds the host hook injection limit");
|
|
700
776
|
}
|
|
@@ -702,6 +778,45 @@ export async function runHook(payload = null) {
|
|
|
702
778
|
receipt: preflight.receipt, input, scope, resolvedSources, prompt: promptFromInput(input),
|
|
703
779
|
now: input.timestamp || new Date(), env: process.env, consume: true
|
|
704
780
|
})) throw new Error("preflight receipt could not be consumed atomically for this exact turn");
|
|
781
|
+
if (event === "UserPromptSubmit") {
|
|
782
|
+
const activeCanaries = briefing.learning.filter((item) => ["active", "revalidating"].includes(item.outcomeStatus));
|
|
783
|
+
if (!activeCanaries.length) {
|
|
784
|
+
preflight.learningApplications = {
|
|
785
|
+
status: "not-applicable", receipts: [], authority: "context-only"
|
|
786
|
+
};
|
|
787
|
+
} else try {
|
|
788
|
+
const application = await recordLearningApplications({
|
|
789
|
+
root, items: briefing.learning,
|
|
790
|
+
scope: {
|
|
791
|
+
personaId: scope.entityId, userId: scope.userId, tenantId: scope.tenantId,
|
|
792
|
+
projectId: scope.projectId, groupId: scope.groupId, taskId: scope.currentTaskId
|
|
793
|
+
},
|
|
794
|
+
preflightReceipt: preflight.receipt,
|
|
795
|
+
sessionBriefingDigest: createHash("sha256").update(JSON.stringify(briefing)).digest("hex"),
|
|
796
|
+
projectedAt: input.timestamp || new Date()
|
|
797
|
+
});
|
|
798
|
+
preflight.learningApplications = {
|
|
799
|
+
status: application.receipts.length ? "recorded" : "not-applicable",
|
|
800
|
+
receipts: application.receipts.map((item) => ({
|
|
801
|
+
id: item.id, learningId: item.learningId, projectedAt: item.projectedAt, expiresAt: item.expiresAt
|
|
802
|
+
})),
|
|
803
|
+
authority: "context-only"
|
|
804
|
+
};
|
|
805
|
+
} catch (error) {
|
|
806
|
+
briefing.learning = briefing.learning.filter((item) => !["active", "revalidating"].includes(item.outcomeStatus));
|
|
807
|
+
preflight.learningApplications = {
|
|
808
|
+
status: "degraded", receipts: [], reason: error.message, authority: "context-only"
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
const enriched = renderContext(event, catalog, briefing, signal, attentionEvent, selfstarter, channelEvent,
|
|
812
|
+
resolvedSources.diagnostics, preflight);
|
|
813
|
+
if (Buffer.byteLength(enriched) <= hostContextLimit(preflight)) context = enriched;
|
|
814
|
+
else if (preflight.learningApplications.status === "degraded") {
|
|
815
|
+
preflight.learningApplications = null;
|
|
816
|
+
context = renderContext(event, catalog, briefing, signal, attentionEvent, selfstarter, channelEvent,
|
|
817
|
+
resolvedSources.diagnostics, preflight);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
705
820
|
if (payload) return { blocked: false, context, briefing, preflight, signal, attentionEvent, channelEvent, catalogPath };
|
|
706
821
|
process.stdout.write(`${JSON.stringify(hookOutput(event, context))}\n`);
|
|
707
822
|
return;
|
|
@@ -728,12 +843,16 @@ export async function runHook(payload = null) {
|
|
|
728
843
|
}
|
|
729
844
|
}
|
|
730
845
|
|
|
731
|
-
if (payload) return { blocked: false, attentionEvent, selfstarter };
|
|
846
|
+
if (payload) return { blocked: false, attentionEvent, selfstarter, learningDelivery };
|
|
732
847
|
process.stdout.write("{}\n");
|
|
733
848
|
}
|
|
734
849
|
|
|
735
850
|
if (isMainModule(import.meta.url)) {
|
|
736
|
-
|
|
851
|
+
const args = process.argv.slice(2);
|
|
852
|
+
const silentOversizePostToolUse = args.length === 1 && args[0] === SILENT_OVERSIZE_POST_TOOL_USE_ARG;
|
|
853
|
+
const argumentError = args.length && !silentOversizePostToolUse
|
|
854
|
+
? new Error(`unsupported hook argument: ${args[0]}`) : null;
|
|
855
|
+
(argumentError ? Promise.reject(argumentError) : runHook(null, { silentOversizePostToolUse })).catch((error) => {
|
|
737
856
|
process.stderr.write(`AgentSpine hook: ${String(error.message).slice(0, 2048)}\n`);
|
|
738
857
|
// Claude command hooks treat exit 2 as a blocking failure. Exit 1 is
|
|
739
858
|
// fail-open, which is unsafe for malformed pre-answer payloads.
|
|
@@ -12,9 +12,11 @@ export {
|
|
|
12
12
|
recordActivity, recordAttentionEvent, resolveAttention, upsertAttention
|
|
13
13
|
} from "./lib/attention.js";
|
|
14
14
|
export {
|
|
15
|
-
acceptContinuityLearning, addLearningEvidence, configureLearning, deleteLearning, evaluateLearning,
|
|
15
|
+
acceptContinuityLearning, addLearningEvidence, beginLearningRevalidation, configureLearning, deleteLearning, evaluateLearning,
|
|
16
16
|
learningContext, learningOutcomeStatus, loadLearning, proposeLearning, purgeLearningBySubject,
|
|
17
|
-
recordLearningOutcome,
|
|
17
|
+
purgeStaleLearningApplications, recordLearningOutcome, registerLearningEvaluation, renewLearningValidation,
|
|
18
|
+
reviewLearning, revokeLearningApplication, revokeLearningDelivery, revokeLearningEvaluation, revokeLearningEvidence, revokeLearningMeasurement, revokeLearningOutcome, revokeLearningTrialFailure, revokeLearningValidation,
|
|
19
|
+
rollbackLearning
|
|
18
20
|
} from "./lib/learning.js";
|
|
19
21
|
export {
|
|
20
22
|
captureContinuityPrompt, configureContinuity, continuityFindings,
|
|
@@ -47,6 +47,24 @@ function authorityViolations(graph, attention, learning, continuity, coordinatio
|
|
|
47
47
|
]).filter(Boolean),
|
|
48
48
|
...(learning.outcomes || []),
|
|
49
49
|
...(learning.outcomes || []).map((outcome) => outcome.measurement).filter(Boolean),
|
|
50
|
+
...(learning.measurements || []),
|
|
51
|
+
...(learning.measurements || []).map((measurement) => measurement.measurement).filter(Boolean),
|
|
52
|
+
...(learning.measurementLineage || []),
|
|
53
|
+
...(learning.applications || []),
|
|
54
|
+
...(learning.evaluations || []),
|
|
55
|
+
...(learning.evaluatorRegistry || []),
|
|
56
|
+
...(learning.evaluationBindings || []),
|
|
57
|
+
...(learning.validationLeases || []),
|
|
58
|
+
...(learning.trialFailures || []),
|
|
59
|
+
...(learning.trialFailureRevocations || []),
|
|
60
|
+
...(learning.trialRetryExhaustions || []),
|
|
61
|
+
...(learning.evaluationRevocations || []),
|
|
62
|
+
...(learning.validationRevocations || []),
|
|
63
|
+
...(learning.evidenceRevocations || []),
|
|
64
|
+
...(learning.measurementRevocations || []),
|
|
65
|
+
...(learning.applicationRevocations || []),
|
|
66
|
+
...(learning.deliveryRevocations || []),
|
|
67
|
+
...(learning.outcomeRevocations || []),
|
|
50
68
|
...learning.history,
|
|
51
69
|
...learning.history.map((entry) => entry.value).filter(Boolean),
|
|
52
70
|
...continuity.signals,
|
|
@@ -270,7 +288,7 @@ export async function runAudit(root = process.cwd(), { host = null } = {}) {
|
|
|
270
288
|
gate(2, "Discovery", catalog.schema === "agentspine.catalog/v1"
|
|
271
289
|
&& (!host || (sourceResolution?.status === "loaded" && !sourceResolutionError)), sourceResolutionError
|
|
272
290
|
? `${catalog.documents.length} project documents; host-native source resolution failed closed: ${sourceResolutionError}`
|
|
273
|
-
: sourceResolution ? `${sourceResolution.scopes.user} user, ${sourceResolution.scopes.project} project, and ${sourceResolution.scopes["project-memory"]} memory sources; broad home scan disabled`
|
|
291
|
+
: sourceResolution ? `${sourceResolution.scopes.user} user, ${sourceResolution.scopes.project} project, and ${sourceResolution.scopes["project-memory"]} memory sources; broad home scan disabled; ${(sourceResolution.skippedInaccessibleDirectories || []).length} inaccessible directories skipped`
|
|
274
292
|
: `${catalog.documents.length} Markdown documents indexed`),
|
|
275
293
|
gate(3, "State isolation", [catalogPath, graphPath, attentionPath, learningPath, continuityPath,
|
|
276
294
|
policyPath, coordinationPath, executionPolicyPath, selfstarterPath, channelPolicyPath,
|
|
@@ -286,7 +304,7 @@ export async function runAudit(root = process.cwd(), { host = null } = {}) {
|
|
|
286
304
|
gate(7, "Authority boundary", authority.length === 0 && forbidden.length === 0 && policyIssues.length === 0 && coordinationAuthorityIssues.length === 0 && executionPolicyIssues.length === 0 && selfstarterAuthorityIssues.length === 0 && channelPolicyIssues.length === 0 && personaPolicyIssues.length === 0 && gatewayPolicyIssues.length === 0 && sharingAuthorityIssues.length === 0 && !preflightError, preflightError
|
|
287
305
|
? `preflight policy or receipt state failed closed: ${preflightError}`
|
|
288
306
|
: `${authority.length} context authority violations; ${forbidden.length} forbidden entity records; ${policyIssues.length} delegation policy findings; ${coordinationAuthorityIssues.length} assignment findings; ${executionPolicyIssues.length} execution policy findings; ${selfstarterAuthorityIssues.length} self-starter authority findings; ${channelPolicyIssues.length} channel policy findings; ${personaPolicyIssues.length} persona policy findings; ${gatewayPolicyIssues.length} gateway policy findings; ${sharingAuthorityIssues.length} shared authority findings; preflight ${preflight.status}`),
|
|
289
|
-
gate(8, "Context privacy", privacyInvalid.length === 0 && attentionGroupInvalid.length === 0 && attentionConfigValid && attentionIssues.length === 0 && learningIssues.length === 0 && continuityIssues.length === 0 && coordinationContextIssues.length === 0 && selfstarterStateIssues.length === 0 && channelRuntimeIssues.length === 0 && personaStateIssues.length === 0 && gatewayStateIssues.length === 0 && sharingContextIssues.length === 0 && authenticationIssues.length === 0, `${graph.entities.length} entities, ${graph.entityEdges.length} relationships, ${attention.signals.length} attention cues, ${attention.events.length} lifecycle events, ${learning.candidates.length} learning records
|
|
307
|
+
gate(8, "Context privacy", privacyInvalid.length === 0 && attentionGroupInvalid.length === 0 && attentionConfigValid && attentionIssues.length === 0 && learningIssues.length === 0 && continuityIssues.length === 0 && coordinationContextIssues.length === 0 && selfstarterStateIssues.length === 0 && channelRuntimeIssues.length === 0 && personaStateIssues.length === 0 && gatewayStateIssues.length === 0 && sharingContextIssues.length === 0 && authenticationIssues.length === 0, `${graph.entities.length} entities, ${graph.entityEdges.length} relationships, ${attention.signals.length} attention cues, ${attention.events.length} lifecycle events, ${learning.candidates.length} learning records, ${(learning.evaluations || []).length} immutable evaluation contracts, ${(learning.evaluations || []).filter((item) => ["agentspine.learning-evaluation/v9", "agentspine.learning-evaluation/v10", "agentspine.learning-evaluation/v11", "agentspine.learning-evaluation/v12", "agentspine.learning-evaluation/v13", "agentspine.learning-evaluation/v14", "agentspine.learning-evaluation/v15", "agentspine.learning-evaluation/v16", "agentspine.learning-evaluation/v17", "agentspine.learning-evaluation/v18", "agentspine.learning-evaluation/v19", "agentspine.learning-evaluation/v20", "agentspine.learning-evaluation/v21", "agentspine.learning-evaluation/v22", "agentspine.learning-evaluation/v23", "agentspine.learning-evaluation/v24", "agentspine.learning-evaluation/v25"].includes(item.schema)).length} exact-target contracts, ${(learning.evaluations || []).filter((item) => ["agentspine.learning-evaluation/v8", "agentspine.learning-evaluation/v9", "agentspine.learning-evaluation/v10", "agentspine.learning-evaluation/v11", "agentspine.learning-evaluation/v12", "agentspine.learning-evaluation/v13", "agentspine.learning-evaluation/v14", "agentspine.learning-evaluation/v15", "agentspine.learning-evaluation/v16", "agentspine.learning-evaluation/v17", "agentspine.learning-evaluation/v18", "agentspine.learning-evaluation/v19", "agentspine.learning-evaluation/v20", "agentspine.learning-evaluation/v21", "agentspine.learning-evaluation/v22", "agentspine.learning-evaluation/v23", "agentspine.learning-evaluation/v24", "agentspine.learning-evaluation/v25"].includes(item.schema)).length} precommitted initial-trial contracts, ${(learning.evaluations || []).filter((item) => ["agentspine.learning-evaluation/v10", "agentspine.learning-evaluation/v11", "agentspine.learning-evaluation/v12", "agentspine.learning-evaluation/v13", "agentspine.learning-evaluation/v14", "agentspine.learning-evaluation/v15", "agentspine.learning-evaluation/v16", "agentspine.learning-evaluation/v17", "agentspine.learning-evaluation/v18", "agentspine.learning-evaluation/v19", "agentspine.learning-evaluation/v20", "agentspine.learning-evaluation/v21", "agentspine.learning-evaluation/v22", "agentspine.learning-evaluation/v23", "agentspine.learning-evaluation/v24", "agentspine.learning-evaluation/v25"].includes(item.schema)).length} deadline-bound contracts, ${(learning.evaluations || []).filter((item) => ["agentspine.learning-evaluation/v14", "agentspine.learning-evaluation/v15", "agentspine.learning-evaluation/v16", "agentspine.learning-evaluation/v17", "agentspine.learning-evaluation/v18", "agentspine.learning-evaluation/v19", "agentspine.learning-evaluation/v20", "agentspine.learning-evaluation/v21", "agentspine.learning-evaluation/v22", "agentspine.learning-evaluation/v23", "agentspine.learning-evaluation/v24", "agentspine.learning-evaluation/v25"].includes(item.schema)).length} staleness-bound contracts, ${(learning.evaluations || []).filter((item) => ["agentspine.learning-evaluation/v16", "agentspine.learning-evaluation/v17", "agentspine.learning-evaluation/v18", "agentspine.learning-evaluation/v19", "agentspine.learning-evaluation/v20", "agentspine.learning-evaluation/v21", "agentspine.learning-evaluation/v22", "agentspine.learning-evaluation/v23", "agentspine.learning-evaluation/v24", "agentspine.learning-evaluation/v25"].includes(item.schema)).length} promotion-bound contracts, ${(learning.evaluations || []).filter((item) => ["agentspine.learning-evaluation/v18", "agentspine.learning-evaluation/v19", "agentspine.learning-evaluation/v20", "agentspine.learning-evaluation/v21", "agentspine.learning-evaluation/v22", "agentspine.learning-evaluation/v23", "agentspine.learning-evaluation/v24", "agentspine.learning-evaluation/v25"].includes(item.schema)).length} candidate-admission contracts, ${(learning.evaluations || []).filter((item) => ["agentspine.learning-evaluation/v20", "agentspine.learning-evaluation/v21", "agentspine.learning-evaluation/v22", "agentspine.learning-evaluation/v23", "agentspine.learning-evaluation/v24", "agentspine.learning-evaluation/v25"].includes(item.schema)).length} candidate-evidence-cohort contracts, ${(learning.evaluations || []).filter((item) => ["agentspine.learning-evaluation/v22", "agentspine.learning-evaluation/v23", "agentspine.learning-evaluation/v24", "agentspine.learning-evaluation/v25"].includes(item.schema)).length} blocking-defect-bound contracts, ${(learning.evaluations || []).filter((item) => ["agentspine.learning-evaluation/v24", "agentspine.learning-evaluation/v25"].includes(item.schema)).length} evidence-source-bound contracts, ${(learning.evaluations || []).filter((item) => ["agentspine.learning-evaluation/v13", "agentspine.learning-evaluation/v15", "agentspine.learning-evaluation/v17", "agentspine.learning-evaluation/v19", "agentspine.learning-evaluation/v21", "agentspine.learning-evaluation/v23", "agentspine.learning-evaluation/v25"].includes(item.schema)).length} bounded retry contracts, ${(learning.trialRetryExhaustions || []).length} terminal retry-exhaustion receipts, ${(learning.applications || []).filter((item) => ["agentspine.learning-application/v5", "agentspine.learning-application/v6", "agentspine.learning-application/v7"].includes(item.schema)).length} immutable initial-trial admissions, ${(learning.applications || []).filter((item) => ["agentspine.learning-application/v6", "agentspine.learning-application/v7"].includes(item.schema)).length} exact-target admissions, ${(learning.applications || []).filter((item) => item.schema === "agentspine.learning-application/v7").length} deadline-bound admissions, ${(learning.trialFailures || []).length} blocking trial-failure receipts, ${(learning.evaluatorRegistry || []).length} locally confirmed evaluator roots, ${(learning.evaluationBindings || []).length} evaluator-registry bindings, ${(learning.validationLeases || []).length} immutable validation leases, ${(learning.measurements || []).length} immutable measurement runs, ${(learning.applications || []).length} turn-bound projections, ${(learning.deliveries || []).length} completed model-turn deliveries, ${(learning.evaluationRevocations || []).length} evaluation revocations, ${(learning.evidenceRevocations || []).length} evidence revocations, ${(learning.measurementRevocations || []).length} measurement revocations, ${(learning.applicationRevocations || []).length} application revocations, ${(learning.deliveryRevocations || []).length} delivery revocations, ${(learning.outcomeRevocations || []).length} outcome revocations, ${(learning.outcomes || []).length} outcome receipts, ${(learning.outcomes || []).filter((item) => ["agentspine.learning-outcome/v5", "agentspine.learning-outcome/v6", "agentspine.learning-outcome/v7", "agentspine.learning-outcome/v8", "agentspine.learning-outcome/v9"].includes(item.schema)).length} case-coverage receipts, ${(learning.outcomes || []).filter((item) => ["agentspine.learning-outcome/v6", "agentspine.learning-outcome/v7", "agentspine.learning-outcome/v8", "agentspine.learning-outcome/v9"].includes(item.schema)).length} provenance-bound receipts, ${(learning.outcomes || []).filter((item) => ["agentspine.learning-outcome/v7", "agentspine.learning-outcome/v8", "agentspine.learning-outcome/v9"].includes(item.schema)).length} lineage-bound receipts, ${(learning.outcomes || []).filter((item) => ["agentspine.learning-outcome/v8", "agentspine.learning-outcome/v9"].includes(item.schema)).length} paired-evaluator receipts and ${(learning.outcomes || []).filter((item) => item.schema === "agentspine.learning-outcome/v9").length} evaluator-root-bound receipts, ${continuity.signals.length} continuity signals, ${coordination.tasks.length} coordination items, ${selfstarter.jobs.length} self-starter jobs, ${channelRuntime.events.length} channel events, ${personaRuntime.personas.length} authenticated personas, ${gatewayRuntime.queue.length} gateway queue items, ${gatewayRuntime.outbox.length} delivery records, ${sharing.records.length} shared records, ${trust.records.length} trusted keys, ${registry.signers.length} local signers, and ${feedState.feeds.length} feed receipts checked`),
|
|
290
308
|
gate(9, "Context budget", loadedBytes <= context.budget.maxBytes && briefingBudgetValid, briefingError
|
|
291
309
|
? `${loadedBytes}/${context.budget.maxBytes} source bytes; briefing failed closed: ${briefingError}`
|
|
292
310
|
: `${loadedBytes}/${context.budget.maxBytes} source bytes; ${briefingBytes}/${briefing.budget.maxBytes} briefing bytes`),
|
|
@@ -8,6 +8,11 @@ export function isFileLockContention(error, platform = process.platform) {
|
|
|
8
8
|
|| (platform === "win32" && ["EACCES", "EPERM"].includes(error?.code));
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
export function isTransientLockMetadataError(error, platform = process.platform) {
|
|
12
|
+
return error?.code === "ENOENT"
|
|
13
|
+
|| (platform === "win32" && WINDOWS_TRANSIENT_CODES.has(error?.code));
|
|
14
|
+
}
|
|
15
|
+
|
|
11
16
|
export async function replaceFileWithRetry(temporary, target, options = {}) {
|
|
12
17
|
const renameFile = options.renameFile || rename;
|
|
13
18
|
const wait = options.wait || delay;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { appendFile, mkdir } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { stateRoot } from "./paths.js";
|
|
4
|
+
|
|
5
|
+
export function hookScanAuditPath(env = process.env) {
|
|
6
|
+
return join(stateRoot(env), "hook-scan-audit.jsonl");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function recordHookScanAudit({ event = "PreToolUse", toolName = null, phase, error = null,
|
|
10
|
+
path = null, operation = null, now = new Date(), env = process.env }) {
|
|
11
|
+
try {
|
|
12
|
+
const target = hookScanAuditPath(env);
|
|
13
|
+
await mkdir(dirname(target), { recursive: true, mode: 0o700 });
|
|
14
|
+
const record = {
|
|
15
|
+
schema: "agentspine.hook-scan-audit/v1",
|
|
16
|
+
at: new Date(now).toISOString(), event, toolName, phase,
|
|
17
|
+
code: String(error?.code || "SCAN_ERROR").slice(0, 64),
|
|
18
|
+
error: String(error?.message || error || "filesystem scan skipped").slice(0, 2048),
|
|
19
|
+
path: String(path || error?.path || "unknown").slice(0, 4096),
|
|
20
|
+
operation: operation || error?.syscall || null,
|
|
21
|
+
decision: "allow",
|
|
22
|
+
authority: "diagnostic-only"
|
|
23
|
+
};
|
|
24
|
+
await appendFile(target, `${JSON.stringify(record)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
25
|
+
return true;
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|