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,441 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
ID_RE, INITIAL_TRIAL_EVALUATIONS, DEADLINE_BOUND_EVALUATIONS, DELIVERABLE_APPLICATIONS, INITIAL_TRIAL_APPLICATIONS, TARGET_BOUND_APPLICATIONS,
|
|
4
|
+
DELIVERY_REVOCATION_REASONS, OUTCOME_REVOCATION_REASONS, APPLICATION_REVOCATION_REASONS
|
|
5
|
+
} from "./learning-schema.js";
|
|
6
|
+
import {
|
|
7
|
+
normalizeScope, exactScope, digest, learningTargetForCandidate
|
|
8
|
+
} from "./learning-scope-targets.js";
|
|
9
|
+
import {
|
|
10
|
+
revokedEvaluationForCandidate
|
|
11
|
+
} from "./learning-validation-contracts.js";
|
|
12
|
+
import {
|
|
13
|
+
revokedValidationForCandidate
|
|
14
|
+
} from "./learning-validation-runtime.js";
|
|
15
|
+
import {
|
|
16
|
+
evaluationStalenessPolicy, revokedEvidenceSourceAttestationForCandidate
|
|
17
|
+
} from "./learning-measurement-contracts.js";
|
|
18
|
+
import {
|
|
19
|
+
applicationPayload
|
|
20
|
+
} from "./learning-evaluation-contracts.js";
|
|
21
|
+
import {
|
|
22
|
+
deliveryPayload, applicationRevocationPayload, revokedApplication, revokedApplicationForCandidate, deliveryRevocationPayload, revokedDelivery,
|
|
23
|
+
outcomeRevocationPayload, revokedOutcome
|
|
24
|
+
} from "./learning-delivery-contracts.js";
|
|
25
|
+
import {
|
|
26
|
+
date, safeText, mutation
|
|
27
|
+
} from "./learning-storage.js";
|
|
28
|
+
|
|
29
|
+
export async function recordLearningApplications({
|
|
30
|
+
root = process.cwd(), items, scope, preflightReceipt, sessionBriefingDigest, projectedAt = new Date()
|
|
31
|
+
}) {
|
|
32
|
+
if (!Array.isArray(items)) throw new Error("learning application items must be an array");
|
|
33
|
+
if (!preflightReceipt || preflightReceipt.schema !== "agentspine.preflight/v2"
|
|
34
|
+
|| preflightReceipt.status !== "ready" || !ID_RE.test(preflightReceipt.id || "")
|
|
35
|
+
|| !/^[a-f0-9]{64}$/.test(preflightReceipt.promptDigest || "")
|
|
36
|
+
|| !/^[a-f0-9]{64}$/.test(preflightReceipt.briefingDigest || "")
|
|
37
|
+
|| !ID_RE.test(preflightReceipt.sessionId || "")
|
|
38
|
+
|| !/^[a-f0-9]{64}$/.test(sessionBriefingDigest || "")) {
|
|
39
|
+
throw new Error("learning applications require one valid consumed preflight binding");
|
|
40
|
+
}
|
|
41
|
+
const runtimeScope = normalizeScope(scope);
|
|
42
|
+
const timestamp = date(projectedAt, "projectedAt");
|
|
43
|
+
const preflightCreated = new Date(preflightReceipt.createdAt).getTime();
|
|
44
|
+
const preflightExpires = new Date(preflightReceipt.expiresAt).getTime();
|
|
45
|
+
if (!Number.isFinite(preflightCreated) || !Number.isFinite(preflightExpires)
|
|
46
|
+
|| preflightCreated > new Date(timestamp).getTime() || preflightExpires < new Date(timestamp).getTime()) {
|
|
47
|
+
throw new Error("learning application preflight binding is stale");
|
|
48
|
+
}
|
|
49
|
+
if (preflightReceipt.agentId !== runtimeScope.personaId || preflightReceipt.userId !== runtimeScope.userId
|
|
50
|
+
|| preflightReceipt.tenantId !== runtimeScope.tenantId || preflightReceipt.projectId !== runtimeScope.projectId
|
|
51
|
+
|| preflightReceipt.groupId !== runtimeScope.groupId || preflightReceipt.taskId !== runtimeScope.taskId) {
|
|
52
|
+
throw new Error("learning application preflight scope does not match the projected turn");
|
|
53
|
+
}
|
|
54
|
+
return mutation(root, (state, _catalog, learningPath) => {
|
|
55
|
+
const pending = state.applications.filter((application) =>
|
|
56
|
+
DELIVERABLE_APPLICATIONS.has(application.schema)
|
|
57
|
+
&& application.sessionId === preflightReceipt.sessionId
|
|
58
|
+
&& application.preflightReceiptId !== preflightReceipt.id
|
|
59
|
+
&& new Date(application.deliveryExpiresAt).getTime() >= new Date(timestamp).getTime()
|
|
60
|
+
&& !state.deliveries.some((delivery) => delivery.applicationId === application.id));
|
|
61
|
+
if (pending.length) {
|
|
62
|
+
throw new Error("this session already has an unconfirmed learning application; await Stop or its bounded expiry");
|
|
63
|
+
}
|
|
64
|
+
const receipts = [];
|
|
65
|
+
for (const item of items.filter((entry) => ["active", "revalidating"].includes(entry?.outcomeStatus))) {
|
|
66
|
+
const candidate = state.candidates.find((entry) => entry.id === item.id);
|
|
67
|
+
const canary = candidate?.promotion?.canary;
|
|
68
|
+
const revalidating = item.outcomeStatus === "revalidating";
|
|
69
|
+
if (!candidate || candidate.status !== "accepted" || candidate.promotion?.mode !== "outcome-canary"
|
|
70
|
+
|| revokedEvaluationForCandidate(state, candidate)
|
|
71
|
+
|| revokedEvidenceSourceAttestationForCandidate(state, candidate)
|
|
72
|
+
|| revokedValidationForCandidate(state, candidate)
|
|
73
|
+
|| revokedApplicationForCandidate(state, candidate)
|
|
74
|
+
|| (revalidating ? canary?.status !== "validated" || canary.revalidation?.status !== "active"
|
|
75
|
+
|| new Date(canary.revalidation.expiresAt).getTime() < new Date(timestamp).getTime()
|
|
76
|
+
: canary?.status !== "active")
|
|
77
|
+
|| !exactScope(canary.scope, runtimeScope)) {
|
|
78
|
+
throw new Error(`active learning application no longer matches its exact scope: ${item.id || "unknown"}`);
|
|
79
|
+
}
|
|
80
|
+
const expiresAt = revalidating ? canary.revalidation.expiresAt : canary.expiresAt;
|
|
81
|
+
let deliveryExpiresAt = new Date(Math.min(new Date(expiresAt).getTime(),
|
|
82
|
+
new Date(timestamp).getTime() + 5 * 60_000)).toISOString();
|
|
83
|
+
let outcomeExpiresAt;
|
|
84
|
+
let completionPolicyDigest;
|
|
85
|
+
const material = `${candidate.id}\0${preflightReceipt.id}\0${sessionBriefingDigest}`;
|
|
86
|
+
const id = `application:${createHash("sha256").update(material).digest("hex").slice(0, 32)}`;
|
|
87
|
+
const existing = state.applications.find((entry) => entry.id === id);
|
|
88
|
+
if (existing) {
|
|
89
|
+
const sameBinding = existing.learningId === candidate.id && exactScope(existing.scope, runtimeScope)
|
|
90
|
+
&& existing.preflightReceiptId === preflightReceipt.id && existing.promptDigest === preflightReceipt.promptDigest
|
|
91
|
+
&& existing.preflightBriefingDigest === preflightReceipt.briefingDigest
|
|
92
|
+
&& existing.sessionBriefingDigest === sessionBriefingDigest;
|
|
93
|
+
if (!sameBinding) throw new Error("learning application receipt IDs are immutable");
|
|
94
|
+
receipts.push(existing);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
let schema = "agentspine.learning-application/v2";
|
|
98
|
+
let revalidationAdmission;
|
|
99
|
+
let initialAdmission;
|
|
100
|
+
if (revalidating && ["agentspine.learning-revalidation-window/v3",
|
|
101
|
+
"agentspine.learning-revalidation-window/v4"].includes(canary.revalidation.schema)) {
|
|
102
|
+
const window = canary.revalidation;
|
|
103
|
+
const priorAdmissions = state.applications.filter((application) =>
|
|
104
|
+
["agentspine.learning-application/v3", "agentspine.learning-application/v4"].includes(application.schema)
|
|
105
|
+
&& application.revalidationAdmission.revalidationWindowId === window.id
|
|
106
|
+
&& application.revalidationAdmission.revalidationWindowDigest === window.digest);
|
|
107
|
+
if (priorAdmissions.length < window.selection.requiredDeliveries) {
|
|
108
|
+
const slot = priorAdmissions.length + 1;
|
|
109
|
+
const trial = window.selection.evaluatorRoots[slot - 1];
|
|
110
|
+
schema = window.schema === "agentspine.learning-revalidation-window/v4"
|
|
111
|
+
? "agentspine.learning-application/v4" : "agentspine.learning-application/v3";
|
|
112
|
+
revalidationAdmission = {
|
|
113
|
+
revalidationWindowId: window.id,
|
|
114
|
+
revalidationWindowDigest: window.digest,
|
|
115
|
+
slot,
|
|
116
|
+
evaluatorRootDigest: trial.evaluatorRootDigest,
|
|
117
|
+
...(schema === "agentspine.learning-application/v4" ? {
|
|
118
|
+
evaluatorId: trial.evaluatorId, runId: trial.runId, trialDigest: trial.trialDigest
|
|
119
|
+
} : {}),
|
|
120
|
+
authority: "context-only"
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (!revalidating) {
|
|
125
|
+
const evaluation = state.evaluations.find((entry) => entry.id === canary.evaluationId
|
|
126
|
+
&& entry.digest === canary.evaluationDigest && entry.learningId === candidate.id);
|
|
127
|
+
if (INITIAL_TRIAL_EVALUATIONS.has(evaluation?.schema)) {
|
|
128
|
+
const priorAdmissions = state.applications.filter((application) =>
|
|
129
|
+
INITIAL_TRIAL_APPLICATIONS.has(application.schema)
|
|
130
|
+
&& application.initialAdmission.evaluationId === evaluation.id
|
|
131
|
+
&& application.initialAdmission.evaluationDigest === evaluation.digest);
|
|
132
|
+
if (priorAdmissions.length < evaluation.initialTrials.requiredTrials) {
|
|
133
|
+
const slot = priorAdmissions.length + 1;
|
|
134
|
+
const trial = evaluation.initialTrials.after[slot - 1];
|
|
135
|
+
schema = DEADLINE_BOUND_EVALUATIONS.has(evaluation.schema)
|
|
136
|
+
? "agentspine.learning-application/v7"
|
|
137
|
+
: evaluation.schema === "agentspine.learning-evaluation/v9"
|
|
138
|
+
? "agentspine.learning-application/v6" : "agentspine.learning-application/v5";
|
|
139
|
+
initialAdmission = {
|
|
140
|
+
evaluationId: evaluation.id,
|
|
141
|
+
evaluationDigest: evaluation.digest,
|
|
142
|
+
slot,
|
|
143
|
+
evaluatorId: trial.evaluatorId,
|
|
144
|
+
evaluatorRootDigest: trial.evaluatorRootDigest,
|
|
145
|
+
runId: trial.runId,
|
|
146
|
+
trialDigest: trial.trialDigest,
|
|
147
|
+
...(TARGET_BOUND_APPLICATIONS.has(schema) ? { targetDigest: evaluation.target.digest } : {}),
|
|
148
|
+
authority: "context-only"
|
|
149
|
+
};
|
|
150
|
+
if (schema === "agentspine.learning-application/v7") {
|
|
151
|
+
deliveryExpiresAt = new Date(Math.min(new Date(expiresAt).getTime(),
|
|
152
|
+
new Date(timestamp).getTime() + evaluation.completionPolicy.deliveryTimeoutMs)).toISOString();
|
|
153
|
+
outcomeExpiresAt = new Date(Math.min(new Date(expiresAt).getTime(),
|
|
154
|
+
new Date(timestamp).getTime() + evaluation.completionPolicy.outcomeTimeoutMs)).toISOString();
|
|
155
|
+
completionPolicyDigest = evaluation.completionPolicy.digest;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const payload = applicationPayload({ schema, id, learningId: candidate.id, scope: runtimeScope,
|
|
161
|
+
preflightReceiptId: preflightReceipt.id, promptDigest: preflightReceipt.promptDigest,
|
|
162
|
+
preflightBriefingDigest: preflightReceipt.briefingDigest, sessionBriefingDigest,
|
|
163
|
+
sessionId: preflightReceipt.sessionId, projectedAt: timestamp, deliveryExpiresAt, expiresAt,
|
|
164
|
+
revalidationAdmission, initialAdmission, outcomeExpiresAt, completionPolicyDigest });
|
|
165
|
+
const receipt = { ...payload, digest: digest(payload) };
|
|
166
|
+
if (state.applications.some((entry) => entry.learningId === candidate.id
|
|
167
|
+
&& entry.preflightReceiptId === preflightReceipt.id)) {
|
|
168
|
+
throw new Error("one preflight turn cannot produce conflicting learning application receipts");
|
|
169
|
+
}
|
|
170
|
+
state.applications.push(receipt);
|
|
171
|
+
receipts.push(receipt);
|
|
172
|
+
}
|
|
173
|
+
state.applications.sort((a, b) => a.id.localeCompare(b.id));
|
|
174
|
+
return { schema: "agentspine.learning-application-batch/v2", receipts, learningPath,
|
|
175
|
+
authority: "context-only" };
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export async function revokeLearningApplication({
|
|
180
|
+
root = process.cwd(), applicationId, reasonCode, reason, confirmation, now = new Date()
|
|
181
|
+
}) {
|
|
182
|
+
if (confirmation !== "local-application-revocation-confirmed") {
|
|
183
|
+
throw new Error("application revocation requires explicit local confirmation");
|
|
184
|
+
}
|
|
185
|
+
if (!ID_RE.test(applicationId || "")) throw new Error("applicationId must be a stable identifier");
|
|
186
|
+
if (!APPLICATION_REVOCATION_REASONS.has(reasonCode)) {
|
|
187
|
+
throw new Error("reasonCode must be preflight-invalid, scope-invalid, projection-invalid, duplicate, or other");
|
|
188
|
+
}
|
|
189
|
+
const revokeReason = safeText(reason, "reason", 500);
|
|
190
|
+
const timestamp = date(now, "now");
|
|
191
|
+
return mutation(root, (state, _catalog, learningPath) => {
|
|
192
|
+
const application = state.applications.find((entry) => entry.id === applicationId);
|
|
193
|
+
if (!application) throw new Error(`unknown learning application: ${applicationId}`);
|
|
194
|
+
const candidate = state.candidates.find((entry) => entry.id === application.learningId);
|
|
195
|
+
const evaluationId = application.initialAdmission?.evaluationId || candidate?.promotion?.canary?.evaluationId;
|
|
196
|
+
const evaluation = state.evaluations.find((entry) => entry.id === evaluationId);
|
|
197
|
+
const deliveries = state.deliveries.filter((entry) => entry.applicationId === application.id);
|
|
198
|
+
const outcomes = state.outcomes.filter((entry) => entry.applicationId === application.id);
|
|
199
|
+
if (deliveries.length > 1 || outcomes.length > 1) {
|
|
200
|
+
throw new Error("application revocation requires one unambiguous delivery and outcome lineage");
|
|
201
|
+
}
|
|
202
|
+
const delivery = deliveries[0] || null;
|
|
203
|
+
const outcome = outcomes[0] || null;
|
|
204
|
+
if (!candidate || !evaluation || evaluation.learningId !== candidate.id
|
|
205
|
+
|| application.learningId !== candidate.id || (outcome && outcome.deliveryId !== delivery?.id)) {
|
|
206
|
+
throw new Error("application revocation requires its complete immutable evaluation lineage");
|
|
207
|
+
}
|
|
208
|
+
const targetDigest = learningTargetForCandidate(candidate).digest;
|
|
209
|
+
const reasonDigest = digest(revokeReason);
|
|
210
|
+
const existing = revokedApplication(state, applicationId);
|
|
211
|
+
if (existing) {
|
|
212
|
+
if (existing.reasonCode !== reasonCode || existing.reasonDigest !== reasonDigest
|
|
213
|
+
|| existing.applicationDigest !== application.digest || existing.evaluationDigest !== evaluation.digest
|
|
214
|
+
|| existing.deliveryId !== (delivery?.id || null) || existing.deliveryDigest !== (delivery?.digest || null)
|
|
215
|
+
|| existing.outcomeId !== (outcome?.id || null) || existing.outcomeDigest !== (outcome?.digest || null)
|
|
216
|
+
|| existing.targetDigest !== targetDigest) {
|
|
217
|
+
throw new Error("learning application revocations are immutable");
|
|
218
|
+
}
|
|
219
|
+
return { receipt: existing, learningPath, unchanged: true, authority: "context-only" };
|
|
220
|
+
}
|
|
221
|
+
const id = `application-revocation:${createHash("sha256")
|
|
222
|
+
.update(`${application.id}\0${application.digest}\0${targetDigest}`).digest("hex").slice(0, 32)}`;
|
|
223
|
+
const payload = applicationRevocationPayload({
|
|
224
|
+
id,
|
|
225
|
+
learningId: candidate.id,
|
|
226
|
+
evaluationId: evaluation.id,
|
|
227
|
+
evaluationDigest: evaluation.digest,
|
|
228
|
+
applicationId: application.id,
|
|
229
|
+
applicationDigest: application.digest,
|
|
230
|
+
deliveryId: delivery?.id || null,
|
|
231
|
+
deliveryDigest: delivery?.digest || null,
|
|
232
|
+
outcomeId: outcome?.id || null,
|
|
233
|
+
outcomeDigest: outcome?.digest || null,
|
|
234
|
+
targetDigest,
|
|
235
|
+
reasonCode,
|
|
236
|
+
reasonDigest,
|
|
237
|
+
revokedAt: timestamp
|
|
238
|
+
});
|
|
239
|
+
const receipt = { ...payload, digest: digest(payload) };
|
|
240
|
+
state.applicationRevocations.push(receipt);
|
|
241
|
+
state.applicationRevocations.sort((a, b) => a.id.localeCompare(b.id));
|
|
242
|
+
return { receipt, learningPath, unchanged: false, authority: "context-only" };
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export async function recordLearningDeliveries({
|
|
247
|
+
root = process.cwd(), sessionId, scope, hookEvent, completedAt = new Date()
|
|
248
|
+
}) {
|
|
249
|
+
if (!ID_RE.test(sessionId || "")) throw new Error("learning delivery sessionId is required");
|
|
250
|
+
if (!["Stop", "SubagentStop"].includes(hookEvent)) throw new Error("learning delivery requires Stop or SubagentStop");
|
|
251
|
+
const runtimeScope = normalizeScope(scope);
|
|
252
|
+
const timestamp = date(completedAt, "completedAt");
|
|
253
|
+
return mutation(root, (state, _catalog, learningPath) => {
|
|
254
|
+
const candidates = state.applications.filter((application) =>
|
|
255
|
+
DELIVERABLE_APPLICATIONS.has(application.schema)
|
|
256
|
+
&& application.sessionId === sessionId && exactScope(application.scope, runtimeScope));
|
|
257
|
+
if (!candidates.length) return { schema: "agentspine.learning-delivery-batch/v1", status: "not-applicable",
|
|
258
|
+
receipts: [], learningPath, authority: "context-only" };
|
|
259
|
+
if (candidates.some((application) => revokedApplication(state, application.id))) {
|
|
260
|
+
throw new Error("learning application was explicitly revoked and cannot produce a delivery");
|
|
261
|
+
}
|
|
262
|
+
if (candidates.some((application) => {
|
|
263
|
+
const candidate = state.candidates.find((entry) => entry.id === application.learningId);
|
|
264
|
+
return revokedEvaluationForCandidate(state, candidate);
|
|
265
|
+
})) throw new Error("learning evaluation contract was explicitly revoked and cannot produce a delivery");
|
|
266
|
+
if (candidates.some((application) => {
|
|
267
|
+
const candidate = state.candidates.find((entry) => entry.id === application.learningId);
|
|
268
|
+
return revokedEvidenceSourceAttestationForCandidate(state, candidate);
|
|
269
|
+
})) throw new Error("learning evidence source attestation was explicitly revoked and cannot produce a delivery");
|
|
270
|
+
if (candidates.some((application) => {
|
|
271
|
+
const candidate = state.candidates.find((entry) => entry.id === application.learningId);
|
|
272
|
+
return revokedValidationForCandidate(state, candidate);
|
|
273
|
+
})) throw new Error("learning validation decision was explicitly revoked and cannot produce a delivery");
|
|
274
|
+
const latest = [...candidates].sort((a, b) => b.projectedAt.localeCompare(a.projectedAt))[0];
|
|
275
|
+
const batch = candidates.filter((application) => application.preflightReceiptId === latest.preflightReceiptId);
|
|
276
|
+
if (new Date(timestamp).getTime() > new Date(latest.deliveryExpiresAt).getTime()) {
|
|
277
|
+
return { schema: "agentspine.learning-delivery-batch/v1", status: "stale", receipts: [], learningPath,
|
|
278
|
+
authority: "context-only" };
|
|
279
|
+
}
|
|
280
|
+
const receipts = [];
|
|
281
|
+
for (const application of batch) {
|
|
282
|
+
const existingForApplication = state.deliveries.find((entry) => entry.applicationId === application.id);
|
|
283
|
+
if (existingForApplication) {
|
|
284
|
+
if (existingForApplication.sessionId !== sessionId || existingForApplication.hookEvent !== hookEvent
|
|
285
|
+
|| !exactScope(existingForApplication.scope, runtimeScope)) {
|
|
286
|
+
throw new Error("one learning application cannot have conflicting delivery receipts");
|
|
287
|
+
}
|
|
288
|
+
receipts.push(existingForApplication);
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
const id = `delivery:${createHash("sha256").update(`${application.id}\0${hookEvent}`).digest("hex").slice(0, 32)}`;
|
|
292
|
+
const payload = deliveryPayload({ id, applicationId: application.id, learningId: application.learningId,
|
|
293
|
+
scope: runtimeScope, sessionId, preflightReceiptId: application.preflightReceiptId, hookEvent,
|
|
294
|
+
completedAt: timestamp });
|
|
295
|
+
const receipt = { ...payload, digest: digest(payload) };
|
|
296
|
+
if (state.deliveries.some((entry) => entry.id === id)) throw new Error("learning delivery receipt IDs are immutable");
|
|
297
|
+
state.deliveries.push(receipt);
|
|
298
|
+
receipts.push(receipt);
|
|
299
|
+
}
|
|
300
|
+
state.deliveries.sort((a, b) => a.id.localeCompare(b.id));
|
|
301
|
+
return { schema: "agentspine.learning-delivery-batch/v1", status: receipts.length ? "completed" : "not-applicable",
|
|
302
|
+
receipts, learningPath, authority: "context-only" };
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export async function revokeLearningDelivery({
|
|
307
|
+
root = process.cwd(), deliveryId, reasonCode, reason, confirmation, now = new Date()
|
|
308
|
+
}) {
|
|
309
|
+
if (confirmation !== "local-delivery-revocation-confirmed") {
|
|
310
|
+
throw new Error("delivery revocation requires explicit local confirmation");
|
|
311
|
+
}
|
|
312
|
+
if (!ID_RE.test(deliveryId || "")) throw new Error("deliveryId must be a stable identifier");
|
|
313
|
+
if (!DELIVERY_REVOCATION_REASONS.has(reasonCode)) {
|
|
314
|
+
throw new Error("reasonCode must be host-invalid, session-invalid, hook-invalid, duplicate, or other");
|
|
315
|
+
}
|
|
316
|
+
const revokeReason = safeText(reason, "reason", 500);
|
|
317
|
+
const timestamp = date(now, "now");
|
|
318
|
+
return mutation(root, (state, _catalog, learningPath) => {
|
|
319
|
+
const delivery = state.deliveries.find((entry) => entry.id === deliveryId);
|
|
320
|
+
if (!delivery) throw new Error(`unknown learning delivery: ${deliveryId}`);
|
|
321
|
+
const application = state.applications.find((entry) => entry.id === delivery.applicationId);
|
|
322
|
+
const candidate = state.candidates.find((entry) => entry.id === delivery.learningId);
|
|
323
|
+
const outcomes = state.outcomes.filter((entry) => entry.deliveryId === delivery.id);
|
|
324
|
+
if (outcomes.length > 1) throw new Error("delivery revocation requires one unambiguous bound outcome");
|
|
325
|
+
const outcome = outcomes[0] || null;
|
|
326
|
+
const evaluationId = outcome?.evaluationId || candidate?.promotion?.canary?.evaluationId;
|
|
327
|
+
const evaluation = state.evaluations.find((entry) => entry.id === evaluationId);
|
|
328
|
+
if (!candidate || !application || !evaluation || application.learningId !== candidate.id
|
|
329
|
+
|| evaluation.learningId !== candidate.id) {
|
|
330
|
+
throw new Error("delivery revocation requires its bound candidate, application, and evaluation contract");
|
|
331
|
+
}
|
|
332
|
+
const targetDigest = learningTargetForCandidate(candidate).digest;
|
|
333
|
+
const reasonDigest = digest(revokeReason);
|
|
334
|
+
const existing = revokedDelivery(state, deliveryId);
|
|
335
|
+
if (existing) {
|
|
336
|
+
if (existing.reasonCode !== reasonCode || existing.reasonDigest !== reasonDigest
|
|
337
|
+
|| existing.deliveryDigest !== delivery.digest || existing.applicationDigest !== application.digest
|
|
338
|
+
|| existing.evaluationDigest !== evaluation.digest || existing.targetDigest !== targetDigest
|
|
339
|
+
|| existing.outcomeId !== (outcome?.id || null) || existing.outcomeDigest !== (outcome?.digest || null)) {
|
|
340
|
+
throw new Error("learning delivery revocations are immutable");
|
|
341
|
+
}
|
|
342
|
+
return { receipt: existing, learningPath, unchanged: true, authority: "context-only" };
|
|
343
|
+
}
|
|
344
|
+
const id = `delivery-revocation:${createHash("sha256")
|
|
345
|
+
.update(`${delivery.id}\0${delivery.digest}\0${targetDigest}`).digest("hex").slice(0, 32)}`;
|
|
346
|
+
const payload = deliveryRevocationPayload({
|
|
347
|
+
id,
|
|
348
|
+
learningId: candidate.id,
|
|
349
|
+
evaluationId: evaluation.id,
|
|
350
|
+
evaluationDigest: evaluation.digest,
|
|
351
|
+
applicationId: application.id,
|
|
352
|
+
applicationDigest: application.digest,
|
|
353
|
+
deliveryId: delivery.id,
|
|
354
|
+
deliveryDigest: delivery.digest,
|
|
355
|
+
outcomeId: outcome?.id || null,
|
|
356
|
+
outcomeDigest: outcome?.digest || null,
|
|
357
|
+
targetDigest,
|
|
358
|
+
reasonCode,
|
|
359
|
+
reasonDigest,
|
|
360
|
+
revokedAt: timestamp
|
|
361
|
+
});
|
|
362
|
+
const receipt = { ...payload, digest: digest(payload) };
|
|
363
|
+
state.deliveryRevocations.push(receipt);
|
|
364
|
+
state.deliveryRevocations.sort((a, b) => a.id.localeCompare(b.id));
|
|
365
|
+
return { receipt, learningPath, unchanged: false, authority: "context-only" };
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export async function revokeLearningOutcome({
|
|
370
|
+
root = process.cwd(), outcomeId, reasonCode, reason, confirmation, now = new Date()
|
|
371
|
+
}) {
|
|
372
|
+
if (confirmation !== "local-outcome-revocation-confirmed") {
|
|
373
|
+
throw new Error("outcome revocation requires explicit local confirmation");
|
|
374
|
+
}
|
|
375
|
+
if (!ID_RE.test(outcomeId || "")) throw new Error("outcomeId must be a stable identifier");
|
|
376
|
+
if (!OUTCOME_REVOCATION_REASONS.has(reasonCode)) {
|
|
377
|
+
throw new Error("reasonCode must be binding-invalid, phase-invalid, scope-invalid, duplicate, or other");
|
|
378
|
+
}
|
|
379
|
+
const revokeReason = safeText(reason, "reason", 500);
|
|
380
|
+
const timestamp = date(now, "now");
|
|
381
|
+
return mutation(root, (state, _catalog, learningPath) => {
|
|
382
|
+
const outcome = state.outcomes.find((entry) => entry.id === outcomeId);
|
|
383
|
+
if (!outcome) throw new Error(`unknown learning outcome: ${outcomeId}`);
|
|
384
|
+
const candidate = state.candidates.find((entry) => entry.id === outcome.learningId);
|
|
385
|
+
const evaluation = state.evaluations.find((entry) => entry.id === outcome.evaluationId);
|
|
386
|
+
const measurement = outcome.measurementReceiptId
|
|
387
|
+
? state.measurements.find((entry) => entry.id === outcome.measurementReceiptId) : null;
|
|
388
|
+
const application = outcome.applicationId
|
|
389
|
+
? state.applications.find((entry) => entry.id === outcome.applicationId) : null;
|
|
390
|
+
const delivery = outcome.deliveryId
|
|
391
|
+
? state.deliveries.find((entry) => entry.id === outcome.deliveryId) : null;
|
|
392
|
+
if (!candidate || !evaluation || evaluation.learningId !== candidate.id
|
|
393
|
+
|| (outcome.measurementReceiptId && !measurement)
|
|
394
|
+
|| (outcome.applicationId && !application) || (outcome.deliveryId && !delivery)) {
|
|
395
|
+
throw new Error("outcome revocation requires its complete immutable evaluation lineage");
|
|
396
|
+
}
|
|
397
|
+
const targetDigest = learningTargetForCandidate(candidate).digest;
|
|
398
|
+
const reasonDigest = digest(revokeReason);
|
|
399
|
+
const existing = revokedOutcome(state, outcomeId);
|
|
400
|
+
if (existing) {
|
|
401
|
+
if (existing.reasonCode !== reasonCode || existing.reasonDigest !== reasonDigest
|
|
402
|
+
|| existing.outcomeDigest !== outcome.digest || existing.evaluationDigest !== evaluation.digest
|
|
403
|
+
|| existing.measurementDigest !== (measurement?.digest || null)
|
|
404
|
+
|| existing.applicationDigest !== (application?.digest || null)
|
|
405
|
+
|| existing.deliveryDigest !== (delivery?.digest || null) || existing.targetDigest !== targetDigest) {
|
|
406
|
+
throw new Error("learning outcome revocations are immutable");
|
|
407
|
+
}
|
|
408
|
+
return { receipt: existing, learningPath, unchanged: true, authority: "context-only" };
|
|
409
|
+
}
|
|
410
|
+
const id = `outcome-revocation:${createHash("sha256")
|
|
411
|
+
.update(`${outcome.id}\0${outcome.digest}\0${targetDigest}`).digest("hex").slice(0, 32)}`;
|
|
412
|
+
const payload = outcomeRevocationPayload({
|
|
413
|
+
id,
|
|
414
|
+
learningId: candidate.id,
|
|
415
|
+
evaluationId: evaluation.id,
|
|
416
|
+
evaluationDigest: evaluation.digest,
|
|
417
|
+
outcomeId: outcome.id,
|
|
418
|
+
outcomeDigest: outcome.digest,
|
|
419
|
+
measurementId: measurement?.id || null,
|
|
420
|
+
measurementDigest: measurement?.digest || null,
|
|
421
|
+
applicationId: application?.id || null,
|
|
422
|
+
applicationDigest: application?.digest || null,
|
|
423
|
+
deliveryId: delivery?.id || null,
|
|
424
|
+
deliveryDigest: delivery?.digest || null,
|
|
425
|
+
targetDigest,
|
|
426
|
+
reasonCode,
|
|
427
|
+
reasonDigest,
|
|
428
|
+
revokedAt: timestamp
|
|
429
|
+
});
|
|
430
|
+
const receipt = { ...payload, digest: digest(payload) };
|
|
431
|
+
state.outcomeRevocations.push(receipt);
|
|
432
|
+
state.outcomeRevocations.sort((a, b) => a.id.localeCompare(b.id));
|
|
433
|
+
return { receipt, learningPath, unchanged: false, authority: "context-only" };
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export function outcomeFresh(receipt, contract, config, now) {
|
|
438
|
+
const policy = evaluationStalenessPolicy(contract, config);
|
|
439
|
+
return new Date(receipt.measuredAt).getTime() >= new Date(now).getTime()
|
|
440
|
+
- policy.outcomeMaxAgeDays * 86400000;
|
|
441
|
+
}
|