blun-king-cli 9.1.567 → 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 +7 -56
- 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,171 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
const CLOSURE_SCHEMA = "agentspine.delivery-premortem-closure/v1";
|
|
4
|
+
const ATTACHMENT_SCHEMA = "agentspine.goal-premortem-attachment/v1";
|
|
5
|
+
const ARTIFACT_SCHEMA = "agentspine.delivery-premortem-artifact/v1";
|
|
6
|
+
const AUTHORITY = "context-only";
|
|
7
|
+
const DIGEST_RE = /^[a-f0-9]{64}$/;
|
|
8
|
+
const CATEGORIES = ["baseline-environment", "contract-tests", "delivery-path"];
|
|
9
|
+
const RESULT_LIMIT = 1024;
|
|
10
|
+
const VALUE_LIMIT = 512;
|
|
11
|
+
const FAILURE_RE = /^this delivery fails because \S/i;
|
|
12
|
+
export const DELIVERY_PREMORTEM_SECRET_RE = /-----BEGIN [A-Z ]*PRIVATE KEY-----|\b(?:sk|gh[opusr])_[A-Za-z0-9_-]{20,}\b|\bBearer\s+[A-Za-z0-9._~+/-]{20,}|\b(?:api[-_ ]?key|token|password|passwort|secret|geheimnis|credential)\s*[:=]\s*\S{8,}|\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/i;
|
|
13
|
+
|
|
14
|
+
export function hasSecretShapedText(value) {
|
|
15
|
+
return typeof value === "string" && DELIVERY_PREMORTEM_SECRET_RE.test(value);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function canonical(value) {
|
|
19
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
20
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
21
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function sha256(value) {
|
|
25
|
+
return createHash("sha256").update(typeof value === "string" ? value : canonical(value)).digest("hex");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function sealed(value, key = "digest") {
|
|
29
|
+
const material = { ...value };
|
|
30
|
+
delete material[key];
|
|
31
|
+
return { ...material, [key]: sha256(material) };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function validSeal(value, key = "digest") {
|
|
35
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || !DIGEST_RE.test(value[key] || "")) return false;
|
|
36
|
+
const material = { ...value };
|
|
37
|
+
delete material[key];
|
|
38
|
+
return value[key] === sha256(material);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function bounded(value, name, limit = VALUE_LIMIT) {
|
|
42
|
+
if (typeof value !== "string" || !value || value.length > limit || /[\0\r\n]/.test(value)) {
|
|
43
|
+
throw new Error(`premortem ${name} is invalid`);
|
|
44
|
+
}
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function normalizedItems(items) {
|
|
49
|
+
if (!Array.isArray(items) || items.length !== CATEGORIES.length) {
|
|
50
|
+
throw new Error("premortem requires exactly three items");
|
|
51
|
+
}
|
|
52
|
+
const output = items.map((item) => {
|
|
53
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) throw new Error("premortem item is invalid");
|
|
54
|
+
const category = bounded(item.category, "category", 64);
|
|
55
|
+
if (!CATEGORIES.includes(category)) throw new Error(`premortem category is invalid: ${category}`);
|
|
56
|
+
const failure = bounded(item.failure, "failure");
|
|
57
|
+
if (!FAILURE_RE.test(failure)) {
|
|
58
|
+
throw new Error(`premortem ${category} failure must start with 'this delivery fails because '`);
|
|
59
|
+
}
|
|
60
|
+
const check = bounded(item.check, "check");
|
|
61
|
+
if (!check.trim()) throw new Error(`premortem ${category} check is invalid`);
|
|
62
|
+
if (hasSecretShapedText(failure) || hasSecretShapedText(check)) {
|
|
63
|
+
const error = new Error(`premortem ${category} contains secret-shaped content`);
|
|
64
|
+
error.code = "AGENTSPINE_PREMORTEM_SECRET";
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
return { category, failure, checkId: `check-${sha256({ category, failure, check }).slice(0, 20)}`, check };
|
|
68
|
+
});
|
|
69
|
+
if (new Set(output.map((item) => item.category)).size !== CATEGORIES.length) {
|
|
70
|
+
throw new Error("premortem categories must appear exactly once");
|
|
71
|
+
}
|
|
72
|
+
return CATEGORIES.map((category) => output.find((item) => item.category === category));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function createPremortemArtifact({ requirement, items, recordedAt }) {
|
|
76
|
+
return sealed({ schema: ARTIFACT_SCHEMA, requirementId: requirement.requirementId,
|
|
77
|
+
laneDigest: requirement.laneDigest, items: normalizedItems(items), recordedAt,
|
|
78
|
+
authority: AUTHORITY });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function validPremortemArtifact(value, state) {
|
|
82
|
+
if (!value || value.schema !== ARTIFACT_SCHEMA || value.authority !== AUTHORITY
|
|
83
|
+
|| value.laneDigest !== state.laneDigest || value.requirementId !== state.requirement?.requirementId
|
|
84
|
+
|| typeof value.recordedAt !== "string" || !Number.isFinite(new Date(value.recordedAt).getTime())
|
|
85
|
+
|| !validSeal(value)) return false;
|
|
86
|
+
try {
|
|
87
|
+
return canonical(value.items) === canonical(normalizedItems(value.items.map(({ category, failure, check }) =>
|
|
88
|
+
({ category, failure, check }))));
|
|
89
|
+
} catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function parsedClosure(message) {
|
|
95
|
+
const text = typeof message === "string" ? message.replace(/\r\n/g, "\n").trimEnd() : "";
|
|
96
|
+
const lines = text.split("\n").slice(-5);
|
|
97
|
+
const artifact = lines[0]?.match(/^Premortem closure sha256 ([a-f0-9]{64})$/i);
|
|
98
|
+
const write = lines[1]?.match(/^Premortem latest write sha256 ([a-f0-9]{64})$/i);
|
|
99
|
+
const artifactDigests = artifact ? [artifact[1].toLowerCase()] : [];
|
|
100
|
+
const writeDigests = write ? [write[1].toLowerCase()] : [];
|
|
101
|
+
const checks = [];
|
|
102
|
+
const expression = /^- (baseline-environment|contract-tests|delivery-path) ([^:\s]{1,80}): (PASS|FAIL|PENDING) — (.*)$/i;
|
|
103
|
+
for (const line of lines.slice(2)) {
|
|
104
|
+
const match = line.match(expression);
|
|
105
|
+
if (match) checks.push({ category: match[1].toLowerCase(),
|
|
106
|
+
checkId: match[2], status: match[3].toUpperCase(), result: match[4].trim() });
|
|
107
|
+
}
|
|
108
|
+
return { artifactDigests, writeDigests, checks };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function reviewPremortemClosure(message, artifact, lastWriteDigest) {
|
|
112
|
+
const parsed = parsedClosure(message);
|
|
113
|
+
const missing = [];
|
|
114
|
+
if (parsed.artifactDigests.length !== 1 || parsed.artifactDigests[0] !== artifact.digest) {
|
|
115
|
+
missing.push("closure-digest");
|
|
116
|
+
}
|
|
117
|
+
if (parsed.writeDigests.length !== 1 || parsed.writeDigests[0] !== lastWriteDigest) {
|
|
118
|
+
missing.push("latest-write-digest");
|
|
119
|
+
}
|
|
120
|
+
const checks = [];
|
|
121
|
+
for (const item of artifact.items) {
|
|
122
|
+
const found = parsed.checks.filter((check) => check.category === item.category);
|
|
123
|
+
if (found.length !== 1 || found[0].checkId !== item.checkId || found[0].status !== "PASS"
|
|
124
|
+
|| !found[0].result || found[0].result.length > RESULT_LIMIT
|
|
125
|
+
|| /[\0\r\n]/.test(found[0].result) || hasSecretShapedText(found[0].result)) missing.push(item.category);
|
|
126
|
+
else checks.push(found[0]);
|
|
127
|
+
}
|
|
128
|
+
return { missing: [...new Set(missing)], checks };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function createPremortemClosure({ requirementId, artifactDigest, lastWriteDigest, checks, closedAt,
|
|
132
|
+
testStateDigest = null }) {
|
|
133
|
+
if (testStateDigest !== null && !DIGEST_RE.test(testStateDigest)) {
|
|
134
|
+
throw new Error("completion test-state digest is invalid");
|
|
135
|
+
}
|
|
136
|
+
return sealed({ schema: CLOSURE_SCHEMA, requirementId, artifactDigest, lastWriteDigest,
|
|
137
|
+
...(testStateDigest ? { completionSource: "mcp", testStateDigest } : {}),
|
|
138
|
+
checks, closedAt, authority: AUTHORITY });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function validPremortemClosure(value, state) {
|
|
142
|
+
if (value && (value.completionSource !== undefined || value.testStateDigest !== undefined)
|
|
143
|
+
&& (value.completionSource !== "mcp" || !DIGEST_RE.test(value.testStateDigest || ""))) return false;
|
|
144
|
+
if (!value || !state.artifact || !state.lastWrite || value.schema !== CLOSURE_SCHEMA
|
|
145
|
+
|| value.authority !== AUTHORITY || value.requirementId !== state.requirement?.requirementId
|
|
146
|
+
|| value.artifactDigest !== state.artifact.digest || value.lastWriteDigest !== state.lastWrite.digest
|
|
147
|
+
|| typeof value.closedAt !== "string" || !Number.isFinite(new Date(value.closedAt).getTime())
|
|
148
|
+
|| !validSeal(value) || !Array.isArray(value.checks) || value.checks.length !== CATEGORIES.length) return false;
|
|
149
|
+
return CATEGORIES.every((category) => {
|
|
150
|
+
const expected = state.artifact.items.find((item) => item.category === category);
|
|
151
|
+
const matches = value.checks.filter((item) => item.category === category);
|
|
152
|
+
return matches.length === 1 && matches[0].checkId === expected?.checkId && matches[0].status === "PASS"
|
|
153
|
+
&& typeof matches[0].result === "string" && matches[0].result.trim().length > 0
|
|
154
|
+
&& matches[0].result.length <= RESULT_LIMIT && !/[\0\r\n]/.test(matches[0].result)
|
|
155
|
+
&& !hasSecretShapedText(matches[0].result);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function createPremortemAttachment(state) {
|
|
160
|
+
return sealed({ schema: ATTACHMENT_SCHEMA, goalId: state.binding.goalId,
|
|
161
|
+
goalStepId: state.binding.goalStepId, queueId: state.binding.queueId,
|
|
162
|
+
planDefinitionsDigest: state.binding.planDefinitionsDigest, laneDigest: state.laneDigest,
|
|
163
|
+
sessionDigest: sha256(state.binding.sessionId), host: state.binding.host,
|
|
164
|
+
projectId: state.binding.projectId, entityId: state.binding.entityId,
|
|
165
|
+
groupId: state.binding.groupId, taskId: state.binding.taskId,
|
|
166
|
+
gatewayAttempt: state.binding.gatewayAttempt,
|
|
167
|
+
premortemText: state.artifact.items.map((item) => `${item.failure} Check: ${item.check}`).join("\n"),
|
|
168
|
+
premortemDigest: state.artifact.digest, lastWriteDigest: state.lastWrite.digest,
|
|
169
|
+
checkResults: structuredClone(state.closure.checks), closureDigest: state.closure.digest,
|
|
170
|
+
authority: AUTHORITY }, "attachmentDigest");
|
|
171
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
const DIGEST_RE = /^[a-f0-9]{64}$/;
|
|
4
|
+
|
|
5
|
+
export function canonicalPremortem(value) {
|
|
6
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
7
|
+
if (Array.isArray(value)) return `[${value.map(canonicalPremortem).join(",")}]`;
|
|
8
|
+
return `{${Object.keys(value).sort().map((key) =>
|
|
9
|
+
`${JSON.stringify(key)}:${canonicalPremortem(value[key])}`).join(",")}}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function premortemSha256(value) {
|
|
13
|
+
return createHash("sha256").update(typeof value === "string"
|
|
14
|
+
? value : canonicalPremortem(value)).digest("hex");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function premortemTime(value) {
|
|
18
|
+
const parsed = new Date(value ?? Date.now());
|
|
19
|
+
if (!Number.isFinite(parsed.getTime())) throw new Error("premortem timestamp is invalid");
|
|
20
|
+
return parsed.toISOString();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function sealPremortem(value, digestKey = "digest") {
|
|
24
|
+
const material = { ...value };
|
|
25
|
+
delete material[digestKey];
|
|
26
|
+
return { ...material, [digestKey]: premortemSha256(material) };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function validPremortemSeal(value, key = "digest") {
|
|
30
|
+
if (!value || typeof value !== "object" || Array.isArray(value)
|
|
31
|
+
|| !DIGEST_RE.test(value[key] || "")) return false;
|
|
32
|
+
const material = { ...value };
|
|
33
|
+
delete material[key];
|
|
34
|
+
return value[key] === premortemSha256(material);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function validPremortemTime(value) {
|
|
38
|
+
return typeof value === "string" && Number.isFinite(new Date(value).getTime());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function premortemMismatchError(message) {
|
|
42
|
+
const error = new Error(message);
|
|
43
|
+
error.code = "AGENTSPINE_PREMORTEM_MISMATCH";
|
|
44
|
+
return error;
|
|
45
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { mkdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { withOwnedFileLock } from "./owned-file-lock.js";
|
|
4
|
+
import { beginDeliveryAssignment, assignmentPremortemBinding } from "./delivery-assignment.js";
|
|
5
|
+
import { inspectPremortemState, preparePremortemRequirement } from "./delivery-premortem.js";
|
|
6
|
+
import { writePremortemFile } from "./delivery-premortem-file.js";
|
|
7
|
+
import { premortemBlock as block, premortemBoundary as boundary } from "./delivery-premortem-results.js";
|
|
8
|
+
import { canonicalPath, projectStateDir } from "./paths.js";
|
|
9
|
+
import { premortemSha256 as sha256, premortemTime as at,
|
|
10
|
+
sealPremortem as seal, validPremortemSeal as validSeal,
|
|
11
|
+
validPremortemTime as validTime } from "./delivery-premortem-codec.js";
|
|
12
|
+
|
|
13
|
+
const SCHEMA = "agentspine.delivery-premortem-recovery/v1";
|
|
14
|
+
const AUTHORITY = "context-only";
|
|
15
|
+
const MAX_BYTES = 12 * 1024;
|
|
16
|
+
const DIGEST_RE = /^[a-f0-9]{64}$/;
|
|
17
|
+
|
|
18
|
+
function valid(value) {
|
|
19
|
+
return value && value.schema === SCHEMA && value.authority === AUTHORITY
|
|
20
|
+
&& typeof value.predecessorRequirementId === "string"
|
|
21
|
+
&& typeof value.requirementId === "string"
|
|
22
|
+
&& DIGEST_RE.test(value.predecessorArtifactDigest || "")
|
|
23
|
+
&& DIGEST_RE.test(value.bindingDigest || "")
|
|
24
|
+
&& /^assignment:[a-f0-9]{64}$/.test(value.assignmentId || "")
|
|
25
|
+
&& validTime(value.recoveredAt) && validSeal(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function read(path) {
|
|
29
|
+
try {
|
|
30
|
+
const text = await readFile(path, "utf8");
|
|
31
|
+
if (Buffer.byteLength(text) > MAX_BYTES) throw new Error("premortem recovery exceeds 12 KiB");
|
|
32
|
+
const value = JSON.parse(text);
|
|
33
|
+
if (!valid(value)) throw new Error("premortem recovery failed integrity validation");
|
|
34
|
+
return value;
|
|
35
|
+
} catch (error) {
|
|
36
|
+
if (error.code === "ENOENT") return null;
|
|
37
|
+
if (error instanceof SyntaxError) throw new Error("premortem recovery is not valid JSON");
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function storeRecovery(root, receipt) {
|
|
43
|
+
const digest = sha256({ predecessorRequirementId: receipt.predecessorRequirementId,
|
|
44
|
+
requirementId: receipt.requirementId });
|
|
45
|
+
const directory = join(await projectStateDir(await canonicalPath(root)),
|
|
46
|
+
"delivery-premortem-recoveries");
|
|
47
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
48
|
+
const path = join(directory, `${digest}.json`);
|
|
49
|
+
return withOwnedFileLock(`${path}.lock`, async ({ assertOwned }) => {
|
|
50
|
+
const existing = await read(path);
|
|
51
|
+
if (existing) {
|
|
52
|
+
if (existing.predecessorRequirementId !== receipt.predecessorRequirementId
|
|
53
|
+
|| existing.requirementId !== receipt.requirementId) {
|
|
54
|
+
throw new Error("premortem recovery receipt conflicts with history");
|
|
55
|
+
}
|
|
56
|
+
return existing;
|
|
57
|
+
}
|
|
58
|
+
await writePremortemFile(path, receipt, assertOwned, MAX_BYTES);
|
|
59
|
+
return receipt;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function recoverDeliveryPremortem({ root, predecessorRequirementId,
|
|
64
|
+
taskId = null, now = new Date() }) {
|
|
65
|
+
try {
|
|
66
|
+
const predecessor = await inspectPremortemState({ root,
|
|
67
|
+
requirementId: predecessorRequirementId });
|
|
68
|
+
if (predecessor.status === "degraded" || predecessor.blocked) return predecessor;
|
|
69
|
+
if (predecessor.status === "absent" || !predecessor.requirement) {
|
|
70
|
+
return block("stale", "The predecessor premortem requirement does not exist.",
|
|
71
|
+
{ predecessorRequirementId });
|
|
72
|
+
}
|
|
73
|
+
if (!predecessor.conflicted) {
|
|
74
|
+
return block("not-needed",
|
|
75
|
+
"The predecessor was not poisoned; reuse its first valid registration or start the next assignment.",
|
|
76
|
+
{ predecessorRequirementId });
|
|
77
|
+
}
|
|
78
|
+
const source = { ...predecessor.binding, taskId: taskId || predecessor.binding.taskId,
|
|
79
|
+
assignmentId: null };
|
|
80
|
+
const assignment = await beginDeliveryAssignment({ root, binding: source,
|
|
81
|
+
eventId: `recovery:${predecessorRequirementId}`, now });
|
|
82
|
+
if (assignment.blocked) return assignment;
|
|
83
|
+
const binding = assignmentPremortemBinding(source, assignment);
|
|
84
|
+
const prepared = await preparePremortemRequirement({ root, binding, now });
|
|
85
|
+
if (prepared.blocked || prepared.status === "degraded") return prepared;
|
|
86
|
+
const receipt = seal({ schema: SCHEMA,
|
|
87
|
+
predecessorRequirementId,
|
|
88
|
+
predecessorArtifactDigest: predecessor.artifactDigest,
|
|
89
|
+
requirementId: prepared.requirementId,
|
|
90
|
+
assignmentId: assignment.assignmentId,
|
|
91
|
+
bindingDigest: sha256(binding),
|
|
92
|
+
recoveredAt: at(now), authority: AUTHORITY });
|
|
93
|
+
const stored = await storeRecovery(root, receipt);
|
|
94
|
+
return { status: "recovered", blocked: false,
|
|
95
|
+
predecessorRequirementId, requirementId: prepared.requirementId,
|
|
96
|
+
assignmentId: assignment.assignmentId, recoveryReceipt: stored,
|
|
97
|
+
instruction: "Run session_briefing, delivery_knowledge_query, and record_delivery_premortem for the new requirement. Recovery grants no authority." };
|
|
98
|
+
} catch (error) {
|
|
99
|
+
return boundary(error);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { replaceFileWithRetry } from "./filesystem-retry.js";
|
|
4
|
+
import { premortemMismatchError as mismatchError } from "./delivery-premortem-codec.js";
|
|
5
|
+
|
|
6
|
+
export async function writePremortemFile(path, value, assertOwned, maxBytes = Infinity) {
|
|
7
|
+
const text = typeof value === "string" ? value : `${JSON.stringify(value, null, 2)}\n`;
|
|
8
|
+
if (Buffer.byteLength(text) > maxBytes) throw mismatchError("delivery premortem state exceeds 64 KiB");
|
|
9
|
+
const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
10
|
+
try {
|
|
11
|
+
await writeFile(temporary, text, { mode: 0o600 });
|
|
12
|
+
await replaceFileWithRetry(temporary, path, { beforeAttempt: assertOwned });
|
|
13
|
+
// A stale lock holder can lose ownership in the narrow interval around rename.
|
|
14
|
+
// Do not report this replacement as a committed state transition in that case.
|
|
15
|
+
await assertOwned();
|
|
16
|
+
} finally {
|
|
17
|
+
await unlink(temporary).catch((error) => {
|
|
18
|
+
if (error.code !== "ENOENT") throw error;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|