blun-king-cli 9.1.567 → 9.1.570
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/CHANGELOG.md +33 -0
- 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 +3 -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 +2861 -1173
- package/codebase-index/codebase_index.py +4 -3
- package/package.json +5 -18
- 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,431 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export const KNOWLEDGE_GAP_SCHEMA = "agentspine.knowledge-gap/v1";
|
|
4
|
+
export const SELF_HELP_REPORT_SCHEMA = "agentspine.self-help-report/v1";
|
|
5
|
+
export const SELF_HELP_REQUIREMENT_SCHEMA = "agentspine.self-help-requirement/v1";
|
|
6
|
+
export const SELF_HELP_ESCALATION_SCHEMA = "agentspine.self-help-escalation/v1";
|
|
7
|
+
|
|
8
|
+
const ID_RE = /^[A-Za-z0-9][A-Za-z0-9:_.@/+~-]{0,255}$/;
|
|
9
|
+
const DIGEST_RE = /^[a-f0-9]{64}$/;
|
|
10
|
+
const COMMIT_RE = /^[a-f0-9]{40}$/;
|
|
11
|
+
const KNOWLEDGE_EVIDENCE = new Set(["owner-input", "objective-observation"]);
|
|
12
|
+
const REPOSITORY_KINDS = new Set(["current-tree", "documentation", "test", "history", "issue", "pull-request"]);
|
|
13
|
+
const EXTERNAL_KINDS = new Set(["official-documentation", "standard", "research-paper", "public-repository"]);
|
|
14
|
+
const SECRET_RE = /-----BEGIN [A-Z ]*PRIVATE KEY-----|\b(?:sk|gh[opusu])_[A-Za-z0-9_-]{20,}\b|\bBearer\s+[A-Za-z0-9._~+/-]{20,}|\b(?:api[-_ ]?key|token|password|secret)\s*[:=]\s*\S{8,}/i;
|
|
15
|
+
const AUTHORITY_RE = /\b(?:permission|rights?|roles?|owner|trusted|delegat|authorized|approval|production|payment|spending|tool capability|send capability)\b/i;
|
|
16
|
+
|
|
17
|
+
function sha256(value) { return createHash("sha256").update(value).digest("hex"); }
|
|
18
|
+
|
|
19
|
+
function exactId(value, field) {
|
|
20
|
+
if (typeof value !== "string" || !ID_RE.test(value) || value.includes("*")) {
|
|
21
|
+
throw new Error(field + " must be an exact stable ID without wildcards");
|
|
22
|
+
}
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function safeText(value, field, maximum) {
|
|
27
|
+
if (typeof value !== "string" || !value.trim()) throw new Error(field + " is required");
|
|
28
|
+
const text = value.trim().replace(/\s+/g, " ").slice(0, maximum);
|
|
29
|
+
if (SECRET_RE.test(text)) throw new Error(field + " appears to contain a secret");
|
|
30
|
+
return text;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function safeKnowledgeText(value, field, maximum) {
|
|
34
|
+
const text = safeText(value, field, maximum);
|
|
35
|
+
if (AUTHORITY_RE.test(text)) throw new Error(field + " cannot grant or describe authority");
|
|
36
|
+
return text;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function timestamp(value, field = "timestamp") {
|
|
40
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
41
|
+
if (!Number.isFinite(date.getTime())) throw new Error(field + " is invalid");
|
|
42
|
+
return date.toISOString();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function knowledgeGapSubjectMaterial(gap) {
|
|
46
|
+
return {
|
|
47
|
+
goalId: gap.goalId, goalStepId: gap.goalStepId, planDefinitionsDigest: gap.planDefinitionsDigest,
|
|
48
|
+
question: gap.question, reason: gap.reason, requiredEvidence: gap.requiredEvidence,
|
|
49
|
+
authority: "context-only"
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function knowledgeGapRequestMaterial(gap) {
|
|
54
|
+
return {
|
|
55
|
+
subjectDigest: gap.subjectDigest, requestedAt: gap.requestedAt,
|
|
56
|
+
requestedByQueueId: gap.requestedByQueueId, authority: "context-only"
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function knowledgeGapResolutionMaterial(gap) {
|
|
61
|
+
return {
|
|
62
|
+
requestDigest: gap.requestDigest, answer: gap.answer, answerSource: gap.answerSource,
|
|
63
|
+
sourceDigest: gap.sourceDigest, resolvedAt: gap.resolvedAt,
|
|
64
|
+
resolvedBySubjectId: gap.resolvedBySubjectId, authority: "context-only"
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function selfHelpRequirementMaterial(requirement) {
|
|
69
|
+
return { goalId: requirement.goalId, goalStepId: requirement.goalStepId,
|
|
70
|
+
planDefinitionsDigest: requirement.planDefinitionsDigest, question: requirement.question,
|
|
71
|
+
reason: requirement.reason, requestedAt: requirement.requestedAt,
|
|
72
|
+
requestedByQueueId: requirement.requestedByQueueId, authority: "context-only-research" };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function validKnowledgeGap(gap) {
|
|
76
|
+
if (!(gap && gap.schema === KNOWLEDGE_GAP_SCHEMA && ID_RE.test(gap.gapId || "")
|
|
77
|
+
&& ID_RE.test(gap.goalId || "") && ID_RE.test(gap.goalStepId || "")
|
|
78
|
+
&& DIGEST_RE.test(gap.planDefinitionsDigest || "")
|
|
79
|
+
&& typeof gap.question === "string" && gap.question.length <= 500
|
|
80
|
+
&& typeof gap.reason === "string" && gap.reason.length <= 500
|
|
81
|
+
&& KNOWLEDGE_EVIDENCE.has(gap.requiredEvidence)
|
|
82
|
+
&& ID_RE.test(gap.requestedByQueueId || "")
|
|
83
|
+
&& Number.isFinite(new Date(gap.requestedAt).getTime())
|
|
84
|
+
&& DIGEST_RE.test(gap.subjectDigest || "") && DIGEST_RE.test(gap.requestDigest || "")
|
|
85
|
+
&& ["open", "resolved"].includes(gap.status) && gap.authority === "context-only")) return false;
|
|
86
|
+
try {
|
|
87
|
+
if (safeKnowledgeText(gap.question, "knowledgeGap.question", 500) !== gap.question
|
|
88
|
+
|| safeKnowledgeText(gap.reason, "knowledgeGap.reason", 500) !== gap.reason) return false;
|
|
89
|
+
} catch { return false; }
|
|
90
|
+
if (gap.subjectDigest !== sha256(JSON.stringify(knowledgeGapSubjectMaterial(gap)))
|
|
91
|
+
|| gap.gapId !== "knowledge-gap:" + gap.subjectDigest.slice(0, 32)
|
|
92
|
+
|| gap.requestDigest !== sha256(JSON.stringify(knowledgeGapRequestMaterial(gap)))) return false;
|
|
93
|
+
if (gap.status === "open") return gap.answer === null && gap.answerSource === null && gap.sourceDigest === null
|
|
94
|
+
&& gap.resolvedAt === null && gap.resolvedBySubjectId === null && gap.resolutionDigest === null;
|
|
95
|
+
if (!(typeof gap.answer === "string" && gap.answer.length <= 2000
|
|
96
|
+
&& gap.answerSource === gap.requiredEvidence
|
|
97
|
+
&& (gap.sourceDigest === null || DIGEST_RE.test(gap.sourceDigest))
|
|
98
|
+
&& Number.isFinite(new Date(gap.resolvedAt).getTime()) && ID_RE.test(gap.resolvedBySubjectId || "")
|
|
99
|
+
&& DIGEST_RE.test(gap.resolutionDigest || ""))) return false;
|
|
100
|
+
if (new Date(gap.resolvedAt) < new Date(gap.requestedAt)) return false;
|
|
101
|
+
if ((gap.answerSource === "objective-observation") !== (gap.sourceDigest !== null)) return false;
|
|
102
|
+
try { if (safeKnowledgeText(gap.answer, "knowledgeGap.answer", 2000) !== gap.answer) return false; } catch { return false; }
|
|
103
|
+
return gap.resolutionDigest === sha256(JSON.stringify(knowledgeGapResolutionMaterial(gap)));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function createKnowledgeGap(goal, step, queueId, request, now) {
|
|
107
|
+
const gap = {
|
|
108
|
+
schema: KNOWLEDGE_GAP_SCHEMA, gapId: null, goalId: goal.goalId, goalStepId: step.stepId,
|
|
109
|
+
planDefinitionsDigest: goal.plan.definitionsDigest,
|
|
110
|
+
question: safeKnowledgeText(request?.question, "knowledgeGap.question", 500),
|
|
111
|
+
reason: safeKnowledgeText(request?.reason, "knowledgeGap.reason", 500),
|
|
112
|
+
requiredEvidence: request?.requiredEvidence,
|
|
113
|
+
requestedAt: timestamp(now), requestedByQueueId: exactId(queueId, "requestedByQueueId"),
|
|
114
|
+
subjectDigest: null, requestDigest: null, status: "open", answer: null, answerSource: null,
|
|
115
|
+
sourceDigest: null, resolvedAt: null, resolvedBySubjectId: null, resolutionDigest: null,
|
|
116
|
+
authority: "context-only"
|
|
117
|
+
};
|
|
118
|
+
if (!KNOWLEDGE_EVIDENCE.has(gap.requiredEvidence)) {
|
|
119
|
+
throw new Error("knowledgeGap.requiredEvidence must be owner-input or objective-observation");
|
|
120
|
+
}
|
|
121
|
+
gap.subjectDigest = sha256(JSON.stringify(knowledgeGapSubjectMaterial(gap)));
|
|
122
|
+
gap.gapId = "knowledge-gap:" + gap.subjectDigest.slice(0, 32);
|
|
123
|
+
gap.requestDigest = sha256(JSON.stringify(knowledgeGapRequestMaterial(gap)));
|
|
124
|
+
if (!validKnowledgeGap(gap)) throw new Error("knowledge gap request is invalid");
|
|
125
|
+
return gap;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function resolveKnowledgeGapCandidate(gap, { answer, answerSource, sourceDigest = null,
|
|
129
|
+
resolvedAt, resolvedBySubjectId }) {
|
|
130
|
+
answer = safeKnowledgeText(answer, "answer", 2000);
|
|
131
|
+
if (!KNOWLEDGE_EVIDENCE.has(answerSource) || answerSource !== gap.requiredEvidence) {
|
|
132
|
+
throw new Error("answer source does not satisfy the requested evidence class");
|
|
133
|
+
}
|
|
134
|
+
sourceDigest = sourceDigest === null || sourceDigest === undefined || sourceDigest === "" ? null : String(sourceDigest);
|
|
135
|
+
if ((answerSource === "objective-observation") !== DIGEST_RE.test(sourceDigest || "")) {
|
|
136
|
+
throw new Error("objective observation answers require one exact SHA-256 source digest");
|
|
137
|
+
}
|
|
138
|
+
const candidate = { ...gap, status: "resolved", answer, answerSource, sourceDigest,
|
|
139
|
+
resolvedAt: timestamp(resolvedAt), resolvedBySubjectId: exactId(resolvedBySubjectId, "resolvedBySubjectId"),
|
|
140
|
+
resolutionDigest: null };
|
|
141
|
+
candidate.resolutionDigest = sha256(JSON.stringify(knowledgeGapResolutionMaterial(candidate)));
|
|
142
|
+
if (!validKnowledgeGap(candidate)) throw new Error("knowledge gap resolution is invalid");
|
|
143
|
+
return candidate;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function sameKnowledgeGapResolution(gap, resolution) {
|
|
147
|
+
const repeated = resolveKnowledgeGapCandidate(gap, { ...resolution,
|
|
148
|
+
resolvedAt: gap.resolvedAt, resolvedBySubjectId: gap.resolvedBySubjectId });
|
|
149
|
+
return gap.resolutionDigest === repeated.resolutionDigest;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function createSelfHelpRequirement(goal, step, queueId, request, now) {
|
|
153
|
+
if (request?.requiredEvidence !== "objective-observation") {
|
|
154
|
+
throw new Error("self-help requirements only replace objective-observation questions");
|
|
155
|
+
}
|
|
156
|
+
const result = { schema: SELF_HELP_REQUIREMENT_SCHEMA, requirementId: null,
|
|
157
|
+
goalId: exactId(goal.goalId, "goalId"), goalStepId: exactId(step.stepId, "goalStepId"),
|
|
158
|
+
planDefinitionsDigest: goal.plan.definitionsDigest,
|
|
159
|
+
question: safeKnowledgeText(request?.question, "knowledgeGap.question", 500),
|
|
160
|
+
reason: safeKnowledgeText(request?.reason, "knowledgeGap.reason", 500),
|
|
161
|
+
requestedAt: timestamp(now), requestedByQueueId: exactId(queueId, "requestedByQueueId"),
|
|
162
|
+
requirementDigest: null, authority: "context-only-research" };
|
|
163
|
+
result.requirementDigest = sha256(JSON.stringify(selfHelpRequirementMaterial(result)));
|
|
164
|
+
result.requirementId = "self-help-requirement:" + result.requirementDigest.slice(0, 32);
|
|
165
|
+
if (!validSelfHelpRequirement(result)) throw new Error("self-help requirement is invalid");
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function validSelfHelpRequirement(requirement) {
|
|
170
|
+
if (!(requirement && requirement.schema === SELF_HELP_REQUIREMENT_SCHEMA
|
|
171
|
+
&& ID_RE.test(requirement.requirementId || "") && ID_RE.test(requirement.goalId || "")
|
|
172
|
+
&& ID_RE.test(requirement.goalStepId || "") && DIGEST_RE.test(requirement.planDefinitionsDigest || "")
|
|
173
|
+
&& ID_RE.test(requirement.requestedByQueueId || "")
|
|
174
|
+
&& Number.isFinite(new Date(requirement.requestedAt).getTime())
|
|
175
|
+
&& DIGEST_RE.test(requirement.requirementDigest || "")
|
|
176
|
+
&& requirement.authority === "context-only-research")) return false;
|
|
177
|
+
try {
|
|
178
|
+
if (safeKnowledgeText(requirement.question, "knowledgeGap.question", 500) !== requirement.question
|
|
179
|
+
|| safeKnowledgeText(requirement.reason, "knowledgeGap.reason", 500) !== requirement.reason) return false;
|
|
180
|
+
} catch { return false; }
|
|
181
|
+
return requirement.requirementDigest === sha256(JSON.stringify(selfHelpRequirementMaterial(requirement)))
|
|
182
|
+
&& requirement.requirementId === "self-help-requirement:" + requirement.requirementDigest.slice(0, 32);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function pendingSelfHelpRequirement(step) {
|
|
186
|
+
const reports = step.selfHelpReports || [];
|
|
187
|
+
return [...(step.selfHelpRequirements || [])].reverse().find((requirement) =>
|
|
188
|
+
!reports.some((report) => report.question === requirement.question && report.reason === requirement.reason)) || null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function validStepKnowledgeState(step, planDefinitionsDigest) {
|
|
192
|
+
const gaps = step.knowledgeGaps;
|
|
193
|
+
const reports = step.selfHelpReports;
|
|
194
|
+
const requirements = step.selfHelpRequirements;
|
|
195
|
+
return (gaps === undefined || (Array.isArray(gaps) && gaps.length <= 16
|
|
196
|
+
&& new Set(gaps.map((gap) => gap.gapId)).size === gaps.length
|
|
197
|
+
&& gaps.every((gap) => validKnowledgeGap(gap) && gap.goalStepId === step.stepId
|
|
198
|
+
&& gap.planDefinitionsDigest === planDefinitionsDigest)))
|
|
199
|
+
&& (reports === undefined || (Array.isArray(reports) && reports.length <= 16
|
|
200
|
+
&& new Set(reports.map((report) => report.reportId)).size === reports.length
|
|
201
|
+
&& reports.every((report) => validSelfHelpReport(report) && report.goalStepId === step.stepId
|
|
202
|
+
&& report.planDefinitionsDigest === planDefinitionsDigest)))
|
|
203
|
+
&& (requirements === undefined || (Array.isArray(requirements) && requirements.length <= 16
|
|
204
|
+
&& new Set(requirements.map((item) => item.requirementId)).size === requirements.length
|
|
205
|
+
&& requirements.every((item) => validSelfHelpRequirement(item) && item.goalStepId === step.stepId
|
|
206
|
+
&& item.planDefinitionsDigest === planDefinitionsDigest)
|
|
207
|
+
&& requirements.filter((item) => !reports?.some((report) => report.question === item.question
|
|
208
|
+
&& report.reason === item.reason)).length <= 1));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function safeRelativePath(value, field) {
|
|
212
|
+
const path = safeText(value, field, 500).replaceAll("\\", "/");
|
|
213
|
+
if (path.startsWith("/") || /^[A-Za-z]:\//.test(path) || path.split("/").includes("..")) {
|
|
214
|
+
throw new Error(field + " must stay inside the researched repository");
|
|
215
|
+
}
|
|
216
|
+
return path;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function publicHttpsUrl(value, field) {
|
|
220
|
+
let url;
|
|
221
|
+
try { url = new URL(value); } catch { throw new Error(field + " must be an HTTPS URL"); }
|
|
222
|
+
const host = url.hostname.toLowerCase();
|
|
223
|
+
if (url.protocol !== "https:" || url.username || url.password || host === "localhost"
|
|
224
|
+
|| host.endsWith(".local") || /^\d+(?:\.\d+){3}$/.test(host) || host.includes(":")) {
|
|
225
|
+
throw new Error(field + " must be a public HTTPS URL without credentials");
|
|
226
|
+
}
|
|
227
|
+
return url.toString();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function evidenceTime(value, field, completedAt) {
|
|
231
|
+
const observedAt = timestamp(value, field);
|
|
232
|
+
const completed = new Date(completedAt);
|
|
233
|
+
const observed = new Date(observedAt);
|
|
234
|
+
if (observed > completed || completed.getTime() - observed.getTime() > 86400000) {
|
|
235
|
+
throw new Error(field + " must be fresh and no later than the report");
|
|
236
|
+
}
|
|
237
|
+
return observedAt;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function normalizeRepositorySource(source, index, completedAt) {
|
|
241
|
+
const field = `selfHelp.repositorySources[${index}]`;
|
|
242
|
+
if (!REPOSITORY_KINDS.has(source?.kind)) throw new Error(field + ".kind is unsupported");
|
|
243
|
+
if (!DIGEST_RE.test(source?.sourceDigest || "")) throw new Error(field + ".sourceDigest must be SHA-256");
|
|
244
|
+
if (!COMMIT_RE.test(source?.commit || "")) throw new Error(field + ".commit must be an exact Git commit");
|
|
245
|
+
return { kind: source.kind, path: safeRelativePath(source.path, field + ".path"), commit: source.commit,
|
|
246
|
+
sourceDigest: source.sourceDigest, observedAt: evidenceTime(source.observedAt, field + ".observedAt", completedAt),
|
|
247
|
+
relevance: safeKnowledgeText(source.relevance, field + ".relevance", 500) };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function normalizeExternalSource(source, index, completedAt) {
|
|
251
|
+
const field = `selfHelp.externalSources[${index}]`;
|
|
252
|
+
if (!EXTERNAL_KINDS.has(source?.kind)) throw new Error(field + ".kind is unsupported");
|
|
253
|
+
if (!DIGEST_RE.test(source?.sourceDigest || "")) throw new Error(field + ".sourceDigest must be SHA-256");
|
|
254
|
+
return { kind: source.kind, url: publicHttpsUrl(source.url, field + ".url"),
|
|
255
|
+
version: safeText(source.version, field + ".version", 200),
|
|
256
|
+
license: safeText(source.license, field + ".license", 200), sourceDigest: source.sourceDigest,
|
|
257
|
+
observedAt: evidenceTime(source.observedAt, field + ".observedAt", completedAt),
|
|
258
|
+
relevance: safeKnowledgeText(source.relevance, field + ".relevance", 500), untrusted: true };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function selfHelpMaterial(report) {
|
|
262
|
+
return { goalId: report.goalId, goalStepId: report.goalStepId,
|
|
263
|
+
planDefinitionsDigest: report.planDefinitionsDigest, question: report.question, reason: report.reason,
|
|
264
|
+
repositorySufficient: report.repositorySufficient, repositorySources: report.repositorySources,
|
|
265
|
+
externalSources: report.externalSources, conclusion: report.conclusion, completedAt: report.completedAt,
|
|
266
|
+
authority: "context-only-research" };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function selfHelpEscalationMaterial(report) {
|
|
270
|
+
return { goalId: report.goalId, goalStepId: report.goalStepId,
|
|
271
|
+
planDefinitionsDigest: report.planDefinitionsDigest, question: report.question, reason: report.reason,
|
|
272
|
+
repositorySufficient: false, repositorySources: report.repositorySources,
|
|
273
|
+
externalSources: report.externalSources, outcome: "needs-owner-input",
|
|
274
|
+
conflictSourceDigests: report.conflictSourceDigests, ownerQuestion: report.ownerQuestion,
|
|
275
|
+
ownerReason: report.ownerReason, options: report.options, completedAt: report.completedAt,
|
|
276
|
+
authority: "context-only-research" };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function validSelfHelpReport(report) {
|
|
280
|
+
if (report?.schema === SELF_HELP_ESCALATION_SCHEMA) {
|
|
281
|
+
try {
|
|
282
|
+
const normalized = createSelfHelpEscalation({ goal: { goalId: report.goalId,
|
|
283
|
+
plan: { definitionsDigest: report.planDefinitionsDigest } }, step: { stepId: report.goalStepId },
|
|
284
|
+
report, now: report.completedAt });
|
|
285
|
+
return JSON.stringify(normalized) === JSON.stringify(report);
|
|
286
|
+
} catch { return false; }
|
|
287
|
+
}
|
|
288
|
+
if (!(report && report.schema === SELF_HELP_REPORT_SCHEMA && ID_RE.test(report.reportId || "")
|
|
289
|
+
&& ID_RE.test(report.goalId || "") && ID_RE.test(report.goalStepId || "")
|
|
290
|
+
&& DIGEST_RE.test(report.planDefinitionsDigest || "") && typeof report.repositorySufficient === "boolean"
|
|
291
|
+
&& Array.isArray(report.repositorySources) && report.repositorySources.length >= 1 && report.repositorySources.length <= 16
|
|
292
|
+
&& Array.isArray(report.externalSources) && report.externalSources.length <= 8
|
|
293
|
+
&& typeof report.conclusion === "string" && report.conclusion.length <= 2000
|
|
294
|
+
&& Number.isFinite(new Date(report.completedAt).getTime()) && DIGEST_RE.test(report.reportDigest || "")
|
|
295
|
+
&& report.authority === "context-only-research")) return false;
|
|
296
|
+
try {
|
|
297
|
+
const normalized = createSelfHelpReport({ goal: { goalId: report.goalId,
|
|
298
|
+
plan: { definitionsDigest: report.planDefinitionsDigest } }, step: { stepId: report.goalStepId },
|
|
299
|
+
report, now: report.completedAt });
|
|
300
|
+
return JSON.stringify(normalized) === JSON.stringify(report);
|
|
301
|
+
} catch { return false; }
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function createSelfHelpReport({ goal, step, report, now }) {
|
|
305
|
+
if (report?.outcome === "needs-owner-input") {
|
|
306
|
+
return createSelfHelpEscalation({ goal, step, report, now });
|
|
307
|
+
}
|
|
308
|
+
const completedAt = timestamp(now);
|
|
309
|
+
const repositorySources = (report?.repositorySources || []).map((source, index) =>
|
|
310
|
+
normalizeRepositorySource(source, index, completedAt));
|
|
311
|
+
const externalSources = (report?.externalSources || []).map((source, index) =>
|
|
312
|
+
normalizeExternalSource(source, index, completedAt));
|
|
313
|
+
if (repositorySources.length < 1 || repositorySources.length > 16) {
|
|
314
|
+
throw new Error("self-help requires 1-16 repository evidence sources before escalation");
|
|
315
|
+
}
|
|
316
|
+
if (externalSources.length > 8) throw new Error("self-help external research exceeds eight sources");
|
|
317
|
+
const repositorySufficient = report?.repositorySufficient === true;
|
|
318
|
+
if (repositorySufficient && externalSources.length) {
|
|
319
|
+
throw new Error("self-help cannot use external sources when repository evidence is sufficient");
|
|
320
|
+
}
|
|
321
|
+
if (!repositorySufficient) {
|
|
322
|
+
const origins = new Set(externalSources.map((source) => new URL(source.url).origin));
|
|
323
|
+
if (externalSources.length < 2 || origins.size < 2) {
|
|
324
|
+
throw new Error("insufficient repository evidence requires two independent public primary sources");
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const lastRepositoryRead = repositorySources.reduce((latest, source) =>
|
|
328
|
+
source.observedAt > latest ? source.observedAt : latest, repositorySources[0].observedAt);
|
|
329
|
+
if (externalSources.some((source) => source.observedAt < lastRepositoryRead)) {
|
|
330
|
+
throw new Error("self-help must inspect the repository before external research");
|
|
331
|
+
}
|
|
332
|
+
const result = { schema: SELF_HELP_REPORT_SCHEMA, reportId: null,
|
|
333
|
+
goalId: exactId(goal.goalId, "goalId"), goalStepId: exactId(step.stepId, "goalStepId"),
|
|
334
|
+
planDefinitionsDigest: goal.plan.definitionsDigest,
|
|
335
|
+
question: safeKnowledgeText(report?.question, "selfHelp.question", 500),
|
|
336
|
+
reason: safeKnowledgeText(report?.reason, "selfHelp.reason", 500), repositorySufficient,
|
|
337
|
+
repositorySources, externalSources, conclusion: safeKnowledgeText(report?.conclusion, "selfHelp.conclusion", 2000),
|
|
338
|
+
completedAt, reportDigest: null, authority: "context-only-research" };
|
|
339
|
+
result.reportDigest = sha256(JSON.stringify(selfHelpMaterial(result)));
|
|
340
|
+
result.reportId = "self-help-report:" + result.reportDigest.slice(0, 32);
|
|
341
|
+
return result;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export function createSelfHelpEscalation({ goal, step, report, now }) {
|
|
345
|
+
const completedAt = timestamp(now);
|
|
346
|
+
if (report?.repositorySufficient !== false) {
|
|
347
|
+
throw new Error("owner escalation requires exhausted repository evidence");
|
|
348
|
+
}
|
|
349
|
+
const repositorySources = (report?.repositorySources || []).map((source, index) =>
|
|
350
|
+
normalizeRepositorySource(source, index, completedAt));
|
|
351
|
+
const externalSources = (report?.externalSources || []).map((source, index) =>
|
|
352
|
+
normalizeExternalSource(source, index, completedAt));
|
|
353
|
+
if (repositorySources.length < 1 || repositorySources.length > 16) {
|
|
354
|
+
throw new Error("owner escalation requires 1-16 repository evidence sources");
|
|
355
|
+
}
|
|
356
|
+
const origins = new Set(externalSources.map((source) => new URL(source.url).origin));
|
|
357
|
+
if (externalSources.length < 2 || externalSources.length > 8 || origins.size < 2) {
|
|
358
|
+
throw new Error("owner escalation requires two independent public primary sources");
|
|
359
|
+
}
|
|
360
|
+
const lastRepositoryRead = repositorySources.reduce((latest, source) =>
|
|
361
|
+
source.observedAt > latest ? source.observedAt : latest, repositorySources[0].observedAt);
|
|
362
|
+
if (externalSources.some((source) => source.observedAt < lastRepositoryRead)) {
|
|
363
|
+
throw new Error("self-help must inspect the repository before external research");
|
|
364
|
+
}
|
|
365
|
+
const conflictSourceDigests = [...new Set(report?.conflictSourceDigests || [])].sort();
|
|
366
|
+
if (conflictSourceDigests.length !== 2 || conflictSourceDigests.some((digest) => !DIGEST_RE.test(digest))) {
|
|
367
|
+
throw new Error("owner escalation requires exactly two conflicting source digests");
|
|
368
|
+
}
|
|
369
|
+
const conflictSources = conflictSourceDigests.map((digest) =>
|
|
370
|
+
externalSources.find((source) => source.sourceDigest === digest));
|
|
371
|
+
if (conflictSources.some((source) => !source)
|
|
372
|
+
|| new Set(conflictSources.map((source) => new URL(source.url).origin)).size !== 2) {
|
|
373
|
+
throw new Error("conflicting digests must bind two independent external primary sources");
|
|
374
|
+
}
|
|
375
|
+
const options = (report?.options || []).map((option, index) =>
|
|
376
|
+
safeKnowledgeText(option, `selfHelp.options[${index}]`, 500));
|
|
377
|
+
if (options.length < 2 || options.length > 8 || new Set(options).size !== options.length) {
|
|
378
|
+
throw new Error("owner escalation requires 2-8 distinct bounded options");
|
|
379
|
+
}
|
|
380
|
+
const result = { schema: SELF_HELP_ESCALATION_SCHEMA, reportId: null,
|
|
381
|
+
goalId: exactId(goal.goalId, "goalId"), goalStepId: exactId(step.stepId, "goalStepId"),
|
|
382
|
+
planDefinitionsDigest: goal.plan.definitionsDigest,
|
|
383
|
+
question: safeKnowledgeText(report?.question, "selfHelp.question", 500),
|
|
384
|
+
reason: safeKnowledgeText(report?.reason, "selfHelp.reason", 500), repositorySufficient: false,
|
|
385
|
+
repositorySources, externalSources, outcome: "needs-owner-input", conflictSourceDigests,
|
|
386
|
+
ownerQuestion: safeKnowledgeText(report?.ownerQuestion, "selfHelp.ownerQuestion", 500),
|
|
387
|
+
ownerReason: safeKnowledgeText(report?.ownerReason, "selfHelp.ownerReason", 500), options,
|
|
388
|
+
completedAt, reportDigest: null, authority: "context-only-research" };
|
|
389
|
+
result.reportDigest = sha256(JSON.stringify(selfHelpEscalationMaterial(result)));
|
|
390
|
+
result.reportId = "self-help-escalation:" + result.reportDigest.slice(0, 32);
|
|
391
|
+
return result;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export function createSelfHelpResolution({ goal, step, queueId, report, agentId, now }) {
|
|
395
|
+
const evidence = createSelfHelpReport({ goal, step, report, now });
|
|
396
|
+
const requirement = pendingSelfHelpRequirement(step);
|
|
397
|
+
const repeated = (step.selfHelpReports || []).some((existing) =>
|
|
398
|
+
existing.question === evidence.question && existing.reason === evidence.reason);
|
|
399
|
+
if (!requirement && !repeated) {
|
|
400
|
+
throw new Error("self-help report requires an exact pending repository-first requirement");
|
|
401
|
+
}
|
|
402
|
+
if (requirement && (requirement.question !== evidence.question || requirement.reason !== evidence.reason)) {
|
|
403
|
+
throw new Error("self-help report does not answer the pending repository-first requirement");
|
|
404
|
+
}
|
|
405
|
+
const needsOwner = evidence.schema === SELF_HELP_ESCALATION_SCHEMA;
|
|
406
|
+
const gap = createKnowledgeGap(goal, step, queueId, { question: needsOwner ? evidence.ownerQuestion : evidence.question,
|
|
407
|
+
reason: needsOwner ? evidence.ownerReason : evidence.reason,
|
|
408
|
+
requiredEvidence: needsOwner ? "owner-input" : "objective-observation" }, now);
|
|
409
|
+
if (needsOwner) return { status: "needs-owner-input", report: evidence, gap };
|
|
410
|
+
return { status: "resolved", report: evidence, gap: resolveKnowledgeGapCandidate(gap, {
|
|
411
|
+
answer: evidence.conclusion, answerSource: "objective-observation", sourceDigest: evidence.reportDigest,
|
|
412
|
+
resolvedAt: now, resolvedBySubjectId: agentId
|
|
413
|
+
}) };
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export function selfHelpPolicyForWorkItem(step = null) {
|
|
417
|
+
const pending = step && pendingSelfHelpRequirement(step);
|
|
418
|
+
return { schema: "agentspine.self-help-policy/v1", order: ["repository", "public-primary-sources"],
|
|
419
|
+
repositoryFirst: true, externalOnlyWhenRepositoryInsufficient: true, maxRepositorySources: 16,
|
|
420
|
+
maxExternalSources: 8, externalContentTrust: "untrusted", reportSchema: SELF_HELP_REPORT_SCHEMA,
|
|
421
|
+
reportFields: ["question", "reason", "repositorySufficient", "repositorySources", "externalSources", "conclusion"],
|
|
422
|
+
escalationSchema: SELF_HELP_ESCALATION_SCHEMA, escalationOutcome: "needs-owner-input",
|
|
423
|
+
escalationFields: ["question", "reason", "repositorySufficient", "repositorySources", "externalSources",
|
|
424
|
+
"outcome", "conflictSourceDigests", "ownerQuestion", "ownerReason", "options"],
|
|
425
|
+
escalationRequiresIndependentConflict: true,
|
|
426
|
+
repositorySourceFields: ["kind", "path", "commit", "sourceDigest", "observedAt", "relevance"],
|
|
427
|
+
externalSourceFields: ["kind", "url", "version", "license", "sourceDigest", "observedAt", "relevance"],
|
|
428
|
+
repositoryKinds: [...REPOSITORY_KINDS], externalKinds: [...EXTERNAL_KINDS],
|
|
429
|
+
objectiveQuestionRequiresSelfHelp: true, pendingRequirement: pending ? structuredClone(pending) : null,
|
|
430
|
+
mayGrantAuthority: false, authority: "context-only-research" };
|
|
431
|
+
}
|