@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/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 +50 -4
- package/dist/index.js +519 -131
- 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,24 +2376,34 @@ 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
|
});
|
|
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
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
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
|
-
|
|
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
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
2616
|
+
const assertOwned = () => {
|
|
2617
|
+
mutationLock.assertOwned();
|
|
2618
|
+
input.assertRunOwned();
|
|
2619
|
+
};
|
|
2620
|
+
assertOwned();
|
|
2531
2621
|
const transactionRoot = mutationLock.transactionRoot;
|
|
2532
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2720
|
+
target,
|
|
2721
|
+
reason,
|
|
2722
|
+
currentHash
|
|
2554
2723
|
);
|
|
2724
|
+
return activationResult2 ? { ...blocked, activationResult: activationResult2 } : blocked;
|
|
2555
2725
|
}
|
|
2556
|
-
if (currentHash !==
|
|
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
|
|
2564
|
-
|
|
2565
|
-
|
|
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
|
-
|
|
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(
|
|
2749
|
+
throw new Error(`knowledge ${action} plan unexpectedly contained no file changes`);
|
|
2579
2750
|
}
|
|
2751
|
+
transactionId = pending.transactionId;
|
|
2580
2752
|
try {
|
|
2581
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2624
|
-
input.
|
|
2625
|
-
|
|
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
|
-
|
|
2633
|
-
input.assertRunOwned();
|
|
2795
|
+
assertOwned();
|
|
2634
2796
|
} catch (ownershipError) {
|
|
2635
2797
|
throw new AggregateError(
|
|
2636
2798
|
[error, ownershipError],
|
|
2637
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2880
|
+
recoveryOwner,
|
|
2881
|
+
validate: (transaction) => assertCandidateTransitionTransaction(transaction, candidateRef, target),
|
|
2882
|
+
deferFinish: input.activation !== void 0
|
|
2693
2883
|
}
|
|
2694
2884
|
}
|
|
2695
2885
|
);
|
|
2696
2886
|
}
|
|
2697
|
-
|
|
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
|
|
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
|
|
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
|
|
2770
|
-
const isolationRoot = await mkdtemp(join(tmpdir(), "agent-knowledge-
|
|
2771
|
-
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");
|
|
2772
3142
|
try {
|
|
2773
|
-
await copyKnowledgeWorkspace(sourceRoot,
|
|
2774
|
-
if (await hashKnowledgeBase(
|
|
2775
|
-
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`);
|
|
2776
3146
|
}
|
|
2777
|
-
const result = await use(
|
|
2778
|
-
if (await hashKnowledgeBase(
|
|
2779
|
-
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`);
|
|
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
|
|
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
|
|
3247
|
-
|
|
3616
|
+
function knowledgeCandidateTransitionPurpose(candidate, target) {
|
|
3617
|
+
const action = target === "candidate" ? "promotion" : "restore";
|
|
3618
|
+
return `knowledge-${action}:${contentHash(candidate)}`;
|
|
3248
3619
|
}
|
|
3249
|
-
async function
|
|
3620
|
+
async function knowledgeFilePlanEntries(sourceRoot, targetRoot) {
|
|
3250
3621
|
const [before, after] = await Promise.all([
|
|
3251
|
-
knowledgeHashEntries(
|
|
3252
|
-
knowledgeHashEntries(
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
3286
|
-
const
|
|
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
|
|
3661
|
+
`knowledge candidate plan changed after approval: expected ${candidate.promotionPlanHash}, got ${actualPlanHash}`
|
|
3290
3662
|
);
|
|
3291
3663
|
}
|
|
3292
3664
|
}
|
|
3293
|
-
|
|
3294
|
-
|
|
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
|
|
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 !==
|
|
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
|
|
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
|
|
3889
|
+
return sha256DigestSchema2.parse(`sha256:${value}`);
|
|
3505
3890
|
}
|
|
3506
3891
|
function rawDigest(value) {
|
|
3507
|
-
return
|
|
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
|