@evo-dev/core 0.0.1-alpha.19 → 0.0.1-alpha.20
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/config/index.js +59 -8
- package/dist/index.js +803 -230
- package/package.json +1 -1
- package/src/evolution/evidence/session-memory/retention.ts +359 -78
- package/src/evolution/evidence/session-memory/segment.ts +15 -1
- package/src/evolution/evidence/session-memory/storage.ts +37 -6
- package/src/evolution/evidence/session-memory/types.ts +5 -2
- package/src/evolution/evidence/session-memory/updater.ts +23 -15
- package/src/evolution/knowledge/changes.ts +6 -0
- package/src/evolution/knowledge/freshness.ts +17 -57
- package/src/evolution/knowledge/index.ts +204 -12
- package/src/evolution/knowledge/support.ts +135 -0
- package/src/hooks/index.ts +124 -20
|
@@ -198,6 +198,37 @@ export async function appendRawEvent(
|
|
|
198
198
|
return { lineNumber: existingLineCount + 1 };
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
export async function resetSessionRawEvents(path: string): Promise<void> {
|
|
202
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
203
|
+
await writeFile(path, "", { encoding: "utf8", mode: 0o600 });
|
|
204
|
+
await chmod(path, 0o600);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export async function rewriteSessionRawEvents(
|
|
208
|
+
path: string,
|
|
209
|
+
lines: string[],
|
|
210
|
+
expectedCurrent: string,
|
|
211
|
+
): Promise<boolean> {
|
|
212
|
+
await ensurePrivateSessionMemoryDirectory(dirname(path));
|
|
213
|
+
const temporaryPath = join(dirname(path), `.${randomUUID()}.tmp`);
|
|
214
|
+
try {
|
|
215
|
+
const handle = await open(temporaryPath, "wx", 0o600);
|
|
216
|
+
try {
|
|
217
|
+
await handle.writeFile(lines.length === 0 ? "" : `${lines.join("\n")}\n`, "utf8");
|
|
218
|
+
await handle.sync();
|
|
219
|
+
} finally {
|
|
220
|
+
await handle.close();
|
|
221
|
+
}
|
|
222
|
+
const current = await readFile(path, "utf8").catch(() => null);
|
|
223
|
+
if (current !== expectedCurrent) return false;
|
|
224
|
+
await rename(temporaryPath, path);
|
|
225
|
+
await chmod(path, 0o600);
|
|
226
|
+
return true;
|
|
227
|
+
} finally {
|
|
228
|
+
await rm(temporaryPath, { force: true }).catch(() => undefined);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
201
232
|
export async function readLineRange(
|
|
202
233
|
path: string,
|
|
203
234
|
fromLine: number,
|
|
@@ -401,7 +432,7 @@ export async function writeSessionEvidenceSegmentAtomic(input: {
|
|
|
401
432
|
homeDir: string;
|
|
402
433
|
segment: SessionEvidenceSegmentV1;
|
|
403
434
|
}): Promise<void> {
|
|
404
|
-
|
|
435
|
+
assertSessionEvidenceRetentionState(input.segment);
|
|
405
436
|
const paths = resolveSessionMemoryPaths({
|
|
406
437
|
homeDir: input.homeDir,
|
|
407
438
|
projectKey: input.segment.projectKey,
|
|
@@ -410,9 +441,9 @@ export async function writeSessionEvidenceSegmentAtomic(input: {
|
|
|
410
441
|
await writePrivateAtomicJson(paths.segmentPath(input.segment.id), input.segment);
|
|
411
442
|
}
|
|
412
443
|
|
|
413
|
-
function
|
|
414
|
-
if (segment.
|
|
415
|
-
throw new Error("Atomic Session Evidence rewrite
|
|
444
|
+
function assertSessionEvidenceRetentionState(segment: SessionEvidenceSegmentV1): void {
|
|
445
|
+
if (segment.retention === undefined) {
|
|
446
|
+
throw new Error("Atomic Session Evidence rewrite requires retention metadata.");
|
|
416
447
|
}
|
|
417
448
|
if (segment.retention.rawState === "available") {
|
|
418
449
|
if (
|
|
@@ -421,7 +452,7 @@ function assertHistoricalRetentionState(segment: SessionEvidenceSegmentV1): void
|
|
|
421
452
|
segment.rawExcerpt.sha256 !== sha256Hex(segment.rawExcerpt.content) ||
|
|
422
453
|
segment.retention.originalRawSha256 !== segment.rawExcerpt.sha256
|
|
423
454
|
) {
|
|
424
|
-
throw new Error("Available
|
|
455
|
+
throw new Error("Available Session Evidence raw state is invalid.");
|
|
425
456
|
}
|
|
426
457
|
return;
|
|
427
458
|
}
|
|
@@ -433,7 +464,7 @@ function assertHistoricalRetentionState(segment: SessionEvidenceSegmentV1): void
|
|
|
433
464
|
segment.retention.rawPurgedAt === null ||
|
|
434
465
|
segment.lifecycle.status !== "raw-expired"
|
|
435
466
|
) {
|
|
436
|
-
throw new Error("Purged
|
|
467
|
+
throw new Error("Purged Session Evidence raw state is invalid.");
|
|
437
468
|
}
|
|
438
469
|
}
|
|
439
470
|
|
|
@@ -104,7 +104,7 @@ export interface HistoricalImportOriginV1 {
|
|
|
104
104
|
recordCount: number;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
export interface
|
|
107
|
+
export interface SessionEvidenceRetentionV1 {
|
|
108
108
|
policyDays: number;
|
|
109
109
|
expiresAt: string;
|
|
110
110
|
rawState: "available" | "purged";
|
|
@@ -112,6 +112,8 @@ export interface HistoricalImportRetentionV1 {
|
|
|
112
112
|
originalRawSha256: string;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
export type HistoricalImportRetentionV1 = SessionEvidenceRetentionV1;
|
|
116
|
+
|
|
115
117
|
export interface HistoricalImportStoredRecordV1 {
|
|
116
118
|
schemaVersion: 1;
|
|
117
119
|
kind: "historical-import-record";
|
|
@@ -168,7 +170,7 @@ export interface SessionEvidenceSegmentV1 {
|
|
|
168
170
|
reason: SessionMemorySegmentReason;
|
|
169
171
|
strength: SessionMemorySignalStrength;
|
|
170
172
|
origin?: HistoricalImportOriginV1;
|
|
171
|
-
retention?:
|
|
173
|
+
retention?: SessionEvidenceRetentionV1;
|
|
172
174
|
source: {
|
|
173
175
|
traceRefId: string | null;
|
|
174
176
|
sourcePath: string | null;
|
|
@@ -233,6 +235,7 @@ export interface SessionMemoryRawEventV1 {
|
|
|
233
235
|
rawPayloadJson: string;
|
|
234
236
|
rawPayloadByteLength: number;
|
|
235
237
|
rawPayloadTruncated: boolean;
|
|
238
|
+
rawPayloadExpired?: boolean;
|
|
236
239
|
secretsDetected: boolean;
|
|
237
240
|
sensitivity?: SessionMemorySensitivity;
|
|
238
241
|
sensitivityReasons?: SessionMemorySensitivityReason[];
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
appendRawEvent,
|
|
22
22
|
readSessionCursor,
|
|
23
23
|
readSessionState,
|
|
24
|
+
resetSessionRawEvents,
|
|
24
25
|
writeJson,
|
|
25
26
|
writeSessionIndex,
|
|
26
27
|
} from "./storage.ts";
|
|
@@ -126,21 +127,24 @@ export async function updateSessionMemoryFromHook(
|
|
|
126
127
|
segmentPath = paths.segmentPath(segment.id);
|
|
127
128
|
await writeJson(segmentPath, segment);
|
|
128
129
|
await writeSessionIndex(paths, state, segment, now);
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
130
|
+
if (shouldQueueSegmentForEvolution(segment)) {
|
|
131
|
+
const trigger = await enqueueSegmentEvolutionTrigger({
|
|
132
|
+
homeDir: input.homeDir,
|
|
133
|
+
projectKey: segment.projectKey,
|
|
134
|
+
sessionKey: segment.sessionKey,
|
|
135
|
+
runId: segment.runId,
|
|
136
|
+
roleId: segment.roleId,
|
|
137
|
+
segmentId: segment.id,
|
|
138
|
+
segmentPath,
|
|
139
|
+
strength: segment.strength,
|
|
140
|
+
reason: segment.reason,
|
|
141
|
+
summary: segment.normalized.summary,
|
|
142
|
+
now,
|
|
143
|
+
});
|
|
144
|
+
queuedSegmentTriggerId = trigger.id;
|
|
145
|
+
}
|
|
146
|
+
await resetSessionRawEvents(paths.eventsPath);
|
|
147
|
+
cursor.lastCapturedLine = 0;
|
|
144
148
|
cursor.lastCapturedEventId = input.event.eventId;
|
|
145
149
|
cursor.updatedAt = now;
|
|
146
150
|
state.lastSegmentId = segment.id;
|
|
@@ -179,6 +183,10 @@ export async function updateSessionMemoryFromHook(
|
|
|
179
183
|
};
|
|
180
184
|
}
|
|
181
185
|
|
|
186
|
+
function shouldQueueSegmentForEvolution(segment: SessionEvidenceSegmentV1): boolean {
|
|
187
|
+
return segment.reason !== "session-memory-threshold" && segment.reason !== "session-memory-init";
|
|
188
|
+
}
|
|
189
|
+
|
|
182
190
|
function emptyResult(): SessionMemoryUpdateResult {
|
|
183
191
|
return {
|
|
184
192
|
state: null,
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
OkfKnowledgeLifecycleStatus,
|
|
5
5
|
OkfKnowledgePlanCandidate,
|
|
6
6
|
} from "./index.ts";
|
|
7
|
+
import type { OkfKnowledgeSupportRef } from "./support.ts";
|
|
7
8
|
|
|
8
9
|
export const OKF_KNOWLEDGE_RENDER_CONTRACT_VERSION = 2;
|
|
9
10
|
|
|
@@ -46,6 +47,7 @@ export interface OkfKnowledgeRuntimeProjectionV1 {
|
|
|
46
47
|
pathScopes: string[];
|
|
47
48
|
};
|
|
48
49
|
relatedConceptLinks: string[];
|
|
50
|
+
supportRef: OkfKnowledgeSupportRef | null;
|
|
49
51
|
lifecycle: {
|
|
50
52
|
status: OkfKnowledgeLifecycleStatus;
|
|
51
53
|
supersedes: string[];
|
|
@@ -97,6 +99,7 @@ export function createOkfKnowledgeRuntimeProjectionFromCandidate(
|
|
|
97
99
|
pathScopes: candidate.pathScopes,
|
|
98
100
|
},
|
|
99
101
|
relatedConceptLinks: candidate.relatedConceptLinks,
|
|
102
|
+
supportRef: candidate.supportRef ?? null,
|
|
100
103
|
lifecycle: {
|
|
101
104
|
status: candidate.decision === "revoke" ? "revoked" : "active",
|
|
102
105
|
supersedes: [],
|
|
@@ -139,6 +142,7 @@ export function createOkfKnowledgeRuntimeProjectionFromConcept(
|
|
|
139
142
|
relatedConceptLinks: readSectionList(sections, "Related Concepts").filter(
|
|
140
143
|
(item) => item !== "No related concepts yet.",
|
|
141
144
|
),
|
|
145
|
+
supportRef: concept.supportRef ?? null,
|
|
142
146
|
lifecycle: {
|
|
143
147
|
status: concept.lifecycle.status,
|
|
144
148
|
supersedes: concept.lifecycle.supersedes,
|
|
@@ -343,6 +347,7 @@ function normalizeRuntimeProjection(
|
|
|
343
347
|
pathScopes: normalizeArray(value.scopes.pathScopes),
|
|
344
348
|
},
|
|
345
349
|
relatedConceptLinks: normalizeArray(value.relatedConceptLinks),
|
|
350
|
+
supportRef: value.supportRef,
|
|
346
351
|
lifecycle: {
|
|
347
352
|
status: value.lifecycle.status,
|
|
348
353
|
supersedes: normalizeArray(value.lifecycle.supersedes),
|
|
@@ -375,6 +380,7 @@ function listChangedProjectionFields(
|
|
|
375
380
|
["scopes.workflowTags", base.scopes.workflowTags, candidate.scopes.workflowTags],
|
|
376
381
|
["scopes.pathScopes", base.scopes.pathScopes, candidate.scopes.pathScopes],
|
|
377
382
|
["relatedConceptLinks", base.relatedConceptLinks, candidate.relatedConceptLinks],
|
|
383
|
+
["supportRef", base.supportRef, candidate.supportRef],
|
|
378
384
|
["lifecycle", base.lifecycle, candidate.lifecycle],
|
|
379
385
|
];
|
|
380
386
|
return fields
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { OkfKnowledgeConcept, OkfKnowledgePlanCandidate } from "./index.ts";
|
|
2
|
+
import { isDirectKnowledgeSupport, resolveOkfKnowledgeRuntimeEligibility } from "./support.ts";
|
|
2
3
|
|
|
3
4
|
export type OkfKnowledgeFreshness = "verified" | "review-due" | "unknown" | "stale";
|
|
4
5
|
|
|
@@ -27,46 +28,22 @@ export function deriveOkfKnowledgeFreshness(input: {
|
|
|
27
28
|
repositoryFingerprintChanged?: boolean;
|
|
28
29
|
verifiedContradiction?: boolean;
|
|
29
30
|
}): OkfKnowledgeFreshnessResult {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
input.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
) {
|
|
44
|
-
return {
|
|
45
|
-
freshness: "stale",
|
|
46
|
-
reason:
|
|
47
|
-
input.verifiedContradiction === true
|
|
48
|
-
? "Verified evidence contradicts the active claim."
|
|
49
|
-
: "Lifecycle status or staleAfter excludes this concept from new runtime context.",
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
if (concept.verificationSnapshot === null) {
|
|
53
|
-
return {
|
|
54
|
-
freshness: "unknown",
|
|
55
|
-
reason: "No verifiable knowledge snapshot is stored for this legacy concept.",
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
if (input.repositoryFingerprintChanged === true || isDue(concept.lifecycle.reviewAfter, now)) {
|
|
59
|
-
return {
|
|
60
|
-
freshness: "review-due",
|
|
61
|
-
reason:
|
|
62
|
-
input.repositoryFingerprintChanged === true
|
|
63
|
-
? "A checked repository path changed after the last verification."
|
|
64
|
-
: `Review was due at ${concept.lifecycle.reviewAfter}.`,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
31
|
+
const eligibility = resolveOkfKnowledgeRuntimeEligibility({
|
|
32
|
+
reviewState: input.concept.reviewState,
|
|
33
|
+
lifecycle: input.concept.lifecycle,
|
|
34
|
+
supportRef: input.concept.supportRef ?? null,
|
|
35
|
+
now: input.now,
|
|
36
|
+
sourceStatus:
|
|
37
|
+
input.repositoryFingerprintChanged === true
|
|
38
|
+
? "changed"
|
|
39
|
+
: input.repositoryFingerprintChanged === false
|
|
40
|
+
? "current"
|
|
41
|
+
: undefined,
|
|
42
|
+
verifiedContradiction: input.verifiedContradiction,
|
|
43
|
+
});
|
|
67
44
|
return {
|
|
68
|
-
freshness:
|
|
69
|
-
reason:
|
|
45
|
+
freshness: eligibility.freshness,
|
|
46
|
+
reason: eligibility.reason,
|
|
70
47
|
};
|
|
71
48
|
}
|
|
72
49
|
|
|
@@ -75,10 +52,7 @@ export function createOkfKnowledgeVerificationSnapshot(input: {
|
|
|
75
52
|
verifiedAt: string;
|
|
76
53
|
projectKey: string;
|
|
77
54
|
}): OkfKnowledgeVerificationSnapshotV1 | null {
|
|
78
|
-
|
|
79
|
-
input.candidate.bodySections.verification.length > 0 ||
|
|
80
|
-
input.candidate.verificationNotApplicableReason !== undefined;
|
|
81
|
-
if (!hasVerification) return null;
|
|
55
|
+
if (!isDirectKnowledgeSupport(input.candidate.supportRef)) return null;
|
|
82
56
|
const verifiedAt = normalizeIso(input.verifiedAt);
|
|
83
57
|
if (verifiedAt === null) return null;
|
|
84
58
|
return {
|
|
@@ -89,20 +63,6 @@ export function createOkfKnowledgeVerificationSnapshot(input: {
|
|
|
89
63
|
};
|
|
90
64
|
}
|
|
91
65
|
|
|
92
|
-
function normalizeNow(value: string | Date | undefined): Date {
|
|
93
|
-
if (value instanceof Date && Number.isFinite(value.getTime())) return value;
|
|
94
|
-
if (typeof value === "string") {
|
|
95
|
-
const normalized = normalizeIso(value);
|
|
96
|
-
if (normalized !== null) return new Date(normalized);
|
|
97
|
-
}
|
|
98
|
-
return new Date();
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function isDue(value: string, now: Date): boolean {
|
|
102
|
-
const normalized = normalizeIso(value);
|
|
103
|
-
return normalized !== null && Date.parse(normalized) <= now.getTime();
|
|
104
|
-
}
|
|
105
|
-
|
|
106
66
|
function normalizeIso(value: string): string | null {
|
|
107
67
|
const time = Date.parse(value);
|
|
108
68
|
return Number.isFinite(time) ? new Date(time).toISOString() : null;
|