@polintpro/proposit-core 0.3.0 → 0.5.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/README.md +13 -0
- package/dist/cli/commands/sources.d.ts +3 -0
- package/dist/cli/commands/sources.d.ts.map +1 -0
- package/dist/cli/commands/sources.js +118 -0
- package/dist/cli/commands/sources.js.map +1 -0
- package/dist/cli/commands/variables.d.ts.map +1 -1
- package/dist/cli/commands/variables.js +3 -0
- package/dist/cli/commands/variables.js.map +1 -1
- package/dist/cli/config.d.ts +2 -0
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +6 -0
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/engine.d.ts.map +1 -1
- package/dist/cli/engine.js +35 -2
- package/dist/cli/engine.js.map +1 -1
- package/dist/cli/import.d.ts.map +1 -1
- package/dist/cli/import.js +7 -1
- package/dist/cli/import.js.map +1 -1
- package/dist/cli/schemata.d.ts +8 -0
- package/dist/cli/schemata.d.ts.map +1 -1
- package/dist/cli/schemata.js +10 -0
- package/dist/cli/schemata.js.map +1 -1
- package/dist/cli/storage/sources.d.ts +11 -0
- package/dist/cli/storage/sources.d.ts.map +1 -0
- package/dist/cli/storage/sources.js +105 -0
- package/dist/cli/storage/sources.js.map +1 -0
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/extensions/ieee/index.d.ts +3 -0
- package/dist/extensions/ieee/index.d.ts.map +1 -0
- package/dist/extensions/ieee/index.js +3 -0
- package/dist/extensions/ieee/index.js.map +1 -0
- package/dist/extensions/ieee/references.d.ts +620 -0
- package/dist/extensions/ieee/references.d.ts.map +1 -0
- package/dist/extensions/ieee/references.js +450 -0
- package/dist/extensions/ieee/references.js.map +1 -0
- package/dist/extensions/ieee/source.d.ts +286 -0
- package/dist/extensions/ieee/source.d.ts.map +1 -0
- package/dist/extensions/ieee/source.js +12 -0
- package/dist/extensions/ieee/source.js.map +1 -0
- package/dist/lib/consts.d.ts.map +1 -1
- package/dist/lib/consts.js +31 -1
- package/dist/lib/consts.js.map +1 -1
- package/dist/lib/core/argument-engine.d.ts +25 -117
- package/dist/lib/core/argument-engine.d.ts.map +1 -1
- package/dist/lib/core/argument-engine.js +239 -123
- package/dist/lib/core/argument-engine.js.map +1 -1
- package/dist/lib/core/assertion-library.d.ts +27 -0
- package/dist/lib/core/assertion-library.d.ts.map +1 -0
- package/dist/lib/core/assertion-library.js +129 -0
- package/dist/lib/core/assertion-library.js.map +1 -0
- package/dist/lib/core/change-collector.d.ts +7 -5
- package/dist/lib/core/change-collector.d.ts.map +1 -1
- package/dist/lib/core/change-collector.js +18 -5
- package/dist/lib/core/change-collector.js.map +1 -1
- package/dist/lib/core/diff.d.ts +6 -2
- package/dist/lib/core/diff.d.ts.map +1 -1
- package/dist/lib/core/diff.js +80 -1
- package/dist/lib/core/diff.js.map +1 -1
- package/dist/lib/core/interfaces/argument-engine.interfaces.d.ts +338 -0
- package/dist/lib/core/interfaces/argument-engine.interfaces.d.ts.map +1 -0
- package/dist/lib/core/interfaces/argument-engine.interfaces.js +2 -0
- package/dist/lib/core/interfaces/argument-engine.interfaces.js.map +1 -0
- package/dist/lib/core/interfaces/index.d.ts +6 -0
- package/dist/lib/core/interfaces/index.d.ts.map +1 -0
- package/dist/lib/core/interfaces/index.js +2 -0
- package/dist/lib/core/interfaces/index.js.map +1 -0
- package/dist/lib/core/interfaces/library.interfaces.d.ts +19 -0
- package/dist/lib/core/interfaces/library.interfaces.d.ts.map +1 -0
- package/dist/lib/core/interfaces/library.interfaces.js +2 -0
- package/dist/lib/core/interfaces/library.interfaces.js.map +1 -0
- package/dist/lib/core/interfaces/premise-engine.interfaces.d.ts +318 -0
- package/dist/lib/core/interfaces/premise-engine.interfaces.d.ts.map +1 -0
- package/dist/lib/core/interfaces/premise-engine.interfaces.js +2 -0
- package/dist/lib/core/interfaces/premise-engine.interfaces.js.map +1 -0
- package/dist/lib/core/interfaces/shared.interfaces.d.ts +24 -0
- package/dist/lib/core/interfaces/shared.interfaces.d.ts.map +1 -0
- package/dist/lib/core/interfaces/shared.interfaces.js +2 -0
- package/dist/lib/core/interfaces/shared.interfaces.js.map +1 -0
- package/dist/lib/core/interfaces/source-management.interfaces.d.ts +63 -0
- package/dist/lib/core/interfaces/source-management.interfaces.d.ts.map +1 -0
- package/dist/lib/core/interfaces/source-management.interfaces.js +2 -0
- package/dist/lib/core/interfaces/source-management.interfaces.js.map +1 -0
- package/dist/lib/core/premise-engine.d.ts +10 -144
- package/dist/lib/core/premise-engine.d.ts.map +1 -1
- package/dist/lib/core/premise-engine.js +82 -143
- package/dist/lib/core/premise-engine.js.map +1 -1
- package/dist/lib/core/source-library.d.ts +27 -0
- package/dist/lib/core/source-library.d.ts.map +1 -0
- package/dist/lib/core/source-library.js +129 -0
- package/dist/lib/core/source-library.js.map +1 -0
- package/dist/lib/core/source-manager.d.ts +38 -0
- package/dist/lib/core/source-manager.d.ts.map +1 -0
- package/dist/lib/core/source-manager.js +266 -0
- package/dist/lib/core/source-manager.js.map +1 -0
- package/dist/lib/index.d.ts +6 -1
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +4 -1
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/schemata/assertion.d.ts +9 -0
- package/dist/lib/schemata/assertion.d.ts.map +1 -0
- package/dist/lib/schemata/assertion.js +18 -0
- package/dist/lib/schemata/assertion.js.map +1 -0
- package/dist/lib/schemata/index.d.ts +2 -0
- package/dist/lib/schemata/index.d.ts.map +1 -1
- package/dist/lib/schemata/index.js +2 -0
- package/dist/lib/schemata/index.js.map +1 -1
- package/dist/lib/schemata/propositional.d.ts +2 -0
- package/dist/lib/schemata/propositional.d.ts.map +1 -1
- package/dist/lib/schemata/propositional.js +5 -1
- package/dist/lib/schemata/propositional.js.map +1 -1
- package/dist/lib/schemata/source.d.ts +30 -0
- package/dist/lib/schemata/source.d.ts.map +1 -0
- package/dist/lib/schemata/source.js +45 -0
- package/dist/lib/schemata/source.js.map +1 -0
- package/dist/lib/types/checksum.d.ts +8 -0
- package/dist/lib/types/checksum.d.ts.map +1 -1
- package/dist/lib/types/diff.d.ts +5 -1
- package/dist/lib/types/diff.d.ts.map +1 -1
- package/dist/lib/types/evaluation.d.ts +1 -1
- package/dist/lib/types/evaluation.d.ts.map +1 -1
- package/dist/lib/types/mutation.d.ts +3 -0
- package/dist/lib/types/mutation.d.ts.map +1 -1
- package/dist/lib/types/reactive.d.ts +3 -1
- package/dist/lib/types/reactive.d.ts.map +1 -1
- package/package.json +5 -1
|
@@ -7,6 +7,7 @@ import { kleeneAnd, kleeneNot } from "./evaluation/kleene.js";
|
|
|
7
7
|
import { makeErrorIssue, makeValidationResult, } from "./evaluation/validation.js";
|
|
8
8
|
import { PremiseEngine } from "./premise-engine.js";
|
|
9
9
|
import { VariableManager } from "./variable-manager.js";
|
|
10
|
+
import { SourceManager } from "./source-manager.js";
|
|
10
11
|
/**
|
|
11
12
|
* Manages a propositional logic argument composed of premises, variable
|
|
12
13
|
* assignments, and logical roles (supporting premises and a conclusion).
|
|
@@ -18,6 +19,9 @@ export class ArgumentEngine {
|
|
|
18
19
|
argument;
|
|
19
20
|
premises;
|
|
20
21
|
variables;
|
|
22
|
+
sourceManager;
|
|
23
|
+
assertionLibrary;
|
|
24
|
+
sourceLibrary;
|
|
21
25
|
conclusionPremiseId;
|
|
22
26
|
checksumConfig;
|
|
23
27
|
positionConfig;
|
|
@@ -29,26 +33,26 @@ export class ArgumentEngine {
|
|
|
29
33
|
argument: true,
|
|
30
34
|
variables: true,
|
|
31
35
|
roles: true,
|
|
36
|
+
sources: true,
|
|
32
37
|
premiseIds: new Set(),
|
|
33
38
|
allPremises: true,
|
|
34
39
|
};
|
|
35
40
|
cachedReactiveSnapshot;
|
|
36
|
-
constructor(argument, options) {
|
|
41
|
+
constructor(argument, assertionLibrary, sourceLibrary, options) {
|
|
37
42
|
this.argument = { ...argument };
|
|
43
|
+
this.assertionLibrary = assertionLibrary;
|
|
44
|
+
this.sourceLibrary = sourceLibrary;
|
|
38
45
|
this.premises = new Map();
|
|
46
|
+
this.checksumConfig = options?.checksumConfig;
|
|
47
|
+
this.positionConfig = options?.positionConfig;
|
|
39
48
|
this.variables = new VariableManager({
|
|
40
49
|
checksumConfig: this.checksumConfig,
|
|
41
50
|
positionConfig: this.positionConfig,
|
|
42
51
|
});
|
|
52
|
+
this.sourceManager = new SourceManager();
|
|
43
53
|
this.expressionIndex = new Map();
|
|
44
54
|
this.conclusionPremiseId = undefined;
|
|
45
|
-
this.checksumConfig = options?.checksumConfig;
|
|
46
|
-
this.positionConfig = options?.positionConfig;
|
|
47
55
|
}
|
|
48
|
-
/**
|
|
49
|
-
* Registers a listener that is called after every mutation.
|
|
50
|
-
* Returns an unsubscribe function.
|
|
51
|
-
*/
|
|
52
56
|
subscribe = (listener) => {
|
|
53
57
|
this.listeners.add(listener);
|
|
54
58
|
return () => {
|
|
@@ -70,6 +74,7 @@ export class ArgumentEngine {
|
|
|
70
74
|
!dirty.argument &&
|
|
71
75
|
!dirty.variables &&
|
|
72
76
|
!dirty.roles &&
|
|
77
|
+
!dirty.sources &&
|
|
73
78
|
dirty.premiseIds.size === 0 &&
|
|
74
79
|
!dirty.allPremises) {
|
|
75
80
|
return prev;
|
|
@@ -108,17 +113,36 @@ export class ArgumentEngine {
|
|
|
108
113
|
}
|
|
109
114
|
}
|
|
110
115
|
}
|
|
116
|
+
let varAssocRecord;
|
|
117
|
+
let exprAssocRecord;
|
|
118
|
+
if (dirty.sources || !prev) {
|
|
119
|
+
varAssocRecord = {};
|
|
120
|
+
for (const a of this.sourceManager.getAllVariableSourceAssociations()) {
|
|
121
|
+
varAssocRecord[a.id] = a;
|
|
122
|
+
}
|
|
123
|
+
exprAssocRecord = {};
|
|
124
|
+
for (const a of this.sourceManager.getAllExpressionSourceAssociations()) {
|
|
125
|
+
exprAssocRecord[a.id] = a;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
varAssocRecord = prev.variableSourceAssociations;
|
|
130
|
+
exprAssocRecord = prev.expressionSourceAssociations;
|
|
131
|
+
}
|
|
111
132
|
const snapshot = {
|
|
112
133
|
argument,
|
|
113
134
|
variables,
|
|
114
135
|
premises,
|
|
115
136
|
roles,
|
|
137
|
+
variableSourceAssociations: varAssocRecord,
|
|
138
|
+
expressionSourceAssociations: exprAssocRecord,
|
|
116
139
|
};
|
|
117
140
|
this.cachedReactiveSnapshot = snapshot;
|
|
118
141
|
this.reactiveDirty = {
|
|
119
142
|
argument: false,
|
|
120
143
|
variables: false,
|
|
121
144
|
roles: false,
|
|
145
|
+
sources: false,
|
|
122
146
|
premiseIds: new Set(),
|
|
123
147
|
allPremises: false,
|
|
124
148
|
};
|
|
@@ -178,12 +202,14 @@ export class ArgumentEngine {
|
|
|
178
202
|
this.reactiveDirty.premiseIds.add(p.id);
|
|
179
203
|
}
|
|
180
204
|
}
|
|
205
|
+
if (changes.variableSourceAssociations ||
|
|
206
|
+
changes.expressionSourceAssociations) {
|
|
207
|
+
this.reactiveDirty.sources = true;
|
|
208
|
+
}
|
|
181
209
|
}
|
|
182
|
-
/** Returns a shallow copy of the argument metadata with checksum attached. */
|
|
183
210
|
getArgument() {
|
|
184
211
|
return { ...this.argument, checksum: this.checksum() };
|
|
185
212
|
}
|
|
186
|
-
/** Renders the argument as a multi-line string with role labels for each premise. */
|
|
187
213
|
toDisplayString() {
|
|
188
214
|
const lines = [];
|
|
189
215
|
const arg = this.getArgument();
|
|
@@ -206,19 +232,9 @@ export class ArgumentEngine {
|
|
|
206
232
|
}
|
|
207
233
|
return lines.join("\n");
|
|
208
234
|
}
|
|
209
|
-
/**
|
|
210
|
-
* Creates a new premise with an auto-generated UUID and registers it
|
|
211
|
-
* with this engine.
|
|
212
|
-
*/
|
|
213
235
|
createPremise(extras) {
|
|
214
236
|
return this.createPremiseWithId(randomUUID(), extras);
|
|
215
237
|
}
|
|
216
|
-
/**
|
|
217
|
-
* Creates a premise with a caller-supplied ID and registers it with
|
|
218
|
-
* this engine.
|
|
219
|
-
*
|
|
220
|
-
* @throws If a premise with the given ID already exists.
|
|
221
|
-
*/
|
|
222
238
|
createPremiseWithId(id, extras) {
|
|
223
239
|
if (this.premises.has(id)) {
|
|
224
240
|
throw new Error(`Premise "${id}" already exists.`);
|
|
@@ -233,6 +249,7 @@ export class ArgumentEngine {
|
|
|
233
249
|
argument: this.argument,
|
|
234
250
|
variables: this.variables,
|
|
235
251
|
expressionIndex: this.expressionIndex,
|
|
252
|
+
sourceManager: this.sourceManager,
|
|
236
253
|
}, {
|
|
237
254
|
checksumConfig: this.checksumConfig,
|
|
238
255
|
positionConfig: this.positionConfig,
|
|
@@ -257,21 +274,21 @@ export class ArgumentEngine {
|
|
|
257
274
|
changes,
|
|
258
275
|
};
|
|
259
276
|
}
|
|
260
|
-
/**
|
|
261
|
-
* Removes a premise and clears any role assignments that reference it.
|
|
262
|
-
* Returns the removed premise data, or `undefined` if not found.
|
|
263
|
-
*/
|
|
264
277
|
removePremise(premiseId) {
|
|
265
278
|
const pm = this.premises.get(premiseId);
|
|
266
279
|
if (!pm)
|
|
267
280
|
return { result: undefined, changes: {} };
|
|
268
281
|
const data = pm.toPremiseData();
|
|
269
|
-
|
|
282
|
+
const collector = new ChangeCollector();
|
|
283
|
+
// Clean up expression index and source associations for removed premise's expressions
|
|
270
284
|
for (const expr of pm.getExpressions()) {
|
|
271
285
|
this.expressionIndex.delete(expr.id);
|
|
286
|
+
const sourceResult = this.sourceManager.removeAssociationsForExpression(expr.id);
|
|
287
|
+
for (const assoc of sourceResult.removedExpressionAssociations) {
|
|
288
|
+
collector.removedExpressionSourceAssociation(assoc);
|
|
289
|
+
}
|
|
272
290
|
}
|
|
273
291
|
this.premises.delete(premiseId);
|
|
274
|
-
const collector = new ChangeCollector();
|
|
275
292
|
collector.removedPremise(data);
|
|
276
293
|
if (this.conclusionPremiseId === premiseId) {
|
|
277
294
|
this.conclusionPremiseId = undefined;
|
|
@@ -286,31 +303,20 @@ export class ArgumentEngine {
|
|
|
286
303
|
changes,
|
|
287
304
|
};
|
|
288
305
|
}
|
|
289
|
-
/** Returns the premise with the given ID, or `undefined` if not found. */
|
|
290
306
|
getPremise(premiseId) {
|
|
291
307
|
return this.premises.get(premiseId);
|
|
292
308
|
}
|
|
293
|
-
/** Returns `true` if a premise with the given ID exists. */
|
|
294
309
|
hasPremise(premiseId) {
|
|
295
310
|
return this.premises.has(premiseId);
|
|
296
311
|
}
|
|
297
|
-
/** Returns all premise IDs in lexicographic order. */
|
|
298
312
|
listPremiseIds() {
|
|
299
313
|
return Array.from(this.premises.keys()).sort((a, b) => a.localeCompare(b));
|
|
300
314
|
}
|
|
301
|
-
/** Returns all premises in lexicographic ID order. */
|
|
302
315
|
listPremises() {
|
|
303
316
|
return this.listPremiseIds()
|
|
304
317
|
.map((id) => this.premises.get(id))
|
|
305
318
|
.filter((pm) => pm !== undefined);
|
|
306
319
|
}
|
|
307
|
-
/**
|
|
308
|
-
* Registers a propositional variable for use across all premises.
|
|
309
|
-
*
|
|
310
|
-
* @throws If `variable.symbol` is already in use.
|
|
311
|
-
* @throws If `variable.id` already exists.
|
|
312
|
-
* @throws If the variable does not belong to this argument.
|
|
313
|
-
*/
|
|
314
320
|
addVariable(variable) {
|
|
315
321
|
if (variable.argumentId !== this.argument.id) {
|
|
316
322
|
throw new Error(`Variable argumentId "${variable.argumentId}" does not match engine argument ID "${this.argument.id}".`);
|
|
@@ -318,6 +324,10 @@ export class ArgumentEngine {
|
|
|
318
324
|
if (variable.argumentVersion !== this.argument.version) {
|
|
319
325
|
throw new Error(`Variable argumentVersion "${variable.argumentVersion}" does not match engine argument version "${this.argument.version}".`);
|
|
320
326
|
}
|
|
327
|
+
// Validate assertion reference
|
|
328
|
+
if (!this.assertionLibrary.get(variable.assertionId, variable.assertionVersion)) {
|
|
329
|
+
throw new Error(`Assertion "${variable.assertionId}" version ${variable.assertionVersion} does not exist in the assertion library.`);
|
|
330
|
+
}
|
|
321
331
|
const withChecksum = this.attachVariableChecksum({ ...variable });
|
|
322
332
|
this.variables.addVariable(withChecksum);
|
|
323
333
|
const collector = new ChangeCollector();
|
|
@@ -332,13 +342,19 @@ export class ArgumentEngine {
|
|
|
332
342
|
changes,
|
|
333
343
|
};
|
|
334
344
|
}
|
|
335
|
-
/**
|
|
336
|
-
* Updates fields on an existing variable. Since all premises share the
|
|
337
|
-
* same VariableManager, the update is immediately visible everywhere.
|
|
338
|
-
*
|
|
339
|
-
* @throws If the new symbol is already in use by a different variable.
|
|
340
|
-
*/
|
|
341
345
|
updateVariable(variableId, updates) {
|
|
346
|
+
// Validate: assertionId and assertionVersion must be provided together
|
|
347
|
+
const hasAssertionId = updates.assertionId !== undefined;
|
|
348
|
+
const hasAssertionVersion = updates.assertionVersion !== undefined;
|
|
349
|
+
if (hasAssertionId !== hasAssertionVersion) {
|
|
350
|
+
throw new Error("assertionId and assertionVersion must be provided together.");
|
|
351
|
+
}
|
|
352
|
+
// Validate assertion reference if provided
|
|
353
|
+
if (hasAssertionId && hasAssertionVersion) {
|
|
354
|
+
if (!this.assertionLibrary.get(updates.assertionId, updates.assertionVersion)) {
|
|
355
|
+
throw new Error(`Assertion "${updates.assertionId}" version ${updates.assertionVersion} does not exist in the assertion library.`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
342
358
|
const updated = this.variables.updateVariable(variableId, updates);
|
|
343
359
|
const collector = new ChangeCollector();
|
|
344
360
|
if (updated) {
|
|
@@ -363,10 +379,6 @@ export class ArgumentEngine {
|
|
|
363
379
|
changes: collector.toChangeset(),
|
|
364
380
|
};
|
|
365
381
|
}
|
|
366
|
-
/**
|
|
367
|
-
* Removes a variable and cascade-deletes all expressions referencing it
|
|
368
|
-
* across every premise (including subtrees and operator collapse).
|
|
369
|
-
*/
|
|
370
382
|
removeVariable(variableId) {
|
|
371
383
|
const variable = this.variables.getVariable(variableId);
|
|
372
384
|
if (!variable) {
|
|
@@ -374,6 +386,8 @@ export class ArgumentEngine {
|
|
|
374
386
|
}
|
|
375
387
|
const collector = new ChangeCollector();
|
|
376
388
|
// Cascade: delete referencing expressions in every premise
|
|
389
|
+
// (PremiseEngine.removeExpression already cascades expression-source
|
|
390
|
+
// associations via the Task 12 logic)
|
|
377
391
|
for (const pm of this.listPremises()) {
|
|
378
392
|
const { changes } = pm.deleteExpressionsUsingVariable(variableId);
|
|
379
393
|
if (changes.expressions) {
|
|
@@ -381,6 +395,16 @@ export class ArgumentEngine {
|
|
|
381
395
|
collector.removedExpression(e);
|
|
382
396
|
}
|
|
383
397
|
}
|
|
398
|
+
if (changes.expressionSourceAssociations) {
|
|
399
|
+
for (const a of changes.expressionSourceAssociations.removed) {
|
|
400
|
+
collector.removedExpressionSourceAssociation(a);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
// Cascade: remove variable-source associations
|
|
405
|
+
const varAssocResult = this.sourceManager.removeAssociationsForVariable(variableId);
|
|
406
|
+
for (const assoc of varAssocResult.removedVariableAssociations) {
|
|
407
|
+
collector.removedVariableSourceAssociation(assoc);
|
|
384
408
|
}
|
|
385
409
|
this.variables.removeVariable(variableId);
|
|
386
410
|
collector.removedVariable(variable);
|
|
@@ -394,27 +418,18 @@ export class ArgumentEngine {
|
|
|
394
418
|
changes,
|
|
395
419
|
};
|
|
396
420
|
}
|
|
397
|
-
/** Returns all registered variables sorted by ID. */
|
|
398
421
|
getVariables() {
|
|
399
422
|
return this.variables.toArray();
|
|
400
423
|
}
|
|
401
|
-
/** Returns the variable with the given ID, or `undefined` if not found. */
|
|
402
424
|
getVariable(variableId) {
|
|
403
425
|
return this.variables.getVariable(variableId);
|
|
404
426
|
}
|
|
405
|
-
/** Returns `true` if a variable with the given ID exists. */
|
|
406
427
|
hasVariable(variableId) {
|
|
407
428
|
return this.variables.hasVariable(variableId);
|
|
408
429
|
}
|
|
409
|
-
/** Returns the variable with the given symbol, or `undefined` if not found. */
|
|
410
430
|
getVariableBySymbol(symbol) {
|
|
411
431
|
return this.variables.getVariableBySymbol(symbol);
|
|
412
432
|
}
|
|
413
|
-
/**
|
|
414
|
-
* Builds a Map keyed by a caller-supplied function over all variables.
|
|
415
|
-
* Useful for indexing by extension fields (e.g. statementId).
|
|
416
|
-
* The caller should cache the result — this is O(n) per call.
|
|
417
|
-
*/
|
|
418
433
|
buildVariableIndex(keyFn) {
|
|
419
434
|
const map = new Map();
|
|
420
435
|
for (const v of this.variables.toArray()) {
|
|
@@ -422,29 +437,24 @@ export class ArgumentEngine {
|
|
|
422
437
|
}
|
|
423
438
|
return map;
|
|
424
439
|
}
|
|
425
|
-
/** Returns an expression by ID from any premise, or `undefined` if not found. */
|
|
426
440
|
getExpression(expressionId) {
|
|
427
441
|
const premiseId = this.expressionIndex.get(expressionId);
|
|
428
442
|
if (premiseId === undefined)
|
|
429
443
|
return undefined;
|
|
430
444
|
return this.premises.get(premiseId)?.getExpression(expressionId);
|
|
431
445
|
}
|
|
432
|
-
/** Returns `true` if an expression with the given ID exists in any premise. */
|
|
433
446
|
hasExpression(expressionId) {
|
|
434
447
|
return this.expressionIndex.has(expressionId);
|
|
435
448
|
}
|
|
436
|
-
/** Returns the premise ID that contains the given expression, or `undefined`. */
|
|
437
449
|
getExpressionPremiseId(expressionId) {
|
|
438
450
|
return this.expressionIndex.get(expressionId);
|
|
439
451
|
}
|
|
440
|
-
/** Returns the PremiseEngine containing the given expression, or `undefined`. */
|
|
441
452
|
findPremiseByExpressionId(expressionId) {
|
|
442
453
|
const premiseId = this.expressionIndex.get(expressionId);
|
|
443
454
|
if (premiseId === undefined)
|
|
444
455
|
return undefined;
|
|
445
456
|
return this.premises.get(premiseId);
|
|
446
457
|
}
|
|
447
|
-
/** Returns all expressions across all premises, sorted by ID. */
|
|
448
458
|
getAllExpressions() {
|
|
449
459
|
const all = [];
|
|
450
460
|
for (const pe of this.listPremises()) {
|
|
@@ -452,10 +462,6 @@ export class ArgumentEngine {
|
|
|
452
462
|
}
|
|
453
463
|
return all.sort((a, b) => a.id.localeCompare(b.id));
|
|
454
464
|
}
|
|
455
|
-
/**
|
|
456
|
-
* Returns all expressions that reference the given variable ID,
|
|
457
|
-
* across all premises.
|
|
458
|
-
*/
|
|
459
465
|
getExpressionsByVariableId(variableId) {
|
|
460
466
|
const result = [];
|
|
461
467
|
for (const pe of this.listPremises()) {
|
|
@@ -471,7 +477,6 @@ export class ArgumentEngine {
|
|
|
471
477
|
}
|
|
472
478
|
return result;
|
|
473
479
|
}
|
|
474
|
-
/** Returns the root expression from each premise that has one. */
|
|
475
480
|
listRootExpressions() {
|
|
476
481
|
const roots = [];
|
|
477
482
|
for (const pe of this.listPremises()) {
|
|
@@ -481,7 +486,6 @@ export class ArgumentEngine {
|
|
|
481
486
|
}
|
|
482
487
|
return roots;
|
|
483
488
|
}
|
|
484
|
-
/** Returns the current role assignments (conclusion premise ID only; supporting is derived). */
|
|
485
489
|
getRoleState() {
|
|
486
490
|
return {
|
|
487
491
|
...(this.conclusionPremiseId !== undefined
|
|
@@ -489,11 +493,6 @@ export class ArgumentEngine {
|
|
|
489
493
|
: {}),
|
|
490
494
|
};
|
|
491
495
|
}
|
|
492
|
-
/**
|
|
493
|
-
* Designates a premise as the argument's conclusion.
|
|
494
|
-
*
|
|
495
|
-
* @throws If the premise does not exist.
|
|
496
|
-
*/
|
|
497
496
|
setConclusionPremise(premiseId) {
|
|
498
497
|
const premise = this.premises.get(premiseId);
|
|
499
498
|
if (!premise) {
|
|
@@ -512,7 +511,6 @@ export class ArgumentEngine {
|
|
|
512
511
|
changes,
|
|
513
512
|
};
|
|
514
513
|
}
|
|
515
|
-
/** Clears the conclusion designation. */
|
|
516
514
|
clearConclusionPremise() {
|
|
517
515
|
this.conclusionPremiseId = undefined;
|
|
518
516
|
const roles = this.getRoleState();
|
|
@@ -527,21 +525,138 @@ export class ArgumentEngine {
|
|
|
527
525
|
changes,
|
|
528
526
|
};
|
|
529
527
|
}
|
|
530
|
-
/** Returns the conclusion premise, or `undefined` if none is set. */
|
|
531
528
|
getConclusionPremise() {
|
|
532
529
|
if (this.conclusionPremiseId === undefined) {
|
|
533
530
|
return undefined;
|
|
534
531
|
}
|
|
535
532
|
return this.premises.get(this.conclusionPremiseId);
|
|
536
533
|
}
|
|
537
|
-
/**
|
|
538
|
-
* Returns all supporting premises (derived: inference premises that are
|
|
539
|
-
* not the conclusion) in lexicographic ID order.
|
|
540
|
-
*/
|
|
541
534
|
listSupportingPremises() {
|
|
542
535
|
return this.listPremises().filter((pm) => pm.isInference() && pm.getId() !== this.conclusionPremiseId);
|
|
543
536
|
}
|
|
544
|
-
|
|
537
|
+
// -------------------------------------------------------------------------
|
|
538
|
+
// Source association management
|
|
539
|
+
// -------------------------------------------------------------------------
|
|
540
|
+
addVariableSourceAssociation(sourceId, sourceVersion, variableId) {
|
|
541
|
+
if (!this.sourceLibrary.get(sourceId, sourceVersion)) {
|
|
542
|
+
throw new Error(`Source "${sourceId}" version ${sourceVersion} does not exist in the source library.`);
|
|
543
|
+
}
|
|
544
|
+
if (!this.variables.hasVariable(variableId)) {
|
|
545
|
+
throw new Error(`Variable "${variableId}" does not exist.`);
|
|
546
|
+
}
|
|
547
|
+
const assoc = {
|
|
548
|
+
id: randomUUID(),
|
|
549
|
+
sourceId,
|
|
550
|
+
sourceVersion,
|
|
551
|
+
variableId,
|
|
552
|
+
argumentId: this.argument.id,
|
|
553
|
+
argumentVersion: this.argument.version,
|
|
554
|
+
checksum: "",
|
|
555
|
+
};
|
|
556
|
+
const fields = this.checksumConfig?.variableSourceAssociationFields ??
|
|
557
|
+
DEFAULT_CHECKSUM_CONFIG.variableSourceAssociationFields;
|
|
558
|
+
const assocWithChecksum = {
|
|
559
|
+
...assoc,
|
|
560
|
+
checksum: entityChecksum(assoc, fields),
|
|
561
|
+
};
|
|
562
|
+
this.sourceManager.addVariableSourceAssociation(assocWithChecksum);
|
|
563
|
+
const collector = new ChangeCollector();
|
|
564
|
+
collector.addedVariableSourceAssociation(assocWithChecksum);
|
|
565
|
+
this.markDirty();
|
|
566
|
+
const changes = collector.toChangeset();
|
|
567
|
+
this.markReactiveDirty(changes);
|
|
568
|
+
this.notifySubscribers();
|
|
569
|
+
return { result: assocWithChecksum, changes };
|
|
570
|
+
}
|
|
571
|
+
removeVariableSourceAssociation(associationId) {
|
|
572
|
+
const allVarAssocs = this.sourceManager.getAllVariableSourceAssociations();
|
|
573
|
+
if (!allVarAssocs.some((a) => a.id === associationId)) {
|
|
574
|
+
return { result: undefined, changes: {} };
|
|
575
|
+
}
|
|
576
|
+
const removalResult = this.sourceManager.removeVariableSourceAssociation(associationId);
|
|
577
|
+
const collector = new ChangeCollector();
|
|
578
|
+
for (const assoc of removalResult.removedVariableAssociations) {
|
|
579
|
+
collector.removedVariableSourceAssociation(assoc);
|
|
580
|
+
}
|
|
581
|
+
this.markDirty();
|
|
582
|
+
const changes = collector.toChangeset();
|
|
583
|
+
this.markReactiveDirty(changes);
|
|
584
|
+
this.notifySubscribers();
|
|
585
|
+
return {
|
|
586
|
+
result: removalResult.removedVariableAssociations[0],
|
|
587
|
+
changes,
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
addExpressionSourceAssociation(sourceId, sourceVersion, expressionId, premiseId) {
|
|
591
|
+
if (!this.sourceLibrary.get(sourceId, sourceVersion)) {
|
|
592
|
+
throw new Error(`Source "${sourceId}" version ${sourceVersion} does not exist in the source library.`);
|
|
593
|
+
}
|
|
594
|
+
const pm = this.premises.get(premiseId);
|
|
595
|
+
if (!pm) {
|
|
596
|
+
throw new Error(`Premise "${premiseId}" does not exist.`);
|
|
597
|
+
}
|
|
598
|
+
if (!pm.getExpression(expressionId)) {
|
|
599
|
+
throw new Error(`Expression "${expressionId}" does not exist in premise "${premiseId}".`);
|
|
600
|
+
}
|
|
601
|
+
const assoc = {
|
|
602
|
+
id: randomUUID(),
|
|
603
|
+
sourceId,
|
|
604
|
+
sourceVersion,
|
|
605
|
+
expressionId,
|
|
606
|
+
premiseId,
|
|
607
|
+
argumentId: this.argument.id,
|
|
608
|
+
argumentVersion: this.argument.version,
|
|
609
|
+
checksum: "",
|
|
610
|
+
};
|
|
611
|
+
const fields = this.checksumConfig?.expressionSourceAssociationFields ??
|
|
612
|
+
DEFAULT_CHECKSUM_CONFIG.expressionSourceAssociationFields;
|
|
613
|
+
const assocWithChecksum = {
|
|
614
|
+
...assoc,
|
|
615
|
+
checksum: entityChecksum(assoc, fields),
|
|
616
|
+
};
|
|
617
|
+
this.sourceManager.addExpressionSourceAssociation(assocWithChecksum);
|
|
618
|
+
const collector = new ChangeCollector();
|
|
619
|
+
collector.addedExpressionSourceAssociation(assocWithChecksum);
|
|
620
|
+
this.markDirty();
|
|
621
|
+
const changes = collector.toChangeset();
|
|
622
|
+
this.markReactiveDirty(changes);
|
|
623
|
+
this.notifySubscribers();
|
|
624
|
+
return { result: assocWithChecksum, changes };
|
|
625
|
+
}
|
|
626
|
+
removeExpressionSourceAssociation(associationId) {
|
|
627
|
+
const allExprAssocs = this.sourceManager.getAllExpressionSourceAssociations();
|
|
628
|
+
if (!allExprAssocs.some((a) => a.id === associationId)) {
|
|
629
|
+
return { result: undefined, changes: {} };
|
|
630
|
+
}
|
|
631
|
+
const removalResult = this.sourceManager.removeExpressionSourceAssociation(associationId);
|
|
632
|
+
const collector = new ChangeCollector();
|
|
633
|
+
for (const assoc of removalResult.removedExpressionAssociations) {
|
|
634
|
+
collector.removedExpressionSourceAssociation(assoc);
|
|
635
|
+
}
|
|
636
|
+
this.markDirty();
|
|
637
|
+
const changes = collector.toChangeset();
|
|
638
|
+
this.markReactiveDirty(changes);
|
|
639
|
+
this.notifySubscribers();
|
|
640
|
+
return {
|
|
641
|
+
result: removalResult.removedExpressionAssociations[0],
|
|
642
|
+
changes,
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
getAssociationsForSource(sourceId) {
|
|
646
|
+
return this.sourceManager.getAssociationsForSource(sourceId);
|
|
647
|
+
}
|
|
648
|
+
getAssociationsForVariable(variableId) {
|
|
649
|
+
return this.sourceManager.getAssociationsForVariable(variableId);
|
|
650
|
+
}
|
|
651
|
+
getAssociationsForExpression(expressionId) {
|
|
652
|
+
return this.sourceManager.getAssociationsForExpression(expressionId);
|
|
653
|
+
}
|
|
654
|
+
getAllVariableSourceAssociations() {
|
|
655
|
+
return this.sourceManager.getAllVariableSourceAssociations();
|
|
656
|
+
}
|
|
657
|
+
getAllExpressionSourceAssociations() {
|
|
658
|
+
return this.sourceManager.getAllExpressionSourceAssociations();
|
|
659
|
+
}
|
|
545
660
|
snapshot() {
|
|
546
661
|
return {
|
|
547
662
|
argument: { ...this.argument },
|
|
@@ -554,18 +669,23 @@ export class ArgumentEngine {
|
|
|
554
669
|
checksumConfig: this.checksumConfig,
|
|
555
670
|
positionConfig: this.positionConfig,
|
|
556
671
|
},
|
|
672
|
+
sources: this.sourceManager.snapshot(),
|
|
557
673
|
};
|
|
558
674
|
}
|
|
559
675
|
/** Creates a new ArgumentEngine from a previously captured snapshot. */
|
|
560
|
-
static fromSnapshot(snapshot) {
|
|
561
|
-
const engine = new ArgumentEngine(snapshot.argument, snapshot.config);
|
|
676
|
+
static fromSnapshot(snapshot, assertionLibrary, sourceLibrary) {
|
|
677
|
+
const engine = new ArgumentEngine(snapshot.argument, assertionLibrary, sourceLibrary, snapshot.config);
|
|
562
678
|
// Restore variables
|
|
563
679
|
for (const v of snapshot.variables.variables) {
|
|
564
680
|
engine.addVariable(v);
|
|
565
681
|
}
|
|
682
|
+
// Restore source manager (before premises, so PremiseEngines get the correct reference)
|
|
683
|
+
if (snapshot.sources) {
|
|
684
|
+
engine.sourceManager = SourceManager.fromSnapshot(snapshot.sources);
|
|
685
|
+
}
|
|
566
686
|
// Restore premises using PremiseEngine.fromSnapshot
|
|
567
687
|
for (const premiseSnap of snapshot.premises) {
|
|
568
|
-
const pe = PremiseEngine.fromSnapshot(premiseSnap, snapshot.argument, engine.variables, engine.expressionIndex);
|
|
688
|
+
const pe = PremiseEngine.fromSnapshot(premiseSnap, snapshot.argument, engine.variables, engine.expressionIndex, engine.sourceManager);
|
|
569
689
|
engine.premises.set(pe.getId(), pe);
|
|
570
690
|
const premiseId = pe.getId();
|
|
571
691
|
pe.setOnMutate(() => {
|
|
@@ -583,8 +703,8 @@ export class ArgumentEngine {
|
|
|
583
703
|
* `premiseId` field and loaded in BFS order (roots first, then children
|
|
584
704
|
* of already-added nodes) to satisfy parent-existence requirements.
|
|
585
705
|
*/
|
|
586
|
-
static fromData(argument, variables, premises, expressions, roles, config) {
|
|
587
|
-
const engine = new ArgumentEngine(argument, config);
|
|
706
|
+
static fromData(argument, assertionLibrary, sourceLibrary, variables, premises, expressions, roles, config) {
|
|
707
|
+
const engine = new ArgumentEngine(argument, assertionLibrary, sourceLibrary, config);
|
|
588
708
|
// Register variables
|
|
589
709
|
for (const v of variables) {
|
|
590
710
|
engine.addVariable(v);
|
|
@@ -632,16 +752,18 @@ export class ArgumentEngine {
|
|
|
632
752
|
}
|
|
633
753
|
return engine;
|
|
634
754
|
}
|
|
635
|
-
/** Restores the engine to a previously captured snapshot state. */
|
|
636
755
|
rollback(snapshot) {
|
|
637
756
|
this.argument = { ...snapshot.argument };
|
|
638
757
|
this.checksumConfig = snapshot.config?.checksumConfig;
|
|
639
758
|
this.positionConfig = snapshot.config?.positionConfig;
|
|
640
759
|
this.variables = VariableManager.fromSnapshot(snapshot.variables);
|
|
760
|
+
this.sourceManager = snapshot.sources
|
|
761
|
+
? SourceManager.fromSnapshot(snapshot.sources)
|
|
762
|
+
: new SourceManager();
|
|
641
763
|
this.premises = new Map();
|
|
642
764
|
this.expressionIndex = new Map();
|
|
643
765
|
for (const premiseSnap of snapshot.premises) {
|
|
644
|
-
const pe = PremiseEngine.fromSnapshot(premiseSnap, this.argument, this.variables, this.expressionIndex);
|
|
766
|
+
const pe = PremiseEngine.fromSnapshot(premiseSnap, this.argument, this.variables, this.expressionIndex, this.sourceManager);
|
|
645
767
|
this.premises.set(pe.getId(), pe);
|
|
646
768
|
}
|
|
647
769
|
this.conclusionPremiseId = snapshot.conclusionPremiseId;
|
|
@@ -657,16 +779,12 @@ export class ArgumentEngine {
|
|
|
657
779
|
argument: true,
|
|
658
780
|
variables: true,
|
|
659
781
|
roles: true,
|
|
782
|
+
sources: true,
|
|
660
783
|
premiseIds: new Set(),
|
|
661
784
|
allPremises: true,
|
|
662
785
|
};
|
|
663
786
|
this.notifySubscribers();
|
|
664
787
|
}
|
|
665
|
-
/**
|
|
666
|
-
* Returns an argument-level checksum combining argument metadata, role
|
|
667
|
-
* state, and all premise checksums. Computed lazily -- only recalculated
|
|
668
|
-
* when the engine's own state has changed.
|
|
669
|
-
*/
|
|
670
788
|
checksum() {
|
|
671
789
|
if (this.checksumDirty || this.cachedChecksum === undefined) {
|
|
672
790
|
this.cachedChecksum = this.computeChecksum();
|
|
@@ -689,6 +807,13 @@ export class ArgumentEngine {
|
|
|
689
807
|
for (const pe of this.listPremises()) {
|
|
690
808
|
checksumMap[pe.getId()] = pe.checksum();
|
|
691
809
|
}
|
|
810
|
+
// Association checksums
|
|
811
|
+
for (const a of this.sourceManager.getAllVariableSourceAssociations()) {
|
|
812
|
+
checksumMap[a.id] = a.checksum;
|
|
813
|
+
}
|
|
814
|
+
for (const a of this.sourceManager.getAllExpressionSourceAssociations()) {
|
|
815
|
+
checksumMap[a.id] = a.checksum;
|
|
816
|
+
}
|
|
692
817
|
return computeHash(canonicalSerialize(checksumMap));
|
|
693
818
|
}
|
|
694
819
|
markDirty() {
|
|
@@ -708,10 +833,6 @@ export class ArgumentEngine {
|
|
|
708
833
|
checksum: entityChecksum(v, fields),
|
|
709
834
|
};
|
|
710
835
|
}
|
|
711
|
-
/**
|
|
712
|
-
* Collects all variables referenced by expressions across all premises,
|
|
713
|
-
* indexed both by variable ID and by symbol.
|
|
714
|
-
*/
|
|
715
836
|
collectReferencedVariables() {
|
|
716
837
|
const byIdTmp = new Map();
|
|
717
838
|
const bySymbolTmp = new Map();
|
|
@@ -758,12 +879,6 @@ export class ArgumentEngine {
|
|
|
758
879
|
bySymbol,
|
|
759
880
|
};
|
|
760
881
|
}
|
|
761
|
-
/**
|
|
762
|
-
* Validates that this argument is structurally ready for evaluation:
|
|
763
|
-
* a conclusion must be set, all role references must point to existing
|
|
764
|
-
* premises, variable ID/symbol mappings must be consistent, and every
|
|
765
|
-
* premise must be individually evaluable.
|
|
766
|
-
*/
|
|
767
882
|
validateEvaluability() {
|
|
768
883
|
const issues = [];
|
|
769
884
|
if (this.conclusionPremiseId === undefined) {
|
|
@@ -814,21 +929,32 @@ export class ArgumentEngine {
|
|
|
814
929
|
const premiseValidation = premise.validateEvaluability();
|
|
815
930
|
issues.push(...premiseValidation.issues);
|
|
816
931
|
}
|
|
932
|
+
// Source validation
|
|
933
|
+
for (const assoc of this.sourceManager.getAllVariableSourceAssociations()) {
|
|
934
|
+
if (!this.variables.hasVariable(assoc.variableId)) {
|
|
935
|
+
issues.push(makeErrorIssue({
|
|
936
|
+
code: "SOURCE_VARIABLE_ASSOCIATION_INVALID_VARIABLE",
|
|
937
|
+
message: `Variable-source association "${assoc.id}" references non-existent variable "${assoc.variableId}".`,
|
|
938
|
+
}));
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
for (const assoc of this.sourceManager.getAllExpressionSourceAssociations()) {
|
|
942
|
+
const premise = this.premises.get(assoc.premiseId);
|
|
943
|
+
if (!premise) {
|
|
944
|
+
issues.push(makeErrorIssue({
|
|
945
|
+
code: "SOURCE_EXPRESSION_ASSOCIATION_INVALID_PREMISE",
|
|
946
|
+
message: `Expression-source association "${assoc.id}" references non-existent premise "${assoc.premiseId}".`,
|
|
947
|
+
}));
|
|
948
|
+
}
|
|
949
|
+
else if (!premise.getExpression(assoc.expressionId)) {
|
|
950
|
+
issues.push(makeErrorIssue({
|
|
951
|
+
code: "SOURCE_EXPRESSION_ASSOCIATION_INVALID_EXPRESSION",
|
|
952
|
+
message: `Expression-source association "${assoc.id}" references non-existent expression "${assoc.expressionId}" in premise "${assoc.premiseId}".`,
|
|
953
|
+
}));
|
|
954
|
+
}
|
|
955
|
+
}
|
|
817
956
|
return makeValidationResult(issues);
|
|
818
957
|
}
|
|
819
|
-
/**
|
|
820
|
-
* Evaluates the argument under a three-valued expression assignment.
|
|
821
|
-
*
|
|
822
|
-
* Variables may be `true`, `false`, or `null` (unknown). Expressions
|
|
823
|
-
* listed in `rejectedExpressionIds` evaluate to `false` (children
|
|
824
|
-
* skipped). All result flags (`isAdmissibleAssignment`,
|
|
825
|
-
* `allSupportingPremisesTrue`, `conclusionTrue`, `isCounterexample`,
|
|
826
|
-
* `preservesTruthUnderAssignment`) are three-valued: `null` means
|
|
827
|
-
* the result is indeterminate due to unknown variable values.
|
|
828
|
-
*
|
|
829
|
-
* Returns `{ ok: false }` with validation details if the argument is
|
|
830
|
-
* not structurally evaluable.
|
|
831
|
-
*/
|
|
832
958
|
evaluate(assignment, options) {
|
|
833
959
|
const validateFirst = options?.validateFirst ?? true;
|
|
834
960
|
if (validateFirst) {
|
|
@@ -922,16 +1048,6 @@ export class ArgumentEngine {
|
|
|
922
1048
|
};
|
|
923
1049
|
}
|
|
924
1050
|
}
|
|
925
|
-
/**
|
|
926
|
-
* Enumerates all 2^n variable assignments and checks for counterexamples.
|
|
927
|
-
*
|
|
928
|
-
* A counterexample is an admissible assignment where all supporting
|
|
929
|
-
* premises are true but the conclusion is false. The argument is valid
|
|
930
|
-
* if no counterexamples exist.
|
|
931
|
-
*
|
|
932
|
-
* Supports early termination (`firstCounterexample` mode) and
|
|
933
|
-
* configurable limits on variables and assignments checked.
|
|
934
|
-
*/
|
|
935
1051
|
checkValidity(options) {
|
|
936
1052
|
const validateFirst = options?.validateFirst ?? true;
|
|
937
1053
|
if (validateFirst) {
|