@tangle-network/agent-knowledge 3.1.0 → 3.2.1
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/README.md +19 -6
- package/dist/{chunk-2BJ5JR3L.js → chunk-EUAXSBMH.js} +95 -27
- package/dist/chunk-EUAXSBMH.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +48 -5
- package/dist/index.js +447 -85
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-2BJ5JR3L.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -55,7 +55,7 @@ import {
|
|
|
55
55
|
writeJsonDurableWithinRoot,
|
|
56
56
|
writeKnowledgeIndex,
|
|
57
57
|
writeSourceRegistry
|
|
58
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-EUAXSBMH.js";
|
|
59
59
|
import {
|
|
60
60
|
AgentMemoryHitSchema,
|
|
61
61
|
AgentMemoryKindSchema,
|
|
@@ -653,7 +653,7 @@ function createAdaptiveResearchDriver(options = {}) {
|
|
|
653
653
|
|
|
654
654
|
// src/agent-candidate.ts
|
|
655
655
|
import {
|
|
656
|
-
sha256DigestSchema
|
|
656
|
+
sha256DigestSchema as sha256DigestSchema2
|
|
657
657
|
} from "@tangle-network/agent-interface";
|
|
658
658
|
|
|
659
659
|
// src/kb-improvement.ts
|
|
@@ -666,6 +666,13 @@ import {
|
|
|
666
666
|
contentHash,
|
|
667
667
|
validateRunRecord
|
|
668
668
|
} from "@tangle-network/agent-eval";
|
|
669
|
+
import {
|
|
670
|
+
agentImprovementActivationResultSchema,
|
|
671
|
+
agentImprovementActivationSchema,
|
|
672
|
+
canonicalCandidateDigest,
|
|
673
|
+
omitTopLevelDigest,
|
|
674
|
+
sha256DigestSchema
|
|
675
|
+
} from "@tangle-network/agent-interface";
|
|
669
676
|
import { z } from "zod";
|
|
670
677
|
|
|
671
678
|
// src/claim-grounding.ts
|
|
@@ -2022,6 +2029,20 @@ function assertNotAborted(signal) {
|
|
|
2022
2029
|
var digestSchema = z.string().regex(/^[a-f0-9]{64}$/);
|
|
2023
2030
|
var runIdSchema = z.string().min(1).max(2048);
|
|
2024
2031
|
var safePathSegmentSchema = z.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/);
|
|
2032
|
+
var knowledgeImprovementMutationReceiptSchema = z.object({
|
|
2033
|
+
target: z.enum(["candidate", "baseline"]),
|
|
2034
|
+
beforeHash: digestSchema,
|
|
2035
|
+
afterHash: digestSchema,
|
|
2036
|
+
changed: z.boolean(),
|
|
2037
|
+
transactionId: z.string().uuid().nullable(),
|
|
2038
|
+
recovered: z.boolean()
|
|
2039
|
+
}).strict();
|
|
2040
|
+
var knowledgeImprovementActivationRecordSchema = z.object({
|
|
2041
|
+
kind: z.literal("knowledge-improvement-activation-result"),
|
|
2042
|
+
candidateId: safePathSegmentSchema,
|
|
2043
|
+
mutation: knowledgeImprovementMutationReceiptSchema,
|
|
2044
|
+
result: agentImprovementActivationResultSchema
|
|
2045
|
+
}).strict();
|
|
2025
2046
|
var improvementStatusSchema = z.enum([
|
|
2026
2047
|
"running",
|
|
2027
2048
|
"candidate-ready",
|
|
@@ -2268,6 +2289,23 @@ async function loadKnowledgeImprovementState(root, runId) {
|
|
|
2268
2289
|
throw error;
|
|
2269
2290
|
}
|
|
2270
2291
|
}
|
|
2292
|
+
async function loadKnowledgeImprovementActivationResult(options) {
|
|
2293
|
+
assertExactCandidatePlatform();
|
|
2294
|
+
const candidate = Object.freeze(KnowledgeImprovementCandidateRefSchema.parse(options.candidate));
|
|
2295
|
+
const activation = verifyCanonicalKnowledgeActivation(options.activation);
|
|
2296
|
+
const target = targetForKnowledgeActivation(activation);
|
|
2297
|
+
assertKnowledgeActivationAuthority(activation, candidate, target, options.identity);
|
|
2298
|
+
return withKnowledgeImprovementRun(options.root, candidate.runId, false, async (runDir) => {
|
|
2299
|
+
const record = await loadKnowledgeActivationRecord(
|
|
2300
|
+
runDir,
|
|
2301
|
+
candidate,
|
|
2302
|
+
activation,
|
|
2303
|
+
target,
|
|
2304
|
+
options.identity
|
|
2305
|
+
);
|
|
2306
|
+
return record?.result ?? null;
|
|
2307
|
+
});
|
|
2308
|
+
}
|
|
2271
2309
|
async function loadKnowledgeImprovementStateFromRun(root, runId, runDir) {
|
|
2272
2310
|
const stateFile = await readRegularFileWithinRoot(runDir, "state.json");
|
|
2273
2311
|
const raw = JSON.parse(stateFile.bytes.toString("utf8"));
|
|
@@ -2287,9 +2325,46 @@ function knowledgeImprovementCandidateRef(result) {
|
|
|
2287
2325
|
if (!result.candidate) throw new Error("knowledge improvement result has no candidate");
|
|
2288
2326
|
return candidateRefFor(result.runId, result.state, result.candidate);
|
|
2289
2327
|
}
|
|
2328
|
+
async function withKnowledgeImprovementComparison(options, use) {
|
|
2329
|
+
assertExactCandidatePlatform();
|
|
2330
|
+
const reference = Object.freeze(KnowledgeImprovementCandidateRefSchema.parse(options.candidate));
|
|
2331
|
+
return withKnowledgeImprovementRun(options.root, reference.runId, false, async (runDir) => {
|
|
2332
|
+
const state = await loadKnowledgeImprovementStateFromRun(options.root, reference.runId, runDir);
|
|
2333
|
+
return withMeasuredCandidateSnapshot(
|
|
2334
|
+
options.root,
|
|
2335
|
+
runDir,
|
|
2336
|
+
state,
|
|
2337
|
+
reference,
|
|
2338
|
+
(resolved) => withBaselineSnapshot(
|
|
2339
|
+
runDir,
|
|
2340
|
+
reference.baseHash,
|
|
2341
|
+
(baselineRoot) => withIsolatedKnowledgeCopy(
|
|
2342
|
+
baselineRoot,
|
|
2343
|
+
reference.baseHash,
|
|
2344
|
+
"baseline",
|
|
2345
|
+
(baseline) => withIsolatedKnowledgeCopy(
|
|
2346
|
+
resolved.root,
|
|
2347
|
+
reference.candidateHash,
|
|
2348
|
+
"candidate",
|
|
2349
|
+
(candidate) => use(
|
|
2350
|
+
Object.freeze({
|
|
2351
|
+
reference,
|
|
2352
|
+
evaluation: immutableJsonValue(structuredClone(resolved.evidence.evaluation)),
|
|
2353
|
+
baseline: Object.freeze({ root: baseline, hash: reference.baseHash }),
|
|
2354
|
+
candidate: Object.freeze({ root: candidate, hash: reference.candidateHash })
|
|
2355
|
+
})
|
|
2356
|
+
)
|
|
2357
|
+
)
|
|
2358
|
+
)
|
|
2359
|
+
)
|
|
2360
|
+
);
|
|
2361
|
+
});
|
|
2362
|
+
}
|
|
2290
2363
|
async function withKnowledgeImprovementCandidate(options, use) {
|
|
2291
2364
|
assertExactCandidatePlatform();
|
|
2292
|
-
const candidateRef =
|
|
2365
|
+
const candidateRef = Object.freeze(
|
|
2366
|
+
KnowledgeImprovementCandidateRefSchema.parse(options.candidate)
|
|
2367
|
+
);
|
|
2293
2368
|
return withKnowledgeImprovementRun(options.root, candidateRef.runId, false, async (runDir) => {
|
|
2294
2369
|
const state = await loadKnowledgeImprovementStateFromRun(
|
|
2295
2370
|
options.root,
|
|
@@ -2301,14 +2376,17 @@ async function withKnowledgeImprovementCandidate(options, use) {
|
|
|
2301
2376
|
runDir,
|
|
2302
2377
|
state,
|
|
2303
2378
|
candidateRef,
|
|
2304
|
-
(resolved) =>
|
|
2379
|
+
(resolved) => withIsolatedKnowledgeCopy(
|
|
2305
2380
|
resolved.root,
|
|
2306
2381
|
candidateRef.candidateHash,
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2382
|
+
"candidate",
|
|
2383
|
+
(root) => use(
|
|
2384
|
+
Object.freeze({
|
|
2385
|
+
root,
|
|
2386
|
+
candidate: candidateRef,
|
|
2387
|
+
evaluation: immutableJsonValue(structuredClone(resolved.evidence.evaluation))
|
|
2388
|
+
})
|
|
2389
|
+
)
|
|
2312
2390
|
)
|
|
2313
2391
|
);
|
|
2314
2392
|
});
|
|
@@ -2325,6 +2403,7 @@ async function transitionKnowledgeCandidate(options, target) {
|
|
|
2325
2403
|
KnowledgeImprovementCandidateRefSchema.parse(options.candidate)
|
|
2326
2404
|
);
|
|
2327
2405
|
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
2406
|
+
const activation = options.activation ? resolveKnowledgeActivationPersistence(options.activation, candidateRef, target) : void 0;
|
|
2328
2407
|
return withKnowledgeImprovementRun(options.root, candidateRef.runId, false, async (runDir) => {
|
|
2329
2408
|
const lease = await acquireRunLease(runDir, {
|
|
2330
2409
|
ownerId: options.ownerId ?? `pid-${process.pid}`,
|
|
@@ -2346,7 +2425,8 @@ async function transitionKnowledgeCandidate(options, target) {
|
|
|
2346
2425
|
leaseTtlMs: options.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS,
|
|
2347
2426
|
assertRunOwned: lease.assertOwned,
|
|
2348
2427
|
now,
|
|
2349
|
-
onState: options.onState
|
|
2428
|
+
onState: options.onState,
|
|
2429
|
+
activation
|
|
2350
2430
|
},
|
|
2351
2431
|
target
|
|
2352
2432
|
);
|
|
@@ -2405,33 +2485,32 @@ async function improveKnowledgeBaseInRun(options, runId, runDir, now) {
|
|
|
2405
2485
|
if (state.status === "promoted" && !promotedCandidate) {
|
|
2406
2486
|
throw new Error("promoted knowledge state has no promoted candidate");
|
|
2407
2487
|
}
|
|
2408
|
-
const resumablePromotion = state.candidates.find(
|
|
2409
|
-
(candidate2) => (candidate2.status === "candidate-ready" || candidate2.status === "promoted") && candidate2.evidenceHash !== void 0 && candidate2.promotionPlanHash !== void 0
|
|
2410
|
-
);
|
|
2411
|
-
const resumableCandidateRef = resumablePromotion ? candidateRefFor(runId, state, resumablePromotion) : void 0;
|
|
2412
|
-
await withKnowledgeMutation(options.root, () => void 0, {
|
|
2413
|
-
resumeTransaction: resumableCandidateRef ? {
|
|
2414
|
-
purpose: knowledgeCandidateTransitionPurpose(resumableCandidateRef, "candidate"),
|
|
2415
|
-
validate: (transaction) => assertCandidateTransitionTransaction(transaction, resumableCandidateRef, "candidate")
|
|
2416
|
-
} : void 0
|
|
2417
|
-
});
|
|
2418
|
-
await ensureBaselineSnapshot(runDir, options.root, state.baseHash);
|
|
2419
2488
|
if (state.status === "promoted") {
|
|
2420
2489
|
const promoted = promotedCandidate;
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2490
|
+
const promotedState = state;
|
|
2491
|
+
return withKnowledgeMutation(options.root, async () => {
|
|
2492
|
+
const currentHash = await hashKnowledgeBase(options.root);
|
|
2493
|
+
if (currentHash !== promoted.candidateHash) {
|
|
2494
|
+
throw new Error(
|
|
2495
|
+
`promoted knowledge base changed: expected ${promoted.candidateHash}, got ${currentHash}`
|
|
2496
|
+
);
|
|
2497
|
+
}
|
|
2498
|
+
const evidence2 = await assertCandidateEvidence(
|
|
2424
2499
|
runDir,
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2500
|
+
candidateRefFor(runId, promotedState, promoted)
|
|
2501
|
+
);
|
|
2502
|
+
return {
|
|
2503
|
+
runId,
|
|
2504
|
+
state: promotedState,
|
|
2505
|
+
candidate: promoted,
|
|
2506
|
+
evaluation: evidence2.evaluation,
|
|
2507
|
+
promoted: true,
|
|
2508
|
+
blocked: false
|
|
2509
|
+
};
|
|
2510
|
+
});
|
|
2434
2511
|
}
|
|
2512
|
+
await withKnowledgeMutation(options.root, () => void 0);
|
|
2513
|
+
await ensureBaselineSnapshot(runDir, options.root, state.baseHash);
|
|
2435
2514
|
if (state.status === "blocked") {
|
|
2436
2515
|
return { runId, state, promoted: false, blocked: true };
|
|
2437
2516
|
}
|
|
@@ -2532,19 +2611,85 @@ async function applyKnowledgeCandidateTarget(input, target) {
|
|
|
2532
2611
|
const { candidateRef, runDir, state } = input;
|
|
2533
2612
|
assertStateIdentity(input.root, candidateRef, state);
|
|
2534
2613
|
const candidate = state.candidates.find((entry) => entry.candidateId === candidateRef.candidateId);
|
|
2535
|
-
if (!candidate || canonicalJson(
|
|
2614
|
+
if (!candidate || canonicalJson(candidateIdentityFor(candidateRef.runId, state, candidate)) !== canonicalJson(candidateRef)) {
|
|
2536
2615
|
throw new Error("knowledge candidate approval does not match the measured candidate");
|
|
2537
2616
|
}
|
|
2538
2617
|
const action = target === "candidate" ? "promotion" : "restore";
|
|
2539
2618
|
const desiredHash = target === "candidate" ? candidateRef.candidateHash : candidateRef.baseHash;
|
|
2540
2619
|
const purpose = knowledgeCandidateTransitionPurpose(candidateRef, target);
|
|
2620
|
+
const recoveryOwner = input.activation ? `knowledge-improvement-activation:${input.activation.activation.digest}` : "knowledge-improvement-candidate-transition";
|
|
2541
2621
|
return withKnowledgeMutation(
|
|
2542
2622
|
input.root,
|
|
2543
2623
|
async (mutationLock) => {
|
|
2544
|
-
|
|
2624
|
+
const assertOwned = () => {
|
|
2625
|
+
mutationLock.assertOwned();
|
|
2626
|
+
input.assertRunOwned();
|
|
2627
|
+
};
|
|
2628
|
+
assertOwned();
|
|
2545
2629
|
const transactionRoot = mutationLock.transactionRoot;
|
|
2546
|
-
|
|
2630
|
+
const recovery = mutationLock.recovery?.purpose === purpose ? mutationLock.recovery : void 0;
|
|
2631
|
+
const existingActivation = input.activation ? await loadKnowledgeActivationRecord(
|
|
2632
|
+
runDir,
|
|
2633
|
+
candidateRef,
|
|
2634
|
+
input.activation.activation,
|
|
2635
|
+
target,
|
|
2636
|
+
input.activation.identity
|
|
2637
|
+
) : null;
|
|
2638
|
+
if (existingActivation) {
|
|
2639
|
+
if (recovery?.direction === "rollback") {
|
|
2640
|
+
throw new Error("stored knowledge activation result conflicts with a pending rollback");
|
|
2641
|
+
}
|
|
2642
|
+
if (recovery) {
|
|
2643
|
+
const recoveredHash = await hashKnowledgeBase(input.root);
|
|
2644
|
+
if (recoveredHash !== existingActivation.mutation.afterHash) {
|
|
2645
|
+
throw new Error("stored knowledge activation result does not match the recovered files");
|
|
2646
|
+
}
|
|
2647
|
+
await finishKnowledgeFileTransaction({
|
|
2648
|
+
root: input.root,
|
|
2649
|
+
transactionRoot,
|
|
2650
|
+
transaction: recovery.transaction,
|
|
2651
|
+
assertOwned
|
|
2652
|
+
});
|
|
2653
|
+
}
|
|
2654
|
+
if (existingActivation.result.outcome.status === "conflict") {
|
|
2655
|
+
const reason = candidateTransitionConflictReason(
|
|
2656
|
+
target,
|
|
2657
|
+
candidateRef,
|
|
2658
|
+
existingActivation.mutation.afterHash
|
|
2659
|
+
);
|
|
2660
|
+
const blocked = await blockCandidateTransition(
|
|
2661
|
+
input,
|
|
2662
|
+
candidate,
|
|
2663
|
+
target,
|
|
2664
|
+
reason,
|
|
2665
|
+
existingActivation.mutation.afterHash
|
|
2666
|
+
);
|
|
2667
|
+
return { ...blocked, activationResult: existingActivation.result };
|
|
2668
|
+
}
|
|
2669
|
+
return candidateTransitionResult(
|
|
2670
|
+
input,
|
|
2671
|
+
candidate,
|
|
2672
|
+
target === "candidate" && state.status === "promoted",
|
|
2673
|
+
false,
|
|
2674
|
+
existingActivation.mutation,
|
|
2675
|
+
existingActivation.result
|
|
2676
|
+
);
|
|
2677
|
+
}
|
|
2678
|
+
if (recovery?.direction === "rollback") {
|
|
2679
|
+
await finishKnowledgeFileTransaction({
|
|
2680
|
+
root: input.root,
|
|
2681
|
+
transactionRoot,
|
|
2682
|
+
transaction: recovery.transaction,
|
|
2683
|
+
assertOwned
|
|
2684
|
+
});
|
|
2685
|
+
}
|
|
2686
|
+
const recovered = recovery?.direction === "apply" ? recovery : void 0;
|
|
2687
|
+
let pending = input.activation && recovered ? recovered.transaction : null;
|
|
2547
2688
|
const currentHash = await hashKnowledgeBase(input.root);
|
|
2689
|
+
let transactionId = recovered?.transactionId ?? null;
|
|
2690
|
+
if (recovered && currentHash !== desiredHash) {
|
|
2691
|
+
throw new Error(`recovered knowledge ${action} does not match the approved target`);
|
|
2692
|
+
}
|
|
2548
2693
|
if (state.status === "promoted" && state.promotedCandidateId !== candidate.candidateId) {
|
|
2549
2694
|
throw new Error(
|
|
2550
2695
|
`knowledge run already promoted '${state.promotedCandidateId ?? "unknown"}'`
|
|
@@ -2562,9 +2707,31 @@ async function applyKnowledgeCandidateTarget(input, target) {
|
|
|
2562
2707
|
}
|
|
2563
2708
|
if (currentHash !== state.baseHash && currentHash !== candidateRef.candidateHash) {
|
|
2564
2709
|
const reason = target === "candidate" ? `base changed before promotion: expected ${state.baseHash}, got ${currentHash}` : `knowledge changed before restore: expected ${candidateRef.candidateHash}, got ${currentHash}`;
|
|
2565
|
-
|
|
2710
|
+
const mutation2 = Object.freeze({
|
|
2711
|
+
target,
|
|
2712
|
+
beforeHash: currentHash,
|
|
2713
|
+
afterHash: currentHash,
|
|
2714
|
+
changed: false,
|
|
2715
|
+
transactionId: null,
|
|
2716
|
+
recovered: false
|
|
2717
|
+
});
|
|
2718
|
+
const activationResult2 = input.activation ? await persistKnowledgeActivationResult(
|
|
2719
|
+
runDir,
|
|
2720
|
+
candidateRef,
|
|
2721
|
+
input.activation,
|
|
2722
|
+
target,
|
|
2723
|
+
mutation2
|
|
2724
|
+
) : void 0;
|
|
2725
|
+
const blocked = await blockCandidateTransition(
|
|
2726
|
+
input,
|
|
2727
|
+
candidate,
|
|
2728
|
+
target,
|
|
2729
|
+
reason,
|
|
2730
|
+
currentHash
|
|
2731
|
+
);
|
|
2732
|
+
return activationResult2 ? { ...blocked, activationResult: activationResult2 } : blocked;
|
|
2566
2733
|
}
|
|
2567
|
-
if (currentHash !== desiredHash) {
|
|
2734
|
+
if (currentHash !== desiredHash && !pending) {
|
|
2568
2735
|
pending = await withMeasuredCandidateSnapshot(
|
|
2569
2736
|
input.root,
|
|
2570
2737
|
runDir,
|
|
@@ -2579,6 +2746,7 @@ async function applyKnowledgeCandidateTarget(input, target) {
|
|
|
2579
2746
|
root: input.root,
|
|
2580
2747
|
transactionRoot,
|
|
2581
2748
|
purpose,
|
|
2749
|
+
recoveryOwner,
|
|
2582
2750
|
mutations: await knowledgePlanMutations(targetRoot, plan),
|
|
2583
2751
|
includeUnchanged: true,
|
|
2584
2752
|
now: input.now
|
|
@@ -2588,6 +2756,7 @@ async function applyKnowledgeCandidateTarget(input, target) {
|
|
|
2588
2756
|
if (!pending) {
|
|
2589
2757
|
throw new Error(`knowledge ${action} plan unexpectedly contained no file changes`);
|
|
2590
2758
|
}
|
|
2759
|
+
transactionId = pending.transactionId;
|
|
2591
2760
|
try {
|
|
2592
2761
|
assertCandidateTransitionTransaction(pending, candidateRef, target);
|
|
2593
2762
|
} catch (error) {
|
|
@@ -2596,19 +2765,13 @@ async function applyKnowledgeCandidateTarget(input, target) {
|
|
|
2596
2765
|
root: input.root,
|
|
2597
2766
|
transactionRoot,
|
|
2598
2767
|
transaction: pending,
|
|
2599
|
-
beforeCommit
|
|
2600
|
-
mutationLock.assertOwned();
|
|
2601
|
-
input.assertRunOwned();
|
|
2602
|
-
}
|
|
2768
|
+
beforeCommit: assertOwned
|
|
2603
2769
|
});
|
|
2604
2770
|
await finishKnowledgeFileTransaction({
|
|
2605
2771
|
root: input.root,
|
|
2606
2772
|
transactionRoot,
|
|
2607
2773
|
transaction: pending,
|
|
2608
|
-
assertOwned
|
|
2609
|
-
mutationLock.assertOwned();
|
|
2610
|
-
input.assertRunOwned();
|
|
2611
|
-
}
|
|
2774
|
+
assertOwned
|
|
2612
2775
|
});
|
|
2613
2776
|
} catch (cleanupError) {
|
|
2614
2777
|
throw new AggregateError(
|
|
@@ -2625,23 +2788,19 @@ async function applyKnowledgeCandidateTarget(input, target) {
|
|
|
2625
2788
|
root: input.root,
|
|
2626
2789
|
transactionRoot,
|
|
2627
2790
|
transaction: pending,
|
|
2628
|
-
beforeCommit
|
|
2629
|
-
mutationLock.assertOwned();
|
|
2630
|
-
input.assertRunOwned();
|
|
2631
|
-
}
|
|
2791
|
+
beforeCommit: assertOwned
|
|
2632
2792
|
});
|
|
2633
2793
|
}
|
|
2634
|
-
|
|
2635
|
-
input.assertRunOwned();
|
|
2794
|
+
assertOwned();
|
|
2636
2795
|
if (await hashKnowledgeBase(input.root) !== desiredHash) {
|
|
2637
2796
|
throw new Error(`knowledge ${action} content does not match the approved target`);
|
|
2638
2797
|
}
|
|
2639
2798
|
await writeKnowledgeIndex(input.root);
|
|
2640
2799
|
} catch (error) {
|
|
2641
2800
|
if (!pending) throw error;
|
|
2801
|
+
if (recovered && input.activation) throw error;
|
|
2642
2802
|
try {
|
|
2643
|
-
|
|
2644
|
-
input.assertRunOwned();
|
|
2803
|
+
assertOwned();
|
|
2645
2804
|
} catch (ownershipError) {
|
|
2646
2805
|
throw new AggregateError(
|
|
2647
2806
|
[error, ownershipError],
|
|
@@ -2653,19 +2812,13 @@ async function applyKnowledgeCandidateTarget(input, target) {
|
|
|
2653
2812
|
root: input.root,
|
|
2654
2813
|
transactionRoot,
|
|
2655
2814
|
transaction: pending,
|
|
2656
|
-
beforeCommit
|
|
2657
|
-
mutationLock.assertOwned();
|
|
2658
|
-
input.assertRunOwned();
|
|
2659
|
-
}
|
|
2815
|
+
beforeCommit: assertOwned
|
|
2660
2816
|
});
|
|
2661
2817
|
await finishKnowledgeFileTransaction({
|
|
2662
2818
|
root: input.root,
|
|
2663
2819
|
transactionRoot,
|
|
2664
2820
|
transaction: pending,
|
|
2665
|
-
assertOwned
|
|
2666
|
-
mutationLock.assertOwned();
|
|
2667
|
-
input.assertRunOwned();
|
|
2668
|
-
}
|
|
2821
|
+
assertOwned
|
|
2669
2822
|
});
|
|
2670
2823
|
await writeKnowledgeIndex(input.root);
|
|
2671
2824
|
} catch (rollbackError) {
|
|
@@ -2685,29 +2838,64 @@ async function applyKnowledgeCandidateTarget(input, target) {
|
|
|
2685
2838
|
state.updatedAt = input.now().toISOString();
|
|
2686
2839
|
await saveState(runDir, state, input.onState);
|
|
2687
2840
|
await ensureCandidateTransitionEvent(runDir, candidateRef, target);
|
|
2841
|
+
let finalHash = await hashKnowledgeBase(input.root);
|
|
2842
|
+
if (finalHash !== desiredHash) {
|
|
2843
|
+
throw new Error(`knowledge ${action} changed before its result was returned`);
|
|
2844
|
+
}
|
|
2845
|
+
const mutation = Object.freeze({
|
|
2846
|
+
target,
|
|
2847
|
+
beforeHash: transactionId ? sourceKnowledgeHash(candidateRef, target) : currentHash,
|
|
2848
|
+
afterHash: finalHash,
|
|
2849
|
+
changed: transactionId !== null,
|
|
2850
|
+
transactionId,
|
|
2851
|
+
recovered: recovered !== void 0
|
|
2852
|
+
});
|
|
2853
|
+
const activationResult = input.activation ? await persistKnowledgeActivationResult(
|
|
2854
|
+
runDir,
|
|
2855
|
+
candidateRef,
|
|
2856
|
+
input.activation,
|
|
2857
|
+
target,
|
|
2858
|
+
mutation
|
|
2859
|
+
) : void 0;
|
|
2860
|
+
if (await hashKnowledgeBase(input.root) !== finalHash) {
|
|
2861
|
+
throw new Error(`knowledge ${action} changed while its result was persisted`);
|
|
2862
|
+
}
|
|
2688
2863
|
if (pending) {
|
|
2689
2864
|
await finishKnowledgeFileTransaction({
|
|
2690
2865
|
root: input.root,
|
|
2691
2866
|
transactionRoot,
|
|
2692
2867
|
transaction: pending,
|
|
2693
|
-
assertOwned
|
|
2694
|
-
mutationLock.assertOwned();
|
|
2695
|
-
input.assertRunOwned();
|
|
2696
|
-
}
|
|
2868
|
+
assertOwned
|
|
2697
2869
|
});
|
|
2698
2870
|
}
|
|
2699
|
-
|
|
2871
|
+
finalHash = await hashKnowledgeBase(input.root);
|
|
2872
|
+
if (finalHash !== desiredHash) {
|
|
2873
|
+
throw new Error(`knowledge ${action} changed before its result was returned`);
|
|
2874
|
+
}
|
|
2875
|
+
return candidateTransitionResult(
|
|
2876
|
+
input,
|
|
2877
|
+
candidate,
|
|
2878
|
+
target === "candidate",
|
|
2879
|
+
false,
|
|
2880
|
+
mutation,
|
|
2881
|
+
activationResult
|
|
2882
|
+
);
|
|
2700
2883
|
},
|
|
2701
2884
|
{
|
|
2702
2885
|
staleMs: input.leaseTtlMs,
|
|
2703
2886
|
resumeTransaction: {
|
|
2704
2887
|
purpose,
|
|
2705
|
-
|
|
2888
|
+
recoveryOwner,
|
|
2889
|
+
validate: (transaction) => assertCandidateTransitionTransaction(transaction, candidateRef, target),
|
|
2890
|
+
deferFinish: input.activation !== void 0
|
|
2706
2891
|
}
|
|
2707
2892
|
}
|
|
2708
2893
|
);
|
|
2709
2894
|
}
|
|
2710
|
-
|
|
2895
|
+
function candidateTransitionConflictReason(target, candidate, currentHash) {
|
|
2896
|
+
return target === "candidate" ? `base changed before promotion: expected ${candidate.baseHash}, got ${currentHash}` : `knowledge changed before restore: expected ${candidate.candidateHash}, got ${currentHash}`;
|
|
2897
|
+
}
|
|
2898
|
+
async function blockCandidateTransition(input, candidate, target, reason, currentHash) {
|
|
2711
2899
|
if (input.state.status === "promoted") {
|
|
2712
2900
|
candidate.status = "blocked";
|
|
2713
2901
|
candidate.updatedAt = input.now().toISOString();
|
|
@@ -2720,19 +2908,194 @@ async function blockCandidateTransition(input, candidate, target, reason) {
|
|
|
2720
2908
|
candidateId: candidate.candidateId,
|
|
2721
2909
|
reason
|
|
2722
2910
|
});
|
|
2723
|
-
return candidateTransitionResult(input, candidate, false, true
|
|
2911
|
+
return candidateTransitionResult(input, candidate, false, true, {
|
|
2912
|
+
target,
|
|
2913
|
+
beforeHash: currentHash,
|
|
2914
|
+
afterHash: currentHash,
|
|
2915
|
+
changed: false,
|
|
2916
|
+
transactionId: null,
|
|
2917
|
+
recovered: false
|
|
2918
|
+
});
|
|
2724
2919
|
}
|
|
2725
|
-
function candidateTransitionResult(input, candidate, promoted, blocked) {
|
|
2920
|
+
function candidateTransitionResult(input, candidate, promoted, blocked, mutation, activationResult) {
|
|
2726
2921
|
return {
|
|
2727
2922
|
runId: input.candidateRef.runId,
|
|
2728
2923
|
state: input.state,
|
|
2729
2924
|
candidate,
|
|
2730
2925
|
...input.lifecycle ? { lifecycle: input.lifecycle } : {},
|
|
2926
|
+
mutation,
|
|
2927
|
+
...activationResult ? { activationResult } : {},
|
|
2731
2928
|
promoted,
|
|
2732
2929
|
blocked
|
|
2733
2930
|
};
|
|
2734
2931
|
}
|
|
2932
|
+
function sourceKnowledgeHash(candidate, target) {
|
|
2933
|
+
return target === "candidate" ? candidate.baseHash : candidate.candidateHash;
|
|
2934
|
+
}
|
|
2935
|
+
function targetForKnowledgeActivation(activation) {
|
|
2936
|
+
return activation.intent === "activate-candidate" ? "candidate" : "baseline";
|
|
2937
|
+
}
|
|
2938
|
+
function resolveKnowledgeActivationPersistence(input, candidate, target) {
|
|
2939
|
+
const activation = verifyCanonicalKnowledgeActivation(input.activation);
|
|
2940
|
+
assertKnowledgeActivationAuthority(activation, candidate, target, input.identity);
|
|
2941
|
+
const attemptedAt = z.iso.datetime().parse(input.attemptedAt);
|
|
2942
|
+
if (Date.parse(attemptedAt) < Date.parse(activation.authorizedAt) || Date.parse(attemptedAt) >= Date.parse(activation.expiresAt)) {
|
|
2943
|
+
throw new Error("knowledge activation attempt is outside its authorization window");
|
|
2944
|
+
}
|
|
2945
|
+
return Object.freeze({
|
|
2946
|
+
activation,
|
|
2947
|
+
attemptedAt,
|
|
2948
|
+
identity: input.identity,
|
|
2949
|
+
createResult: input.createResult
|
|
2950
|
+
});
|
|
2951
|
+
}
|
|
2952
|
+
function assertKnowledgeActivationAuthority(activation, candidate, target, identity) {
|
|
2953
|
+
if (!identity.trim()) throw new Error("knowledge activation identity is required");
|
|
2954
|
+
if (targetForKnowledgeActivation(activation) !== target) {
|
|
2955
|
+
throw new Error("knowledge activation intent does not match the requested transition");
|
|
2956
|
+
}
|
|
2957
|
+
if (activation.targets.length !== 1) {
|
|
2958
|
+
throw new Error("knowledge activation requires exactly one target");
|
|
2959
|
+
}
|
|
2960
|
+
const authorizedTarget = activation.targets[0];
|
|
2961
|
+
if (authorizedTarget.surface !== "knowledge" || authorizedTarget.identity !== identity) {
|
|
2962
|
+
throw new Error("knowledge activation target does not match this knowledge base");
|
|
2963
|
+
}
|
|
2964
|
+
if (authorizedTarget.expectedBaseDigest !== prefixedKnowledgeDigest(sourceKnowledgeHash(candidate, target))) {
|
|
2965
|
+
throw new Error("knowledge activation does not authorize the measured source state");
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
function verifyCanonicalKnowledgeActivation(value) {
|
|
2969
|
+
const activation = agentImprovementActivationSchema.parse(value);
|
|
2970
|
+
if (canonicalCandidateDigest(omitTopLevelDigest(activation)) !== activation.digest) {
|
|
2971
|
+
throw new Error("knowledge activation digest does not match its canonical content");
|
|
2972
|
+
}
|
|
2973
|
+
return immutableJsonValue(structuredClone(activation));
|
|
2974
|
+
}
|
|
2975
|
+
function verifyCanonicalKnowledgeActivationResult(value) {
|
|
2976
|
+
const result = agentImprovementActivationResultSchema.parse(value);
|
|
2977
|
+
if (canonicalCandidateDigest(omitTopLevelDigest(result)) !== result.digest) {
|
|
2978
|
+
throw new Error("knowledge activation result digest does not match its canonical content");
|
|
2979
|
+
}
|
|
2980
|
+
return immutableJsonValue(structuredClone(result));
|
|
2981
|
+
}
|
|
2982
|
+
async function persistKnowledgeActivationResult(runDir, candidate, persistence, target, mutation) {
|
|
2983
|
+
const result = assertKnowledgeActivationResult(
|
|
2984
|
+
persistence.activation,
|
|
2985
|
+
candidate,
|
|
2986
|
+
target,
|
|
2987
|
+
persistence.identity,
|
|
2988
|
+
mutation,
|
|
2989
|
+
await persistence.createResult(Object.freeze({ ...mutation })),
|
|
2990
|
+
persistence.attemptedAt
|
|
2991
|
+
);
|
|
2992
|
+
const record = knowledgeImprovementActivationRecordSchema.parse({
|
|
2993
|
+
kind: "knowledge-improvement-activation-result",
|
|
2994
|
+
candidateId: candidate.candidateId,
|
|
2995
|
+
mutation,
|
|
2996
|
+
result
|
|
2997
|
+
});
|
|
2998
|
+
const existing = await loadKnowledgeActivationRecord(
|
|
2999
|
+
runDir,
|
|
3000
|
+
candidate,
|
|
3001
|
+
persistence.activation,
|
|
3002
|
+
target,
|
|
3003
|
+
persistence.identity
|
|
3004
|
+
);
|
|
3005
|
+
if (existing) {
|
|
3006
|
+
if (canonicalJson(existing) !== canonicalJson(record)) {
|
|
3007
|
+
throw new Error("knowledge activation result identity conflicts with durable content");
|
|
3008
|
+
}
|
|
3009
|
+
return existing.result;
|
|
3010
|
+
}
|
|
3011
|
+
await writeJsonDurableWithinRoot(
|
|
3012
|
+
runDir,
|
|
3013
|
+
knowledgeActivationResultPath(persistence.activation.digest),
|
|
3014
|
+
record
|
|
3015
|
+
);
|
|
3016
|
+
const stored = await loadKnowledgeActivationRecord(
|
|
3017
|
+
runDir,
|
|
3018
|
+
candidate,
|
|
3019
|
+
persistence.activation,
|
|
3020
|
+
target,
|
|
3021
|
+
persistence.identity
|
|
3022
|
+
);
|
|
3023
|
+
if (!stored || canonicalJson(stored) !== canonicalJson(record)) {
|
|
3024
|
+
throw new Error("knowledge activation result was not durably persisted");
|
|
3025
|
+
}
|
|
3026
|
+
return stored.result;
|
|
3027
|
+
}
|
|
3028
|
+
async function loadKnowledgeActivationRecord(runDir, candidate, activation, target, identity) {
|
|
3029
|
+
let raw;
|
|
3030
|
+
try {
|
|
3031
|
+
const file = await readRegularFileWithinRoot(
|
|
3032
|
+
runDir,
|
|
3033
|
+
knowledgeActivationResultPath(activation.digest)
|
|
3034
|
+
);
|
|
3035
|
+
raw = JSON.parse(file.bytes.toString("utf8"));
|
|
3036
|
+
} catch (error) {
|
|
3037
|
+
if (isMissingFile(error)) return null;
|
|
3038
|
+
throw error;
|
|
3039
|
+
}
|
|
3040
|
+
const record = knowledgeImprovementActivationRecordSchema.parse(raw);
|
|
3041
|
+
if (record.candidateId !== candidate.candidateId) {
|
|
3042
|
+
throw new Error("knowledge activation result belongs to another candidate");
|
|
3043
|
+
}
|
|
3044
|
+
const result = assertKnowledgeActivationResult(
|
|
3045
|
+
activation,
|
|
3046
|
+
candidate,
|
|
3047
|
+
target,
|
|
3048
|
+
identity,
|
|
3049
|
+
record.mutation,
|
|
3050
|
+
record.result
|
|
3051
|
+
);
|
|
3052
|
+
return immutableJsonValue({ ...record, result });
|
|
3053
|
+
}
|
|
3054
|
+
function assertKnowledgeActivationResult(activation, candidate, target, identity, mutationInput, resultInput, attemptedAt) {
|
|
3055
|
+
assertKnowledgeActivationAuthority(activation, candidate, target, identity);
|
|
3056
|
+
const mutation = knowledgeImprovementMutationReceiptSchema.parse(mutationInput);
|
|
3057
|
+
const result = verifyCanonicalKnowledgeActivationResult(resultInput);
|
|
3058
|
+
if (result.idempotencyKey !== activation.digest || attemptedAt !== void 0 && result.attemptedAt !== attemptedAt || Date.parse(result.attemptedAt) < Date.parse(activation.authorizedAt) || Date.parse(result.attemptedAt) >= Date.parse(activation.expiresAt) || mutation.target !== target || mutation.changed !== (mutation.beforeHash !== mutation.afterHash) || !mutation.changed && (mutation.transactionId !== null || mutation.recovered)) {
|
|
3059
|
+
throw new Error("knowledge activation result does not bind its authorized mutation");
|
|
3060
|
+
}
|
|
3061
|
+
const sourceDigest = prefixedKnowledgeDigest(sourceKnowledgeHash(candidate, target));
|
|
3062
|
+
const desiredDigest = prefixedKnowledgeDigest(
|
|
3063
|
+
target === "candidate" ? candidate.candidateHash : candidate.baseHash
|
|
3064
|
+
);
|
|
3065
|
+
const beforeDigest = prefixedKnowledgeDigest(mutation.beforeHash);
|
|
3066
|
+
const afterDigest = prefixedKnowledgeDigest(mutation.afterHash);
|
|
3067
|
+
const outcome = result.outcome;
|
|
3068
|
+
if (mutation.changed) {
|
|
3069
|
+
if (mutation.transactionId === null || beforeDigest !== sourceDigest || afterDigest !== desiredDigest || outcome.status !== "applied" || outcome.transactionId !== mutation.transactionId || outcome.targets.length !== 1 || outcome.targets[0]?.surface !== "knowledge" || outcome.targets[0]?.identity !== identity || outcome.targets[0]?.beforeDigest !== beforeDigest || outcome.targets[0]?.afterDigest !== afterDigest) {
|
|
3070
|
+
throw new Error("knowledge activation result does not prove the applied transaction");
|
|
3071
|
+
}
|
|
3072
|
+
return result;
|
|
3073
|
+
}
|
|
3074
|
+
const expectedStatus = afterDigest === desiredDigest ? "already-applied" : "conflict";
|
|
3075
|
+
if (afterDigest === sourceDigest || outcome.status !== expectedStatus || outcome.targets.length !== 1 || outcome.targets[0]?.surface !== "knowledge" || outcome.targets[0]?.identity !== identity || outcome.targets[0]?.currentDigest !== afterDigest) {
|
|
3076
|
+
throw new Error("knowledge activation result does not prove the observed target state");
|
|
3077
|
+
}
|
|
3078
|
+
return result;
|
|
3079
|
+
}
|
|
3080
|
+
function knowledgeActivationResultPath(digest) {
|
|
3081
|
+
const parsed = sha256DigestSchema.parse(digest);
|
|
3082
|
+
return `activation-results/${parsed.slice("sha256:".length)}.json`;
|
|
3083
|
+
}
|
|
3084
|
+
function prefixedKnowledgeDigest(hash) {
|
|
3085
|
+
return sha256DigestSchema.parse(`sha256:${digestSchema.parse(hash)}`);
|
|
3086
|
+
}
|
|
3087
|
+
function immutableJsonValue(value) {
|
|
3088
|
+
if (value === null || typeof value !== "object") return value;
|
|
3089
|
+
for (const child of Object.values(value)) immutableJsonValue(child);
|
|
3090
|
+
return Object.freeze(value);
|
|
3091
|
+
}
|
|
2735
3092
|
function candidateRefFor(runId, state, candidate) {
|
|
3093
|
+
if (candidate.status !== "candidate-ready" && candidate.status !== "promoted") {
|
|
3094
|
+
throw new Error(`knowledge candidate '${candidate.candidateId}' is not ready`);
|
|
3095
|
+
}
|
|
3096
|
+
return candidateIdentityFor(runId, state, candidate);
|
|
3097
|
+
}
|
|
3098
|
+
function candidateIdentityFor(runId, state, candidate) {
|
|
2736
3099
|
if (!candidate.candidateHash) {
|
|
2737
3100
|
throw new Error(`knowledge candidate '${candidate.candidateId}' has no content hash`);
|
|
2738
3101
|
}
|
|
@@ -2742,9 +3105,6 @@ function candidateRefFor(runId, state, candidate) {
|
|
|
2742
3105
|
if (!candidate.promotionPlanHash) {
|
|
2743
3106
|
throw new Error(`knowledge candidate '${candidate.candidateId}' has no promotion plan hash`);
|
|
2744
3107
|
}
|
|
2745
|
-
if (candidate.status !== "candidate-ready" && candidate.status !== "promoted") {
|
|
2746
|
-
throw new Error(`knowledge candidate '${candidate.candidateId}' is not ready`);
|
|
2747
|
-
}
|
|
2748
3108
|
return Object.freeze({
|
|
2749
3109
|
kind: "knowledge-improvement-candidate",
|
|
2750
3110
|
runId,
|
|
@@ -2784,17 +3144,17 @@ async function withMeasuredCandidateSnapshot(liveRoot, runDir, state, candidateR
|
|
|
2784
3144
|
return result;
|
|
2785
3145
|
});
|
|
2786
3146
|
}
|
|
2787
|
-
async function
|
|
2788
|
-
const isolationRoot = await mkdtemp(join(tmpdir(), "agent-knowledge-
|
|
2789
|
-
const
|
|
3147
|
+
async function withIsolatedKnowledgeCopy(sourceRoot, expectedHash, target, use) {
|
|
3148
|
+
const isolationRoot = await mkdtemp(join(tmpdir(), "agent-knowledge-snapshot-"));
|
|
3149
|
+
const snapshotRoot = join(isolationRoot, "snapshot");
|
|
2790
3150
|
try {
|
|
2791
|
-
await copyKnowledgeWorkspace(sourceRoot,
|
|
2792
|
-
if (await hashKnowledgeBase(
|
|
2793
|
-
throw new Error(
|
|
3151
|
+
await copyKnowledgeWorkspace(sourceRoot, snapshotRoot);
|
|
3152
|
+
if (await hashKnowledgeBase(snapshotRoot) !== expectedHash) {
|
|
3153
|
+
throw new Error(`isolated knowledge ${target} does not match its measured content`);
|
|
2794
3154
|
}
|
|
2795
|
-
const result = await use(
|
|
2796
|
-
if (await hashKnowledgeBase(
|
|
2797
|
-
throw new Error(
|
|
3155
|
+
const result = await use(snapshotRoot);
|
|
3156
|
+
if (await hashKnowledgeBase(snapshotRoot) !== expectedHash) {
|
|
3157
|
+
throw new Error(`knowledge ${target} snapshot changed during use`);
|
|
2798
3158
|
}
|
|
2799
3159
|
return result;
|
|
2800
3160
|
} finally {
|
|
@@ -3534,10 +3894,10 @@ function fromAgentCandidateKnowledgeRef(candidate) {
|
|
|
3534
3894
|
});
|
|
3535
3895
|
}
|
|
3536
3896
|
function prefixedDigest(value) {
|
|
3537
|
-
return
|
|
3897
|
+
return sha256DigestSchema2.parse(`sha256:${value}`);
|
|
3538
3898
|
}
|
|
3539
3899
|
function rawDigest(value) {
|
|
3540
|
-
return
|
|
3900
|
+
return sha256DigestSchema2.parse(value).slice("sha256:".length);
|
|
3541
3901
|
}
|
|
3542
3902
|
|
|
3543
3903
|
// src/changes.ts
|
|
@@ -5966,6 +6326,7 @@ export {
|
|
|
5966
6326
|
layoutFor,
|
|
5967
6327
|
lensDistribution,
|
|
5968
6328
|
lintKnowledgeIndex,
|
|
6329
|
+
loadKnowledgeImprovementActivationResult,
|
|
5969
6330
|
loadKnowledgeImprovementEvents,
|
|
5970
6331
|
loadKnowledgeImprovementState,
|
|
5971
6332
|
loadKnowledgePages,
|
|
@@ -6036,6 +6397,7 @@ export {
|
|
|
6036
6397
|
validateKnowledgeIndex,
|
|
6037
6398
|
withCitedClaim,
|
|
6038
6399
|
withKnowledgeImprovementCandidate,
|
|
6400
|
+
withKnowledgeImprovementComparison,
|
|
6039
6401
|
writeJson,
|
|
6040
6402
|
writeKnowledgeIndex,
|
|
6041
6403
|
writeSourceRegistry
|