blun-king-cli 9.1.566 → 9.1.569
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-spine-plugin/.claude-plugin/marketplace.json +1 -1
- package/agent-spine-plugin/.claude-plugin/plugin.json +1 -1
- package/agent-spine-plugin/.codex-plugin/plugin.json +2 -1
- package/agent-spine-plugin/CHANGELOG.md +1581 -0
- package/agent-spine-plugin/README.md +30 -4
- package/agent-spine-plugin/blun.plugin.json +3 -3
- package/agent-spine-plugin/docs/acceptance.md +61 -0
- package/agent-spine-plugin/docs/assignment-continuation.md +48 -0
- package/agent-spine-plugin/docs/host-integration.md +178 -0
- package/agent-spine-plugin/docs/preflight-recall.md +69 -0
- package/agent-spine-plugin/docs/preservation-contract.md +53 -0
- package/agent-spine-plugin/docs/quality-gates.md +50 -0
- package/agent-spine-plugin/docs/releasing.md +85 -0
- package/agent-spine-plugin/docs/session-timeline.md +251 -0
- package/agent-spine-plugin/docs/source-roots.md +113 -0
- package/agent-spine-plugin/docs/structured-completion.md +67 -0
- package/agent-spine-plugin/docs/world-model.md +94 -0
- package/agent-spine-plugin/hooks/codex.json +2 -2
- package/agent-spine-plugin/hooks/hooks.json +1 -1
- package/agent-spine-plugin/hooks/version.json +1 -1
- package/agent-spine-plugin/package.json +13 -3
- package/agent-spine-plugin/scripts/check-codex-install.js +226 -0
- package/agent-spine-plugin/scripts/check-hosts.js +14 -5
- package/agent-spine-plugin/scripts/check-install-hook.js +226 -0
- package/agent-spine-plugin/scripts/check-install-selfstarter.js +154 -0
- package/agent-spine-plugin/scripts/check-install.js +478 -0
- package/agent-spine-plugin/scripts/check-line-budget.js +58 -0
- package/agent-spine-plugin/scripts/check-syntax.js +29 -0
- package/agent-spine-plugin/scripts/github-actions.js +11 -0
- package/agent-spine-plugin/scripts/hermetic-process.js +183 -0
- package/agent-spine-plugin/scripts/release-check.js +145 -0
- package/agent-spine-plugin/scripts/run-acceptance.js +19 -0
- package/agent-spine-plugin/scripts/run-checks.js +47 -0
- package/agent-spine-plugin/scripts/run-tests-hermetic.js +89 -0
- package/agent-spine-plugin/skills/agent-spine/SKILL.md +16 -2
- package/agent-spine-plugin/spine-example/1-identity.md +12 -0
- package/agent-spine-plugin/spine-example/2-voice.md +6 -0
- package/agent-spine-plugin/spine-example/3-conduct.md +8 -0
- package/agent-spine-plugin/spine-example/4-history.md +4 -0
- package/agent-spine-plugin/src/cli-agent.js +296 -0
- package/agent-spine-plugin/src/cli-attention.js +95 -0
- package/agent-spine-plugin/src/cli-autonomy.js +36 -0
- package/agent-spine-plugin/src/cli-common.js +71 -0
- package/agent-spine-plugin/src/cli-continuity.js +116 -0
- package/agent-spine-plugin/src/cli-core.js +128 -0
- package/agent-spine-plugin/src/cli-diagnostics.js +291 -0
- package/agent-spine-plugin/src/cli-host.js +21 -0
- package/agent-spine-plugin/src/cli-learning.js +305 -0
- package/agent-spine-plugin/src/cli-premortem.js +16 -0
- package/agent-spine-plugin/src/cli-sharing.js +230 -0
- package/agent-spine-plugin/src/cli.js +40 -1350
- package/agent-spine-plugin/src/codex-reader-launcher.js +182 -0
- package/agent-spine-plugin/src/hook.js +185 -567
- package/agent-spine-plugin/src/index.js +16 -2
- package/agent-spine-plugin/src/lib/acceptance.js +113 -11
- package/agent-spine-plugin/src/lib/action-lesson-recall.js +53 -0
- package/agent-spine-plugin/src/lib/attention-context.js +167 -0
- package/agent-spine-plugin/src/lib/attention-events.js +113 -0
- package/agent-spine-plugin/src/lib/attention-privacy.js +72 -0
- package/agent-spine-plugin/src/lib/attention-schema.js +164 -0
- package/agent-spine-plugin/src/lib/attention-storage.js +93 -0
- package/agent-spine-plugin/src/lib/attention.js +9 -600
- package/agent-spine-plugin/src/lib/audit-premortem.js +223 -0
- package/agent-spine-plugin/src/lib/audit.js +56 -6
- package/agent-spine-plugin/src/lib/autonomy-policy.js +110 -0
- package/agent-spine-plugin/src/lib/autonomy-store.js +202 -0
- package/agent-spine-plugin/src/lib/autonomy.js +8 -0
- package/agent-spine-plugin/src/lib/briefing.js +67 -4
- package/agent-spine-plugin/src/lib/catalog-document-read.js +51 -0
- package/agent-spine-plugin/src/lib/catalog.js +1 -1
- package/agent-spine-plugin/src/lib/codex-installation.js +231 -0
- package/agent-spine-plugin/src/lib/codex-skill-installation.js +211 -0
- package/agent-spine-plugin/src/lib/context.js +3 -1
- package/agent-spine-plugin/src/lib/delivery-agent-usage.js +224 -0
- package/agent-spine-plugin/src/lib/delivery-assignment.js +220 -0
- package/agent-spine-plugin/src/lib/delivery-command-actions.js +453 -0
- package/agent-spine-plugin/src/lib/delivery-knowledge.js +78 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-binding.js +107 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-closure.js +171 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-codec.js +45 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-correction.js +101 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-file.js +21 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-index.js +493 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-inspection.js +37 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-recovery.js +120 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-rejection.js +65 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-results.js +28 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-session-guard.js +46 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-write-ledger.js +285 -0
- package/agent-spine-plugin/src/lib/delivery-premortem.js +499 -0
- package/agent-spine-plugin/src/lib/delivery-shell-heredoc.js +115 -0
- package/agent-spine-plugin/src/lib/delivery-shell-substitutions.js +111 -0
- package/agent-spine-plugin/src/lib/delivery-shell-wrapper.js +64 -0
- package/agent-spine-plugin/src/lib/delivery-target.js +73 -0
- package/agent-spine-plugin/src/lib/delivery-verification.js +445 -0
- package/agent-spine-plugin/src/lib/documents.js +27 -5
- package/agent-spine-plugin/src/lib/filesystem-retry.js +2 -0
- package/agent-spine-plugin/src/lib/gateway-common.js +68 -0
- package/agent-spine-plugin/src/lib/gateway-control.js +302 -0
- package/agent-spine-plugin/src/lib/gateway-delivery.js +103 -0
- package/agent-spine-plugin/src/lib/gateway-execution.js +350 -0
- package/agent-spine-plugin/src/lib/gateway-host-lifecycle.js +185 -0
- package/agent-spine-plugin/src/lib/gateway-inspection.js +85 -0
- package/agent-spine-plugin/src/lib/gateway-knowledge.js +129 -0
- package/agent-spine-plugin/src/lib/gateway-policy-provenance.js +197 -0
- package/agent-spine-plugin/src/lib/gateway-premortem-disposition.js +82 -0
- package/agent-spine-plugin/src/lib/gateway-premortem.js +363 -0
- package/agent-spine-plugin/src/lib/gateway-runs.js +300 -0
- package/agent-spine-plugin/src/lib/gateway-runtime-identity.js +31 -0
- package/agent-spine-plugin/src/lib/gateway-runtime-records.js +21 -0
- package/agent-spine-plugin/src/lib/gateway-runtime.js +18 -1623
- package/agent-spine-plugin/src/lib/gateway-state-transaction.js +356 -0
- package/agent-spine-plugin/src/lib/gateway-state.js +342 -0
- package/agent-spine-plugin/src/lib/hook-artifact-guards.js +356 -0
- package/agent-spine-plugin/src/lib/hook-audit.js +16 -2
- package/agent-spine-plugin/src/lib/hook-briefing-use.js +68 -0
- package/agent-spine-plugin/src/lib/hook-context.js +424 -0
- package/agent-spine-plugin/src/lib/hook-final-message.js +26 -0
- package/agent-spine-plugin/src/lib/hook-input.js +27 -0
- package/agent-spine-plugin/src/lib/hook-output.js +140 -0
- package/agent-spine-plugin/src/lib/hook-premortem.js +257 -0
- package/agent-spine-plugin/src/lib/hook-process-advisory.js +37 -0
- package/agent-spine-plugin/src/lib/hook-protection.js +95 -0
- package/agent-spine-plugin/src/lib/hook-stop-verification.js +84 -0
- package/agent-spine-plugin/src/lib/hook-timeline.js +91 -0
- package/agent-spine-plugin/src/lib/identifier-analysis.js +446 -0
- package/agent-spine-plugin/src/lib/indexed-memory.js +23 -6
- package/agent-spine-plugin/src/lib/knowledge-evidence.js +431 -0
- package/agent-spine-plugin/src/lib/learning-applications.js +441 -0
- package/agent-spine-plugin/src/lib/learning-candidates.js +274 -0
- package/agent-spine-plugin/src/lib/learning-context.js +130 -0
- package/agent-spine-plugin/src/lib/learning-delivery-contracts.js +310 -0
- package/agent-spine-plugin/src/lib/learning-evaluation-contracts.js +350 -0
- package/agent-spine-plugin/src/lib/learning-evaluation-registration.js +372 -0
- package/agent-spine-plugin/src/lib/learning-evaluation-revocation.js +292 -0
- package/agent-spine-plugin/src/lib/learning-evidence-contracts.js +321 -0
- package/agent-spine-plugin/src/lib/learning-findings.js +458 -0
- package/agent-spine-plugin/src/lib/learning-measurement-contracts.js +285 -0
- package/agent-spine-plugin/src/lib/learning-measurements.js +265 -0
- package/agent-spine-plugin/src/lib/learning-outcome-contracts.js +165 -0
- package/agent-spine-plugin/src/lib/learning-outcomes.js +343 -0
- package/agent-spine-plugin/src/lib/learning-reconciliation.js +290 -0
- package/agent-spine-plugin/src/lib/learning-retry-contracts.js +188 -0
- package/agent-spine-plugin/src/lib/learning-schema.js +229 -0
- package/agent-spine-plugin/src/lib/learning-scope-targets.js +424 -0
- package/agent-spine-plugin/src/lib/learning-state-upgrade.js +477 -0
- package/agent-spine-plugin/src/lib/learning-status-configuration.js +476 -0
- package/agent-spine-plugin/src/lib/learning-storage.js +203 -0
- package/agent-spine-plugin/src/lib/learning-trial-recovery.js +218 -0
- package/agent-spine-plugin/src/lib/learning-validation-contracts.js +375 -0
- package/agent-spine-plugin/src/lib/learning-validation-renewal.js +298 -0
- package/agent-spine-plugin/src/lib/learning-validation-runtime.js +234 -0
- package/agent-spine-plugin/src/lib/learning.js +36 -6923
- package/agent-spine-plugin/src/lib/lesson-recall-session.js +172 -0
- package/agent-spine-plugin/src/lib/mcp-autonomy-tools.js +37 -0
- package/agent-spine-plugin/src/lib/mcp-delivery-completion.js +124 -0
- package/agent-spine-plugin/src/lib/mcp-delivery-tools.js +45 -0
- package/agent-spine-plugin/src/lib/mcp-premortem.js +68 -0
- package/agent-spine-plugin/src/lib/mcp-runtime.js +269 -0
- package/agent-spine-plugin/src/lib/mcp-source-context.js +51 -0
- package/agent-spine-plugin/src/lib/mcp-timeline-tools.js +164 -0
- package/agent-spine-plugin/src/lib/mcp-world-tools.js +52 -0
- package/agent-spine-plugin/src/lib/owned-file-lock.js +30 -10
- package/agent-spine-plugin/src/lib/project-portfolio.js +176 -0
- package/agent-spine-plugin/src/lib/selfstarter-core.js +382 -0
- package/agent-spine-plugin/src/lib/selfstarter-jobs.js +149 -0
- package/agent-spine-plugin/src/lib/selfstarter-lease.js +204 -0
- package/agent-spine-plugin/src/lib/selfstarter-policy.js +90 -0
- package/agent-spine-plugin/src/lib/selfstarter-workspace.js +111 -0
- package/agent-spine-plugin/src/lib/selfstarter.js +11 -911
- package/agent-spine-plugin/src/lib/session-timeline-auth.js +316 -0
- package/agent-spine-plugin/src/lib/session-timeline-codex.js +58 -0
- package/agent-spine-plugin/src/lib/session-timeline-contract.js +48 -0
- package/agent-spine-plugin/src/lib/session-timeline-enrollment-source.js +41 -0
- package/agent-spine-plugin/src/lib/session-timeline-enrollment-storage.js +132 -0
- package/agent-spine-plugin/src/lib/session-timeline-enrollment-transport.js +17 -0
- package/agent-spine-plugin/src/lib/session-timeline-enrollment.js +500 -0
- package/agent-spine-plugin/src/lib/session-timeline-event-extract.js +157 -0
- package/agent-spine-plugin/src/lib/session-timeline-host-origin.js +74 -0
- package/agent-spine-plugin/src/lib/session-timeline-host-receipt.js +117 -0
- package/agent-spine-plugin/src/lib/session-timeline-invocation.js +201 -0
- package/agent-spine-plugin/src/lib/session-timeline-king.js +79 -0
- package/agent-spine-plugin/src/lib/session-timeline-prior.js +59 -0
- package/agent-spine-plugin/src/lib/session-timeline-provider.js +34 -0
- package/agent-spine-plugin/src/lib/session-timeline-query.js +55 -0
- package/agent-spine-plugin/src/lib/session-timeline-results.js +31 -0
- package/agent-spine-plugin/src/lib/session-timeline-root.js +11 -0
- package/agent-spine-plugin/src/lib/session-timeline-search.js +82 -0
- package/agent-spine-plugin/src/lib/session-timeline-sid-acl.js +217 -0
- package/agent-spine-plugin/src/lib/session-timeline-source.js +83 -0
- package/agent-spine-plugin/src/lib/session-timeline-state.js +45 -0
- package/agent-spine-plugin/src/lib/session-timeline-transport.js +50 -0
- package/agent-spine-plugin/src/lib/session-timeline-windows-acl.js +148 -0
- package/agent-spine-plugin/src/lib/session-timeline.js +453 -0
- package/agent-spine-plugin/src/lib/source-roots.js +69 -136
- package/agent-spine-plugin/src/lib/source-tree-scan.js +178 -0
- package/agent-spine-plugin/src/lib/task-knowledge-context.js +78 -0
- package/agent-spine-plugin/src/lib/timeline-tool-guard.js +202 -0
- package/agent-spine-plugin/src/lib/world-knowledge.js +249 -0
- package/agent-spine-plugin/src/lib/world-model.js +278 -0
- package/agent-spine-plugin/src/mcp.js +20 -161
- package/agent-spine-plugin/src/version.js +1 -1
- package/agent-spine-plugin/src/worker.js +22 -5
- package/bin/agent-resume-snapshot.cjs +2 -2
- package/bin/agentspine-king-goal-inbox.mjs +111 -0
- package/bin/agentspine-king-goal-intake.mjs +106 -0
- package/bin/baseline-skill-performance-policy.cjs +1 -16
- package/bin/core-bootstrap.js +2 -0
- package/bin/curiosity-scout-policy.cjs +5 -1
- package/bin/input-draft-persistence.cjs +2 -2
- package/bin/king-tui-function-contract.json +33 -0
- package/bin/launcher-restart-policy.cjs +150 -0
- package/bin/launcher-runtime.js +56 -44
- package/bin/managed-context-startup-policy.cjs +27 -0
- package/bin/managed-plugin-selection.cjs +116 -0
- package/bin/mistake-relevance-policy.cjs +1 -1
- package/bin/observer-hooks.cjs +14 -0
- package/bin/oversized-context-offload-policy.cjs +86 -0
- package/bin/plugin-bootstrap.js +7 -40
- package/bin/proactive-compaction-policy.cjs +1 -1
- package/bin/provider-model-refresh-deadline.cjs +53 -0
- package/bin/provider-model-refresh-policy.cjs +107 -0
- package/bin/release-artifact-freeze-policy.cjs +30 -0
- package/bin/repeated-user-message-projection.cjs +3 -126
- package/bin/research-page-result.cjs +74 -0
- package/bin/runtime-exit-ledger.cjs +1 -0
- package/bin/session-compaction-policy.cjs +84 -0
- package/bin/skill-listing-performance-policy.cjs +2 -2
- package/bin/standard-tools-bootstrap.js +0 -37
- package/bin/subagent-skill-policy.cjs +3 -1
- package/bin/telegram-approval-relay.cjs +12 -7
- package/bin/telegram-private-conversation-policy.cjs +3 -2
- package/bin/telegram-queue-handoff-policy.cjs +24 -0
- package/bin/thinking-activity-status-policy.cjs +1 -1
- package/bin/thinking-only-guard.cjs +16 -12
- package/bin/tool-call-loop-policy.cjs +0 -2
- package/bin/tool-result-offload-policy.cjs +11 -2
- package/bin/tui-functional-contract.cjs +55 -0
- package/bin/update-notice.js +18 -14
- package/bin/user-message-offload-policy.cjs +1 -1
- package/bin/user-prompt-hook-origin-policy.cjs +34 -0
- package/bin/windows-node-crash-dump.cjs +110 -0
- package/blun.mjs +2517 -1048
- package/codebase-index/codebase_index.py +4 -3
- package/package.json +3 -17
- package/standard-skills/research-evidence/SKILL.md +39 -0
- package/standard-skills/research-evidence/references/evidence-format.md +82 -0
- package/standard-skills/research-evidence/scripts/evidence-collection.cjs +254 -0
- package/standard-skills/research-evidence/scripts/score-report.cjs +112 -0
- package/standard-skills/web-lesen/SKILL.md +37 -22
- package/standard-skills/web-lesen/scripts/crawl_public.py +376 -0
- package/telegram-plugin/DELIVERY.md +36 -0
- package/telegram-plugin/bin/telegram-approval-relay.cjs +13 -7
- package/telegram-plugin/bin/telegram-launcher-status-queue.cjs +122 -0
- package/telegram-plugin/bin/telegram-private-conversation-policy.cjs +3 -2
- package/telegram-plugin/bin/telegram-reply-parts.cjs +149 -0
- package/telegram-plugin/dist/bridge.mjs +11 -57
- package/telegram-plugin/dist/mcp-server.mjs +33 -4
- package/agent-spine-plugin/skill/SKILL.md +0 -76
- package/bin/mnemo-connect-heartbeat.cjs +0 -204
- package/bin/mnemo-tool-agent-policy.cjs +0 -22
- package/telegram-plugin/bin/telegram-mnemo-capture.cjs +0 -297
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { loadGraph } from "./graph.js";
|
|
3
|
+
import {
|
|
4
|
+
KINDS, ID_RE, DIGEST_RE, PROTECTED_LESSON_RE, TARGET_BOUND_EVALUATIONS, EVIDENCE_REVOCATION_REASONS
|
|
5
|
+
} from "./learning-schema.js";
|
|
6
|
+
import {
|
|
7
|
+
normalizeScope, exactScope, digest, learningTargetForCandidate, evidenceRevocationPayload, revokedEvidence,
|
|
8
|
+
revokedMeasurementForCandidate, evaluatorRecordPayload
|
|
9
|
+
} from "./learning-scope-targets.js";
|
|
10
|
+
import {
|
|
11
|
+
revokedOutcomeForCandidate
|
|
12
|
+
} from "./learning-delivery-contracts.js";
|
|
13
|
+
import {
|
|
14
|
+
date, safeText, assertSafeClaim, validateScope, mutation, preserve,
|
|
15
|
+
normalizeEvidence, evidenceConfidence
|
|
16
|
+
} from "./learning-storage.js";
|
|
17
|
+
|
|
18
|
+
export async function proposeLearning({
|
|
19
|
+
root = process.cwd(), id = `learning:${randomUUID()}`, kind, claim, subjectId = null,
|
|
20
|
+
privacy = "private", groupId = null, scope = null, evidence, supersedesId = null, now = new Date(),
|
|
21
|
+
catalog: providedCatalog = null
|
|
22
|
+
}) {
|
|
23
|
+
if (!ID_RE.test(id)) throw new Error("id must be a stable, whitespace-free identifier");
|
|
24
|
+
if (!KINDS.has(kind)) throw new Error(`unsupported learning kind: ${kind}`);
|
|
25
|
+
claim = safeText(claim, "claim", 1000);
|
|
26
|
+
assertSafeClaim(claim);
|
|
27
|
+
const normalizedScope = normalizeScope(scope, subjectId, groupId);
|
|
28
|
+
if (normalizedScope.groupId !== (groupId ?? null)) throw new Error("scope.groupId must match the privacy groupId");
|
|
29
|
+
const timestamp = date(now, "now");
|
|
30
|
+
return mutation(root, async (state, catalog, learningPath) => {
|
|
31
|
+
if (state.candidates.some((candidate) => candidate.id === id)) {
|
|
32
|
+
throw new Error("learning candidate IDs are immutable; add evidence or propose a superseding candidate");
|
|
33
|
+
}
|
|
34
|
+
const { graph } = await loadGraph(catalog.root, catalog);
|
|
35
|
+
validateScope(privacy, groupId, graph, subjectId);
|
|
36
|
+
const duplicate = state.candidates.find((candidate) => candidate.kind === kind
|
|
37
|
+
&& candidate.claim === claim && exactScope(candidate.scope, normalizedScope)
|
|
38
|
+
&& candidate.privacy === privacy && candidate.status !== "rejected" && candidate.status !== "rolled-back");
|
|
39
|
+
if (duplicate) return { candidate: duplicate, learningPath, unchanged: true };
|
|
40
|
+
const normalizedEvidence = normalizeEvidence(evidence, catalog, timestamp);
|
|
41
|
+
const superseded = supersedesId ? state.candidates.find((candidate) => candidate.id === supersedesId) : null;
|
|
42
|
+
if (supersedesId && (!superseded || superseded.status !== "accepted")) {
|
|
43
|
+
throw new Error(`supersedesId must reference an accepted learning: ${supersedesId}`);
|
|
44
|
+
}
|
|
45
|
+
if (superseded && (superseded.kind !== kind || superseded.subjectId !== subjectId || superseded.privacy !== privacy
|
|
46
|
+
|| superseded.groupId !== groupId || !exactScope(superseded.scope, normalizedScope))) {
|
|
47
|
+
throw new Error("a superseding candidate must keep kind, subject, and privacy scope");
|
|
48
|
+
}
|
|
49
|
+
const conflictsWith = state.candidates.filter((candidate) => candidate.kind === kind
|
|
50
|
+
&& candidate.claim !== claim && exactScope(candidate.scope, normalizedScope)
|
|
51
|
+
&& ["candidate", "accepted"].includes(candidate.status) && candidate.id !== supersedesId)
|
|
52
|
+
.map((candidate) => candidate.id).sort();
|
|
53
|
+
if (conflictsWith.length) {
|
|
54
|
+
state.candidates = state.candidates.map((candidate) => conflictsWith.includes(candidate.id)
|
|
55
|
+
? { ...candidate, conflictsWith: [...new Set([...(candidate.conflictsWith || []), id])].sort(), updatedAt: timestamp }
|
|
56
|
+
: candidate);
|
|
57
|
+
}
|
|
58
|
+
const candidate = {
|
|
59
|
+
id,
|
|
60
|
+
kind,
|
|
61
|
+
claim,
|
|
62
|
+
subjectId,
|
|
63
|
+
privacy,
|
|
64
|
+
groupId,
|
|
65
|
+
scope: normalizedScope,
|
|
66
|
+
status: "candidate",
|
|
67
|
+
evidence: [normalizedEvidence],
|
|
68
|
+
confidence: normalizedEvidence.confidence,
|
|
69
|
+
supersedesId,
|
|
70
|
+
supersededIds: [],
|
|
71
|
+
conflictsWith,
|
|
72
|
+
requiresLocalReview: PROTECTED_LESSON_RE.test(claim),
|
|
73
|
+
automatic: false,
|
|
74
|
+
createdAt: timestamp,
|
|
75
|
+
updatedAt: timestamp,
|
|
76
|
+
acceptedAt: null,
|
|
77
|
+
authority: "context-only"
|
|
78
|
+
};
|
|
79
|
+
state.candidates.push(candidate);
|
|
80
|
+
state.candidates.sort((a, b) => a.id.localeCompare(b.id));
|
|
81
|
+
return { candidate, learningPath };
|
|
82
|
+
}, providedCatalog);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function addLearningEvidence({
|
|
86
|
+
root = process.cwd(), id, evidence, now = new Date(), catalog: providedCatalog = null
|
|
87
|
+
}) {
|
|
88
|
+
if (!ID_RE.test(id || "")) throw new Error("id is required");
|
|
89
|
+
const timestamp = date(now, "now");
|
|
90
|
+
return mutation(root, (state, catalog, learningPath) => {
|
|
91
|
+
const previous = state.candidates.find((candidate) => candidate.id === id);
|
|
92
|
+
if (!previous) throw new Error(`unknown learning candidate: ${id}`);
|
|
93
|
+
if (previous.status !== "candidate") throw new Error("evidence can only be added to an unreviewed candidate");
|
|
94
|
+
if (revokedEvidence(state, previous)) {
|
|
95
|
+
throw new Error("revoked evidence freezes this learning target; propose a superseding candidate instead");
|
|
96
|
+
}
|
|
97
|
+
if (state.measurementRevocations.some((receipt) => receipt.learningId === id)) {
|
|
98
|
+
throw new Error("revoked measurement evidence freezes this learning target; propose a superseding candidate instead");
|
|
99
|
+
}
|
|
100
|
+
if (state.outcomeRevocations.some((receipt) => receipt.learningId === id)) {
|
|
101
|
+
throw new Error("revoked outcome evidence freezes this learning target; propose a superseding candidate instead");
|
|
102
|
+
}
|
|
103
|
+
if (state.evaluations.some((contract) => contract.learningId === id
|
|
104
|
+
&& TARGET_BOUND_EVALUATIONS.has(contract.schema))) {
|
|
105
|
+
throw new Error("evaluated learning target is immutable; propose a superseding candidate and evaluation contract");
|
|
106
|
+
}
|
|
107
|
+
const item = normalizeEvidence(evidence, catalog, timestamp);
|
|
108
|
+
if (previous.evidence.some((entry) => entry.id === item.id)) throw new Error(`duplicate evidence id: ${item.id}`);
|
|
109
|
+
preserve(state, "learning-candidate", previous, timestamp);
|
|
110
|
+
const candidate = {
|
|
111
|
+
...previous,
|
|
112
|
+
evidence: [...previous.evidence, item],
|
|
113
|
+
confidence: evidenceConfidence([...previous.evidence, item]),
|
|
114
|
+
updatedAt: timestamp,
|
|
115
|
+
authority: "context-only"
|
|
116
|
+
};
|
|
117
|
+
state.candidates = state.candidates.map((entry) => entry.id === id ? candidate : entry);
|
|
118
|
+
return { candidate, learningPath };
|
|
119
|
+
}, providedCatalog);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function revokeLearningEvidence({
|
|
123
|
+
root = process.cwd(), learningId, evidenceId, reasonCode, reason,
|
|
124
|
+
confirmation, now = new Date()
|
|
125
|
+
}) {
|
|
126
|
+
if (confirmation !== "local-evidence-revocation-confirmed") {
|
|
127
|
+
throw new Error("evidence revocation requires explicit local confirmation");
|
|
128
|
+
}
|
|
129
|
+
if (!ID_RE.test(learningId || "") || !ID_RE.test(evidenceId || "")) {
|
|
130
|
+
throw new Error("learningId and evidenceId must be stable identifiers");
|
|
131
|
+
}
|
|
132
|
+
if (!EVIDENCE_REVOCATION_REASONS.has(reasonCode)) {
|
|
133
|
+
throw new Error("reasonCode must be retracted, source-invalid, measurement-invalid, duplicate, or other");
|
|
134
|
+
}
|
|
135
|
+
const revokeReason = safeText(reason, "reason", 500);
|
|
136
|
+
const timestamp = date(now, "now");
|
|
137
|
+
return mutation(root, (state, _catalog, learningPath) => {
|
|
138
|
+
const candidate = state.candidates.find((entry) => entry.id === learningId);
|
|
139
|
+
if (!candidate) throw new Error(`unknown learning candidate: ${learningId}`);
|
|
140
|
+
const evidence = candidate.evidence.find((entry) => entry.id === evidenceId);
|
|
141
|
+
if (!evidence) throw new Error(`unknown learning evidence: ${evidenceId}`);
|
|
142
|
+
const targetDigest = learningTargetForCandidate(candidate).digest;
|
|
143
|
+
const id = `evidence-revocation:${createHash("sha256")
|
|
144
|
+
.update(`${learningId}\0${evidenceId}\0${targetDigest}`).digest("hex").slice(0, 32)}`;
|
|
145
|
+
const existing = state.evidenceRevocations.find((entry) => entry.learningId === learningId
|
|
146
|
+
&& entry.evidenceId === evidenceId);
|
|
147
|
+
const reasonDigest = digest(revokeReason);
|
|
148
|
+
if (existing) {
|
|
149
|
+
if (existing.reasonCode !== reasonCode || existing.reasonDigest !== reasonDigest
|
|
150
|
+
|| existing.evidenceDigest !== digest(evidence) || existing.targetDigest !== targetDigest) {
|
|
151
|
+
throw new Error("learning evidence revocations are immutable");
|
|
152
|
+
}
|
|
153
|
+
return { receipt: existing, learningPath, unchanged: true, authority: "context-only" };
|
|
154
|
+
}
|
|
155
|
+
const payload = evidenceRevocationPayload({
|
|
156
|
+
id, learningId, evidenceId, evidenceDigest: digest(evidence), targetDigest, reasonCode,
|
|
157
|
+
reasonDigest, revokedAt: timestamp
|
|
158
|
+
});
|
|
159
|
+
const receipt = { ...payload, digest: digest(payload) };
|
|
160
|
+
state.evidenceRevocations.push(receipt);
|
|
161
|
+
state.evidenceRevocations.sort((a, b) => a.id.localeCompare(b.id));
|
|
162
|
+
return { receipt, learningPath, unchanged: false, authority: "context-only" };
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function acceptCandidate(state, candidate, timestamp, automatic, promotion = null) {
|
|
167
|
+
preserve(state, "learning-candidate", candidate, timestamp);
|
|
168
|
+
const superseded = candidate.supersedesId
|
|
169
|
+
? state.candidates.find((entry) => entry.id === candidate.supersedesId && entry.status === "accepted")
|
|
170
|
+
: null;
|
|
171
|
+
if (candidate.supersedesId && !superseded) throw new Error("the learning being superseded is no longer active");
|
|
172
|
+
if (superseded) {
|
|
173
|
+
preserve(state, "learning-candidate", superseded, timestamp);
|
|
174
|
+
state.candidates = state.candidates.map((entry) => entry.id === superseded.id
|
|
175
|
+
? { ...entry, status: "superseded", updatedAt: timestamp, authority: "context-only" }
|
|
176
|
+
: entry);
|
|
177
|
+
}
|
|
178
|
+
const accepted = {
|
|
179
|
+
...candidate,
|
|
180
|
+
status: "accepted",
|
|
181
|
+
supersededIds: superseded ? [superseded.id] : [],
|
|
182
|
+
automatic,
|
|
183
|
+
promotion,
|
|
184
|
+
acceptedAt: timestamp,
|
|
185
|
+
updatedAt: timestamp,
|
|
186
|
+
authority: "context-only"
|
|
187
|
+
};
|
|
188
|
+
state.candidates = state.candidates.map((entry) => entry.id === candidate.id ? accepted : entry);
|
|
189
|
+
return accepted;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export async function reviewLearning({
|
|
193
|
+
root = process.cwd(), id, decision, reason, confirmedByUser = false, now = new Date()
|
|
194
|
+
}) {
|
|
195
|
+
if (!ID_RE.test(id || "")) throw new Error("id is required");
|
|
196
|
+
if (!new Set(["accept", "reject"]).has(decision)) throw new Error("decision must be accept or reject");
|
|
197
|
+
const reviewReason = safeText(reason, "reason", 500);
|
|
198
|
+
const timestamp = date(now, "now");
|
|
199
|
+
return mutation(root, (state, _catalog, learningPath) => {
|
|
200
|
+
const candidate = state.candidates.find((entry) => entry.id === id);
|
|
201
|
+
if (!candidate) throw new Error(`unknown learning candidate: ${id}`);
|
|
202
|
+
if (candidate.status !== "candidate") throw new Error("only an unreviewed candidate can be reviewed");
|
|
203
|
+
if (decision === "accept") {
|
|
204
|
+
if (!confirmedByUser) throw new Error("acceptance requires explicit user confirmation");
|
|
205
|
+
if (revokedEvidence(state, candidate)) throw new Error("learning evidence was revoked; propose a new candidate before acceptance");
|
|
206
|
+
if (revokedMeasurementForCandidate(state, candidate)) {
|
|
207
|
+
throw new Error("learning measurement was revoked; propose a new candidate before acceptance");
|
|
208
|
+
}
|
|
209
|
+
if (revokedOutcomeForCandidate(state, candidate)) {
|
|
210
|
+
throw new Error("learning outcome was revoked; propose a new candidate before acceptance");
|
|
211
|
+
}
|
|
212
|
+
const accepted = acceptCandidate(state, candidate, timestamp, false, null);
|
|
213
|
+
accepted.review = { decision, reason: reviewReason, confirmedByUser: true, reviewedAt: timestamp, authority: "context-only" };
|
|
214
|
+
return { candidate: accepted, learningPath };
|
|
215
|
+
}
|
|
216
|
+
preserve(state, "learning-candidate", candidate, timestamp);
|
|
217
|
+
const rejected = {
|
|
218
|
+
...candidate,
|
|
219
|
+
status: "rejected",
|
|
220
|
+
updatedAt: timestamp,
|
|
221
|
+
review: { decision, reason: reviewReason, confirmedByUser: false, reviewedAt: timestamp, authority: "context-only" },
|
|
222
|
+
authority: "context-only"
|
|
223
|
+
};
|
|
224
|
+
state.candidates = state.candidates.map((entry) => entry.id === id ? rejected : entry);
|
|
225
|
+
return { candidate: rejected, learningPath };
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function distinctEvidence(candidate) {
|
|
230
|
+
return new Set(candidate.evidence.map((item) => item.sourceSha256 || item.sourceDocument || item.id)).size;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export async function registerLearningEvaluator({
|
|
234
|
+
root = process.cwd(), id, principalDigest, confirmLocalEvaluator = false, now = new Date()
|
|
235
|
+
}) {
|
|
236
|
+
if (!confirmLocalEvaluator) throw new Error("evaluator registration requires explicit local confirmation");
|
|
237
|
+
if (!ID_RE.test(id || "")) throw new Error("evaluator id must be a stable identifier");
|
|
238
|
+
if (!DIGEST_RE.test(principalDigest || "")) throw new Error("evaluator principalDigest must be SHA-256");
|
|
239
|
+
const timestamp = date(now, "now");
|
|
240
|
+
return mutation(root, (state, _catalog, learningPath) => {
|
|
241
|
+
const existing = state.evaluatorRegistry.find((record) => record.id === id || record.principalDigest === principalDigest);
|
|
242
|
+
if (existing) {
|
|
243
|
+
if (existing.id === id && existing.principalDigest === principalDigest && existing.status === "active") {
|
|
244
|
+
return { evaluator: existing, learningPath, unchanged: true };
|
|
245
|
+
}
|
|
246
|
+
throw new Error("evaluator IDs and principal roots are immutable; register a new distinct evaluator instead");
|
|
247
|
+
}
|
|
248
|
+
const payload = evaluatorRecordPayload({ id, principalDigest, status: "active", registeredAt: timestamp,
|
|
249
|
+
revokedAt: null, reason: null });
|
|
250
|
+
const evaluator = { ...payload, digest: digest(payload) };
|
|
251
|
+
state.evaluatorRegistry.push(evaluator);
|
|
252
|
+
state.evaluatorRegistry.sort((a, b) => a.id.localeCompare(b.id));
|
|
253
|
+
return { evaluator, learningPath, unchanged: false };
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export async function revokeLearningEvaluator({
|
|
258
|
+
root = process.cwd(), id, reason, confirmLocalEvaluator = false, now = new Date()
|
|
259
|
+
}) {
|
|
260
|
+
if (!confirmLocalEvaluator) throw new Error("evaluator revocation requires explicit local confirmation");
|
|
261
|
+
if (!ID_RE.test(id || "")) throw new Error("evaluator id must be a stable identifier");
|
|
262
|
+
const revokeReason = safeText(reason, "reason", 500);
|
|
263
|
+
const timestamp = date(now, "now");
|
|
264
|
+
return mutation(root, (state, _catalog, learningPath) => {
|
|
265
|
+
const previous = state.evaluatorRegistry.find((record) => record.id === id);
|
|
266
|
+
if (!previous) throw new Error(`unknown learning evaluator: ${id}`);
|
|
267
|
+
if (previous.status !== "active") throw new Error("learning evaluator is already revoked");
|
|
268
|
+
preserve(state, "learning-evaluator", previous, timestamp);
|
|
269
|
+
const payload = evaluatorRecordPayload({ ...previous, status: "revoked", revokedAt: timestamp, reason: revokeReason });
|
|
270
|
+
const evaluator = { ...payload, digest: digest(payload) };
|
|
271
|
+
state.evaluatorRegistry = state.evaluatorRegistry.map((record) => record.id === id ? evaluator : record);
|
|
272
|
+
return { evaluator, learningPath };
|
|
273
|
+
});
|
|
274
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { buildCatalog } from "./catalog.js";
|
|
2
|
+
import { loadGraph } from "./graph.js";
|
|
3
|
+
import {
|
|
4
|
+
KINDS
|
|
5
|
+
} from "./learning-schema.js";
|
|
6
|
+
import {
|
|
7
|
+
validConfig, normalizeScope, scopeContains, exactScope
|
|
8
|
+
} from "./learning-scope-targets.js";
|
|
9
|
+
import {
|
|
10
|
+
canaryValidity
|
|
11
|
+
} from "./learning-validation-runtime.js";
|
|
12
|
+
import {
|
|
13
|
+
date, integer, loadLearning
|
|
14
|
+
} from "./learning-storage.js";
|
|
15
|
+
|
|
16
|
+
export function groupEntities(graph, groupId, includePrivate) {
|
|
17
|
+
const result = new Set();
|
|
18
|
+
if (!groupId) return result;
|
|
19
|
+
result.add(groupId);
|
|
20
|
+
for (const edge of graph.entityEdges) {
|
|
21
|
+
if (edge.relation !== "member-of" || (!includePrivate && edge.privacy === "private")) continue;
|
|
22
|
+
if (edge.to === groupId) result.add(edge.from);
|
|
23
|
+
if (edge.from === groupId) result.add(edge.to);
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function visible(candidate, entities, audience, includePrivate, groupId) {
|
|
29
|
+
if (candidate.privacy === "private" && !includePrivate) return false;
|
|
30
|
+
if (candidate.privacy === "group" && (!groupId || candidate.groupId !== groupId)) return false;
|
|
31
|
+
if (candidate.privacy === "group" && candidate.subjectId && !audience.has(candidate.subjectId)) return false;
|
|
32
|
+
const subject = candidate.subjectId ? entities.get(candidate.subjectId) : null;
|
|
33
|
+
if (subject?.privacy === "private" && !includePrivate) return false;
|
|
34
|
+
if (subject?.privacy === "group" && !audience.has(subject.id)) return false;
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function learningContext({
|
|
39
|
+
root = process.cwd(), includePrivate = false, groupId = null, kinds = null,
|
|
40
|
+
subjectIds = null, scope = null, maxItems = null, catalog: providedCatalog = null, now = new Date()
|
|
41
|
+
} = {}) {
|
|
42
|
+
const catalog = providedCatalog || await buildCatalog(root);
|
|
43
|
+
const { learning } = await loadLearning(catalog.root, catalog);
|
|
44
|
+
if (!validConfig(learning.config)) throw new Error("learning configuration is invalid; run the audit before using learned context");
|
|
45
|
+
const { graph } = await loadGraph(catalog.root, catalog);
|
|
46
|
+
const entities = new Map(graph.entities.map((entity) => [entity.id, entity]));
|
|
47
|
+
if (groupId !== null) {
|
|
48
|
+
const group = entities.get(groupId);
|
|
49
|
+
if (!group || group.kind !== "group") throw new Error(`unknown group entity: ${groupId}`);
|
|
50
|
+
}
|
|
51
|
+
const audience = groupEntities(graph, groupId, includePrivate);
|
|
52
|
+
const runtimeScope = normalizeScope(scope, null, groupId);
|
|
53
|
+
const kindFilter = kinds === null ? null : new Set(kinds);
|
|
54
|
+
if (kindFilter && [...kindFilter].some((kind) => !KINDS.has(kind))) throw new Error("kinds contains an unsupported learning kind");
|
|
55
|
+
const subjectFilter = subjectIds === null ? null : new Set(subjectIds);
|
|
56
|
+
const limit = maxItems === null ? learning.config.maxContextItems : integer(maxItems, "maxItems", 0, 50);
|
|
57
|
+
const timestamp = date(now, "now");
|
|
58
|
+
const applicable = learning.candidates
|
|
59
|
+
.filter((candidate) => candidate.status === "accepted")
|
|
60
|
+
.filter((candidate) => scope === null || scopeContains(candidate.scope, runtimeScope))
|
|
61
|
+
.filter((candidate) => candidate.promotion?.mode !== "outcome-canary"
|
|
62
|
+
|| exactScope(candidate.promotion.canary.scope, runtimeScope))
|
|
63
|
+
.filter((candidate) => !kindFilter || kindFilter.has(candidate.kind))
|
|
64
|
+
.filter((candidate) => !subjectFilter || subjectFilter.has(candidate.subjectId))
|
|
65
|
+
.filter((candidate) => candidate.privacy !== "group" || (groupId && candidate.groupId === groupId));
|
|
66
|
+
const invalidCanaries = new Map(applicable.map((candidate) => [candidate.id,
|
|
67
|
+
canaryValidity(learning, candidate, timestamp)]).filter(([, validity]) => ![
|
|
68
|
+
"not-applicable", "current-active", "current-validated", "legacy-validated"
|
|
69
|
+
].includes(validity.status)));
|
|
70
|
+
const revokedTrialFailureCandidates = learning.candidates
|
|
71
|
+
.filter((candidate) => candidate.status === "rolled-back"
|
|
72
|
+
&& learning.trialFailureRevocations.some((receipt) => receipt.learningId === candidate.id))
|
|
73
|
+
.filter((candidate) => scope === null || scopeContains(candidate.scope, runtimeScope))
|
|
74
|
+
.filter((candidate) => !kindFilter || kindFilter.has(candidate.kind))
|
|
75
|
+
.filter((candidate) => !subjectFilter || subjectFilter.has(candidate.subjectId))
|
|
76
|
+
.filter((candidate) => candidate.privacy !== "group" || (groupId && candidate.groupId === groupId))
|
|
77
|
+
.filter((candidate) => visible(candidate, entities, audience, includePrivate, groupId));
|
|
78
|
+
const items = applicable
|
|
79
|
+
.filter((candidate) => !invalidCanaries.has(candidate.id))
|
|
80
|
+
.filter((candidate) => visible(candidate, entities, audience, includePrivate, groupId))
|
|
81
|
+
.sort((a, b) => b.confidence - a.confidence || b.updatedAt.localeCompare(a.updatedAt) || a.id.localeCompare(b.id))
|
|
82
|
+
.slice(0, limit)
|
|
83
|
+
.map((candidate) => ({
|
|
84
|
+
id: candidate.id,
|
|
85
|
+
kind: candidate.kind,
|
|
86
|
+
claim: candidate.claim,
|
|
87
|
+
subjectId: candidate.subjectId,
|
|
88
|
+
privacy: candidate.privacy,
|
|
89
|
+
groupId: candidate.groupId,
|
|
90
|
+
confidence: candidate.confidence,
|
|
91
|
+
evidenceCount: candidate.evidence.length,
|
|
92
|
+
automatic: candidate.automatic,
|
|
93
|
+
acceptedAt: candidate.acceptedAt,
|
|
94
|
+
outcomeStatus: candidate.promotion?.mode === "outcome-canary"
|
|
95
|
+
? (candidate.promotion.canary.status === "validated"
|
|
96
|
+
&& candidate.promotion.canary.revalidation?.status === "active"
|
|
97
|
+
&& new Date(candidate.promotion.canary.revalidation.expiresAt).getTime() >= new Date(timestamp).getTime()
|
|
98
|
+
? "revalidating" : candidate.promotion.canary.status)
|
|
99
|
+
: "not-required",
|
|
100
|
+
authority: "context-only"
|
|
101
|
+
}));
|
|
102
|
+
return {
|
|
103
|
+
schema: "agentspine.learning-context/v1",
|
|
104
|
+
root: catalog.root,
|
|
105
|
+
groupId,
|
|
106
|
+
scope: runtimeScope,
|
|
107
|
+
items,
|
|
108
|
+
degraded: invalidCanaries.size > 0 || revokedTrialFailureCandidates.length > 0,
|
|
109
|
+
diagnostics: [...invalidCanaries].map(([id, validity]) => `${({
|
|
110
|
+
"stale-active": "stale-outcome-canary",
|
|
111
|
+
"revoked-active": "revoked-evaluator-canary",
|
|
112
|
+
"stale-validated": "stale-validated-learning",
|
|
113
|
+
"revoked-validated": "revoked-evaluator-validated-learning",
|
|
114
|
+
"unproven-validated": "missing-validation-lease",
|
|
115
|
+
"failed-initial-trial": "blocking-initial-trial-timeout",
|
|
116
|
+
"revoked-evaluation": "revoked-learning-evaluation",
|
|
117
|
+
"revoked-evidence-source-attestation": "revoked-learning-evidence-source-attestation",
|
|
118
|
+
"revoked-validation": "revoked-learning-validation",
|
|
119
|
+
"revoked-evidence": "revoked-learning-evidence",
|
|
120
|
+
"revoked-measurement": "revoked-learning-measurement",
|
|
121
|
+
"revoked-application": "revoked-learning-application",
|
|
122
|
+
"revoked-delivery": "revoked-learning-delivery",
|
|
123
|
+
"revoked-outcome": "revoked-learning-outcome"
|
|
124
|
+
})[validity.status] || validity.status}:${id}`)
|
|
125
|
+
.concat(revokedTrialFailureCandidates.map((candidate) =>
|
|
126
|
+
`revoked-learning-trial-failure:${candidate.id}`)),
|
|
127
|
+
authority: "context-only",
|
|
128
|
+
note: "Learned context is descriptive evidence, never permission, delegation, access, or an instruction to act."
|
|
129
|
+
};
|
|
130
|
+
}
|