@polintpro/proposit-core 0.3.0 → 0.4.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 +175 -0
- package/dist/cli/commands/sources.js.map +1 -0
- 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 +39 -1
- package/dist/cli/engine.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 +19 -0
- package/dist/lib/consts.js.map +1 -1
- package/dist/lib/core/argument-engine.d.ts +42 -134
- package/dist/lib/core/argument-engine.d.ts.map +1 -1
- package/dist/lib/core/argument-engine.js +299 -116
- package/dist/lib/core/argument-engine.js.map +1 -1
- package/dist/lib/core/change-collector.d.ts +12 -2
- package/dist/lib/core/change-collector.d.ts.map +1 -1
- package/dist/lib/core/change-collector.js +27 -0
- package/dist/lib/core/change-collector.js.map +1 -1
- package/dist/lib/core/diff.d.ts +8 -2
- package/dist/lib/core/diff.d.ts.map +1 -1
- package/dist/lib/core/diff.js +58 -0
- package/dist/lib/core/diff.js.map +1 -1
- package/dist/lib/core/interfaces/argument-engine.interfaces.d.ts +334 -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 +5 -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/premise-engine.interfaces.d.ts +317 -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 +110 -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 +19 -153
- package/dist/lib/core/premise-engine.d.ts.map +1 -1
- package/dist/lib/core/premise-engine.js +95 -143
- package/dist/lib/core/premise-engine.js.map +1 -1
- package/dist/lib/core/source-manager.d.ts +109 -0
- package/dist/lib/core/source-manager.d.ts.map +1 -0
- package/dist/lib/core/source-manager.js +420 -0
- package/dist/lib/core/source-manager.js.map +1 -0
- package/dist/lib/index.d.ts +4 -1
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +2 -1
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/schemata/index.d.ts +1 -0
- package/dist/lib/schemata/index.d.ts.map +1 -1
- package/dist/lib/schemata/index.js +1 -0
- package/dist/lib/schemata/index.js.map +1 -1
- package/dist/lib/schemata/source.d.ts +28 -0
- package/dist/lib/schemata/source.d.ts.map +1 -0
- package/dist/lib/schemata/source.js +35 -0
- package/dist/lib/schemata/source.js.map +1 -0
- package/dist/lib/types/checksum.d.ts +6 -0
- package/dist/lib/types/checksum.d.ts.map +1 -1
- package/dist/lib/types/diff.d.ts +9 -3
- 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 +7 -3
- package/dist/lib/types/mutation.d.ts.map +1 -1
- package/dist/lib/types/reactive.d.ts +5 -2
- 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,7 @@ export class ArgumentEngine {
|
|
|
18
19
|
argument;
|
|
19
20
|
premises;
|
|
20
21
|
variables;
|
|
22
|
+
sourceManager;
|
|
21
23
|
conclusionPremiseId;
|
|
22
24
|
checksumConfig;
|
|
23
25
|
positionConfig;
|
|
@@ -29,6 +31,7 @@ export class ArgumentEngine {
|
|
|
29
31
|
argument: true,
|
|
30
32
|
variables: true,
|
|
31
33
|
roles: true,
|
|
34
|
+
sources: true,
|
|
32
35
|
premiseIds: new Set(),
|
|
33
36
|
allPremises: true,
|
|
34
37
|
};
|
|
@@ -40,15 +43,12 @@ export class ArgumentEngine {
|
|
|
40
43
|
checksumConfig: this.checksumConfig,
|
|
41
44
|
positionConfig: this.positionConfig,
|
|
42
45
|
});
|
|
46
|
+
this.sourceManager = new SourceManager();
|
|
43
47
|
this.expressionIndex = new Map();
|
|
44
48
|
this.conclusionPremiseId = undefined;
|
|
45
49
|
this.checksumConfig = options?.checksumConfig;
|
|
46
50
|
this.positionConfig = options?.positionConfig;
|
|
47
51
|
}
|
|
48
|
-
/**
|
|
49
|
-
* Registers a listener that is called after every mutation.
|
|
50
|
-
* Returns an unsubscribe function.
|
|
51
|
-
*/
|
|
52
52
|
subscribe = (listener) => {
|
|
53
53
|
this.listeners.add(listener);
|
|
54
54
|
return () => {
|
|
@@ -70,6 +70,7 @@ export class ArgumentEngine {
|
|
|
70
70
|
!dirty.argument &&
|
|
71
71
|
!dirty.variables &&
|
|
72
72
|
!dirty.roles &&
|
|
73
|
+
!dirty.sources &&
|
|
73
74
|
dirty.premiseIds.size === 0 &&
|
|
74
75
|
!dirty.allPremises) {
|
|
75
76
|
return prev;
|
|
@@ -108,17 +109,43 @@ export class ArgumentEngine {
|
|
|
108
109
|
}
|
|
109
110
|
}
|
|
110
111
|
}
|
|
112
|
+
let sourcesRecord;
|
|
113
|
+
let varAssocRecord;
|
|
114
|
+
let exprAssocRecord;
|
|
115
|
+
if (dirty.sources || !prev) {
|
|
116
|
+
sourcesRecord = {};
|
|
117
|
+
for (const s of this.sourceManager.getSources()) {
|
|
118
|
+
sourcesRecord[s.id] = s;
|
|
119
|
+
}
|
|
120
|
+
varAssocRecord = {};
|
|
121
|
+
for (const a of this.sourceManager.getAllVariableSourceAssociations()) {
|
|
122
|
+
varAssocRecord[a.id] = a;
|
|
123
|
+
}
|
|
124
|
+
exprAssocRecord = {};
|
|
125
|
+
for (const a of this.sourceManager.getAllExpressionSourceAssociations()) {
|
|
126
|
+
exprAssocRecord[a.id] = a;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
sourcesRecord = prev.sources;
|
|
131
|
+
varAssocRecord = prev.variableSourceAssociations;
|
|
132
|
+
exprAssocRecord = prev.expressionSourceAssociations;
|
|
133
|
+
}
|
|
111
134
|
const snapshot = {
|
|
112
135
|
argument,
|
|
113
136
|
variables,
|
|
114
137
|
premises,
|
|
115
138
|
roles,
|
|
139
|
+
sources: sourcesRecord,
|
|
140
|
+
variableSourceAssociations: varAssocRecord,
|
|
141
|
+
expressionSourceAssociations: exprAssocRecord,
|
|
116
142
|
};
|
|
117
143
|
this.cachedReactiveSnapshot = snapshot;
|
|
118
144
|
this.reactiveDirty = {
|
|
119
145
|
argument: false,
|
|
120
146
|
variables: false,
|
|
121
147
|
roles: false,
|
|
148
|
+
sources: false,
|
|
122
149
|
premiseIds: new Set(),
|
|
123
150
|
allPremises: false,
|
|
124
151
|
};
|
|
@@ -178,12 +205,15 @@ export class ArgumentEngine {
|
|
|
178
205
|
this.reactiveDirty.premiseIds.add(p.id);
|
|
179
206
|
}
|
|
180
207
|
}
|
|
208
|
+
if (changes.sources ||
|
|
209
|
+
changes.variableSourceAssociations ||
|
|
210
|
+
changes.expressionSourceAssociations) {
|
|
211
|
+
this.reactiveDirty.sources = true;
|
|
212
|
+
}
|
|
181
213
|
}
|
|
182
|
-
/** Returns a shallow copy of the argument metadata with checksum attached. */
|
|
183
214
|
getArgument() {
|
|
184
215
|
return { ...this.argument, checksum: this.checksum() };
|
|
185
216
|
}
|
|
186
|
-
/** Renders the argument as a multi-line string with role labels for each premise. */
|
|
187
217
|
toDisplayString() {
|
|
188
218
|
const lines = [];
|
|
189
219
|
const arg = this.getArgument();
|
|
@@ -206,19 +236,9 @@ export class ArgumentEngine {
|
|
|
206
236
|
}
|
|
207
237
|
return lines.join("\n");
|
|
208
238
|
}
|
|
209
|
-
/**
|
|
210
|
-
* Creates a new premise with an auto-generated UUID and registers it
|
|
211
|
-
* with this engine.
|
|
212
|
-
*/
|
|
213
239
|
createPremise(extras) {
|
|
214
240
|
return this.createPremiseWithId(randomUUID(), extras);
|
|
215
241
|
}
|
|
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
242
|
createPremiseWithId(id, extras) {
|
|
223
243
|
if (this.premises.has(id)) {
|
|
224
244
|
throw new Error(`Premise "${id}" already exists.`);
|
|
@@ -233,6 +253,7 @@ export class ArgumentEngine {
|
|
|
233
253
|
argument: this.argument,
|
|
234
254
|
variables: this.variables,
|
|
235
255
|
expressionIndex: this.expressionIndex,
|
|
256
|
+
sourceManager: this.sourceManager,
|
|
236
257
|
}, {
|
|
237
258
|
checksumConfig: this.checksumConfig,
|
|
238
259
|
positionConfig: this.positionConfig,
|
|
@@ -257,21 +278,24 @@ export class ArgumentEngine {
|
|
|
257
278
|
changes,
|
|
258
279
|
};
|
|
259
280
|
}
|
|
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
281
|
removePremise(premiseId) {
|
|
265
282
|
const pm = this.premises.get(premiseId);
|
|
266
283
|
if (!pm)
|
|
267
284
|
return { result: undefined, changes: {} };
|
|
268
285
|
const data = pm.toPremiseData();
|
|
269
|
-
|
|
286
|
+
const collector = new ChangeCollector();
|
|
287
|
+
// Clean up expression index and source associations for removed premise's expressions
|
|
270
288
|
for (const expr of pm.getExpressions()) {
|
|
271
289
|
this.expressionIndex.delete(expr.id);
|
|
290
|
+
const sourceResult = this.sourceManager.removeAssociationsForExpression(expr.id);
|
|
291
|
+
for (const assoc of sourceResult.removedExpressionAssociations) {
|
|
292
|
+
collector.removedExpressionSourceAssociation(assoc);
|
|
293
|
+
}
|
|
294
|
+
for (const orphan of sourceResult.removedOrphanSources) {
|
|
295
|
+
collector.removedSource(orphan);
|
|
296
|
+
}
|
|
272
297
|
}
|
|
273
298
|
this.premises.delete(premiseId);
|
|
274
|
-
const collector = new ChangeCollector();
|
|
275
299
|
collector.removedPremise(data);
|
|
276
300
|
if (this.conclusionPremiseId === premiseId) {
|
|
277
301
|
this.conclusionPremiseId = undefined;
|
|
@@ -286,31 +310,20 @@ export class ArgumentEngine {
|
|
|
286
310
|
changes,
|
|
287
311
|
};
|
|
288
312
|
}
|
|
289
|
-
/** Returns the premise with the given ID, or `undefined` if not found. */
|
|
290
313
|
getPremise(premiseId) {
|
|
291
314
|
return this.premises.get(premiseId);
|
|
292
315
|
}
|
|
293
|
-
/** Returns `true` if a premise with the given ID exists. */
|
|
294
316
|
hasPremise(premiseId) {
|
|
295
317
|
return this.premises.has(premiseId);
|
|
296
318
|
}
|
|
297
|
-
/** Returns all premise IDs in lexicographic order. */
|
|
298
319
|
listPremiseIds() {
|
|
299
320
|
return Array.from(this.premises.keys()).sort((a, b) => a.localeCompare(b));
|
|
300
321
|
}
|
|
301
|
-
/** Returns all premises in lexicographic ID order. */
|
|
302
322
|
listPremises() {
|
|
303
323
|
return this.listPremiseIds()
|
|
304
324
|
.map((id) => this.premises.get(id))
|
|
305
325
|
.filter((pm) => pm !== undefined);
|
|
306
326
|
}
|
|
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
327
|
addVariable(variable) {
|
|
315
328
|
if (variable.argumentId !== this.argument.id) {
|
|
316
329
|
throw new Error(`Variable argumentId "${variable.argumentId}" does not match engine argument ID "${this.argument.id}".`);
|
|
@@ -332,12 +345,6 @@ export class ArgumentEngine {
|
|
|
332
345
|
changes,
|
|
333
346
|
};
|
|
334
347
|
}
|
|
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
348
|
updateVariable(variableId, updates) {
|
|
342
349
|
const updated = this.variables.updateVariable(variableId, updates);
|
|
343
350
|
const collector = new ChangeCollector();
|
|
@@ -363,10 +370,6 @@ export class ArgumentEngine {
|
|
|
363
370
|
changes: collector.toChangeset(),
|
|
364
371
|
};
|
|
365
372
|
}
|
|
366
|
-
/**
|
|
367
|
-
* Removes a variable and cascade-deletes all expressions referencing it
|
|
368
|
-
* across every premise (including subtrees and operator collapse).
|
|
369
|
-
*/
|
|
370
373
|
removeVariable(variableId) {
|
|
371
374
|
const variable = this.variables.getVariable(variableId);
|
|
372
375
|
if (!variable) {
|
|
@@ -374,6 +377,8 @@ export class ArgumentEngine {
|
|
|
374
377
|
}
|
|
375
378
|
const collector = new ChangeCollector();
|
|
376
379
|
// Cascade: delete referencing expressions in every premise
|
|
380
|
+
// (PremiseEngine.removeExpression already cascades expression-source
|
|
381
|
+
// associations via the Task 12 logic)
|
|
377
382
|
for (const pm of this.listPremises()) {
|
|
378
383
|
const { changes } = pm.deleteExpressionsUsingVariable(variableId);
|
|
379
384
|
if (changes.expressions) {
|
|
@@ -381,6 +386,24 @@ export class ArgumentEngine {
|
|
|
381
386
|
collector.removedExpression(e);
|
|
382
387
|
}
|
|
383
388
|
}
|
|
389
|
+
if (changes.expressionSourceAssociations) {
|
|
390
|
+
for (const a of changes.expressionSourceAssociations.removed) {
|
|
391
|
+
collector.removedExpressionSourceAssociation(a);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (changes.sources) {
|
|
395
|
+
for (const s of changes.sources.removed) {
|
|
396
|
+
collector.removedSource(s);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
// Cascade: remove variable-source associations
|
|
401
|
+
const varAssocResult = this.sourceManager.removeAssociationsForVariable(variableId);
|
|
402
|
+
for (const assoc of varAssocResult.removedVariableAssociations) {
|
|
403
|
+
collector.removedVariableSourceAssociation(assoc);
|
|
404
|
+
}
|
|
405
|
+
for (const orphan of varAssocResult.removedOrphanSources) {
|
|
406
|
+
collector.removedSource(orphan);
|
|
384
407
|
}
|
|
385
408
|
this.variables.removeVariable(variableId);
|
|
386
409
|
collector.removedVariable(variable);
|
|
@@ -394,27 +417,18 @@ export class ArgumentEngine {
|
|
|
394
417
|
changes,
|
|
395
418
|
};
|
|
396
419
|
}
|
|
397
|
-
/** Returns all registered variables sorted by ID. */
|
|
398
420
|
getVariables() {
|
|
399
421
|
return this.variables.toArray();
|
|
400
422
|
}
|
|
401
|
-
/** Returns the variable with the given ID, or `undefined` if not found. */
|
|
402
423
|
getVariable(variableId) {
|
|
403
424
|
return this.variables.getVariable(variableId);
|
|
404
425
|
}
|
|
405
|
-
/** Returns `true` if a variable with the given ID exists. */
|
|
406
426
|
hasVariable(variableId) {
|
|
407
427
|
return this.variables.hasVariable(variableId);
|
|
408
428
|
}
|
|
409
|
-
/** Returns the variable with the given symbol, or `undefined` if not found. */
|
|
410
429
|
getVariableBySymbol(symbol) {
|
|
411
430
|
return this.variables.getVariableBySymbol(symbol);
|
|
412
431
|
}
|
|
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
432
|
buildVariableIndex(keyFn) {
|
|
419
433
|
const map = new Map();
|
|
420
434
|
for (const v of this.variables.toArray()) {
|
|
@@ -422,29 +436,24 @@ export class ArgumentEngine {
|
|
|
422
436
|
}
|
|
423
437
|
return map;
|
|
424
438
|
}
|
|
425
|
-
/** Returns an expression by ID from any premise, or `undefined` if not found. */
|
|
426
439
|
getExpression(expressionId) {
|
|
427
440
|
const premiseId = this.expressionIndex.get(expressionId);
|
|
428
441
|
if (premiseId === undefined)
|
|
429
442
|
return undefined;
|
|
430
443
|
return this.premises.get(premiseId)?.getExpression(expressionId);
|
|
431
444
|
}
|
|
432
|
-
/** Returns `true` if an expression with the given ID exists in any premise. */
|
|
433
445
|
hasExpression(expressionId) {
|
|
434
446
|
return this.expressionIndex.has(expressionId);
|
|
435
447
|
}
|
|
436
|
-
/** Returns the premise ID that contains the given expression, or `undefined`. */
|
|
437
448
|
getExpressionPremiseId(expressionId) {
|
|
438
449
|
return this.expressionIndex.get(expressionId);
|
|
439
450
|
}
|
|
440
|
-
/** Returns the PremiseEngine containing the given expression, or `undefined`. */
|
|
441
451
|
findPremiseByExpressionId(expressionId) {
|
|
442
452
|
const premiseId = this.expressionIndex.get(expressionId);
|
|
443
453
|
if (premiseId === undefined)
|
|
444
454
|
return undefined;
|
|
445
455
|
return this.premises.get(premiseId);
|
|
446
456
|
}
|
|
447
|
-
/** Returns all expressions across all premises, sorted by ID. */
|
|
448
457
|
getAllExpressions() {
|
|
449
458
|
const all = [];
|
|
450
459
|
for (const pe of this.listPremises()) {
|
|
@@ -452,10 +461,6 @@ export class ArgumentEngine {
|
|
|
452
461
|
}
|
|
453
462
|
return all.sort((a, b) => a.id.localeCompare(b.id));
|
|
454
463
|
}
|
|
455
|
-
/**
|
|
456
|
-
* Returns all expressions that reference the given variable ID,
|
|
457
|
-
* across all premises.
|
|
458
|
-
*/
|
|
459
464
|
getExpressionsByVariableId(variableId) {
|
|
460
465
|
const result = [];
|
|
461
466
|
for (const pe of this.listPremises()) {
|
|
@@ -471,7 +476,6 @@ export class ArgumentEngine {
|
|
|
471
476
|
}
|
|
472
477
|
return result;
|
|
473
478
|
}
|
|
474
|
-
/** Returns the root expression from each premise that has one. */
|
|
475
479
|
listRootExpressions() {
|
|
476
480
|
const roots = [];
|
|
477
481
|
for (const pe of this.listPremises()) {
|
|
@@ -481,7 +485,6 @@ export class ArgumentEngine {
|
|
|
481
485
|
}
|
|
482
486
|
return roots;
|
|
483
487
|
}
|
|
484
|
-
/** Returns the current role assignments (conclusion premise ID only; supporting is derived). */
|
|
485
488
|
getRoleState() {
|
|
486
489
|
return {
|
|
487
490
|
...(this.conclusionPremiseId !== undefined
|
|
@@ -489,11 +492,6 @@ export class ArgumentEngine {
|
|
|
489
492
|
: {}),
|
|
490
493
|
};
|
|
491
494
|
}
|
|
492
|
-
/**
|
|
493
|
-
* Designates a premise as the argument's conclusion.
|
|
494
|
-
*
|
|
495
|
-
* @throws If the premise does not exist.
|
|
496
|
-
*/
|
|
497
495
|
setConclusionPremise(premiseId) {
|
|
498
496
|
const premise = this.premises.get(premiseId);
|
|
499
497
|
if (!premise) {
|
|
@@ -512,7 +510,6 @@ export class ArgumentEngine {
|
|
|
512
510
|
changes,
|
|
513
511
|
};
|
|
514
512
|
}
|
|
515
|
-
/** Clears the conclusion designation. */
|
|
516
513
|
clearConclusionPremise() {
|
|
517
514
|
this.conclusionPremiseId = undefined;
|
|
518
515
|
const roles = this.getRoleState();
|
|
@@ -527,21 +524,190 @@ export class ArgumentEngine {
|
|
|
527
524
|
changes,
|
|
528
525
|
};
|
|
529
526
|
}
|
|
530
|
-
/** Returns the conclusion premise, or `undefined` if none is set. */
|
|
531
527
|
getConclusionPremise() {
|
|
532
528
|
if (this.conclusionPremiseId === undefined) {
|
|
533
529
|
return undefined;
|
|
534
530
|
}
|
|
535
531
|
return this.premises.get(this.conclusionPremiseId);
|
|
536
532
|
}
|
|
537
|
-
/**
|
|
538
|
-
* Returns all supporting premises (derived: inference premises that are
|
|
539
|
-
* not the conclusion) in lexicographic ID order.
|
|
540
|
-
*/
|
|
541
533
|
listSupportingPremises() {
|
|
542
534
|
return this.listPremises().filter((pm) => pm.isInference() && pm.getId() !== this.conclusionPremiseId);
|
|
543
535
|
}
|
|
544
|
-
|
|
536
|
+
// -------------------------------------------------------------------------
|
|
537
|
+
// Source management
|
|
538
|
+
// -------------------------------------------------------------------------
|
|
539
|
+
addSource(source) {
|
|
540
|
+
if (source.argumentId !== this.argument.id) {
|
|
541
|
+
throw new Error(`Source argumentId "${source.argumentId}" does not match engine argument ID "${this.argument.id}".`);
|
|
542
|
+
}
|
|
543
|
+
if (source.argumentVersion !== this.argument.version) {
|
|
544
|
+
throw new Error(`Source argumentVersion ${source.argumentVersion} does not match engine argument version ${this.argument.version}.`);
|
|
545
|
+
}
|
|
546
|
+
const fields = this.checksumConfig?.sourceFields ??
|
|
547
|
+
DEFAULT_CHECKSUM_CONFIG.sourceFields;
|
|
548
|
+
const sourceWithChecksum = {
|
|
549
|
+
...source,
|
|
550
|
+
checksum: entityChecksum(source, fields),
|
|
551
|
+
};
|
|
552
|
+
this.sourceManager.addSource(sourceWithChecksum);
|
|
553
|
+
const collector = new ChangeCollector();
|
|
554
|
+
collector.addedSource(sourceWithChecksum);
|
|
555
|
+
this.markDirty();
|
|
556
|
+
const changes = collector.toChangeset();
|
|
557
|
+
this.markReactiveDirty(changes);
|
|
558
|
+
this.notifySubscribers();
|
|
559
|
+
return { result: sourceWithChecksum, changes };
|
|
560
|
+
}
|
|
561
|
+
removeSource(sourceId) {
|
|
562
|
+
const source = this.sourceManager.getSource(sourceId);
|
|
563
|
+
if (!source) {
|
|
564
|
+
return { result: undefined, changes: {} };
|
|
565
|
+
}
|
|
566
|
+
const removalResult = this.sourceManager.removeSource(sourceId);
|
|
567
|
+
const collector = new ChangeCollector();
|
|
568
|
+
collector.removedSource(source);
|
|
569
|
+
for (const assoc of removalResult.removedVariableAssociations) {
|
|
570
|
+
collector.removedVariableSourceAssociation(assoc);
|
|
571
|
+
}
|
|
572
|
+
for (const assoc of removalResult.removedExpressionAssociations) {
|
|
573
|
+
collector.removedExpressionSourceAssociation(assoc);
|
|
574
|
+
}
|
|
575
|
+
this.markDirty();
|
|
576
|
+
const changes = collector.toChangeset();
|
|
577
|
+
this.markReactiveDirty(changes);
|
|
578
|
+
this.notifySubscribers();
|
|
579
|
+
return { result: source, changes };
|
|
580
|
+
}
|
|
581
|
+
addVariableSourceAssociation(sourceId, variableId) {
|
|
582
|
+
if (!this.sourceManager.getSource(sourceId)) {
|
|
583
|
+
throw new Error(`Source "${sourceId}" does not exist.`);
|
|
584
|
+
}
|
|
585
|
+
if (!this.variables.hasVariable(variableId)) {
|
|
586
|
+
throw new Error(`Variable "${variableId}" does not exist.`);
|
|
587
|
+
}
|
|
588
|
+
const assoc = {
|
|
589
|
+
id: randomUUID(),
|
|
590
|
+
sourceId,
|
|
591
|
+
variableId,
|
|
592
|
+
argumentId: this.argument.id,
|
|
593
|
+
argumentVersion: this.argument.version,
|
|
594
|
+
checksum: "",
|
|
595
|
+
};
|
|
596
|
+
const fields = this.checksumConfig?.variableSourceAssociationFields ??
|
|
597
|
+
DEFAULT_CHECKSUM_CONFIG.variableSourceAssociationFields;
|
|
598
|
+
const assocWithChecksum = {
|
|
599
|
+
...assoc,
|
|
600
|
+
checksum: entityChecksum(assoc, fields),
|
|
601
|
+
};
|
|
602
|
+
this.sourceManager.addVariableSourceAssociation(assocWithChecksum);
|
|
603
|
+
const collector = new ChangeCollector();
|
|
604
|
+
collector.addedVariableSourceAssociation(assocWithChecksum);
|
|
605
|
+
this.markDirty();
|
|
606
|
+
const changes = collector.toChangeset();
|
|
607
|
+
this.markReactiveDirty(changes);
|
|
608
|
+
this.notifySubscribers();
|
|
609
|
+
return { result: assocWithChecksum, changes };
|
|
610
|
+
}
|
|
611
|
+
removeVariableSourceAssociation(associationId) {
|
|
612
|
+
const allVarAssocs = this.sourceManager.getAllVariableSourceAssociations();
|
|
613
|
+
if (!allVarAssocs.some((a) => a.id === associationId)) {
|
|
614
|
+
return { result: undefined, changes: {} };
|
|
615
|
+
}
|
|
616
|
+
const removalResult = this.sourceManager.removeVariableSourceAssociation(associationId);
|
|
617
|
+
const collector = new ChangeCollector();
|
|
618
|
+
for (const assoc of removalResult.removedVariableAssociations) {
|
|
619
|
+
collector.removedVariableSourceAssociation(assoc);
|
|
620
|
+
}
|
|
621
|
+
for (const orphan of removalResult.removedOrphanSources) {
|
|
622
|
+
collector.removedSource(orphan);
|
|
623
|
+
}
|
|
624
|
+
this.markDirty();
|
|
625
|
+
const changes = collector.toChangeset();
|
|
626
|
+
this.markReactiveDirty(changes);
|
|
627
|
+
this.notifySubscribers();
|
|
628
|
+
return {
|
|
629
|
+
result: removalResult.removedVariableAssociations[0],
|
|
630
|
+
changes,
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
addExpressionSourceAssociation(sourceId, expressionId, premiseId) {
|
|
634
|
+
if (!this.sourceManager.getSource(sourceId)) {
|
|
635
|
+
throw new Error(`Source "${sourceId}" does not exist.`);
|
|
636
|
+
}
|
|
637
|
+
const pm = this.premises.get(premiseId);
|
|
638
|
+
if (!pm) {
|
|
639
|
+
throw new Error(`Premise "${premiseId}" does not exist.`);
|
|
640
|
+
}
|
|
641
|
+
if (!pm.getExpression(expressionId)) {
|
|
642
|
+
throw new Error(`Expression "${expressionId}" does not exist in premise "${premiseId}".`);
|
|
643
|
+
}
|
|
644
|
+
const assoc = {
|
|
645
|
+
id: randomUUID(),
|
|
646
|
+
sourceId,
|
|
647
|
+
expressionId,
|
|
648
|
+
premiseId,
|
|
649
|
+
argumentId: this.argument.id,
|
|
650
|
+
argumentVersion: this.argument.version,
|
|
651
|
+
checksum: "",
|
|
652
|
+
};
|
|
653
|
+
const fields = this.checksumConfig?.expressionSourceAssociationFields ??
|
|
654
|
+
DEFAULT_CHECKSUM_CONFIG.expressionSourceAssociationFields;
|
|
655
|
+
const assocWithChecksum = {
|
|
656
|
+
...assoc,
|
|
657
|
+
checksum: entityChecksum(assoc, fields),
|
|
658
|
+
};
|
|
659
|
+
this.sourceManager.addExpressionSourceAssociation(assocWithChecksum);
|
|
660
|
+
const collector = new ChangeCollector();
|
|
661
|
+
collector.addedExpressionSourceAssociation(assocWithChecksum);
|
|
662
|
+
this.markDirty();
|
|
663
|
+
const changes = collector.toChangeset();
|
|
664
|
+
this.markReactiveDirty(changes);
|
|
665
|
+
this.notifySubscribers();
|
|
666
|
+
return { result: assocWithChecksum, changes };
|
|
667
|
+
}
|
|
668
|
+
removeExpressionSourceAssociation(associationId) {
|
|
669
|
+
const allExprAssocs = this.sourceManager.getAllExpressionSourceAssociations();
|
|
670
|
+
if (!allExprAssocs.some((a) => a.id === associationId)) {
|
|
671
|
+
return { result: undefined, changes: {} };
|
|
672
|
+
}
|
|
673
|
+
const removalResult = this.sourceManager.removeExpressionSourceAssociation(associationId);
|
|
674
|
+
const collector = new ChangeCollector();
|
|
675
|
+
for (const assoc of removalResult.removedExpressionAssociations) {
|
|
676
|
+
collector.removedExpressionSourceAssociation(assoc);
|
|
677
|
+
}
|
|
678
|
+
for (const orphan of removalResult.removedOrphanSources) {
|
|
679
|
+
collector.removedSource(orphan);
|
|
680
|
+
}
|
|
681
|
+
this.markDirty();
|
|
682
|
+
const changes = collector.toChangeset();
|
|
683
|
+
this.markReactiveDirty(changes);
|
|
684
|
+
this.notifySubscribers();
|
|
685
|
+
return {
|
|
686
|
+
result: removalResult.removedExpressionAssociations[0],
|
|
687
|
+
changes,
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
getSources() {
|
|
691
|
+
return this.sourceManager.getSources();
|
|
692
|
+
}
|
|
693
|
+
getSource(sourceId) {
|
|
694
|
+
return this.sourceManager.getSource(sourceId);
|
|
695
|
+
}
|
|
696
|
+
getAssociationsForSource(sourceId) {
|
|
697
|
+
return this.sourceManager.getAssociationsForSource(sourceId);
|
|
698
|
+
}
|
|
699
|
+
getAssociationsForVariable(variableId) {
|
|
700
|
+
return this.sourceManager.getAssociationsForVariable(variableId);
|
|
701
|
+
}
|
|
702
|
+
getAssociationsForExpression(expressionId) {
|
|
703
|
+
return this.sourceManager.getAssociationsForExpression(expressionId);
|
|
704
|
+
}
|
|
705
|
+
getAllVariableSourceAssociations() {
|
|
706
|
+
return this.sourceManager.getAllVariableSourceAssociations();
|
|
707
|
+
}
|
|
708
|
+
getAllExpressionSourceAssociations() {
|
|
709
|
+
return this.sourceManager.getAllExpressionSourceAssociations();
|
|
710
|
+
}
|
|
545
711
|
snapshot() {
|
|
546
712
|
return {
|
|
547
713
|
argument: { ...this.argument },
|
|
@@ -554,6 +720,7 @@ export class ArgumentEngine {
|
|
|
554
720
|
checksumConfig: this.checksumConfig,
|
|
555
721
|
positionConfig: this.positionConfig,
|
|
556
722
|
},
|
|
723
|
+
sources: this.sourceManager.snapshot(),
|
|
557
724
|
};
|
|
558
725
|
}
|
|
559
726
|
/** Creates a new ArgumentEngine from a previously captured snapshot. */
|
|
@@ -563,9 +730,13 @@ export class ArgumentEngine {
|
|
|
563
730
|
for (const v of snapshot.variables.variables) {
|
|
564
731
|
engine.addVariable(v);
|
|
565
732
|
}
|
|
733
|
+
// Restore source manager (before premises, so PremiseEngines get the correct reference)
|
|
734
|
+
if (snapshot.sources) {
|
|
735
|
+
engine.sourceManager = SourceManager.fromSnapshot(snapshot.sources);
|
|
736
|
+
}
|
|
566
737
|
// Restore premises using PremiseEngine.fromSnapshot
|
|
567
738
|
for (const premiseSnap of snapshot.premises) {
|
|
568
|
-
const pe = PremiseEngine.fromSnapshot(premiseSnap, snapshot.argument, engine.variables, engine.expressionIndex);
|
|
739
|
+
const pe = PremiseEngine.fromSnapshot(premiseSnap, snapshot.argument, engine.variables, engine.expressionIndex, engine.sourceManager);
|
|
569
740
|
engine.premises.set(pe.getId(), pe);
|
|
570
741
|
const premiseId = pe.getId();
|
|
571
742
|
pe.setOnMutate(() => {
|
|
@@ -632,16 +803,18 @@ export class ArgumentEngine {
|
|
|
632
803
|
}
|
|
633
804
|
return engine;
|
|
634
805
|
}
|
|
635
|
-
/** Restores the engine to a previously captured snapshot state. */
|
|
636
806
|
rollback(snapshot) {
|
|
637
807
|
this.argument = { ...snapshot.argument };
|
|
638
808
|
this.checksumConfig = snapshot.config?.checksumConfig;
|
|
639
809
|
this.positionConfig = snapshot.config?.positionConfig;
|
|
640
810
|
this.variables = VariableManager.fromSnapshot(snapshot.variables);
|
|
811
|
+
this.sourceManager = snapshot.sources
|
|
812
|
+
? SourceManager.fromSnapshot(snapshot.sources)
|
|
813
|
+
: new SourceManager();
|
|
641
814
|
this.premises = new Map();
|
|
642
815
|
this.expressionIndex = new Map();
|
|
643
816
|
for (const premiseSnap of snapshot.premises) {
|
|
644
|
-
const pe = PremiseEngine.fromSnapshot(premiseSnap, this.argument, this.variables, this.expressionIndex);
|
|
817
|
+
const pe = PremiseEngine.fromSnapshot(premiseSnap, this.argument, this.variables, this.expressionIndex, this.sourceManager);
|
|
645
818
|
this.premises.set(pe.getId(), pe);
|
|
646
819
|
}
|
|
647
820
|
this.conclusionPremiseId = snapshot.conclusionPremiseId;
|
|
@@ -657,16 +830,12 @@ export class ArgumentEngine {
|
|
|
657
830
|
argument: true,
|
|
658
831
|
variables: true,
|
|
659
832
|
roles: true,
|
|
833
|
+
sources: true,
|
|
660
834
|
premiseIds: new Set(),
|
|
661
835
|
allPremises: true,
|
|
662
836
|
};
|
|
663
837
|
this.notifySubscribers();
|
|
664
838
|
}
|
|
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
839
|
checksum() {
|
|
671
840
|
if (this.checksumDirty || this.cachedChecksum === undefined) {
|
|
672
841
|
this.cachedChecksum = this.computeChecksum();
|
|
@@ -689,6 +858,17 @@ export class ArgumentEngine {
|
|
|
689
858
|
for (const pe of this.listPremises()) {
|
|
690
859
|
checksumMap[pe.getId()] = pe.checksum();
|
|
691
860
|
}
|
|
861
|
+
// Source checksums
|
|
862
|
+
for (const s of this.sourceManager.getSources()) {
|
|
863
|
+
checksumMap[s.id] = s.checksum;
|
|
864
|
+
}
|
|
865
|
+
// Association checksums
|
|
866
|
+
for (const a of this.sourceManager.getAllVariableSourceAssociations()) {
|
|
867
|
+
checksumMap[a.id] = a.checksum;
|
|
868
|
+
}
|
|
869
|
+
for (const a of this.sourceManager.getAllExpressionSourceAssociations()) {
|
|
870
|
+
checksumMap[a.id] = a.checksum;
|
|
871
|
+
}
|
|
692
872
|
return computeHash(canonicalSerialize(checksumMap));
|
|
693
873
|
}
|
|
694
874
|
markDirty() {
|
|
@@ -708,10 +888,6 @@ export class ArgumentEngine {
|
|
|
708
888
|
checksum: entityChecksum(v, fields),
|
|
709
889
|
};
|
|
710
890
|
}
|
|
711
|
-
/**
|
|
712
|
-
* Collects all variables referenced by expressions across all premises,
|
|
713
|
-
* indexed both by variable ID and by symbol.
|
|
714
|
-
*/
|
|
715
891
|
collectReferencedVariables() {
|
|
716
892
|
const byIdTmp = new Map();
|
|
717
893
|
const bySymbolTmp = new Map();
|
|
@@ -758,12 +934,6 @@ export class ArgumentEngine {
|
|
|
758
934
|
bySymbol,
|
|
759
935
|
};
|
|
760
936
|
}
|
|
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
937
|
validateEvaluability() {
|
|
768
938
|
const issues = [];
|
|
769
939
|
if (this.conclusionPremiseId === undefined) {
|
|
@@ -814,21 +984,44 @@ export class ArgumentEngine {
|
|
|
814
984
|
const premiseValidation = premise.validateEvaluability();
|
|
815
985
|
issues.push(...premiseValidation.issues);
|
|
816
986
|
}
|
|
987
|
+
// Source validation
|
|
988
|
+
for (const assoc of this.sourceManager.getAllVariableSourceAssociations()) {
|
|
989
|
+
if (!this.variables.hasVariable(assoc.variableId)) {
|
|
990
|
+
issues.push(makeErrorIssue({
|
|
991
|
+
code: "SOURCE_VARIABLE_ASSOCIATION_INVALID_VARIABLE",
|
|
992
|
+
message: `Variable-source association "${assoc.id}" references non-existent variable "${assoc.variableId}".`,
|
|
993
|
+
}));
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
for (const assoc of this.sourceManager.getAllExpressionSourceAssociations()) {
|
|
997
|
+
const premise = this.premises.get(assoc.premiseId);
|
|
998
|
+
if (!premise) {
|
|
999
|
+
issues.push(makeErrorIssue({
|
|
1000
|
+
code: "SOURCE_EXPRESSION_ASSOCIATION_INVALID_PREMISE",
|
|
1001
|
+
message: `Expression-source association "${assoc.id}" references non-existent premise "${assoc.premiseId}".`,
|
|
1002
|
+
}));
|
|
1003
|
+
}
|
|
1004
|
+
else if (!premise.getExpression(assoc.expressionId)) {
|
|
1005
|
+
issues.push(makeErrorIssue({
|
|
1006
|
+
code: "SOURCE_EXPRESSION_ASSOCIATION_INVALID_EXPRESSION",
|
|
1007
|
+
message: `Expression-source association "${assoc.id}" references non-existent expression "${assoc.expressionId}" in premise "${assoc.premiseId}".`,
|
|
1008
|
+
}));
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
// Orphaned sources (warning, not error)
|
|
1012
|
+
for (const source of this.sourceManager.getSources()) {
|
|
1013
|
+
const assocs = this.sourceManager.getAssociationsForSource(source.id);
|
|
1014
|
+
if (assocs.variable.length === 0 &&
|
|
1015
|
+
assocs.expression.length === 0) {
|
|
1016
|
+
issues.push({
|
|
1017
|
+
severity: "warning",
|
|
1018
|
+
code: "SOURCE_ORPHANED",
|
|
1019
|
+
message: `Source "${source.id}" has no associations.`,
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
817
1023
|
return makeValidationResult(issues);
|
|
818
1024
|
}
|
|
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
1025
|
evaluate(assignment, options) {
|
|
833
1026
|
const validateFirst = options?.validateFirst ?? true;
|
|
834
1027
|
if (validateFirst) {
|
|
@@ -922,16 +1115,6 @@ export class ArgumentEngine {
|
|
|
922
1115
|
};
|
|
923
1116
|
}
|
|
924
1117
|
}
|
|
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
1118
|
checkValidity(options) {
|
|
936
1119
|
const validateFirst = options?.validateFirst ?? true;
|
|
937
1120
|
if (validateFirst) {
|