@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.
@@ -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
- assertHistoricalRetentionState(input.segment);
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 assertHistoricalRetentionState(segment: SessionEvidenceSegmentV1): void {
414
- if (segment.origin?.kind !== "historical-import" || segment.retention === undefined) {
415
- throw new Error("Atomic Session Evidence rewrite is limited to historical retention.");
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 historical Session Evidence raw state is invalid.");
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 historical Session Evidence raw state is invalid.");
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 HistoricalImportRetentionV1 {
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?: HistoricalImportRetentionV1;
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
- const trigger = await enqueueSegmentEvolutionTrigger({
130
- homeDir: input.homeDir,
131
- projectKey: segment.projectKey,
132
- sessionKey: segment.sessionKey,
133
- runId: segment.runId,
134
- roleId: segment.roleId,
135
- segmentId: segment.id,
136
- segmentPath,
137
- strength: segment.strength,
138
- reason: segment.reason,
139
- summary: segment.normalized.summary,
140
- now,
141
- });
142
- queuedSegmentTriggerId = trigger.id;
143
- cursor.lastCapturedLine = appended.lineNumber;
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 now = normalizeNow(input.now);
31
- const concept = input.concept;
32
- if (
33
- input.verifiedContradiction === true ||
34
- concept.lifecycle.status === "stale" ||
35
- concept.lifecycle.status === "deprecated" ||
36
- concept.lifecycle.status === "revoked" ||
37
- concept.lifecycle.status === "superseded" ||
38
- concept.reviewState === "stale" ||
39
- concept.reviewState === "deprecated" ||
40
- concept.reviewState === "revoked" ||
41
- concept.reviewState === "superseded" ||
42
- isDue(concept.lifecycle.staleAfter, now)
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: "verified",
69
- reason: `Verified at ${concept.verificationSnapshot.verifiedAt}.`,
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
- const hasVerification =
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;