@lmzhen/dsh-evolution-activity 0.3.81 → 0.3.82

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/lib/index.js CHANGED
@@ -69,24 +69,57 @@ function parseActivityContent(raw) {
69
69
  }
70
70
  }
71
71
  /**
72
+ * H-06: the read barrier over the sidecar, with the corruption verdict.
73
+ *
74
+ * @param root - the evolution state root (the sidecar lives under it).
75
+ * @param io - the IO provider to read through.
76
+ * @returns the parsed records and whether the bytes were readable.
77
+ */
78
+ async function loadActivityState(root, io) {
79
+ const raw = await io.readText(activityFile(root));
80
+ return {
81
+ records: parseActivityContent(raw),
82
+ corrupt: isCorruptActivity(raw)
83
+ };
84
+ }
85
+ /**
72
86
  * H-06: the read barrier over the sidecar. V24-13 (v24): `loadActivity` now
73
87
  * HAS a production consumer — `evolution-replay` backfills its `/evolution
74
88
  * replay` leaderboard from this store at mount (the two packages'
75
89
  * "persistence is the activity store's job" contract is actually wired
76
90
  * through this call). The single-writer rule is unchanged: `apply()`'s
77
- * transact listener remains the only WRITE path.
91
+ * transact listener remains the only WRITE path. Consumers that must not read
92
+ * an unreadable sidecar as "no history" use {@link loadActivityState}.
78
93
  */
79
94
  async function loadActivity(root, io) {
80
- return parseActivityContent(await io.readText(activityFile(root)));
95
+ return (await loadActivityState(root, io)).records;
81
96
  }
82
97
  /** True when bytes exist but are not a readable activity envelope: unparsable
83
98
  * JSON, or a missing `items` array (a scalar/array/other-shaped file). A missing
84
99
  * file (null) is NOT corruption — it is a first write. */
100
+ /**
101
+ * v43 audit (H-3 / J-6 / FLOW6-6): an UNSUPPORTED version is corrupt for this
102
+ * writer. The read side has always ignored `version` and taken `items` as-is,
103
+ * so a future-version sidecar used to fold fine and then be rewritten as
104
+ * `version: ACTIVITY_FILE_VERSION` on the next append — a silent downgrade that
105
+ * destroyed whatever the newer format carried. This guard is the write side's
106
+ * half of the pair: unknown version ⇒ the original bytes are quarantined (the
107
+ * existing `.corrupt` path) before a current-version file replaces them, which
108
+ * is the posture `evolution-events` already takes for the same shape.
109
+ * Residual, closed by FLOW6-6: `parseActivityContent` still answers `[]` for
110
+ * such a file, but {@link loadActivityState} now carries the verdict beside the
111
+ * records, so a read-only consumer can distinguish "newer format" from "no
112
+ * history" instead of presenting a partial view as the recorded truth.
113
+ * @internal Exported for this package's own tests (siblings `parseActivityContent`
114
+ * and `serializeActivity` are exported for the same reason).
115
+ */
85
116
  function isCorruptActivity(raw) {
86
117
  if (raw === null) return false;
87
118
  try {
88
119
  const parsed = JSON.parse(raw);
89
- return typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.items);
120
+ if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.items)) return true;
121
+ const version = parsed.version;
122
+ return version !== void 0 && version !== 2;
90
123
  } catch {
91
124
  return true;
92
125
  }
@@ -128,4 +161,4 @@ function apply(ctx, rawConfig = {}) {
128
161
  });
129
162
  }
130
163
  //#endregion
131
- export { ACTIVITY_FILE_VERSION, Config, DEFAULT_MAX_ITEMS, activityFile, apply, applyActivityEvent, loadActivity, name, parseActivityContent, serializeActivity };
164
+ export { ACTIVITY_FILE_VERSION, Config, DEFAULT_MAX_ITEMS, activityFile, apply, applyActivityEvent, isCorruptActivity, loadActivity, loadActivityState, name, parseActivityContent, serializeActivity };
@@ -60,7 +60,58 @@ export declare function parseActivityContent(raw: string | null): EvolutionActiv
60
60
  * through this call). The single-writer rule is unchanged: `apply()`'s
61
61
  * transact listener remains the only WRITE path.
62
62
  */
63
+ /**
64
+ * FLOW6-6 (v43, the read side of H-3): the sidecar's records PLUS whether the
65
+ * bytes could be read as THIS format. `parseActivityContent` answers `[]` for
66
+ * a future-version or unparsable file, so a read-only consumer could not tell
67
+ * "newer format" from "no history" and presented a partial/empty view as the
68
+ * recorded truth. The write side already quarantines those bytes
69
+ * ({@link isCorruptActivity}); this is the channel the readers were missing.
70
+ */
71
+ export interface ActivityLoad {
72
+ records: EvolutionActivityRecord[];
73
+ /** Bytes exist but are not a readable current-version envelope. A MISSING
74
+ * file is not corruption — it is a first write. */
75
+ corrupt: boolean;
76
+ }
77
+ /**
78
+ * H-06: the read barrier over the sidecar, with the corruption verdict.
79
+ *
80
+ * @param root - the evolution state root (the sidecar lives under it).
81
+ * @param io - the IO provider to read through.
82
+ * @returns the parsed records and whether the bytes were readable.
83
+ */
84
+ export declare function loadActivityState(root: string, io: EvolutionIoLike): Promise<ActivityLoad>;
85
+ /**
86
+ * H-06: the read barrier over the sidecar. V24-13 (v24): `loadActivity` now
87
+ * HAS a production consumer — `evolution-replay` backfills its `/evolution
88
+ * replay` leaderboard from this store at mount (the two packages'
89
+ * "persistence is the activity store's job" contract is actually wired
90
+ * through this call). The single-writer rule is unchanged: `apply()`'s
91
+ * transact listener remains the only WRITE path. Consumers that must not read
92
+ * an unreadable sidecar as "no history" use {@link loadActivityState}.
93
+ */
63
94
  export declare function loadActivity(root: string, io: EvolutionIoLike): Promise<EvolutionActivityRecord[]>;
95
+ /** True when bytes exist but are not a readable activity envelope: unparsable
96
+ * JSON, or a missing `items` array (a scalar/array/other-shaped file). A missing
97
+ * file (null) is NOT corruption — it is a first write. */
98
+ /**
99
+ * v43 audit (H-3 / J-6 / FLOW6-6): an UNSUPPORTED version is corrupt for this
100
+ * writer. The read side has always ignored `version` and taken `items` as-is,
101
+ * so a future-version sidecar used to fold fine and then be rewritten as
102
+ * `version: ACTIVITY_FILE_VERSION` on the next append — a silent downgrade that
103
+ * destroyed whatever the newer format carried. This guard is the write side's
104
+ * half of the pair: unknown version ⇒ the original bytes are quarantined (the
105
+ * existing `.corrupt` path) before a current-version file replaces them, which
106
+ * is the posture `evolution-events` already takes for the same shape.
107
+ * Residual, closed by FLOW6-6: `parseActivityContent` still answers `[]` for
108
+ * such a file, but {@link loadActivityState} now carries the verdict beside the
109
+ * records, so a read-only consumer can distinguish "newer format" from "no
110
+ * history" instead of presenting a partial view as the recorded truth.
111
+ * @internal Exported for this package's own tests (siblings `parseActivityContent`
112
+ * and `serializeActivity` are exported for the same reason).
113
+ */
114
+ export declare function isCorruptActivity(raw: string | null): boolean;
64
115
  export declare const name = "evolution-activity";
65
116
  export interface Config {
66
117
  /** Bounded sidecar: how many recent outcomes are kept. */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-activity",
3
3
  "description": "Durable activity store for self-evolution plan outcomes (community build)",
4
- "version": "0.3.81",
4
+ "version": "0.3.82",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -27,15 +27,15 @@
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
29
  "@deepseek-ai/schemastery": "^3.18.1",
30
- "@lmzhen/dsh-evolution-core": "^0.3.81"
30
+ "@lmzhen/dsh-evolution-core": "^0.3.82"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "@deepseek-ai/cordis": "^4.0.1"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@deepseek-ai/cordis": "^4.0.1",
37
- "@lmzhen/dsh-evolution-core": "^0.3.81",
38
- "@lmzhen/dsh-evolution-io": "^0.3.81",
39
- "@lmzhen/dsh-evolution-io-node": "^0.3.81"
37
+ "@lmzhen/dsh-evolution-core": "^0.3.82",
38
+ "@lmzhen/dsh-evolution-io": "^0.3.82",
39
+ "@lmzhen/dsh-evolution-io-node": "^0.3.82"
40
40
  }
41
41
  }