@tangle-network/agent-knowledge 3.0.1 → 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,24 +2376,34 @@ 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
  });
2315
2393
  }
2316
2394
  async function promoteKnowledgeCandidate(options) {
2395
+ return transitionKnowledgeCandidate(options, "candidate");
2396
+ }
2397
+ async function restoreKnowledgeCandidateBaseline(options) {
2398
+ return transitionKnowledgeCandidate(options, "baseline");
2399
+ }
2400
+ async function transitionKnowledgeCandidate(options, target) {
2317
2401
  assertExactCandidatePlatform();
2318
2402
  const candidateRef = Object.freeze(
2319
2403
  KnowledgeImprovementCandidateRefSchema.parse(options.candidate)
2320
2404
  );
2321
2405
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
2406
+ const activation = options.activation ? resolveKnowledgeActivationPersistence(options.activation, candidateRef, target) : void 0;
2322
2407
  return withKnowledgeImprovementRun(options.root, candidateRef.runId, false, async (runDir) => {
2323
2408
  const lease = await acquireRunLease(runDir, {
2324
2409
  ownerId: options.ownerId ?? `pid-${process.pid}`,
@@ -2331,16 +2416,20 @@ async function promoteKnowledgeCandidate(options) {
2331
2416
  candidateRef.runId,
2332
2417
  runDir
2333
2418
  );
2334
- return await promoteReadyCandidate({
2335
- root: options.root,
2336
- runDir,
2337
- state,
2338
- candidateRef,
2339
- leaseTtlMs: options.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS,
2340
- assertRunOwned: lease.assertOwned,
2341
- now,
2342
- onState: options.onState
2343
- });
2419
+ return await applyKnowledgeCandidateTarget(
2420
+ {
2421
+ root: options.root,
2422
+ runDir,
2423
+ state,
2424
+ candidateRef,
2425
+ leaseTtlMs: options.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS,
2426
+ assertRunOwned: lease.assertOwned,
2427
+ now,
2428
+ onState: options.onState,
2429
+ activation
2430
+ },
2431
+ target
2432
+ );
2344
2433
  } finally {
2345
2434
  await lease.release();
2346
2435
  }
@@ -2396,29 +2485,23 @@ async function improveKnowledgeBaseInRun(options, runId, runDir, now) {
2396
2485
  if (state.status === "promoted" && !promotedCandidate) {
2397
2486
  throw new Error("promoted knowledge state has no promoted candidate");
2398
2487
  }
2399
- const resumablePromotion = state.candidates.find(
2400
- (candidate2) => (candidate2.status === "candidate-ready" || candidate2.status === "promoted") && candidate2.evidenceHash !== void 0 && candidate2.promotionPlanHash !== void 0
2401
- );
2402
- const resumableCandidateRef = resumablePromotion ? candidateRefFor(runId, state, resumablePromotion) : void 0;
2403
- await withKnowledgeMutation(options.root, () => void 0, {
2404
- resumeTransaction: resumableCandidateRef ? {
2405
- purpose: promotionTransactionPurpose(resumableCandidateRef),
2406
- validate: (transaction) => assertPromotionTransaction(transaction, resumableCandidateRef)
2407
- } : void 0
2408
- });
2488
+ await withKnowledgeMutation(options.root, () => void 0);
2409
2489
  await ensureBaselineSnapshot(runDir, options.root, state.baseHash);
2410
2490
  if (state.status === "promoted") {
2411
2491
  const promoted = promotedCandidate;
2412
- return await promoteReadyCandidate({
2413
- root: options.root,
2414
- runDir,
2415
- state,
2416
- candidateRef: candidateRefFor(runId, state, promoted),
2417
- leaseTtlMs: options.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS,
2418
- assertRunOwned: lease.assertOwned,
2419
- now,
2420
- onState: options.onState
2421
- });
2492
+ return await applyKnowledgeCandidateTarget(
2493
+ {
2494
+ root: options.root,
2495
+ runDir,
2496
+ state,
2497
+ candidateRef: candidateRefFor(runId, state, promoted),
2498
+ leaseTtlMs: options.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS,
2499
+ assertRunOwned: lease.assertOwned,
2500
+ now,
2501
+ onState: options.onState
2502
+ },
2503
+ "candidate"
2504
+ );
2422
2505
  }
2423
2506
  if (state.status === "blocked") {
2424
2507
  return { runId, state, promoted: false, blocked: true };
@@ -2516,93 +2599,176 @@ async function improveKnowledgeBaseInRun(options, runId, runDir, now) {
2516
2599
  await lease.release();
2517
2600
  }
2518
2601
  }
2519
- async function promoteReadyCandidate(input) {
2602
+ async function applyKnowledgeCandidateTarget(input, target) {
2520
2603
  const { candidateRef, runDir, state } = input;
2521
2604
  assertStateIdentity(input.root, candidateRef, state);
2522
2605
  const candidate = state.candidates.find((entry) => entry.candidateId === candidateRef.candidateId);
2523
- if (!candidate || canonicalJson(candidateRefFor(candidateRef.runId, state, candidate)) !== canonicalJson(candidateRef)) {
2606
+ if (!candidate || canonicalJson(candidateIdentityFor(candidateRef.runId, state, candidate)) !== canonicalJson(candidateRef)) {
2524
2607
  throw new Error("knowledge candidate approval does not match the measured candidate");
2525
2608
  }
2526
- const purpose = promotionTransactionPurpose(candidateRef);
2609
+ const action = target === "candidate" ? "promotion" : "restore";
2610
+ const desiredHash = target === "candidate" ? candidateRef.candidateHash : candidateRef.baseHash;
2611
+ const purpose = knowledgeCandidateTransitionPurpose(candidateRef, target);
2612
+ const recoveryOwner = input.activation ? `knowledge-improvement-activation:${input.activation.activation.digest}` : "knowledge-improvement-candidate-transition";
2527
2613
  return withKnowledgeMutation(
2528
2614
  input.root,
2529
2615
  async (mutationLock) => {
2530
- input.assertRunOwned();
2616
+ const assertOwned = () => {
2617
+ mutationLock.assertOwned();
2618
+ input.assertRunOwned();
2619
+ };
2620
+ assertOwned();
2531
2621
  const transactionRoot = mutationLock.transactionRoot;
2532
- 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;
2533
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
+ }
2534
2685
  if (state.status === "promoted" && state.promotedCandidateId !== candidate.candidateId) {
2535
2686
  throw new Error(
2536
2687
  `knowledge run already promoted '${state.promotedCandidateId ?? "unknown"}'`
2537
2688
  );
2538
2689
  }
2539
- if (state.status === "promoted" && currentHash !== candidateRef.candidateHash) {
2690
+ if (target === "candidate" && state.status === "promoted" && currentHash !== candidateRef.candidateHash) {
2540
2691
  throw new Error(
2541
2692
  `promoted knowledge base changed: expected ${candidateRef.candidateHash}, got ${currentHash}`
2542
2693
  );
2543
2694
  }
2544
2695
  if (state.status !== "promoted" && state.status !== "candidate-ready") {
2545
2696
  throw new Error(
2546
- `knowledge candidate is not ready for promotion: run=${state.status}, candidate=${candidate.status}`
2697
+ `knowledge candidate is not ready for ${action}: run=${state.status}, candidate=${candidate.status}`
2547
2698
  );
2548
2699
  }
2549
2700
  if (currentHash !== state.baseHash && currentHash !== candidateRef.candidateHash) {
2550
- return await blockPromotion(
2701
+ const reason = target === "candidate" ? `base changed before promotion: expected ${state.baseHash}, got ${currentHash}` : `knowledge changed before restore: expected ${candidateRef.candidateHash}, got ${currentHash}`;
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(
2551
2718
  input,
2552
2719
  candidate,
2553
- `base changed before promotion: expected ${state.baseHash}, got ${currentHash}`
2720
+ target,
2721
+ reason,
2722
+ currentHash
2554
2723
  );
2724
+ return activationResult2 ? { ...blocked, activationResult: activationResult2 } : blocked;
2555
2725
  }
2556
- if (currentHash !== candidateRef.candidateHash) {
2726
+ if (currentHash !== desiredHash && !pending) {
2557
2727
  pending = await withMeasuredCandidateSnapshot(
2558
2728
  input.root,
2559
2729
  runDir,
2560
2730
  state,
2561
2731
  candidateRef,
2562
2732
  (resolved) => withBaselineSnapshot(runDir, state.baseHash, async (baselineRoot) => {
2563
- const plan = await promotionPlanEntries(baselineRoot, resolved.root);
2564
- if (knowledgeFileTransactionPlanHash(plan) !== candidateRef.promotionPlanHash) {
2565
- throw new Error("knowledge candidate promotion plan changed after approval");
2566
- }
2733
+ const sourceRoot = target === "candidate" ? baselineRoot : resolved.root;
2734
+ const targetRoot = target === "candidate" ? resolved.root : baselineRoot;
2735
+ const plan = await knowledgeFilePlanEntries(sourceRoot, targetRoot);
2736
+ assertCandidateTransitionPlan(plan, candidateRef, target);
2567
2737
  return prepareKnowledgeFileTransaction({
2568
2738
  root: input.root,
2569
2739
  transactionRoot,
2570
2740
  purpose,
2571
- mutations: await promotionMutations(resolved.root, plan),
2741
+ recoveryOwner,
2742
+ mutations: await knowledgePlanMutations(targetRoot, plan),
2572
2743
  includeUnchanged: true,
2573
2744
  now: input.now
2574
2745
  });
2575
2746
  })
2576
2747
  );
2577
2748
  if (!pending) {
2578
- throw new Error("knowledge promotion plan unexpectedly contained no file changes");
2749
+ throw new Error(`knowledge ${action} plan unexpectedly contained no file changes`);
2579
2750
  }
2751
+ transactionId = pending.transactionId;
2580
2752
  try {
2581
- assertPromotionTransaction(pending, candidateRef);
2753
+ assertCandidateTransitionTransaction(pending, candidateRef, target);
2582
2754
  } catch (error) {
2583
2755
  try {
2584
2756
  await rollbackKnowledgeFileTransaction({
2585
2757
  root: input.root,
2586
2758
  transactionRoot,
2587
2759
  transaction: pending,
2588
- beforeCommit() {
2589
- mutationLock.assertOwned();
2590
- input.assertRunOwned();
2591
- }
2760
+ beforeCommit: assertOwned
2592
2761
  });
2593
2762
  await finishKnowledgeFileTransaction({
2594
2763
  root: input.root,
2595
2764
  transactionRoot,
2596
2765
  transaction: pending,
2597
- assertOwned() {
2598
- mutationLock.assertOwned();
2599
- input.assertRunOwned();
2600
- }
2766
+ assertOwned
2601
2767
  });
2602
2768
  } catch (cleanupError) {
2603
2769
  throw new AggregateError(
2604
2770
  [error, cleanupError],
2605
- "invalid knowledge promotion transaction could not be removed"
2771
+ `invalid knowledge ${action} transaction could not be removed`
2606
2772
  );
2607
2773
  }
2608
2774
  throw error;
@@ -2614,27 +2780,23 @@ async function promoteReadyCandidate(input) {
2614
2780
  root: input.root,
2615
2781
  transactionRoot,
2616
2782
  transaction: pending,
2617
- beforeCommit() {
2618
- mutationLock.assertOwned();
2619
- input.assertRunOwned();
2620
- }
2783
+ beforeCommit: assertOwned
2621
2784
  });
2622
2785
  }
2623
- mutationLock.assertOwned();
2624
- input.assertRunOwned();
2625
- if (await hashKnowledgeBase(input.root) !== candidateRef.candidateHash) {
2626
- throw new Error("promoted knowledge content does not match the approved candidate");
2786
+ assertOwned();
2787
+ if (await hashKnowledgeBase(input.root) !== desiredHash) {
2788
+ throw new Error(`knowledge ${action} content does not match the approved target`);
2627
2789
  }
2628
2790
  await writeKnowledgeIndex(input.root);
2629
2791
  } catch (error) {
2630
2792
  if (!pending) throw error;
2793
+ if (recovered && input.activation) throw error;
2631
2794
  try {
2632
- mutationLock.assertOwned();
2633
- input.assertRunOwned();
2795
+ assertOwned();
2634
2796
  } catch (ownershipError) {
2635
2797
  throw new AggregateError(
2636
2798
  [error, ownershipError],
2637
- "knowledge promotion lost its lock and left the transaction pending"
2799
+ `knowledge ${action} lost its lock and left the transaction pending`
2638
2800
  );
2639
2801
  }
2640
2802
  try {
@@ -2642,79 +2804,290 @@ async function promoteReadyCandidate(input) {
2642
2804
  root: input.root,
2643
2805
  transactionRoot,
2644
2806
  transaction: pending,
2645
- beforeCommit() {
2646
- mutationLock.assertOwned();
2647
- input.assertRunOwned();
2648
- }
2807
+ beforeCommit: assertOwned
2649
2808
  });
2650
2809
  await finishKnowledgeFileTransaction({
2651
2810
  root: input.root,
2652
2811
  transactionRoot,
2653
2812
  transaction: pending,
2654
- assertOwned() {
2655
- mutationLock.assertOwned();
2656
- input.assertRunOwned();
2657
- }
2813
+ assertOwned
2658
2814
  });
2659
2815
  await writeKnowledgeIndex(input.root);
2660
2816
  } catch (rollbackError) {
2661
2817
  throw new AggregateError(
2662
2818
  [error, rollbackError],
2663
- "knowledge promotion failed and could not restore the previous files"
2819
+ `knowledge ${action} failed and could not restore the previous files`
2664
2820
  );
2665
2821
  }
2666
2822
  throw error;
2667
2823
  }
2668
- candidate.status = "promoted";
2824
+ candidate.status = target === "candidate" ? "promoted" : "candidate-ready";
2669
2825
  candidate.updatedAt = input.now().toISOString();
2670
- state.status = "promoted";
2671
- state.promotedCandidateId = candidate.candidateId;
2826
+ state.status = candidate.status;
2827
+ if (target === "candidate") state.promotedCandidateId = candidate.candidateId;
2828
+ else delete state.promotedCandidateId;
2829
+ delete state.blockedReason;
2672
2830
  state.updatedAt = input.now().toISOString();
2673
2831
  await saveState(runDir, state, input.onState);
2674
- await ensurePromotionEvent(runDir, candidateRef);
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
+ }
2675
2855
  if (pending) {
2676
2856
  await finishKnowledgeFileTransaction({
2677
2857
  root: input.root,
2678
2858
  transactionRoot,
2679
2859
  transaction: pending,
2680
- assertOwned() {
2681
- mutationLock.assertOwned();
2682
- input.assertRunOwned();
2683
- }
2860
+ assertOwned
2684
2861
  });
2685
2862
  }
2686
- return promotionResult(input, candidate, true, 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
+ );
2687
2875
  },
2688
2876
  {
2689
2877
  staleMs: input.leaseTtlMs,
2690
2878
  resumeTransaction: {
2691
2879
  purpose,
2692
- validate: (transaction) => assertPromotionTransaction(transaction, candidateRef)
2880
+ recoveryOwner,
2881
+ validate: (transaction) => assertCandidateTransitionTransaction(transaction, candidateRef, target),
2882
+ deferFinish: input.activation !== void 0
2693
2883
  }
2694
2884
  }
2695
2885
  );
2696
2886
  }
2697
- async function blockPromotion(input, candidate, 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) {
2891
+ if (input.state.status === "promoted") {
2892
+ candidate.status = "blocked";
2893
+ candidate.updatedAt = input.now().toISOString();
2894
+ delete input.state.promotedCandidateId;
2895
+ }
2698
2896
  await blockRun(input.runDir, input.state, reason, input.onState, input.now);
2699
2897
  await appendLedger(input.runDir, {
2700
- type: "promotion.blocked",
2898
+ type: target === "candidate" ? "promotion.blocked" : "restore.blocked",
2701
2899
  runId: input.candidateRef.runId,
2702
2900
  candidateId: candidate.candidateId,
2703
2901
  reason
2704
2902
  });
2705
- return promotionResult(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
+ });
2706
2911
  }
2707
- function promotionResult(input, candidate, promoted, blocked) {
2912
+ function candidateTransitionResult(input, candidate, promoted, blocked, mutation, activationResult) {
2708
2913
  return {
2709
2914
  runId: input.candidateRef.runId,
2710
2915
  state: input.state,
2711
2916
  candidate,
2712
2917
  ...input.lifecycle ? { lifecycle: input.lifecycle } : {},
2918
+ mutation,
2919
+ ...activationResult ? { activationResult } : {},
2713
2920
  promoted,
2714
2921
  blocked
2715
2922
  };
2716
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
+ }
2717
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) {
2718
3091
  if (!candidate.candidateHash) {
2719
3092
  throw new Error(`knowledge candidate '${candidate.candidateId}' has no content hash`);
2720
3093
  }
@@ -2724,9 +3097,6 @@ function candidateRefFor(runId, state, candidate) {
2724
3097
  if (!candidate.promotionPlanHash) {
2725
3098
  throw new Error(`knowledge candidate '${candidate.candidateId}' has no promotion plan hash`);
2726
3099
  }
2727
- if (candidate.status !== "candidate-ready" && candidate.status !== "promoted") {
2728
- throw new Error(`knowledge candidate '${candidate.candidateId}' is not ready`);
2729
- }
2730
3100
  return Object.freeze({
2731
3101
  kind: "knowledge-improvement-candidate",
2732
3102
  runId,
@@ -2766,17 +3136,17 @@ async function withMeasuredCandidateSnapshot(liveRoot, runDir, state, candidateR
2766
3136
  return result;
2767
3137
  });
2768
3138
  }
2769
- async function withIsolatedCandidateCopy(sourceRoot, expectedHash, use) {
2770
- const isolationRoot = await mkdtemp(join(tmpdir(), "agent-knowledge-candidate-"));
2771
- 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");
2772
3142
  try {
2773
- await copyKnowledgeWorkspace(sourceRoot, candidateRoot);
2774
- if (await hashKnowledgeBase(candidateRoot) !== expectedHash) {
2775
- 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`);
2776
3146
  }
2777
- const result = await use(candidateRoot);
2778
- if (await hashKnowledgeBase(candidateRoot) !== expectedHash) {
2779
- 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`);
2780
3150
  }
2781
3151
  return result;
2782
3152
  } finally {
@@ -3033,7 +3403,7 @@ async function evaluateCandidate(runDir, state, candidate, snapshot, lifecycle,
3033
3403
  }
3034
3404
  candidate.candidateHash = candidateHash;
3035
3405
  candidate.promotionPlanHash = knowledgeFileTransactionPlanHash(
3036
- await promotionPlanEntries(baselineRoot, snapshot.root)
3406
+ await knowledgeFilePlanEntries(baselineRoot, snapshot.root)
3037
3407
  );
3038
3408
  const evidence = KnowledgeImprovementEvidenceSchema.parse(
3039
3409
  JSON.parse(
@@ -3243,13 +3613,14 @@ async function copyKnowledgeWorkspace(sourceRoot, targetRoot) {
3243
3613
  );
3244
3614
  await writeKnowledgeIndex(targetRoot);
3245
3615
  }
3246
- function promotionTransactionPurpose(candidate) {
3247
- return `knowledge-promotion:${contentHash(candidate)}`;
3616
+ function knowledgeCandidateTransitionPurpose(candidate, target) {
3617
+ const action = target === "candidate" ? "promotion" : "restore";
3618
+ return `knowledge-${action}:${contentHash(candidate)}`;
3248
3619
  }
3249
- async function promotionPlanEntries(baselineRoot, candidateRoot) {
3620
+ async function knowledgeFilePlanEntries(sourceRoot, targetRoot) {
3250
3621
  const [before, after] = await Promise.all([
3251
- knowledgeHashEntries(baselineRoot),
3252
- knowledgeHashEntries(candidateRoot)
3622
+ knowledgeHashEntries(sourceRoot),
3623
+ knowledgeHashEntries(targetRoot)
3253
3624
  ]);
3254
3625
  const beforeByPath = new Map(before.map((entry) => [entry.path, entry]));
3255
3626
  const afterByPath = new Map(after.map((entry) => [entry.path, entry]));
@@ -3269,31 +3640,44 @@ async function promotionPlanEntries(baselineRoot, candidateRoot) {
3269
3640
  };
3270
3641
  });
3271
3642
  }
3272
- async function promotionMutations(candidateRoot, plan) {
3643
+ async function knowledgePlanMutations(targetRoot, plan) {
3273
3644
  return Promise.all(
3274
3645
  plan.map(async (entry) => {
3275
3646
  if (entry.afterHash === null) return { path: entry.path, content: null };
3276
- const file = await readRegularFileWithinRoot(candidateRoot, entry.path);
3647
+ const file = await readRegularFileWithinRoot(targetRoot, entry.path);
3277
3648
  const actualHash = createHash("sha256").update(file.bytes).digest("hex");
3278
3649
  if (actualHash !== entry.afterHash || file.mode !== entry.afterMode) {
3279
- throw new Error(`knowledge candidate file changed before promotion: ${entry.path}`);
3650
+ throw new Error(`knowledge target file changed before activation: ${entry.path}`);
3280
3651
  }
3281
3652
  return { path: entry.path, content: file.bytes, mode: file.mode };
3282
3653
  })
3283
3654
  );
3284
3655
  }
3285
- function assertPromotionTransaction(transaction, candidate) {
3286
- const actualPlanHash = knowledgeFileTransactionPlanHash(transaction.entries);
3656
+ function assertCandidateTransitionPlan(plan, candidate, target) {
3657
+ const approvedDirection = target === "candidate" ? plan : reverseKnowledgeFilePlan(plan);
3658
+ const actualPlanHash = knowledgeFileTransactionPlanHash(approvedDirection);
3287
3659
  if (actualPlanHash !== candidate.promotionPlanHash) {
3288
3660
  throw new Error(
3289
- `knowledge promotion plan changed after approval: expected ${candidate.promotionPlanHash}, got ${actualPlanHash}`
3661
+ `knowledge candidate plan changed after approval: expected ${candidate.promotionPlanHash}, got ${actualPlanHash}`
3290
3662
  );
3291
3663
  }
3292
3664
  }
3293
- async function ensurePromotionEvent(runDir, candidateRef) {
3294
- if (await hasPromotionEvent(runDir, candidateRef)) return;
3665
+ function assertCandidateTransitionTransaction(transaction, candidate, target) {
3666
+ assertCandidateTransitionPlan(transaction.entries, candidate, target);
3667
+ }
3668
+ function reverseKnowledgeFilePlan(plan) {
3669
+ return plan.map((entry) => ({
3670
+ path: entry.path,
3671
+ beforeHash: entry.afterHash,
3672
+ afterHash: entry.beforeHash,
3673
+ ...entry.afterMode === void 0 ? {} : { beforeMode: entry.afterMode },
3674
+ ...entry.beforeMode === void 0 ? {} : { afterMode: entry.beforeMode }
3675
+ }));
3676
+ }
3677
+ async function ensureCandidateTransitionEvent(runDir, candidateRef, target) {
3678
+ if (await hasCandidateTransitionEvent(runDir, candidateRef, target)) return;
3295
3679
  await appendLedger(runDir, {
3296
- type: "candidate.promoted",
3680
+ type: target === "candidate" ? "candidate.promoted" : "candidate.restored",
3297
3681
  runId: candidateRef.runId,
3298
3682
  candidateId: candidateRef.candidateId,
3299
3683
  candidateHash: candidateRef.candidateHash,
@@ -3301,12 +3685,13 @@ async function ensurePromotionEvent(runDir, candidateRef) {
3301
3685
  promotionPlanHash: candidateRef.promotionPlanHash
3302
3686
  });
3303
3687
  }
3304
- async function hasPromotionEvent(runDir, candidateRef) {
3688
+ async function hasCandidateTransitionEvent(runDir, candidateRef, target) {
3689
+ const eventType = target === "candidate" ? "candidate.promoted" : "candidate.restored";
3305
3690
  let matched = false;
3306
3691
  for (const row of await loadKnowledgeImprovementEventsFromRun(runDir)) {
3307
- if (row.type !== "candidate.promoted" || row.candidateId !== candidateRef.candidateId) continue;
3692
+ if (row.type !== eventType || row.candidateId !== candidateRef.candidateId) continue;
3308
3693
  if (row.runId !== candidateRef.runId || row.candidateHash !== candidateRef.candidateHash || row.evidenceHash !== candidateRef.evidenceHash || row.promotionPlanHash !== candidateRef.promotionPlanHash) {
3309
- throw new Error("persisted knowledge promotion event conflicts with the approved candidate");
3694
+ throw new Error("persisted knowledge activation event conflicts with the approved candidate");
3310
3695
  }
3311
3696
  matched = true;
3312
3697
  }
@@ -3501,10 +3886,10 @@ function fromAgentCandidateKnowledgeRef(candidate) {
3501
3886
  });
3502
3887
  }
3503
3888
  function prefixedDigest(value) {
3504
- return sha256DigestSchema.parse(`sha256:${value}`);
3889
+ return sha256DigestSchema2.parse(`sha256:${value}`);
3505
3890
  }
3506
3891
  function rawDigest(value) {
3507
- return sha256DigestSchema.parse(value).slice("sha256:".length);
3892
+ return sha256DigestSchema2.parse(value).slice("sha256:".length);
3508
3893
  }
3509
3894
 
3510
3895
  // src/changes.ts
@@ -5933,6 +6318,7 @@ export {
5933
6318
  layoutFor,
5934
6319
  lensDistribution,
5935
6320
  lintKnowledgeIndex,
6321
+ loadKnowledgeImprovementActivationResult,
5936
6322
  loadKnowledgeImprovementEvents,
5937
6323
  loadKnowledgeImprovementState,
5938
6324
  loadKnowledgePages,
@@ -5961,6 +6347,7 @@ export {
5961
6347
  resetRetrievalHoldoutRegistry,
5962
6348
  respondToIndustryMemoryBenchmarkSmokeCase,
5963
6349
  respondToIndustryRagBenchmarkSmokeCase,
6350
+ restoreKnowledgeCandidateBaseline,
5964
6351
  retrievalConfigFromSurface,
5965
6352
  retrievalConfigSurface,
5966
6353
  retrievalHoldoutConfigHash,
@@ -6002,6 +6389,7 @@ export {
6002
6389
  validateKnowledgeIndex,
6003
6390
  withCitedClaim,
6004
6391
  withKnowledgeImprovementCandidate,
6392
+ withKnowledgeImprovementComparison,
6005
6393
  writeJson,
6006
6394
  writeKnowledgeIndex,
6007
6395
  writeSourceRegistry