@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/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 +426 -72
- 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,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
|
-
|
|
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(
|
|
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
|
-
|
|
2616
|
+
const assertOwned = () => {
|
|
2617
|
+
mutationLock.assertOwned();
|
|
2618
|
+
input.assertRunOwned();
|
|
2619
|
+
};
|
|
2620
|
+
assertOwned();
|
|
2545
2621
|
const transactionRoot = mutationLock.transactionRoot;
|
|
2546
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2880
|
+
recoveryOwner,
|
|
2881
|
+
validate: (transaction) => assertCandidateTransitionTransaction(transaction, candidateRef, target),
|
|
2882
|
+
deferFinish: input.activation !== void 0
|
|
2706
2883
|
}
|
|
2707
2884
|
}
|
|
2708
2885
|
);
|
|
2709
2886
|
}
|
|
2710
|
-
|
|
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
|
|
2788
|
-
const isolationRoot = await mkdtemp(join(tmpdir(), "agent-knowledge-
|
|
2789
|
-
const
|
|
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,
|
|
2792
|
-
if (await hashKnowledgeBase(
|
|
2793
|
-
throw new Error(
|
|
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(
|
|
2796
|
-
if (await hashKnowledgeBase(
|
|
2797
|
-
throw new Error(
|
|
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
|
|
3889
|
+
return sha256DigestSchema2.parse(`sha256:${value}`);
|
|
3538
3890
|
}
|
|
3539
3891
|
function rawDigest(value) {
|
|
3540
|
-
return
|
|
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
|