@codyswann/lisa 4.20.0 → 4.21.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/all/copy-contents/.gitattributes +2 -1
- package/all/create-only/.lisa/PROJECT_LEARNINGS.md +1 -1
- package/dist/core/learnings-alias.d.ts +26 -45
- package/dist/core/learnings-alias.d.ts.map +1 -1
- package/dist/core/learnings-alias.js +29 -48
- package/dist/core/learnings-alias.js.map +1 -1
- package/dist/core/learnings-budget-check.d.ts +2 -1
- package/dist/core/learnings-budget-check.d.ts.map +1 -1
- package/dist/core/learnings-budget-check.js +13 -11
- package/dist/core/learnings-budget-check.js.map +1 -1
- package/dist/core/learnings-contract.d.ts +27 -10
- package/dist/core/learnings-contract.d.ts.map +1 -1
- package/dist/core/learnings-contract.js +37 -9
- package/dist/core/learnings-contract.js.map +1 -1
- package/dist/core/learnings-document.d.ts +20 -1
- package/dist/core/learnings-document.d.ts.map +1 -1
- package/dist/core/learnings-document.js +81 -14
- package/dist/core/learnings-document.js.map +1 -1
- package/dist/core/learnings-entry.d.ts +11 -1
- package/dist/core/learnings-entry.d.ts.map +1 -1
- package/dist/core/learnings-entry.js +98 -24
- package/dist/core/learnings-entry.js.map +1 -1
- package/dist/core/learnings-merge-driver.d.ts +4 -2
- package/dist/core/learnings-merge-driver.d.ts.map +1 -1
- package/dist/core/learnings-merge-driver.js +6 -3
- package/dist/core/learnings-merge-driver.js.map +1 -1
- package/dist/core/learnings-merge.d.ts.map +1 -1
- package/dist/core/learnings-merge.js +96 -10
- package/dist/core/learnings-merge.js.map +1 -1
- package/dist/core/learnings-overflow.d.ts.map +1 -1
- package/dist/core/learnings-overflow.js +72 -3
- package/dist/core/learnings-overflow.js.map +1 -1
- package/dist/core/learnings-supersede.d.ts +56 -0
- package/dist/core/learnings-supersede.d.ts.map +1 -0
- package/dist/core/learnings-supersede.js +199 -0
- package/dist/core/learnings-supersede.js.map +1 -0
- package/dist/core/learnings-writer.d.ts +9 -7
- package/dist/core/learnings-writer.d.ts.map +1 -1
- package/dist/core/learnings-writer.js +31 -58
- package/dist/core/learnings-writer.js.map +1 -1
- package/dist/core/learnings.d.ts +3 -0
- package/dist/core/learnings.d.ts.map +1 -1
- package/dist/core/learnings.js +3 -0
- package/dist/core/learnings.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +15 -6
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +3 -2
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-debrief-apply/SKILL.md +5 -4
- package/plugins/lisa/.codex-plugin/skills/lisa-persist-learning/SKILL.md +2 -2
- package/plugins/lisa/agents/learner.md +7 -6
- package/plugins/lisa/rules/reference/project-learnings.md +34 -1
- package/plugins/lisa/skills/lisa-debrief-apply/SKILL.md +5 -4
- package/plugins/lisa/skills/lisa-persist-learning/SKILL.md +2 -2
- package/plugins/lisa-agy/agents/learner.md +7 -6
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-debrief-apply/SKILL.md +5 -4
- package/plugins/lisa-agy/skills/lisa-persist-learning/SKILL.md +2 -2
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/agents/learner.agent.md +7 -6
- package/plugins/lisa-copilot/rules/reference/project-learnings.md +34 -1
- package/plugins/lisa-copilot/skills/lisa-debrief-apply/SKILL.md +5 -4
- package/plugins/lisa-copilot/skills/lisa-persist-learning/SKILL.md +2 -2
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/agents/learner.md +7 -6
- package/plugins/lisa-cursor/rules/project-learnings-reference.mdc +34 -1
- package/plugins/lisa-cursor/skills/lisa-debrief-apply/SKILL.md +5 -4
- package/plugins/lisa-cursor/skills/lisa-persist-learning/SKILL.md +2 -2
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/agents/learner.md +7 -6
- package/plugins/src/base/rules/reference/project-learnings.md +34 -1
- package/plugins/src/base/skills/lisa-debrief-apply/SKILL.md +5 -4
- package/plugins/src/base/skills/lisa-persist-learning/SKILL.md +2 -2
- package/scripts/verify-learner-frontmatter-built.mjs +1 -1
- package/scripts/verify-packed-learnings-contract.mjs +268 -0
|
@@ -27,8 +27,11 @@
|
|
|
27
27
|
* Per id, against the base: if only one side changed it, that side wins
|
|
28
28
|
* (including a removal). If both sides made the SAME change, it is not a
|
|
29
29
|
* conflict. If both sides changed it differently, the merge FAILS rather than
|
|
30
|
-
* guessing — with
|
|
31
|
-
*
|
|
30
|
+
* guessing — with two information-preserving exceptions. First, two distinct
|
|
31
|
+
* fingerprint successors of the same base stamp are a concurrent stable-id
|
|
32
|
+
* fork: both survive deterministically, with the lower fingerprint retaining
|
|
33
|
+
* the stable id and the other keyed by its fingerprint. Second, when the only
|
|
34
|
+
* differing field is `last_confirmed`, the later date wins, because that field is a monotonic
|
|
32
35
|
* "this rule demonstrably applied again" stamp and taking the later one loses
|
|
33
36
|
* no information. A confirmation bump never beats a content edit; that is a
|
|
34
37
|
* genuine conflict, because preferring the later timestamp would silently
|
|
@@ -42,6 +45,7 @@
|
|
|
42
45
|
*/
|
|
43
46
|
import { LEARNINGS_CONTRACT, } from "./learnings-contract.js";
|
|
44
47
|
import { assertDocumentBudget, parseLearningsFile, renderLearningsFile, } from "./learnings-document.js";
|
|
48
|
+
import { validateLearningEntry } from "./learnings-entry.js";
|
|
45
49
|
/**
|
|
46
50
|
* Merge two divergent ledger versions against their common ancestor.
|
|
47
51
|
* @param base - Merge-base document, or undefined when the file is new on both sides
|
|
@@ -100,16 +104,32 @@ function resolveAllIds(base, ours, theirs) {
|
|
|
100
104
|
reason: `Both branches changed learning ${conflictingIds.map(id => `'${id}'`).join(", ")} differently; recompact by hand to keep the intended content`,
|
|
101
105
|
};
|
|
102
106
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
const unsortedEntries = resolutions.flatMap(resolution => resolution === "absent" || resolution === "conflict"
|
|
108
|
+
? []
|
|
109
|
+
: Array.isArray(resolution)
|
|
110
|
+
? resolution
|
|
111
|
+
: [resolution]);
|
|
112
|
+
const entries = sortLearningEntries(unsortedEntries);
|
|
113
|
+
const duplicateId = findDuplicate(entries.map(entry => entry.id));
|
|
114
|
+
if (duplicateId !== undefined) {
|
|
115
|
+
return {
|
|
116
|
+
kind: "conflict",
|
|
117
|
+
reason: `Concurrent learning fork would collide with live id '${duplicateId}'; recompact by hand to preserve both entries`,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const duplicateFingerprint = findDuplicate(entries.map(entry => entry.fingerprint));
|
|
121
|
+
if (duplicateFingerprint !== undefined) {
|
|
122
|
+
return {
|
|
123
|
+
kind: "conflict",
|
|
124
|
+
reason: `Merged project learnings contain duplicate fingerprint '${duplicateFingerprint}'; recompact by hand rather than guessing which identical capture wins`,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return { kind: "entries", entries };
|
|
109
128
|
}
|
|
110
129
|
/**
|
|
111
130
|
* Resolve one id with standard three-way semantics.
|
|
112
131
|
* @param base - Ancestor version, if any
|
|
132
|
+
* @param base - Common ancestor version, if any
|
|
113
133
|
* @param ours - Current-branch version, if any
|
|
114
134
|
* @param theirs - Incoming-branch version, if any
|
|
115
135
|
* @returns The winning entry, `absent` when both sides removed it, or `conflict`
|
|
@@ -126,27 +146,93 @@ function resolveOneId(base, ours, theirs) {
|
|
|
126
146
|
if (sameEntry(base, theirs)) {
|
|
127
147
|
return ours ?? "absent";
|
|
128
148
|
}
|
|
129
|
-
return resolveDivergentEdit(ours, theirs);
|
|
149
|
+
return resolveDivergentEdit(base, ours, theirs);
|
|
130
150
|
}
|
|
131
151
|
/**
|
|
132
152
|
* Resolve the one divergence that is safe to auto-merge: both branches only
|
|
133
153
|
* re-confirmed the same entry, so the later monotonic stamp wins.
|
|
154
|
+
* @param base - Common ancestor version, if any
|
|
134
155
|
* @param ours - Current-branch version, if any
|
|
135
156
|
* @param theirs - Incoming-branch version, if any
|
|
136
157
|
* @returns The later-confirmed entry, or `conflict`
|
|
137
158
|
*/
|
|
138
|
-
function resolveDivergentEdit(ours, theirs) {
|
|
159
|
+
function resolveDivergentEdit(base, ours, theirs) {
|
|
139
160
|
if (ours === undefined || theirs === undefined) {
|
|
140
161
|
// One side edited the entry while the other superseded it. Dropping the
|
|
141
162
|
// edit or resurrecting the removal are both information loss.
|
|
142
163
|
return "conflict";
|
|
143
164
|
}
|
|
165
|
+
const fork = forkConcurrentStableRewrite(base, ours, theirs);
|
|
166
|
+
if (fork !== undefined) {
|
|
167
|
+
return fork;
|
|
168
|
+
}
|
|
144
169
|
// Neutralize only `last_confirmed`: if the entries match once that field is
|
|
145
170
|
// aligned, it was the sole difference and taking the later stamp loses nothing.
|
|
146
171
|
return sameEntry({ ...ours, last_confirmed: theirs.last_confirmed }, theirs)
|
|
147
172
|
? pickLaterConfirmation(ours, theirs)
|
|
148
173
|
: "conflict";
|
|
149
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Preserve both successors when two branches legitimately rewrote the same
|
|
177
|
+
* stable identity from the same fingerprint snapshot.
|
|
178
|
+
*
|
|
179
|
+
* The lower fingerprint keeps the stable public id; the other successor is
|
|
180
|
+
* re-keyed to its own globally unique fingerprint. This ordering is independent
|
|
181
|
+
* of ours/theirs orientation, so reversing merge sides produces identical
|
|
182
|
+
* bytes. A confirmation-versus-rewrite is not a fork: both successors must
|
|
183
|
+
* carry new, distinct fingerprints relative to the base.
|
|
184
|
+
* @param base - Common ancestor entry
|
|
185
|
+
* @param ours - Current-branch successor
|
|
186
|
+
* @param theirs - Incoming-branch successor
|
|
187
|
+
* @returns Two deterministic survivors, or undefined when this is not a fork
|
|
188
|
+
*/
|
|
189
|
+
function forkConcurrentStableRewrite(base, ours, theirs) {
|
|
190
|
+
if (base === undefined ||
|
|
191
|
+
ours.id !== base.id ||
|
|
192
|
+
theirs.id !== base.id ||
|
|
193
|
+
ours.fingerprint === base.fingerprint ||
|
|
194
|
+
theirs.fingerprint === base.fingerprint ||
|
|
195
|
+
ours.fingerprint === theirs.fingerprint) {
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
const [primary, forked] = compareTokens(ours.fingerprint, theirs.fingerprint) <= 0
|
|
199
|
+
? [ours, theirs]
|
|
200
|
+
: [theirs, ours];
|
|
201
|
+
return [
|
|
202
|
+
primary,
|
|
203
|
+
validateLearningEntry({ ...forked, id: forked.fingerprint }),
|
|
204
|
+
];
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Find the first repeated token in encounter order.
|
|
208
|
+
* @param values - Stable tokens to inspect
|
|
209
|
+
* @returns First duplicate, when one exists
|
|
210
|
+
*/
|
|
211
|
+
function findDuplicate(values) {
|
|
212
|
+
return values.find((value, index) => values.indexOf(value) !== index);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Compare stable tokens without locale dependence.
|
|
216
|
+
* @param left - First token
|
|
217
|
+
* @param right - Second token
|
|
218
|
+
* @returns Negative, zero, or positive ordering result
|
|
219
|
+
*/
|
|
220
|
+
function compareTokens(left, right) {
|
|
221
|
+
return left === right ? 0 : left < right ? -1 : 1;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Sort entries by id without mutating a caller-owned or local array.
|
|
225
|
+
* @param entries - Entries in arbitrary order
|
|
226
|
+
* @returns New deterministically ordered array
|
|
227
|
+
*/
|
|
228
|
+
function sortLearningEntries(entries) {
|
|
229
|
+
return entries.reduce((ordered, entry) => {
|
|
230
|
+
const insertion = ordered.findIndex(current => compareTokens(entry.id, current.id) < 0);
|
|
231
|
+
return insertion === -1
|
|
232
|
+
? [...ordered, entry]
|
|
233
|
+
: [...ordered.slice(0, insertion), entry, ...ordered.slice(insertion)];
|
|
234
|
+
}, []);
|
|
235
|
+
}
|
|
150
236
|
/**
|
|
151
237
|
* Choose the later of two confirmation stamps.
|
|
152
238
|
* @param ours - Current-branch version
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"learnings-merge.js","sourceRoot":"","sources":["../../src/core/learnings-merge.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"learnings-merge.js","sourceRoot":"","sources":["../../src/core/learnings-merge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,OAAO,EACL,kBAAkB,GAEnB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAqB7D;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CACrC,IAAwB,EACxB,IAAY,EACZ,MAAc;IAEd,IAAI,CAAC;QACH,MAAM,WAAW,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,UAAU,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,YAAY,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;QACvC,MAAM,QAAQ,GAAG,aAAa,CAAC,WAAW,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC;QACtE,IAAI,QAAQ,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YACjC,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,OAAO,kBAAkB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC9C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,IAAI,EAAE,UAAU;YAChB,MAAM,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;SAC/D,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,SAAS,CAChB,OAA2B;IAE3B,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;QAC5C,OAAO,IAAI,GAAG,EAAE,CAAC;IACnB,CAAC;IACD,OAAO,IAAI,GAAG,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;AAC9E,CAAC;AAED;;;;;;GAMG;AACH,SAAS,aAAa,CACpB,IAAwC,EACxC,IAAwC,EACxC,MAA0C;IAI1C,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,EAAE,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7E,MAAM,WAAW,GAAG,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAC/B,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CACzD,CAAC;IACF,MAAM,UAAU,GAAG,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,UAAU,KAAK,UAAU,CAAC,CAAC;IAC7E,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,cAAc,GAAG,GAAG,CAAC,MAAM,CAC/B,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,KAAK,UAAU,CACtD,CAAC;QACF,OAAO;YACL,IAAI,EAAE,UAAU;YAChB,MAAM,EAAE,kCAAkC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,8DAA8D;SACvJ,CAAC;IACJ,CAAC;IACD,MAAM,eAAe,GAAG,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CACvD,UAAU,KAAK,QAAQ,IAAI,UAAU,KAAK,UAAU;QAClD,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC;YACzB,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,CAAC,UAAU,CAAC,CACnB,CAAC;IACF,MAAM,OAAO,GAAG,mBAAmB,CAAC,eAAe,CAAC,CAAC;IACrD,MAAM,WAAW,GAAG,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;IAClE,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO;YACL,IAAI,EAAE,UAAU;YAChB,MAAM,EAAE,wDAAwD,WAAW,+CAA+C;SAC3H,CAAC;IACJ,CAAC;IACD,MAAM,oBAAoB,GAAG,aAAa,CACxC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,CACxC,CAAC;IACF,IAAI,oBAAoB,KAAK,SAAS,EAAE,CAAC;QACvC,OAAO;YACL,IAAI,EAAE,UAAU;YAChB,MAAM,EAAE,2DAA2D,oBAAoB,wEAAwE;SAChK,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;AACtC,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,YAAY,CACnB,IAA+B,EAC/B,IAA+B,EAC/B,MAAiC;IAEjC,IAAI,SAAS,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;QAC5B,OAAO,IAAI,IAAI,QAAQ,CAAC;IAC1B,CAAC;IACD,wEAAwE;IACxE,sEAAsE;IACtE,IAAI,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;QAC1B,OAAO,MAAM,IAAI,QAAQ,CAAC;IAC5B,CAAC;IACD,IAAI,SAAS,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;QAC5B,OAAO,IAAI,IAAI,QAAQ,CAAC;IAC1B,CAAC;IACD,OAAO,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;AAClD,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,oBAAoB,CAC3B,IAA+B,EAC/B,IAA+B,EAC/B,MAAiC;IAEjC,IAAI,IAAI,KAAK,SAAS,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QAC/C,wEAAwE;QACxE,8DAA8D;QAC9D,OAAO,UAAU,CAAC;IACpB,CAAC;IACD,MAAM,IAAI,GAAG,2BAA2B,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IAC7D,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,4EAA4E;IAC5E,gFAAgF;IAChF,OAAO,SAAS,CAAC,EAAE,GAAG,IAAI,EAAE,cAAc,EAAE,MAAM,CAAC,cAAc,EAAE,EAAE,MAAM,CAAC;QAC1E,CAAC,CAAC,qBAAqB,CAAC,IAAI,EAAE,MAAM,CAAC;QACrC,CAAC,CAAC,UAAU,CAAC;AACjB,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,SAAS,2BAA2B,CAClC,IAA+B,EAC/B,IAAmB,EACnB,MAAqB;IAErB,IACE,IAAI,KAAK,SAAS;QAClB,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE;QACnB,MAAM,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE;QACrB,IAAI,CAAC,WAAW,KAAK,IAAI,CAAC,WAAW;QACrC,MAAM,CAAC,WAAW,KAAK,IAAI,CAAC,WAAW;QACvC,IAAI,CAAC,WAAW,KAAK,MAAM,CAAC,WAAW,EACvC,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,GACrB,aAAa,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC;QACtD,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC;QAChB,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACrB,OAAO;QACL,OAAO;QACP,qBAAqB,CAAC,EAAE,GAAG,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC;KAC7D,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,SAAS,aAAa,CAAC,MAAyB;IAC9C,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,CAAC,CAAC;AACxE,CAAC;AAED;;;;;GAKG;AACH,SAAS,aAAa,CAAC,IAAY,EAAE,KAAa;IAChD,OAAO,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACpD,CAAC;AAED;;;;GAIG;AACH,SAAS,mBAAmB,CAC1B,OAAiC;IAEjC,OAAO,OAAO,CAAC,MAAM,CAA2B,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE;QACjE,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CACjC,OAAO,CAAC,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,CACnD,CAAC;QACF,OAAO,SAAS,KAAK,CAAC,CAAC;YACrB,CAAC,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC;YACrB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;IAC3E,CAAC,EAAE,EAAE,CAAC,CAAC;AACT,CAAC;AAED;;;;;GAKG;AACH,SAAS,qBAAqB,CAC5B,IAAmB,EACnB,MAAqB;IAErB,OAAO,MAAM,CAAC,cAAc,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AACrE,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,SAAS,CAChB,IAA+B,EAC/B,KAAgC;IAEhC,IAAI,IAAI,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QAC9C,OAAO,IAAI,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,CAAC;IACnD,CAAC;IACD,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AACxD,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,kBAAkB,CACzB,OAAiC;IAEjC,MAAM,OAAO,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAC7C,IAAI,CAAC;QACH,oBAAoB,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE,0BAA0B,CAAC,CAAC;QAC1E,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IACrC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtE,OAAO;YACL,IAAI,EAAE,UAAU;YAChB,MAAM,EAAE,GAAG,MAAM,kDAAkD,kBAAkB,CAAC,UAAU,2CAA2C;SAC5I,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"learnings-overflow.d.ts","sourceRoot":"","sources":["../../src/core/learnings-overflow.ts"],"names":[],"mappings":"AAkDA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EACL,oBAAoB,EAIrB,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"learnings-overflow.d.ts","sourceRoot":"","sources":["../../src/core/learnings-overflow.ts"],"names":[],"mappings":"AAkDA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EACL,oBAAoB,EAIrB,MAAM,yBAAyB,CAAC;AAiBjC;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAI1E;AA6CD,6DAA6D;AAC7D,MAAM,WAAW,yBAAyB;IACxC,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,wCAAwC;IACxC,QAAQ,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,CAAC;CAC5C;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,yBAAyB,CAAC,CAIpC;AAED,kEAAkE;AAClE,MAAM,WAAW,sBAAsB;IACrC,8BAA8B;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uDAAuD;IACvD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,0CAA0C;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,SAAS,MAAM,EAAE,GACrB,OAAO,CAAC,sBAAsB,CAAC,CAwBjC;AAoHD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,uBAAuB,CAC3C,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,aAAa,EACpB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,KAAK,CAAC,CA0BhB"}
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* Being tracked means it has the very concurrent-writer problem the ledger just
|
|
24
24
|
* had (CodySwannGT/lisa#1995), which is exactly why it reuses the ledger's
|
|
25
25
|
* canonical document format verbatim: one `.gitattributes` line binds it to the
|
|
26
|
-
* SAME
|
|
26
|
+
* SAME id-and-fingerprint merge driver, `parseLearningsFile` gives it the SAME
|
|
27
27
|
* conflict-marker guard, and `checkLearningsBudget` can gate it in CI unchanged.
|
|
28
28
|
* A bespoke format would have needed all of that written a second time.
|
|
29
29
|
*
|
|
@@ -49,6 +49,7 @@ import * as fse from "fs-extra";
|
|
|
49
49
|
import * as path from "node:path";
|
|
50
50
|
import { writeFileAtomically } from "../utils/atomic-file-write.js";
|
|
51
51
|
import { LearningsBudgetError, assertDocumentBudget, parseLearningsFile, renderLearningsFile, } from "./learnings-document.js";
|
|
52
|
+
import { validateLearningEntry } from "./learnings-entry.js";
|
|
52
53
|
import { assertLearningsUnchanged, assertSafeLearningParents, readExistingLearnings, resolveSafeLearningTarget, } from "./learnings-file-safety.js";
|
|
53
54
|
import { withFileTargetLock } from "./learnings-lock.js";
|
|
54
55
|
import { readProjectConfig, resolveProjectLearningsFile, } from "./project-config.js";
|
|
@@ -156,6 +157,10 @@ export async function drainLearningsOverflow(projectRoot, ids) {
|
|
|
156
157
|
async function publishOverflow(root, target, entries, preImage) {
|
|
157
158
|
const rendered = renderLearningsFile(entries);
|
|
158
159
|
assertDocumentBudget(rendered, entries.length, "Learnings overflow");
|
|
160
|
+
// Budget checks alone do not prove identity uniqueness or exact schema.
|
|
161
|
+
// Reparse the COMPLETE candidate while the overflow lock is held, before a
|
|
162
|
+
// temporary file can be renamed over the last known-good document.
|
|
163
|
+
parseLearningsFile(rendered);
|
|
159
164
|
await fse.ensureDir(path.dirname(target));
|
|
160
165
|
await writeFileAtomically(target, rendered, {
|
|
161
166
|
beforeRename: async () => {
|
|
@@ -164,6 +169,69 @@ async function publishOverflow(root, target, entries, preImage) {
|
|
|
164
169
|
},
|
|
165
170
|
});
|
|
166
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Resolve an incoming capture against overflow identity without losing either
|
|
174
|
+
* version. Exact byte-equivalent identity is the only idempotent case. When a
|
|
175
|
+
* distinct fingerprint arrives under an occupied stable id, the fingerprint
|
|
176
|
+
* becomes its deterministic fork id, matching the merge driver's concurrent
|
|
177
|
+
* stable-id policy. Every other id/fingerprint collision fails before publish.
|
|
178
|
+
* @param entries - Validated overflow entries read under its lock
|
|
179
|
+
* @param entry - Validated dropped capture
|
|
180
|
+
* @returns Complete candidate entries, or undefined for an exact replay
|
|
181
|
+
*/
|
|
182
|
+
function resolveOverflowEntries(entries, entry) {
|
|
183
|
+
const byId = entries.find(current => current.id === entry.id);
|
|
184
|
+
const byFingerprint = entries.find(current => current.fingerprint === entry.fingerprint);
|
|
185
|
+
if (byId !== undefined && byFingerprint === byId) {
|
|
186
|
+
return resolveExactOverflowReplay(byId, entry);
|
|
187
|
+
}
|
|
188
|
+
if (byFingerprint !== undefined) {
|
|
189
|
+
throw new Error(`Duplicate learning fingerprint '${entry.fingerprint}' in the overflow`);
|
|
190
|
+
}
|
|
191
|
+
if (byId === undefined) {
|
|
192
|
+
return [...entries, entry];
|
|
193
|
+
}
|
|
194
|
+
// A self-identifying version (`id === fingerprint`) owns that stable id. For
|
|
195
|
+
// two rewritten versions, the lower fingerprint owns it, exactly as in the
|
|
196
|
+
// merge driver. The rule is independent of lock-arrival order.
|
|
197
|
+
const primary = selectOverflowPrimary(byId, entry);
|
|
198
|
+
const forked = primary === byId ? entry : byId;
|
|
199
|
+
if (entries.some(current => current.id === forked.fingerprint)) {
|
|
200
|
+
throw new Error(`Cannot preserve learning '${entry.id}': deterministic fork id '${forked.fingerprint}' already exists in the overflow`);
|
|
201
|
+
}
|
|
202
|
+
const rekeyed = validateLearningEntry({
|
|
203
|
+
...forked,
|
|
204
|
+
id: forked.fingerprint,
|
|
205
|
+
});
|
|
206
|
+
return entries.flatMap(current => current === byId ? [primary, rekeyed] : [current]);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Accept only a byte-identical replay of one occupied id/fingerprint pair.
|
|
210
|
+
* @param current - Existing overflow identity
|
|
211
|
+
* @param incoming - Incoming entry with the same id and fingerprint
|
|
212
|
+
* @returns Undefined for an exact idempotent replay
|
|
213
|
+
*/
|
|
214
|
+
function resolveExactOverflowReplay(current, incoming) {
|
|
215
|
+
if (JSON.stringify(current) === JSON.stringify(incoming)) {
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
throw new Error(`Duplicate learning identity '${incoming.id}' has different content in the overflow`);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Pick the version that owns a colliding stable id independently of arrival.
|
|
222
|
+
* @param current - Version already in the overflow
|
|
223
|
+
* @param incoming - Distinct version arriving under the same id
|
|
224
|
+
* @returns Stable-id owner
|
|
225
|
+
*/
|
|
226
|
+
function selectOverflowPrimary(current, incoming) {
|
|
227
|
+
if (current.id === current.fingerprint) {
|
|
228
|
+
return current;
|
|
229
|
+
}
|
|
230
|
+
if (incoming.id === incoming.fingerprint) {
|
|
231
|
+
return incoming;
|
|
232
|
+
}
|
|
233
|
+
return incoming.fingerprint < current.fingerprint ? incoming : current;
|
|
234
|
+
}
|
|
167
235
|
/**
|
|
168
236
|
* Preserve a capture the ledger had no budget for, then re-raise the breach.
|
|
169
237
|
*
|
|
@@ -188,11 +256,12 @@ export async function preserveDroppedLearning(projectRoot, entry, breach) {
|
|
|
188
256
|
// Re-establish containment after the lock wait; see drainLearningsOverflow.
|
|
189
257
|
await assertSafeLearningParents(root, path.dirname(target));
|
|
190
258
|
const { entries, image } = await readOverflowEntries(target);
|
|
191
|
-
|
|
259
|
+
const next = resolveOverflowEntries(entries, entry);
|
|
260
|
+
if (next === undefined) {
|
|
192
261
|
return true;
|
|
193
262
|
}
|
|
194
263
|
try {
|
|
195
|
-
await publishOverflow(root, target,
|
|
264
|
+
await publishOverflow(root, target, next, image);
|
|
196
265
|
return true;
|
|
197
266
|
}
|
|
198
267
|
catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"learnings-overflow.js","sourceRoot":"","sources":["../../src/core/learnings-overflow.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"learnings-overflow.js","sourceRoot":"","sources":["../../src/core/learnings-overflow.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAEpE,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EACL,wBAAwB,EACxB,yBAAyB,EACzB,qBAAqB,EACrB,yBAAyB,GAC1B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EACL,iBAAiB,EACjB,2BAA2B,GAC5B,MAAM,qBAAqB,CAAC;AAE7B,wEAAwE;AACxE,MAAM,eAAe,GAAG,WAAW,CAAC;AAEpC;;;;;;;;;GASG;AACH,MAAM,UAAU,4BAA4B,CAAC,aAAqB;IAChE,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IACpD,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IAC7E,OAAO,GAAG,IAAI,GAAG,eAAe,GAAG,SAAS,EAAE,CAAC;AACjD,CAAC;AAQD;;;;GAIG;AACH,KAAK,UAAU,qBAAqB,CAClC,WAAmB;IAEnB,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,WAAW,CAAC,CAAC;IACpD,MAAM,QAAQ,GAAG,4BAA4B,CAC3C,2BAA2B,CAAC,MAAM,CAAC,CACpC,CAAC;IACF,OAAO,yBAAyB,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;AAC1D,CAAC;AAUD;;;;;GAKG;AACH,KAAK,UAAU,mBAAmB,CAAC,MAAc;IAC/C,MAAM,QAAQ,GAAG,MAAM,qBAAqB,CAAC,MAAM,CAAC,CAAC;IACrD,OAAO;QACL,OAAO,EAAE,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,kBAAkB,CAAC,QAAQ,CAAC;QACnE,KAAK,EAAE,QAAQ;KAChB,CAAC;AACJ,CAAC;AAUD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,WAAmB;IAEnB,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,qBAAqB,CAAC,WAAW,CAAC,CAAC;IAC5D,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,mBAAmB,CAAC,MAAM,CAAC,CAAC;IACtD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AACnC,CAAC;AAcD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,WAAmB,EACnB,GAAsB;IAEtB,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC,CAAC;IACpD,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,qBAAqB,CAAC,WAAW,CAAC,CAAC;IAClE,MAAM,yBAAyB,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAC5D,OAAO,kBAAkB,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE;QAC3C,oEAAoE;QACpE,kEAAkE;QAClE,kEAAkE;QAClE,MAAM,yBAAyB,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QAC5D,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,MAAM,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAC7D,MAAM,OAAO,GAAG,OAAO;aACpB,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;aACxC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC1B,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;QACnE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;QACvD,CAAC;QACD,OAAO;YACL,IAAI,EAAE,MAAM;YACZ,OAAO;YACP,MAAM,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YAC1D,SAAS,EAAE,QAAQ,CAAC,MAAM;SAC3B,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,eAAe,CAC5B,IAAY,EACZ,MAAc,EACd,OAAiC,EACjC,QAA4B;IAE5B,MAAM,QAAQ,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAC9C,oBAAoB,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IACrE,wEAAwE;IACxE,2EAA2E;IAC3E,mEAAmE;IACnE,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IAC7B,MAAM,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAC1C,MAAM,mBAAmB,CAAC,MAAM,EAAE,QAAQ,EAAE;QAC1C,YAAY,EAAE,KAAK,IAAI,EAAE;YACvB,MAAM,yBAAyB,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;YAC5D,MAAM,wBAAwB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACnD,CAAC;KACF,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,sBAAsB,CAC7B,OAAiC,EACjC,KAAoB;IAEpB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,KAAK,KAAK,CAAC,EAAE,CAAC,CAAC;IAC9D,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,CAChC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,WAAW,KAAK,KAAK,CAAC,WAAW,CACrD,CAAC;IACF,IAAI,IAAI,KAAK,SAAS,IAAI,aAAa,KAAK,IAAI,EAAE,CAAC;QACjD,OAAO,0BAA0B,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACjD,CAAC;IACD,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CACb,mCAAmC,KAAK,CAAC,WAAW,mBAAmB,CACxE,CAAC;IACJ,CAAC;IACD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC,CAAC;IAC7B,CAAC;IAED,6EAA6E;IAC7E,2EAA2E;IAC3E,+DAA+D;IAC/D,MAAM,OAAO,GAAG,qBAAqB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IAC/C,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,KAAK,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC;QAC/D,MAAM,IAAI,KAAK,CACb,6BAA6B,KAAK,CAAC,EAAE,6BAA6B,MAAM,CAAC,WAAW,kCAAkC,CACvH,CAAC;IACJ,CAAC;IACD,MAAM,OAAO,GAAG,qBAAqB,CAAC;QACpC,GAAG,MAAM;QACT,EAAE,EAAE,MAAM,CAAC,WAAW;KACvB,CAAC,CAAC;IACH,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAC/B,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAClD,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAS,0BAA0B,CACjC,OAAsB,EACtB,QAAuB;IAEvB,IAAI,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,IAAI,KAAK,CACb,gCAAgC,QAAQ,CAAC,EAAE,yCAAyC,CACrF,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAS,qBAAqB,CAC5B,OAAsB,EACtB,QAAuB;IAEvB,IAAI,OAAO,CAAC,EAAE,KAAK,OAAO,CAAC,WAAW,EAAE,CAAC;QACvC,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,IAAI,QAAQ,CAAC,EAAE,KAAK,QAAQ,CAAC,WAAW,EAAE,CAAC;QACzC,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,OAAO,QAAQ,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC;AACzE,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,WAAmB,EACnB,KAAoB,EACpB,MAA4B;IAE5B,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,qBAAqB,CAAC,WAAW,CAAC,CAAC;IAClE,MAAM,yBAAyB,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAC5D,MAAM,SAAS,GAAG,MAAM,kBAAkB,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE;QAC5D,4EAA4E;QAC5E,MAAM,yBAAyB,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QAC5D,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,MAAM,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAC7D,MAAM,IAAI,GAAG,sBAAsB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACpD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,CAAC;YACH,MAAM,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;YACjD,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,oBAAoB,EAAE,CAAC;gBAC1C,OAAO,KAAK,CAAC;YACf,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CAAC;IACH,MAAM,IAAI,oBAAoB,CAC5B,SAAS;QACP,CAAC,CAAC,GAAG,MAAM,CAAC,OAAO,mCAAmC,MAAM,oFAAoF;QAChJ,CAAC,CAAC,GAAG,MAAM,CAAC,OAAO,sBAAsB,MAAM,4GAA4G,CAC9J,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,SAAS,mBAAmB,CAAC,GAAsB;IACjD,IACE,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;QACnB,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,EAC1D,CAAC;QACD,MAAM,IAAI,KAAK,CACb,oEAAoE,CACrE,CAAC;IACJ,CAAC;IACD,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file learnings-supersede.ts
|
|
3
|
+
* @description Plans exact stamped supersedes without mutating shared state
|
|
4
|
+
* @module core/learnings-supersede
|
|
5
|
+
*/
|
|
6
|
+
import { type LearningEntry } from "./learnings-contract.js";
|
|
7
|
+
/** Exact identity and content version a caller observed before consolidation. */
|
|
8
|
+
export interface LearningEntryStamp {
|
|
9
|
+
/** Stable public identity expected in the locked ledger. */
|
|
10
|
+
readonly id: string;
|
|
11
|
+
/** Content fingerprint expected at that identity. */
|
|
12
|
+
readonly fingerprint: string;
|
|
13
|
+
}
|
|
14
|
+
/** Why an observed supersede stamp no longer authorizes replacement. */
|
|
15
|
+
export type StaleSupersedeReason = "absent" | "fingerprint-mismatch";
|
|
16
|
+
/** One target whose locked state differs from the caller's snapshot. */
|
|
17
|
+
export interface StaleSupersedeTarget {
|
|
18
|
+
/** Exact identity and fingerprint the caller observed. */
|
|
19
|
+
readonly expected: LearningEntryStamp;
|
|
20
|
+
/** Current fingerprint; absent when the identity no longer exists. */
|
|
21
|
+
readonly actualFingerprint?: string;
|
|
22
|
+
/** Whether the identity disappeared or now names different content. */
|
|
23
|
+
readonly reason: StaleSupersedeReason;
|
|
24
|
+
}
|
|
25
|
+
/** Immutable plan produced from the state read inside the writer lock. */
|
|
26
|
+
export interface LearningSupersedePlan {
|
|
27
|
+
/** Entry to append or replace, carrying the stable primary id when exact. */
|
|
28
|
+
readonly entry: LearningEntry;
|
|
29
|
+
/** Existing entries retained byte-for-byte. */
|
|
30
|
+
readonly retained: readonly LearningEntry[];
|
|
31
|
+
/** Exact targets removed together; empty on any stale target. */
|
|
32
|
+
readonly removed: readonly LearningEntry[];
|
|
33
|
+
/** Stale targets that forced a safe append instead of replacement. */
|
|
34
|
+
readonly stale: readonly StaleSupersedeTarget[];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Validate caller-supplied version stamps before any filesystem work.
|
|
38
|
+
* @param stamps - Possibly untrusted supersede option
|
|
39
|
+
* @returns Frozen, id-unique stamps in caller order
|
|
40
|
+
*/
|
|
41
|
+
export declare function validateLearningEntryStamps(stamps: readonly LearningEntryStamp[] | undefined): readonly LearningEntryStamp[];
|
|
42
|
+
/**
|
|
43
|
+
* Plan an all-or-nothing stamped replacement against locked current state.
|
|
44
|
+
*
|
|
45
|
+
* Fingerprint collision is checked first because it is the durable dedupe
|
|
46
|
+
* boundary. An exact target set is removed together and the lexicographically
|
|
47
|
+
* first target id becomes the stable primary. Any missing or changed target
|
|
48
|
+
* makes the whole request a safe append: no target is removed and no alias is
|
|
49
|
+
* claimed.
|
|
50
|
+
* @param entries - Current validated entries read inside the lock
|
|
51
|
+
* @param candidate - New validated content composed by the caller
|
|
52
|
+
* @param expected - Validated version stamps from the caller's snapshot
|
|
53
|
+
* @returns Deterministic append-or-replace plan
|
|
54
|
+
*/
|
|
55
|
+
export declare function planLearningSupersede(entries: readonly LearningEntry[], candidate: LearningEntry, expected: readonly LearningEntryStamp[]): LearningSupersedePlan;
|
|
56
|
+
//# sourceMappingURL=learnings-supersede.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learnings-supersede.d.ts","sourceRoot":"","sources":["../../src/core/learnings-supersede.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,yBAAyB,CAAC;AAOjC,iFAAiF;AACjF,MAAM,WAAW,kBAAkB;IACjC,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,wEAAwE;AACxE,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,sBAAsB,CAAC;AAErE,wEAAwE;AACxE,MAAM,WAAW,oBAAoB;IACnC,0DAA0D;IAC1D,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IACtC,sEAAsE;IACtE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;CACvC;AAED,0EAA0E;AAC1E,MAAM,WAAW,qBAAqB;IACpC,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,+CAA+C;IAC/C,QAAQ,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAC;IAC5C,iEAAiE;IACjE,QAAQ,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,CAAC;IAC3C,sEAAsE;IACtE,QAAQ,CAAC,KAAK,EAAE,SAAS,oBAAoB,EAAE,CAAC;CACjD;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,SAAS,kBAAkB,EAAE,GAAG,SAAS,GAChD,SAAS,kBAAkB,EAAE,CAc/B;AAuDD;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,SAAS,EAAE,aAAa,EACxB,QAAQ,EAAE,SAAS,kBAAkB,EAAE,GACtC,qBAAqB,CAgCvB"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file learnings-supersede.ts
|
|
3
|
+
* @description Plans exact stamped supersedes without mutating shared state
|
|
4
|
+
* @module core/learnings-supersede
|
|
5
|
+
*/
|
|
6
|
+
import { LEARNINGS_CONTRACT, MAX_STABLE_TOKEN_BYTES, } from "./learnings-contract.js";
|
|
7
|
+
import { validateLearningEntry } from "./learnings-entry.js";
|
|
8
|
+
const STABLE_TOKEN = /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/;
|
|
9
|
+
const EXACT_STAMP_ERROR = "Invalid supersede stamp: expected exactly {id, fingerprint}";
|
|
10
|
+
/**
|
|
11
|
+
* Validate caller-supplied version stamps before any filesystem work.
|
|
12
|
+
* @param stamps - Possibly untrusted supersede option
|
|
13
|
+
* @returns Frozen, id-unique stamps in caller order
|
|
14
|
+
*/
|
|
15
|
+
export function validateLearningEntryStamps(stamps) {
|
|
16
|
+
if (stamps === undefined) {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
if (!Array.isArray(stamps)) {
|
|
20
|
+
throw new Error("Invalid supersede option: expected learning {id, fingerprint} stamps");
|
|
21
|
+
}
|
|
22
|
+
const validated = validateStampArray(stamps);
|
|
23
|
+
if (new Set(validated.map(stamp => stamp.id)).size !== validated.length) {
|
|
24
|
+
throw new Error("Invalid supersede option: duplicate stamped ids");
|
|
25
|
+
}
|
|
26
|
+
return Object.freeze(validated);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Read a dense, expando-free supersede array entirely through own descriptors.
|
|
30
|
+
* Native iteration methods invoke indexed getters and skip sparse holes, which
|
|
31
|
+
* lets hostile containers change the target set while validation is running.
|
|
32
|
+
* The ledger cap bounds descriptor traversal before any member is inspected.
|
|
33
|
+
* @param stamps - Array already established by Array.isArray
|
|
34
|
+
* @returns Frozen validated stamps in exact index order
|
|
35
|
+
*/
|
|
36
|
+
function validateStampArray(stamps) {
|
|
37
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(stamps, "length");
|
|
38
|
+
if (lengthDescriptor === undefined ||
|
|
39
|
+
!("value" in lengthDescriptor) ||
|
|
40
|
+
typeof lengthDescriptor.value !== "number" ||
|
|
41
|
+
!Number.isSafeInteger(lengthDescriptor.value) ||
|
|
42
|
+
lengthDescriptor.value < 0) {
|
|
43
|
+
throw new Error("Invalid supersede array: expected a dense array");
|
|
44
|
+
}
|
|
45
|
+
const length = lengthDescriptor.value;
|
|
46
|
+
if (length > LEARNINGS_CONTRACT.maxEntries) {
|
|
47
|
+
throw new Error(`Invalid supersede array exceeds maxEntries ${LEARNINGS_CONTRACT.maxEntries}`);
|
|
48
|
+
}
|
|
49
|
+
const expectedKeys = new Set([
|
|
50
|
+
"length",
|
|
51
|
+
...Array.from({ length }, (_unused, index) => String(index)),
|
|
52
|
+
]);
|
|
53
|
+
const ownKeys = Reflect.ownKeys(stamps);
|
|
54
|
+
if (ownKeys.length !== expectedKeys.size ||
|
|
55
|
+
ownKeys.some(key => typeof key !== "string" || !expectedKeys.has(key))) {
|
|
56
|
+
throw new Error("Invalid supersede array: expected dense indexed values without extra or symbol fields");
|
|
57
|
+
}
|
|
58
|
+
return Object.freeze(Array.from({ length }, (_unused, index) => {
|
|
59
|
+
const descriptor = Object.getOwnPropertyDescriptor(stamps, String(index));
|
|
60
|
+
if (descriptor === undefined || !("value" in descriptor)) {
|
|
61
|
+
throw new Error("Invalid supersede array: indexed accessors are not allowed");
|
|
62
|
+
}
|
|
63
|
+
return validateStamp(descriptor.value);
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Plan an all-or-nothing stamped replacement against locked current state.
|
|
68
|
+
*
|
|
69
|
+
* Fingerprint collision is checked first because it is the durable dedupe
|
|
70
|
+
* boundary. An exact target set is removed together and the lexicographically
|
|
71
|
+
* first target id becomes the stable primary. Any missing or changed target
|
|
72
|
+
* makes the whole request a safe append: no target is removed and no alias is
|
|
73
|
+
* claimed.
|
|
74
|
+
* @param entries - Current validated entries read inside the lock
|
|
75
|
+
* @param candidate - New validated content composed by the caller
|
|
76
|
+
* @param expected - Validated version stamps from the caller's snapshot
|
|
77
|
+
* @returns Deterministic append-or-replace plan
|
|
78
|
+
*/
|
|
79
|
+
export function planLearningSupersede(entries, candidate, expected) {
|
|
80
|
+
const stale = expected.flatMap(stamp => staleTarget(entries, stamp));
|
|
81
|
+
const targetIds = new Set(expected.map(stamp => stamp.id));
|
|
82
|
+
const removed = sortLearningEntries(entries.filter(entry => targetIds.has(entry.id)));
|
|
83
|
+
const primary = removed[0];
|
|
84
|
+
const retained = entries.filter(entry => !targetIds.has(entry.id));
|
|
85
|
+
if (entries.some(entry => entry.fingerprint === candidate.fingerprint)) {
|
|
86
|
+
throw new Error(`Duplicate learning fingerprint: ${candidate.fingerprint}`);
|
|
87
|
+
}
|
|
88
|
+
if (stale.length > 0 || expected.length === 0) {
|
|
89
|
+
if (entries.some(entry => entry.id === candidate.id)) {
|
|
90
|
+
throw new Error(`Duplicate learning id: ${candidate.id}`);
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
entry: candidate,
|
|
94
|
+
retained: entries,
|
|
95
|
+
removed: [],
|
|
96
|
+
stale,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (primary === undefined) {
|
|
100
|
+
throw new Error("Invalid supersede plan: exact target set was empty");
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
entry: validateLearningEntry({ ...candidate, id: primary.id }),
|
|
104
|
+
retained,
|
|
105
|
+
removed,
|
|
106
|
+
stale: [],
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Validate one inert, exact two-field stamp.
|
|
111
|
+
* @param candidate - Untrusted stamp value
|
|
112
|
+
* @returns Frozen exact stamp
|
|
113
|
+
*/
|
|
114
|
+
function validateStamp(candidate) {
|
|
115
|
+
if (candidate === null ||
|
|
116
|
+
typeof candidate !== "object" ||
|
|
117
|
+
Array.isArray(candidate)) {
|
|
118
|
+
throw new Error(EXACT_STAMP_ERROR);
|
|
119
|
+
}
|
|
120
|
+
const keys = Reflect.ownKeys(candidate);
|
|
121
|
+
if (keys.length !== 2 ||
|
|
122
|
+
keys.some(key => typeof key !== "string" || (key !== "id" && key !== "fingerprint")) ||
|
|
123
|
+
!Object.hasOwn(candidate, "id") ||
|
|
124
|
+
!Object.hasOwn(candidate, "fingerprint")) {
|
|
125
|
+
throw new Error(EXACT_STAMP_ERROR);
|
|
126
|
+
}
|
|
127
|
+
const idDescriptor = Object.getOwnPropertyDescriptor(candidate, "id");
|
|
128
|
+
const fingerprintDescriptor = Object.getOwnPropertyDescriptor(candidate, "fingerprint");
|
|
129
|
+
if (idDescriptor === undefined || fingerprintDescriptor === undefined) {
|
|
130
|
+
throw new Error(EXACT_STAMP_ERROR);
|
|
131
|
+
}
|
|
132
|
+
const id = readStampToken(idDescriptor, "id");
|
|
133
|
+
const fingerprint = readStampToken(fingerprintDescriptor, "fingerprint");
|
|
134
|
+
return Object.freeze({ id, fingerprint });
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Read one accessor-free stable token from a stamp descriptor.
|
|
138
|
+
* @param descriptor - Own field descriptor
|
|
139
|
+
* @param field - Stamp field being validated
|
|
140
|
+
* @returns Validated stable token
|
|
141
|
+
*/
|
|
142
|
+
function readStampToken(descriptor, field) {
|
|
143
|
+
if (!("value" in descriptor) || typeof descriptor.value !== "string") {
|
|
144
|
+
throw new Error(`Invalid supersede stamp ${field}: expected a data string`);
|
|
145
|
+
}
|
|
146
|
+
const value = descriptor.value;
|
|
147
|
+
if (Buffer.byteLength(value, "utf8") > MAX_STABLE_TOKEN_BYTES) {
|
|
148
|
+
throw new Error(`Invalid supersede stamp ${field}: ${field} exceeds max stable token bytes ${MAX_STABLE_TOKEN_BYTES}`);
|
|
149
|
+
}
|
|
150
|
+
if (value.trim() !== value || !STABLE_TOKEN.test(value)) {
|
|
151
|
+
throw new Error(`Invalid supersede stamp ${field}: expected a stable token`);
|
|
152
|
+
}
|
|
153
|
+
return value;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Describe a target that no longer matches its exact stamp.
|
|
157
|
+
* @param entries - Current entries inside the writer lock
|
|
158
|
+
* @param expected - Caller-observed target stamp
|
|
159
|
+
* @returns Empty for an exact match, otherwise one stale diagnosis
|
|
160
|
+
*/
|
|
161
|
+
function staleTarget(entries, expected) {
|
|
162
|
+
const actual = entries.find(entry => entry.id === expected.id);
|
|
163
|
+
if (actual === undefined) {
|
|
164
|
+
return [{ expected, reason: "absent" }];
|
|
165
|
+
}
|
|
166
|
+
if (actual.fingerprint !== expected.fingerprint) {
|
|
167
|
+
return [
|
|
168
|
+
{
|
|
169
|
+
expected,
|
|
170
|
+
actualFingerprint: actual.fingerprint,
|
|
171
|
+
reason: "fingerprint-mismatch",
|
|
172
|
+
},
|
|
173
|
+
];
|
|
174
|
+
}
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Compare tokens for locale-independent primary selection.
|
|
179
|
+
* @param left - First token
|
|
180
|
+
* @param right - Second token
|
|
181
|
+
* @returns Negative, zero, or positive ordering result
|
|
182
|
+
*/
|
|
183
|
+
function compareTokens(left, right) {
|
|
184
|
+
return left === right ? 0 : left < right ? -1 : 1;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Sort entries by stable id without mutating shared state.
|
|
188
|
+
* @param entries - Entries in arbitrary order
|
|
189
|
+
* @returns New deterministically ordered array
|
|
190
|
+
*/
|
|
191
|
+
function sortLearningEntries(entries) {
|
|
192
|
+
return entries.reduce((ordered, entry) => {
|
|
193
|
+
const insertion = ordered.findIndex(current => compareTokens(entry.id, current.id) < 0);
|
|
194
|
+
return insertion === -1
|
|
195
|
+
? [...ordered, entry]
|
|
196
|
+
: [...ordered.slice(0, insertion), entry, ...ordered.slice(insertion)];
|
|
197
|
+
}, []);
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=learnings-supersede.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learnings-supersede.js","sourceRoot":"","sources":["../../src/core/learnings-supersede.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import { type LearningEntryStamp, type StaleSupersedeTarget } from "./learnings-supersede.js";
|
|
1
2
|
/** Write-time consolidation options for {@link persistConsolidatedLearning}. */
|
|
2
3
|
export interface ConsolidatedLearningOptions {
|
|
3
|
-
/**
|
|
4
|
-
readonly supersede?: readonly
|
|
4
|
+
/** Exact versions of existing entries the new entry merges or replaces. */
|
|
5
|
+
readonly supersede?: readonly LearningEntryStamp[];
|
|
6
|
+
/** Called when any target no longer matches its observed version stamp. */
|
|
7
|
+
readonly onStaleSupersede?: (targets: readonly StaleSupersedeTarget[]) => void;
|
|
5
8
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* succeeds; this is a diagnostic, not a failure hook.
|
|
9
|
+
* Compatibility diagnostic for targets that are absent (not merely changed).
|
|
10
|
+
* @deprecated Use {@link onStaleSupersede}, which distinguishes both cases.
|
|
9
11
|
*/
|
|
10
12
|
readonly onAbsentSupersede?: (ids: readonly string[]) => void;
|
|
11
13
|
/**
|
|
@@ -21,7 +23,7 @@ export interface ConsolidatedLearningOptions {
|
|
|
21
23
|
* entry point; consolidation-aware writers use
|
|
22
24
|
* {@link persistConsolidatedLearning} instead.
|
|
23
25
|
* @param projectRoot - Absolute path to the host project root
|
|
24
|
-
* @param candidate - Untrusted
|
|
26
|
+
* @param candidate - Untrusted eight-field learning entry
|
|
25
27
|
* @returns Absolute path to the persisted learnings file
|
|
26
28
|
*/
|
|
27
29
|
export declare function persistLearningEntry(projectRoot: string, candidate: unknown): Promise<string>;
|
|
@@ -34,7 +36,7 @@ export declare function persistLearningEntry(projectRoot: string, candidate: unk
|
|
|
34
36
|
* consolidation. Without `supersede` this is exactly the append-only
|
|
35
37
|
* {@link persistLearningEntry} behavior, including the duplicate-id throw.
|
|
36
38
|
* @param projectRoot - Absolute path to the host project root
|
|
37
|
-
* @param candidate - Untrusted
|
|
39
|
+
* @param candidate - Untrusted eight-field learning entry
|
|
38
40
|
* @param options - Optional consolidation directives
|
|
39
41
|
* @returns Absolute path to the persisted learnings file
|
|
40
42
|
*/
|