@lmzhen/dsh-evolution-activity 0.17.0 → 0.18.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/lib/index.js +34 -27
- package/lib/types/index.d.ts +34 -25
- package/package.json +6 -6
package/lib/index.js
CHANGED
|
@@ -70,17 +70,36 @@ function parseActivityContent(raw) {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
/**
|
|
73
|
-
*
|
|
73
|
+
* One parse, one verdict — every reader-facing answer above is derived from this one call.
|
|
74
|
+
* @param raw - the sidecar bytes, or null when the file is absent.
|
|
75
|
+
* @returns the verdict.
|
|
76
|
+
*/
|
|
77
|
+
function activityVerdict(raw) {
|
|
78
|
+
if (raw === null) return "absent";
|
|
79
|
+
try {
|
|
80
|
+
const parsed = JSON.parse(raw);
|
|
81
|
+
if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.items)) return "unreadable";
|
|
82
|
+
const version = parsed.version;
|
|
83
|
+
return version !== void 0 && version !== 2 ? "foreign" : "readable";
|
|
84
|
+
} catch {
|
|
85
|
+
return "unreadable";
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* H-06: the read barrier over the sidecar, with the two verdicts it must not conflate.
|
|
74
90
|
*
|
|
75
91
|
* @param root - the evolution state root (the sidecar lives under it).
|
|
76
92
|
* @param io - the IO provider to read through.
|
|
77
|
-
* @returns the parsed records
|
|
93
|
+
* @returns the parsed records, plus whether they are MISSING (`corrupt`) and whether a newer
|
|
94
|
+
* writer wrote the envelope (`foreignVersion`).
|
|
78
95
|
*/
|
|
79
96
|
async function loadActivityState(root, io) {
|
|
80
97
|
const raw = await io.readText(activityFile(root));
|
|
98
|
+
const verdict = activityVerdict(raw);
|
|
81
99
|
return {
|
|
82
100
|
records: parseActivityContent(raw),
|
|
83
|
-
corrupt:
|
|
101
|
+
corrupt: verdict === "unreadable",
|
|
102
|
+
foreignVersion: verdict === "foreign"
|
|
84
103
|
};
|
|
85
104
|
}
|
|
86
105
|
/**
|
|
@@ -96,35 +115,23 @@ async function loadActivityState(root, io) {
|
|
|
96
115
|
async function loadActivity(root, io) {
|
|
97
116
|
return (await loadActivityState(root, io)).records;
|
|
98
117
|
}
|
|
99
|
-
/** True when bytes exist but are not a readable activity envelope: unparsable
|
|
100
|
-
* JSON, or a missing `items` array (a scalar/array/other-shaped file). A missing
|
|
101
|
-
* file (null) is NOT corruption — it is a first write. */
|
|
102
118
|
/**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* is
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* records, so a read-only consumer can distinguish "newer format" from "no
|
|
114
|
-
* history" instead of presenting a partial view as the recorded truth.
|
|
119
|
+
* The WRITE side's quarantine predicate: should these bytes be preserved under `.corrupt` before a
|
|
120
|
+
* current-version file replaces them?
|
|
121
|
+
*
|
|
122
|
+
* v43 audit (H-3 / J-6 / FLOW6-6): yes for an envelope declaring an UNSUPPORTED version. The read
|
|
123
|
+
* side has always ignored `version` and taken `items` as-is, so a future-version sidecar used to
|
|
124
|
+
* fold fine and then be rewritten as `version: ACTIVITY_FILE_VERSION` on the next append — a silent
|
|
125
|
+
* downgrade that destroyed whatever the newer format carried. This is the `foreign` verdict from
|
|
126
|
+
* {@link activityVerdict}, which is why this predicate is NOT the read side's `corrupt`: a foreign
|
|
127
|
+
* envelope's records ARE read (only the bytes are worth preserving), while `corrupt` means there
|
|
128
|
+
* were no readable records in the first place.
|
|
115
129
|
* @internal Exported for this package's own tests (siblings `parseActivityContent`
|
|
116
130
|
* and `serializeActivity` are exported for the same reason).
|
|
117
131
|
*/
|
|
118
132
|
function isCorruptActivity(raw) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const parsed = JSON.parse(raw);
|
|
122
|
-
if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.items)) return true;
|
|
123
|
-
const version = parsed.version;
|
|
124
|
-
return version !== void 0 && version !== 2;
|
|
125
|
-
} catch {
|
|
126
|
-
return true;
|
|
127
|
-
}
|
|
133
|
+
const verdict = activityVerdict(raw);
|
|
134
|
+
return verdict === "unreadable" || verdict === "foreign";
|
|
128
135
|
}
|
|
129
136
|
const name = "evolution-activity";
|
|
130
137
|
const Config = z.object({ maxItems: z.number().min(1).default(200) });
|
package/lib/types/index.d.ts
CHANGED
|
@@ -57,25 +57,39 @@ export declare function applyActivityEvent(items: EvolutionActivityRecord[], eve
|
|
|
57
57
|
export declare function serializeActivity(items: EvolutionActivityRecord[]): string;
|
|
58
58
|
export declare function parseActivityContent(raw: string | null): EvolutionActivityRecord[];
|
|
59
59
|
/**
|
|
60
|
-
* FLOW6-6 (v43, the read side of H-3): the sidecar's records PLUS
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
60
|
+
* FLOW6-6 (v43, the read side of H-3) + T4-08/A50: the sidecar's records PLUS two independent
|
|
61
|
+
* verdicts, because two different things were being folded into one word.
|
|
62
|
+
*
|
|
63
|
+
* `parseActivityContent` answers `[]` for UNPARSABLE bytes or a file with no `items` array — and it
|
|
64
|
+
* reads the items AS-IS when the envelope declares a version this build does not know (the historical
|
|
65
|
+
* behaviour, pinned by activity-version-gate.spec.ts). So "the records are missing" and "a newer
|
|
66
|
+
* writer wrote this" are different facts, and each field names exactly one: `corrupt` is the first,
|
|
67
|
+
* `foreignVersion` the second. (The write side still quarantines the bytes in BOTH cases —
|
|
68
|
+
* {@link isCorruptActivity} — because only a byte-level copy survives the next append.) Collapsing
|
|
69
|
+
* the two made replay backfill its leaderboard from those records AND declare the view "NOT the
|
|
70
|
+
* recorded history".
|
|
66
71
|
*/
|
|
67
72
|
export interface ActivityLoad {
|
|
68
73
|
records: EvolutionActivityRecord[];
|
|
69
|
-
/** Bytes exist but are not a readable
|
|
70
|
-
*
|
|
74
|
+
/** Bytes exist but are not a readable CURRENT-version envelope at all:
|
|
75
|
+
* unparsable JSON, or no `items` array. A MISSING file is not corruption —
|
|
76
|
+
* it is a first write, and `records` is empty in both cases. */
|
|
71
77
|
corrupt: boolean;
|
|
78
|
+
/** T4-08/A50: the envelope declared a `version` this reader does not know,
|
|
79
|
+
* AND its `items` were still read (the historical behaviour, pinned by
|
|
80
|
+
* activity-version-gate.spec.ts). This is NOT corruption: the records ARE the
|
|
81
|
+
* recorded history, just written by a newer writer whose unknown fields this
|
|
82
|
+
* build ignores. A consumer that qualifies such a read as "not the recorded
|
|
83
|
+
* history" contradicts the leaderboard it just backfilled from it. */
|
|
84
|
+
foreignVersion: boolean;
|
|
72
85
|
}
|
|
73
86
|
/**
|
|
74
|
-
* H-06: the read barrier over the sidecar, with the
|
|
87
|
+
* H-06: the read barrier over the sidecar, with the two verdicts it must not conflate.
|
|
75
88
|
*
|
|
76
89
|
* @param root - the evolution state root (the sidecar lives under it).
|
|
77
90
|
* @param io - the IO provider to read through.
|
|
78
|
-
* @returns the parsed records
|
|
91
|
+
* @returns the parsed records, plus whether they are MISSING (`corrupt`) and whether a newer
|
|
92
|
+
* writer wrote the envelope (`foreignVersion`).
|
|
79
93
|
*/
|
|
80
94
|
export declare function loadActivityState(root: string, io: EvolutionIoLike): Promise<ActivityLoad>;
|
|
81
95
|
/**
|
|
@@ -89,22 +103,17 @@ export declare function loadActivityState(root: string, io: EvolutionIoLike): Pr
|
|
|
89
103
|
* sidecar as "no history" use {@link loadActivityState}.
|
|
90
104
|
*/
|
|
91
105
|
export declare function loadActivity(root: string, io: EvolutionIoLike): Promise<EvolutionActivityRecord[]>;
|
|
92
|
-
/** True when bytes exist but are not a readable activity envelope: unparsable
|
|
93
|
-
* JSON, or a missing `items` array (a scalar/array/other-shaped file). A missing
|
|
94
|
-
* file (null) is NOT corruption — it is a first write. */
|
|
95
106
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* is
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* records, so a read-only consumer can distinguish "newer format" from "no
|
|
107
|
-
* history" instead of presenting a partial view as the recorded truth.
|
|
107
|
+
* The WRITE side's quarantine predicate: should these bytes be preserved under `.corrupt` before a
|
|
108
|
+
* current-version file replaces them?
|
|
109
|
+
*
|
|
110
|
+
* v43 audit (H-3 / J-6 / FLOW6-6): yes for an envelope declaring an UNSUPPORTED version. The read
|
|
111
|
+
* side has always ignored `version` and taken `items` as-is, so a future-version sidecar used to
|
|
112
|
+
* fold fine and then be rewritten as `version: ACTIVITY_FILE_VERSION` on the next append — a silent
|
|
113
|
+
* downgrade that destroyed whatever the newer format carried. This is the `foreign` verdict from
|
|
114
|
+
* {@link activityVerdict}, which is why this predicate is NOT the read side's `corrupt`: a foreign
|
|
115
|
+
* envelope's records ARE read (only the bytes are worth preserving), while `corrupt` means there
|
|
116
|
+
* were no readable records in the first place.
|
|
108
117
|
* @internal Exported for this package's own tests (siblings `parseActivityContent`
|
|
109
118
|
* and `serializeActivity` are exported for the same reason).
|
|
110
119
|
*/
|
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.
|
|
4
|
+
"version": "0.18.0",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -26,16 +26,16 @@
|
|
|
26
26
|
],
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
30
|
-
"@lmzhen/dsh-evolution-core": "^0.
|
|
29
|
+
"@deepseek-ai/schemastery": "^3.18.4",
|
|
30
|
+
"@lmzhen/dsh-evolution-core": "^0.18.0"
|
|
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.
|
|
38
|
-
"@lmzhen/dsh-evolution-io": "^0.
|
|
39
|
-
"@lmzhen/dsh-evolution-io-node": "^0.
|
|
37
|
+
"@lmzhen/dsh-evolution-core": "^0.18.0",
|
|
38
|
+
"@lmzhen/dsh-evolution-io": "^0.18.0",
|
|
39
|
+
"@lmzhen/dsh-evolution-io-node": "^0.18.0"
|
|
40
40
|
}
|
|
41
41
|
}
|