@tangle-network/agent-knowledge 3.1.0 → 3.2.0

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/dist/index.js CHANGED
@@ -55,7 +55,7 @@ import {
55
55
  writeJsonDurableWithinRoot,
56
56
  writeKnowledgeIndex,
57
57
  writeSourceRegistry
58
- } from "./chunk-2BJ5JR3L.js";
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 = KnowledgeImprovementCandidateRefSchema.parse(options.candidate);
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) => withIsolatedCandidateCopy(
2379
+ (resolved) => withIsolatedKnowledgeCopy(
2305
2380
  resolved.root,
2306
2381
  candidateRef.candidateHash,
2307
- (root) => use({
2308
- root,
2309
- candidate: candidateRef,
2310
- evaluation: resolved.evidence.evaluation
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,16 +2485,7 @@ 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
- });
2488
+ await withKnowledgeMutation(options.root, () => void 0);
2418
2489
  await ensureBaselineSnapshot(runDir, options.root, state.baseHash);
2419
2490
  if (state.status === "promoted") {
2420
2491
  const promoted = promotedCandidate;
@@ -2532,19 +2603,85 @@ async function applyKnowledgeCandidateTarget(input, target) {
2532
2603
  const { candidateRef, runDir, state } = input;
2533
2604
  assertStateIdentity(input.root, candidateRef, state);
2534
2605
  const candidate = state.candidates.find((entry) => entry.candidateId === candidateRef.candidateId);
2535
- if (!candidate || canonicalJson(candidateRefFor(candidateRef.runId, state, candidate)) !== canonicalJson(candidateRef)) {
2606
+ if (!candidate || canonicalJson(candidateIdentityFor(candidateRef.runId, state, candidate)) !== canonicalJson(candidateRef)) {
2536
2607
  throw new Error("knowledge candidate approval does not match the measured candidate");
2537
2608
  }
2538
2609
  const action = target === "candidate" ? "promotion" : "restore";
2539
2610
  const desiredHash = target === "candidate" ? candidateRef.candidateHash : candidateRef.baseHash;
2540
2611
  const purpose = knowledgeCandidateTransitionPurpose(candidateRef, target);
2612
+ const recoveryOwner = input.activation ? `knowledge-improvement-activation:${input.activation.activation.digest}` : "knowledge-improvement-candidate-transition";
2541
2613
  return withKnowledgeMutation(
2542
2614
  input.root,
2543
2615
  async (mutationLock) => {
2544
- input.assertRunOwned();
2616
+ const assertOwned = () => {
2617
+ mutationLock.assertOwned();
2618
+ input.assertRunOwned();
2619
+ };
2620
+ assertOwned();
2545
2621
  const transactionRoot = mutationLock.transactionRoot;
2546
- let pending = null;
2622
+ const recovery = mutationLock.recovery?.purpose === purpose ? mutationLock.recovery : void 0;
2623
+ const existingActivation = input.activation ? await loadKnowledgeActivationRecord(
2624
+ runDir,
2625
+ candidateRef,
2626
+ input.activation.activation,
2627
+ target,
2628
+ input.activation.identity
2629
+ ) : null;
2630
+ if (existingActivation) {
2631
+ if (recovery?.direction === "rollback") {
2632
+ throw new Error("stored knowledge activation result conflicts with a pending rollback");
2633
+ }
2634
+ if (recovery) {
2635
+ const recoveredHash = await hashKnowledgeBase(input.root);
2636
+ if (recoveredHash !== existingActivation.mutation.afterHash) {
2637
+ throw new Error("stored knowledge activation result does not match the recovered files");
2638
+ }
2639
+ await finishKnowledgeFileTransaction({
2640
+ root: input.root,
2641
+ transactionRoot,
2642
+ transaction: recovery.transaction,
2643
+ assertOwned
2644
+ });
2645
+ }
2646
+ if (existingActivation.result.outcome.status === "conflict") {
2647
+ const reason = candidateTransitionConflictReason(
2648
+ target,
2649
+ candidateRef,
2650
+ existingActivation.mutation.afterHash
2651
+ );
2652
+ const blocked = await blockCandidateTransition(
2653
+ input,
2654
+ candidate,
2655
+ target,
2656
+ reason,
2657
+ existingActivation.mutation.afterHash
2658
+ );
2659
+ return { ...blocked, activationResult: existingActivation.result };
2660
+ }
2661
+ return candidateTransitionResult(
2662
+ input,
2663
+ candidate,
2664
+ target === "candidate" && state.status === "promoted",
2665
+ false,
2666
+ existingActivation.mutation,
2667
+ existingActivation.result
2668
+ );
2669
+ }
2670
+ if (recovery?.direction === "rollback") {
2671
+ await finishKnowledgeFileTransaction({
2672
+ root: input.root,
2673
+ transactionRoot,
2674
+ transaction: recovery.transaction,
2675
+ assertOwned
2676
+ });
2677
+ }
2678
+ const recovered = recovery?.direction === "apply" ? recovery : void 0;
2679
+ let pending = input.activation && recovered ? recovered.transaction : null;
2547
2680
  const currentHash = await hashKnowledgeBase(input.root);
2681
+ let transactionId = recovered?.transactionId ?? null;
2682
+ if (recovered && currentHash !== desiredHash) {
2683
+ throw new Error(`recovered knowledge ${action} does not match the approved target`);
2684
+ }
2548
2685
  if (state.status === "promoted" && state.promotedCandidateId !== candidate.candidateId) {
2549
2686
  throw new Error(
2550
2687
  `knowledge run already promoted '${state.promotedCandidateId ?? "unknown"}'`
@@ -2562,9 +2699,31 @@ async function applyKnowledgeCandidateTarget(input, target) {
2562
2699
  }
2563
2700
  if (currentHash !== state.baseHash && currentHash !== candidateRef.candidateHash) {
2564
2701
  const reason = target === "candidate" ? `base changed before promotion: expected ${state.baseHash}, got ${currentHash}` : `knowledge changed before restore: expected ${candidateRef.candidateHash}, got ${currentHash}`;
2565
- return await blockCandidateTransition(input, candidate, target, reason);
2702
+ const mutation2 = Object.freeze({
2703
+ target,
2704
+ beforeHash: currentHash,
2705
+ afterHash: currentHash,
2706
+ changed: false,
2707
+ transactionId: null,
2708
+ recovered: false
2709
+ });
2710
+ const activationResult2 = input.activation ? await persistKnowledgeActivationResult(
2711
+ runDir,
2712
+ candidateRef,
2713
+ input.activation,
2714
+ target,
2715
+ mutation2
2716
+ ) : void 0;
2717
+ const blocked = await blockCandidateTransition(
2718
+ input,
2719
+ candidate,
2720
+ target,
2721
+ reason,
2722
+ currentHash
2723
+ );
2724
+ return activationResult2 ? { ...blocked, activationResult: activationResult2 } : blocked;
2566
2725
  }
2567
- if (currentHash !== desiredHash) {
2726
+ if (currentHash !== desiredHash && !pending) {
2568
2727
  pending = await withMeasuredCandidateSnapshot(
2569
2728
  input.root,
2570
2729
  runDir,
@@ -2579,6 +2738,7 @@ async function applyKnowledgeCandidateTarget(input, target) {
2579
2738
  root: input.root,
2580
2739
  transactionRoot,
2581
2740
  purpose,
2741
+ recoveryOwner,
2582
2742
  mutations: await knowledgePlanMutations(targetRoot, plan),
2583
2743
  includeUnchanged: true,
2584
2744
  now: input.now
@@ -2588,6 +2748,7 @@ async function applyKnowledgeCandidateTarget(input, target) {
2588
2748
  if (!pending) {
2589
2749
  throw new Error(`knowledge ${action} plan unexpectedly contained no file changes`);
2590
2750
  }
2751
+ transactionId = pending.transactionId;
2591
2752
  try {
2592
2753
  assertCandidateTransitionTransaction(pending, candidateRef, target);
2593
2754
  } catch (error) {
@@ -2596,19 +2757,13 @@ async function applyKnowledgeCandidateTarget(input, target) {
2596
2757
  root: input.root,
2597
2758
  transactionRoot,
2598
2759
  transaction: pending,
2599
- beforeCommit() {
2600
- mutationLock.assertOwned();
2601
- input.assertRunOwned();
2602
- }
2760
+ beforeCommit: assertOwned
2603
2761
  });
2604
2762
  await finishKnowledgeFileTransaction({
2605
2763
  root: input.root,
2606
2764
  transactionRoot,
2607
2765
  transaction: pending,
2608
- assertOwned() {
2609
- mutationLock.assertOwned();
2610
- input.assertRunOwned();
2611
- }
2766
+ assertOwned
2612
2767
  });
2613
2768
  } catch (cleanupError) {
2614
2769
  throw new AggregateError(
@@ -2625,23 +2780,19 @@ async function applyKnowledgeCandidateTarget(input, target) {
2625
2780
  root: input.root,
2626
2781
  transactionRoot,
2627
2782
  transaction: pending,
2628
- beforeCommit() {
2629
- mutationLock.assertOwned();
2630
- input.assertRunOwned();
2631
- }
2783
+ beforeCommit: assertOwned
2632
2784
  });
2633
2785
  }
2634
- mutationLock.assertOwned();
2635
- input.assertRunOwned();
2786
+ assertOwned();
2636
2787
  if (await hashKnowledgeBase(input.root) !== desiredHash) {
2637
2788
  throw new Error(`knowledge ${action} content does not match the approved target`);
2638
2789
  }
2639
2790
  await writeKnowledgeIndex(input.root);
2640
2791
  } catch (error) {
2641
2792
  if (!pending) throw error;
2793
+ if (recovered && input.activation) throw error;
2642
2794
  try {
2643
- mutationLock.assertOwned();
2644
- input.assertRunOwned();
2795
+ assertOwned();
2645
2796
  } catch (ownershipError) {
2646
2797
  throw new AggregateError(
2647
2798
  [error, ownershipError],
@@ -2653,19 +2804,13 @@ async function applyKnowledgeCandidateTarget(input, target) {
2653
2804
  root: input.root,
2654
2805
  transactionRoot,
2655
2806
  transaction: pending,
2656
- beforeCommit() {
2657
- mutationLock.assertOwned();
2658
- input.assertRunOwned();
2659
- }
2807
+ beforeCommit: assertOwned
2660
2808
  });
2661
2809
  await finishKnowledgeFileTransaction({
2662
2810
  root: input.root,
2663
2811
  transactionRoot,
2664
2812
  transaction: pending,
2665
- assertOwned() {
2666
- mutationLock.assertOwned();
2667
- input.assertRunOwned();
2668
- }
2813
+ assertOwned
2669
2814
  });
2670
2815
  await writeKnowledgeIndex(input.root);
2671
2816
  } catch (rollbackError) {
@@ -2685,29 +2830,64 @@ async function applyKnowledgeCandidateTarget(input, target) {
2685
2830
  state.updatedAt = input.now().toISOString();
2686
2831
  await saveState(runDir, state, input.onState);
2687
2832
  await ensureCandidateTransitionEvent(runDir, candidateRef, target);
2833
+ let finalHash = await hashKnowledgeBase(input.root);
2834
+ if (finalHash !== desiredHash) {
2835
+ throw new Error(`knowledge ${action} changed before its result was returned`);
2836
+ }
2837
+ const mutation = Object.freeze({
2838
+ target,
2839
+ beforeHash: transactionId ? sourceKnowledgeHash(candidateRef, target) : currentHash,
2840
+ afterHash: finalHash,
2841
+ changed: transactionId !== null,
2842
+ transactionId,
2843
+ recovered: recovered !== void 0
2844
+ });
2845
+ const activationResult = input.activation ? await persistKnowledgeActivationResult(
2846
+ runDir,
2847
+ candidateRef,
2848
+ input.activation,
2849
+ target,
2850
+ mutation
2851
+ ) : void 0;
2852
+ if (await hashKnowledgeBase(input.root) !== finalHash) {
2853
+ throw new Error(`knowledge ${action} changed while its result was persisted`);
2854
+ }
2688
2855
  if (pending) {
2689
2856
  await finishKnowledgeFileTransaction({
2690
2857
  root: input.root,
2691
2858
  transactionRoot,
2692
2859
  transaction: pending,
2693
- assertOwned() {
2694
- mutationLock.assertOwned();
2695
- input.assertRunOwned();
2696
- }
2860
+ assertOwned
2697
2861
  });
2698
2862
  }
2699
- return candidateTransitionResult(input, candidate, target === "candidate", false);
2863
+ finalHash = await hashKnowledgeBase(input.root);
2864
+ if (finalHash !== desiredHash) {
2865
+ throw new Error(`knowledge ${action} changed before its result was returned`);
2866
+ }
2867
+ return candidateTransitionResult(
2868
+ input,
2869
+ candidate,
2870
+ target === "candidate",
2871
+ false,
2872
+ mutation,
2873
+ activationResult
2874
+ );
2700
2875
  },
2701
2876
  {
2702
2877
  staleMs: input.leaseTtlMs,
2703
2878
  resumeTransaction: {
2704
2879
  purpose,
2705
- validate: (transaction) => assertCandidateTransitionTransaction(transaction, candidateRef, target)
2880
+ recoveryOwner,
2881
+ validate: (transaction) => assertCandidateTransitionTransaction(transaction, candidateRef, target),
2882
+ deferFinish: input.activation !== void 0
2706
2883
  }
2707
2884
  }
2708
2885
  );
2709
2886
  }
2710
- async function blockCandidateTransition(input, candidate, target, reason) {
2887
+ function candidateTransitionConflictReason(target, candidate, currentHash) {
2888
+ return target === "candidate" ? `base changed before promotion: expected ${candidate.baseHash}, got ${currentHash}` : `knowledge changed before restore: expected ${candidate.candidateHash}, got ${currentHash}`;
2889
+ }
2890
+ async function blockCandidateTransition(input, candidate, target, reason, currentHash) {
2711
2891
  if (input.state.status === "promoted") {
2712
2892
  candidate.status = "blocked";
2713
2893
  candidate.updatedAt = input.now().toISOString();
@@ -2720,19 +2900,194 @@ async function blockCandidateTransition(input, candidate, target, reason) {
2720
2900
  candidateId: candidate.candidateId,
2721
2901
  reason
2722
2902
  });
2723
- return candidateTransitionResult(input, candidate, false, true);
2903
+ return candidateTransitionResult(input, candidate, false, true, {
2904
+ target,
2905
+ beforeHash: currentHash,
2906
+ afterHash: currentHash,
2907
+ changed: false,
2908
+ transactionId: null,
2909
+ recovered: false
2910
+ });
2724
2911
  }
2725
- function candidateTransitionResult(input, candidate, promoted, blocked) {
2912
+ function candidateTransitionResult(input, candidate, promoted, blocked, mutation, activationResult) {
2726
2913
  return {
2727
2914
  runId: input.candidateRef.runId,
2728
2915
  state: input.state,
2729
2916
  candidate,
2730
2917
  ...input.lifecycle ? { lifecycle: input.lifecycle } : {},
2918
+ mutation,
2919
+ ...activationResult ? { activationResult } : {},
2731
2920
  promoted,
2732
2921
  blocked
2733
2922
  };
2734
2923
  }
2924
+ function sourceKnowledgeHash(candidate, target) {
2925
+ return target === "candidate" ? candidate.baseHash : candidate.candidateHash;
2926
+ }
2927
+ function targetForKnowledgeActivation(activation) {
2928
+ return activation.intent === "activate-candidate" ? "candidate" : "baseline";
2929
+ }
2930
+ function resolveKnowledgeActivationPersistence(input, candidate, target) {
2931
+ const activation = verifyCanonicalKnowledgeActivation(input.activation);
2932
+ assertKnowledgeActivationAuthority(activation, candidate, target, input.identity);
2933
+ const attemptedAt = z.iso.datetime().parse(input.attemptedAt);
2934
+ if (Date.parse(attemptedAt) < Date.parse(activation.authorizedAt) || Date.parse(attemptedAt) >= Date.parse(activation.expiresAt)) {
2935
+ throw new Error("knowledge activation attempt is outside its authorization window");
2936
+ }
2937
+ return Object.freeze({
2938
+ activation,
2939
+ attemptedAt,
2940
+ identity: input.identity,
2941
+ createResult: input.createResult
2942
+ });
2943
+ }
2944
+ function assertKnowledgeActivationAuthority(activation, candidate, target, identity) {
2945
+ if (!identity.trim()) throw new Error("knowledge activation identity is required");
2946
+ if (targetForKnowledgeActivation(activation) !== target) {
2947
+ throw new Error("knowledge activation intent does not match the requested transition");
2948
+ }
2949
+ if (activation.targets.length !== 1) {
2950
+ throw new Error("knowledge activation requires exactly one target");
2951
+ }
2952
+ const authorizedTarget = activation.targets[0];
2953
+ if (authorizedTarget.surface !== "knowledge" || authorizedTarget.identity !== identity) {
2954
+ throw new Error("knowledge activation target does not match this knowledge base");
2955
+ }
2956
+ if (authorizedTarget.expectedBaseDigest !== prefixedKnowledgeDigest(sourceKnowledgeHash(candidate, target))) {
2957
+ throw new Error("knowledge activation does not authorize the measured source state");
2958
+ }
2959
+ }
2960
+ function verifyCanonicalKnowledgeActivation(value) {
2961
+ const activation = agentImprovementActivationSchema.parse(value);
2962
+ if (canonicalCandidateDigest(omitTopLevelDigest(activation)) !== activation.digest) {
2963
+ throw new Error("knowledge activation digest does not match its canonical content");
2964
+ }
2965
+ return immutableJsonValue(structuredClone(activation));
2966
+ }
2967
+ function verifyCanonicalKnowledgeActivationResult(value) {
2968
+ const result = agentImprovementActivationResultSchema.parse(value);
2969
+ if (canonicalCandidateDigest(omitTopLevelDigest(result)) !== result.digest) {
2970
+ throw new Error("knowledge activation result digest does not match its canonical content");
2971
+ }
2972
+ return immutableJsonValue(structuredClone(result));
2973
+ }
2974
+ async function persistKnowledgeActivationResult(runDir, candidate, persistence, target, mutation) {
2975
+ const result = assertKnowledgeActivationResult(
2976
+ persistence.activation,
2977
+ candidate,
2978
+ target,
2979
+ persistence.identity,
2980
+ mutation,
2981
+ await persistence.createResult(Object.freeze({ ...mutation })),
2982
+ persistence.attemptedAt
2983
+ );
2984
+ const record = knowledgeImprovementActivationRecordSchema.parse({
2985
+ kind: "knowledge-improvement-activation-result",
2986
+ candidateId: candidate.candidateId,
2987
+ mutation,
2988
+ result
2989
+ });
2990
+ const existing = await loadKnowledgeActivationRecord(
2991
+ runDir,
2992
+ candidate,
2993
+ persistence.activation,
2994
+ target,
2995
+ persistence.identity
2996
+ );
2997
+ if (existing) {
2998
+ if (canonicalJson(existing) !== canonicalJson(record)) {
2999
+ throw new Error("knowledge activation result identity conflicts with durable content");
3000
+ }
3001
+ return existing.result;
3002
+ }
3003
+ await writeJsonDurableWithinRoot(
3004
+ runDir,
3005
+ knowledgeActivationResultPath(persistence.activation.digest),
3006
+ record
3007
+ );
3008
+ const stored = await loadKnowledgeActivationRecord(
3009
+ runDir,
3010
+ candidate,
3011
+ persistence.activation,
3012
+ target,
3013
+ persistence.identity
3014
+ );
3015
+ if (!stored || canonicalJson(stored) !== canonicalJson(record)) {
3016
+ throw new Error("knowledge activation result was not durably persisted");
3017
+ }
3018
+ return stored.result;
3019
+ }
3020
+ async function loadKnowledgeActivationRecord(runDir, candidate, activation, target, identity) {
3021
+ let raw;
3022
+ try {
3023
+ const file = await readRegularFileWithinRoot(
3024
+ runDir,
3025
+ knowledgeActivationResultPath(activation.digest)
3026
+ );
3027
+ raw = JSON.parse(file.bytes.toString("utf8"));
3028
+ } catch (error) {
3029
+ if (isMissingFile(error)) return null;
3030
+ throw error;
3031
+ }
3032
+ const record = knowledgeImprovementActivationRecordSchema.parse(raw);
3033
+ if (record.candidateId !== candidate.candidateId) {
3034
+ throw new Error("knowledge activation result belongs to another candidate");
3035
+ }
3036
+ const result = assertKnowledgeActivationResult(
3037
+ activation,
3038
+ candidate,
3039
+ target,
3040
+ identity,
3041
+ record.mutation,
3042
+ record.result
3043
+ );
3044
+ return immutableJsonValue({ ...record, result });
3045
+ }
3046
+ function assertKnowledgeActivationResult(activation, candidate, target, identity, mutationInput, resultInput, attemptedAt) {
3047
+ assertKnowledgeActivationAuthority(activation, candidate, target, identity);
3048
+ const mutation = knowledgeImprovementMutationReceiptSchema.parse(mutationInput);
3049
+ const result = verifyCanonicalKnowledgeActivationResult(resultInput);
3050
+ 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)) {
3051
+ throw new Error("knowledge activation result does not bind its authorized mutation");
3052
+ }
3053
+ const sourceDigest = prefixedKnowledgeDigest(sourceKnowledgeHash(candidate, target));
3054
+ const desiredDigest = prefixedKnowledgeDigest(
3055
+ target === "candidate" ? candidate.candidateHash : candidate.baseHash
3056
+ );
3057
+ const beforeDigest = prefixedKnowledgeDigest(mutation.beforeHash);
3058
+ const afterDigest = prefixedKnowledgeDigest(mutation.afterHash);
3059
+ const outcome = result.outcome;
3060
+ if (mutation.changed) {
3061
+ 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) {
3062
+ throw new Error("knowledge activation result does not prove the applied transaction");
3063
+ }
3064
+ return result;
3065
+ }
3066
+ const expectedStatus = afterDigest === desiredDigest ? "already-applied" : "conflict";
3067
+ 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) {
3068
+ throw new Error("knowledge activation result does not prove the observed target state");
3069
+ }
3070
+ return result;
3071
+ }
3072
+ function knowledgeActivationResultPath(digest) {
3073
+ const parsed = sha256DigestSchema.parse(digest);
3074
+ return `activation-results/${parsed.slice("sha256:".length)}.json`;
3075
+ }
3076
+ function prefixedKnowledgeDigest(hash) {
3077
+ return sha256DigestSchema.parse(`sha256:${digestSchema.parse(hash)}`);
3078
+ }
3079
+ function immutableJsonValue(value) {
3080
+ if (value === null || typeof value !== "object") return value;
3081
+ for (const child of Object.values(value)) immutableJsonValue(child);
3082
+ return Object.freeze(value);
3083
+ }
2735
3084
  function candidateRefFor(runId, state, candidate) {
3085
+ if (candidate.status !== "candidate-ready" && candidate.status !== "promoted") {
3086
+ throw new Error(`knowledge candidate '${candidate.candidateId}' is not ready`);
3087
+ }
3088
+ return candidateIdentityFor(runId, state, candidate);
3089
+ }
3090
+ function candidateIdentityFor(runId, state, candidate) {
2736
3091
  if (!candidate.candidateHash) {
2737
3092
  throw new Error(`knowledge candidate '${candidate.candidateId}' has no content hash`);
2738
3093
  }
@@ -2742,9 +3097,6 @@ function candidateRefFor(runId, state, candidate) {
2742
3097
  if (!candidate.promotionPlanHash) {
2743
3098
  throw new Error(`knowledge candidate '${candidate.candidateId}' has no promotion plan hash`);
2744
3099
  }
2745
- if (candidate.status !== "candidate-ready" && candidate.status !== "promoted") {
2746
- throw new Error(`knowledge candidate '${candidate.candidateId}' is not ready`);
2747
- }
2748
3100
  return Object.freeze({
2749
3101
  kind: "knowledge-improvement-candidate",
2750
3102
  runId,
@@ -2784,17 +3136,17 @@ async function withMeasuredCandidateSnapshot(liveRoot, runDir, state, candidateR
2784
3136
  return result;
2785
3137
  });
2786
3138
  }
2787
- async function withIsolatedCandidateCopy(sourceRoot, expectedHash, use) {
2788
- const isolationRoot = await mkdtemp(join(tmpdir(), "agent-knowledge-candidate-"));
2789
- const candidateRoot = join(isolationRoot, "candidate");
3139
+ async function withIsolatedKnowledgeCopy(sourceRoot, expectedHash, target, use) {
3140
+ const isolationRoot = await mkdtemp(join(tmpdir(), "agent-knowledge-snapshot-"));
3141
+ const snapshotRoot = join(isolationRoot, "snapshot");
2790
3142
  try {
2791
- await copyKnowledgeWorkspace(sourceRoot, candidateRoot);
2792
- if (await hashKnowledgeBase(candidateRoot) !== expectedHash) {
2793
- throw new Error("isolated knowledge candidate does not match its approved content");
3143
+ await copyKnowledgeWorkspace(sourceRoot, snapshotRoot);
3144
+ if (await hashKnowledgeBase(snapshotRoot) !== expectedHash) {
3145
+ throw new Error(`isolated knowledge ${target} does not match its measured content`);
2794
3146
  }
2795
- const result = await use(candidateRoot);
2796
- if (await hashKnowledgeBase(candidateRoot) !== expectedHash) {
2797
- throw new Error("knowledge candidate snapshot changed during use");
3147
+ const result = await use(snapshotRoot);
3148
+ if (await hashKnowledgeBase(snapshotRoot) !== expectedHash) {
3149
+ throw new Error(`knowledge ${target} snapshot changed during use`);
2798
3150
  }
2799
3151
  return result;
2800
3152
  } finally {
@@ -3534,10 +3886,10 @@ function fromAgentCandidateKnowledgeRef(candidate) {
3534
3886
  });
3535
3887
  }
3536
3888
  function prefixedDigest(value) {
3537
- return sha256DigestSchema.parse(`sha256:${value}`);
3889
+ return sha256DigestSchema2.parse(`sha256:${value}`);
3538
3890
  }
3539
3891
  function rawDigest(value) {
3540
- return sha256DigestSchema.parse(value).slice("sha256:".length);
3892
+ return sha256DigestSchema2.parse(value).slice("sha256:".length);
3541
3893
  }
3542
3894
 
3543
3895
  // src/changes.ts
@@ -5966,6 +6318,7 @@ export {
5966
6318
  layoutFor,
5967
6319
  lensDistribution,
5968
6320
  lintKnowledgeIndex,
6321
+ loadKnowledgeImprovementActivationResult,
5969
6322
  loadKnowledgeImprovementEvents,
5970
6323
  loadKnowledgeImprovementState,
5971
6324
  loadKnowledgePages,
@@ -6036,6 +6389,7 @@ export {
6036
6389
  validateKnowledgeIndex,
6037
6390
  withCitedClaim,
6038
6391
  withKnowledgeImprovementCandidate,
6392
+ withKnowledgeImprovementComparison,
6039
6393
  writeJson,
6040
6394
  writeKnowledgeIndex,
6041
6395
  writeSourceRegistry