@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.
Files changed (126) hide show
  1. package/README.md +13 -0
  2. package/dist/cli/commands/sources.d.ts +3 -0
  3. package/dist/cli/commands/sources.d.ts.map +1 -0
  4. package/dist/cli/commands/sources.js +118 -0
  5. package/dist/cli/commands/sources.js.map +1 -0
  6. package/dist/cli/commands/variables.d.ts.map +1 -1
  7. package/dist/cli/commands/variables.js +3 -0
  8. package/dist/cli/commands/variables.js.map +1 -1
  9. package/dist/cli/config.d.ts +2 -0
  10. package/dist/cli/config.d.ts.map +1 -1
  11. package/dist/cli/config.js +6 -0
  12. package/dist/cli/config.js.map +1 -1
  13. package/dist/cli/engine.d.ts.map +1 -1
  14. package/dist/cli/engine.js +35 -2
  15. package/dist/cli/engine.js.map +1 -1
  16. package/dist/cli/import.d.ts.map +1 -1
  17. package/dist/cli/import.js +7 -1
  18. package/dist/cli/import.js.map +1 -1
  19. package/dist/cli/schemata.d.ts +8 -0
  20. package/dist/cli/schemata.d.ts.map +1 -1
  21. package/dist/cli/schemata.js +10 -0
  22. package/dist/cli/schemata.js.map +1 -1
  23. package/dist/cli/storage/sources.d.ts +11 -0
  24. package/dist/cli/storage/sources.d.ts.map +1 -0
  25. package/dist/cli/storage/sources.js +105 -0
  26. package/dist/cli/storage/sources.js.map +1 -0
  27. package/dist/cli.js +2 -0
  28. package/dist/cli.js.map +1 -1
  29. package/dist/extensions/ieee/index.d.ts +3 -0
  30. package/dist/extensions/ieee/index.d.ts.map +1 -0
  31. package/dist/extensions/ieee/index.js +3 -0
  32. package/dist/extensions/ieee/index.js.map +1 -0
  33. package/dist/extensions/ieee/references.d.ts +620 -0
  34. package/dist/extensions/ieee/references.d.ts.map +1 -0
  35. package/dist/extensions/ieee/references.js +450 -0
  36. package/dist/extensions/ieee/references.js.map +1 -0
  37. package/dist/extensions/ieee/source.d.ts +286 -0
  38. package/dist/extensions/ieee/source.d.ts.map +1 -0
  39. package/dist/extensions/ieee/source.js +12 -0
  40. package/dist/extensions/ieee/source.js.map +1 -0
  41. package/dist/lib/consts.d.ts.map +1 -1
  42. package/dist/lib/consts.js +31 -1
  43. package/dist/lib/consts.js.map +1 -1
  44. package/dist/lib/core/argument-engine.d.ts +25 -117
  45. package/dist/lib/core/argument-engine.d.ts.map +1 -1
  46. package/dist/lib/core/argument-engine.js +239 -123
  47. package/dist/lib/core/argument-engine.js.map +1 -1
  48. package/dist/lib/core/assertion-library.d.ts +27 -0
  49. package/dist/lib/core/assertion-library.d.ts.map +1 -0
  50. package/dist/lib/core/assertion-library.js +129 -0
  51. package/dist/lib/core/assertion-library.js.map +1 -0
  52. package/dist/lib/core/change-collector.d.ts +7 -5
  53. package/dist/lib/core/change-collector.d.ts.map +1 -1
  54. package/dist/lib/core/change-collector.js +18 -5
  55. package/dist/lib/core/change-collector.js.map +1 -1
  56. package/dist/lib/core/diff.d.ts +6 -2
  57. package/dist/lib/core/diff.d.ts.map +1 -1
  58. package/dist/lib/core/diff.js +80 -1
  59. package/dist/lib/core/diff.js.map +1 -1
  60. package/dist/lib/core/interfaces/argument-engine.interfaces.d.ts +338 -0
  61. package/dist/lib/core/interfaces/argument-engine.interfaces.d.ts.map +1 -0
  62. package/dist/lib/core/interfaces/argument-engine.interfaces.js +2 -0
  63. package/dist/lib/core/interfaces/argument-engine.interfaces.js.map +1 -0
  64. package/dist/lib/core/interfaces/index.d.ts +6 -0
  65. package/dist/lib/core/interfaces/index.d.ts.map +1 -0
  66. package/dist/lib/core/interfaces/index.js +2 -0
  67. package/dist/lib/core/interfaces/index.js.map +1 -0
  68. package/dist/lib/core/interfaces/library.interfaces.d.ts +19 -0
  69. package/dist/lib/core/interfaces/library.interfaces.d.ts.map +1 -0
  70. package/dist/lib/core/interfaces/library.interfaces.js +2 -0
  71. package/dist/lib/core/interfaces/library.interfaces.js.map +1 -0
  72. package/dist/lib/core/interfaces/premise-engine.interfaces.d.ts +318 -0
  73. package/dist/lib/core/interfaces/premise-engine.interfaces.d.ts.map +1 -0
  74. package/dist/lib/core/interfaces/premise-engine.interfaces.js +2 -0
  75. package/dist/lib/core/interfaces/premise-engine.interfaces.js.map +1 -0
  76. package/dist/lib/core/interfaces/shared.interfaces.d.ts +24 -0
  77. package/dist/lib/core/interfaces/shared.interfaces.d.ts.map +1 -0
  78. package/dist/lib/core/interfaces/shared.interfaces.js +2 -0
  79. package/dist/lib/core/interfaces/shared.interfaces.js.map +1 -0
  80. package/dist/lib/core/interfaces/source-management.interfaces.d.ts +63 -0
  81. package/dist/lib/core/interfaces/source-management.interfaces.d.ts.map +1 -0
  82. package/dist/lib/core/interfaces/source-management.interfaces.js +2 -0
  83. package/dist/lib/core/interfaces/source-management.interfaces.js.map +1 -0
  84. package/dist/lib/core/premise-engine.d.ts +10 -144
  85. package/dist/lib/core/premise-engine.d.ts.map +1 -1
  86. package/dist/lib/core/premise-engine.js +82 -143
  87. package/dist/lib/core/premise-engine.js.map +1 -1
  88. package/dist/lib/core/source-library.d.ts +27 -0
  89. package/dist/lib/core/source-library.d.ts.map +1 -0
  90. package/dist/lib/core/source-library.js +129 -0
  91. package/dist/lib/core/source-library.js.map +1 -0
  92. package/dist/lib/core/source-manager.d.ts +38 -0
  93. package/dist/lib/core/source-manager.d.ts.map +1 -0
  94. package/dist/lib/core/source-manager.js +266 -0
  95. package/dist/lib/core/source-manager.js.map +1 -0
  96. package/dist/lib/index.d.ts +6 -1
  97. package/dist/lib/index.d.ts.map +1 -1
  98. package/dist/lib/index.js +4 -1
  99. package/dist/lib/index.js.map +1 -1
  100. package/dist/lib/schemata/assertion.d.ts +9 -0
  101. package/dist/lib/schemata/assertion.d.ts.map +1 -0
  102. package/dist/lib/schemata/assertion.js +18 -0
  103. package/dist/lib/schemata/assertion.js.map +1 -0
  104. package/dist/lib/schemata/index.d.ts +2 -0
  105. package/dist/lib/schemata/index.d.ts.map +1 -1
  106. package/dist/lib/schemata/index.js +2 -0
  107. package/dist/lib/schemata/index.js.map +1 -1
  108. package/dist/lib/schemata/propositional.d.ts +2 -0
  109. package/dist/lib/schemata/propositional.d.ts.map +1 -1
  110. package/dist/lib/schemata/propositional.js +5 -1
  111. package/dist/lib/schemata/propositional.js.map +1 -1
  112. package/dist/lib/schemata/source.d.ts +30 -0
  113. package/dist/lib/schemata/source.d.ts.map +1 -0
  114. package/dist/lib/schemata/source.js +45 -0
  115. package/dist/lib/schemata/source.js.map +1 -0
  116. package/dist/lib/types/checksum.d.ts +8 -0
  117. package/dist/lib/types/checksum.d.ts.map +1 -1
  118. package/dist/lib/types/diff.d.ts +5 -1
  119. package/dist/lib/types/diff.d.ts.map +1 -1
  120. package/dist/lib/types/evaluation.d.ts +1 -1
  121. package/dist/lib/types/evaluation.d.ts.map +1 -1
  122. package/dist/lib/types/mutation.d.ts +3 -0
  123. package/dist/lib/types/mutation.d.ts.map +1 -1
  124. package/dist/lib/types/reactive.d.ts +3 -1
  125. package/dist/lib/types/reactive.d.ts.map +1 -1
  126. 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
- // Clean up expression index for removed premise's expressions
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
- /** Returns a serializable snapshot of the full engine state. */
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) {