@polintpro/proposit-core 0.6.6 → 0.7.5
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 +201 -0
- package/dist/extensions/basics/schemata.d.ts +7 -0
- package/dist/extensions/basics/schemata.d.ts.map +1 -1
- package/dist/lib/consts.d.ts.map +1 -1
- package/dist/lib/consts.js +34 -2
- package/dist/lib/consts.js.map +1 -1
- package/dist/lib/core/argument-engine.d.ts +38 -2
- package/dist/lib/core/argument-engine.d.ts.map +1 -1
- package/dist/lib/core/argument-engine.js +636 -227
- package/dist/lib/core/argument-engine.js.map +1 -1
- package/dist/lib/core/change-collector.d.ts +1 -0
- package/dist/lib/core/change-collector.d.ts.map +1 -1
- package/dist/lib/core/change-collector.js +3 -0
- package/dist/lib/core/change-collector.js.map +1 -1
- package/dist/lib/core/claim-library.d.ts +4 -0
- package/dist/lib/core/claim-library.d.ts.map +1 -1
- package/dist/lib/core/claim-library.js +126 -59
- package/dist/lib/core/claim-library.js.map +1 -1
- package/dist/lib/core/claim-source-library.d.ts +4 -0
- package/dist/lib/core/claim-source-library.d.ts.map +1 -1
- package/dist/lib/core/claim-source-library.js +114 -38
- package/dist/lib/core/claim-source-library.js.map +1 -1
- package/dist/lib/core/diff.d.ts +10 -0
- package/dist/lib/core/diff.d.ts.map +1 -1
- package/dist/lib/core/diff.js +114 -21
- package/dist/lib/core/diff.js.map +1 -1
- package/dist/lib/core/expression-manager.d.ts +11 -0
- package/dist/lib/core/expression-manager.d.ts.map +1 -1
- package/dist/lib/core/expression-manager.js +379 -20
- package/dist/lib/core/expression-manager.js.map +1 -1
- package/dist/lib/core/fork.d.ts +31 -0
- package/dist/lib/core/fork.d.ts.map +1 -0
- package/dist/lib/core/fork.js +139 -0
- package/dist/lib/core/fork.js.map +1 -0
- package/dist/lib/core/forks-library.d.ts +58 -0
- package/dist/lib/core/forks-library.d.ts.map +1 -0
- package/dist/lib/core/forks-library.js +142 -0
- package/dist/lib/core/forks-library.js.map +1 -0
- package/dist/lib/core/interfaces/argument-engine.interfaces.d.ts +9 -2
- package/dist/lib/core/interfaces/argument-engine.interfaces.d.ts.map +1 -1
- package/dist/lib/core/interfaces/index.d.ts +1 -1
- package/dist/lib/core/interfaces/index.d.ts.map +1 -1
- package/dist/lib/core/interfaces/library.interfaces.d.ts +48 -0
- package/dist/lib/core/interfaces/library.interfaces.d.ts.map +1 -1
- package/dist/lib/core/interfaces/premise-engine.interfaces.d.ts +22 -0
- package/dist/lib/core/interfaces/premise-engine.interfaces.d.ts.map +1 -1
- package/dist/lib/core/invariant-violation-error.d.ts +6 -0
- package/dist/lib/core/invariant-violation-error.d.ts.map +1 -0
- package/dist/lib/core/invariant-violation-error.js +12 -0
- package/dist/lib/core/invariant-violation-error.js.map +1 -0
- package/dist/lib/core/parser/formula.d.ts.map +1 -1
- package/dist/lib/core/parser/formula.js +2 -2
- package/dist/lib/core/parser/formula.js.map +1 -1
- package/dist/lib/core/premise-engine.d.ts +10 -0
- package/dist/lib/core/premise-engine.d.ts.map +1 -1
- package/dist/lib/core/premise-engine.js +699 -536
- package/dist/lib/core/premise-engine.js.map +1 -1
- package/dist/lib/core/source-library.d.ts +4 -0
- package/dist/lib/core/source-library.d.ts.map +1 -1
- package/dist/lib/core/source-library.js +126 -59
- package/dist/lib/core/source-library.js.map +1 -1
- package/dist/lib/core/variable-manager.d.ts +7 -0
- package/dist/lib/core/variable-manager.d.ts.map +1 -1
- package/dist/lib/core/variable-manager.js +65 -1
- package/dist/lib/core/variable-manager.js.map +1 -1
- package/dist/lib/index.d.ts +9 -1
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +8 -1
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/schemata/argument.d.ts +3 -0
- package/dist/lib/schemata/argument.d.ts.map +1 -1
- package/dist/lib/schemata/argument.js +9 -0
- package/dist/lib/schemata/argument.js.map +1 -1
- package/dist/lib/schemata/fork.d.ts +17 -0
- package/dist/lib/schemata/fork.d.ts.map +1 -0
- package/dist/lib/schemata/fork.js +27 -0
- package/dist/lib/schemata/fork.js.map +1 -0
- 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/propositional.d.ts +52 -0
- package/dist/lib/schemata/propositional.d.ts.map +1 -1
- package/dist/lib/schemata/propositional.js +43 -0
- package/dist/lib/schemata/propositional.js.map +1 -1
- package/dist/lib/types/checksum.d.ts +2 -0
- package/dist/lib/types/checksum.d.ts.map +1 -1
- package/dist/lib/types/diff.d.ts +6 -0
- package/dist/lib/types/diff.d.ts.map +1 -1
- package/dist/lib/types/fork.d.ts +25 -0
- package/dist/lib/types/fork.d.ts.map +1 -0
- package/dist/lib/types/fork.js +2 -0
- package/dist/lib/types/fork.js.map +1 -0
- package/dist/lib/types/grammar.d.ts +5 -4
- package/dist/lib/types/grammar.d.ts.map +1 -1
- package/dist/lib/types/grammar.js.map +1 -1
- package/dist/lib/types/validation.d.ts +47 -0
- package/dist/lib/types/validation.d.ts.map +1 -0
- package/dist/lib/types/validation.js +43 -0
- package/dist/lib/types/validation.js.map +1 -0
- package/dist/lib/utils/changeset.d.ts +124 -0
- package/dist/lib/utils/changeset.d.ts.map +1 -0
- package/dist/lib/utils/changeset.js +221 -0
- package/dist/lib/utils/changeset.js.map +1 -0
- package/dist/lib/utils/lookup.d.ts +47 -0
- package/dist/lib/utils/lookup.d.ts.map +1 -0
- package/dist/lib/utils/lookup.js +62 -0
- package/dist/lib/utils/lookup.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { Value } from "typebox/value";
|
|
3
|
+
import { CoreArgumentSchema, isClaimBound, isPremiseBound, } from "../schemata/index.js";
|
|
4
|
+
import { DEFAULT_GRAMMAR_CONFIG, PERMISSIVE_GRAMMAR_CONFIG, } from "../types/grammar.js";
|
|
5
|
+
import { ARG_SCHEMA_INVALID, ARG_OWNERSHIP_MISMATCH, ARG_CLAIM_REF_NOT_FOUND, ARG_PREMISE_REF_NOT_FOUND, ARG_CIRCULARITY_DETECTED, ARG_CONCLUSION_NOT_FOUND, ARG_CHECKSUM_MISMATCH, } from "../types/validation.js";
|
|
4
6
|
import { DEFAULT_CHECKSUM_CONFIG, normalizeChecksumConfig, serializeChecksumConfig, } from "../consts.js";
|
|
5
7
|
import { getOrCreate, sortedUnique } from "../utils/collections.js";
|
|
6
8
|
import { ChangeCollector } from "./change-collector.js";
|
|
7
9
|
import { canonicalSerialize, computeHash, entityChecksum } from "./checksum.js";
|
|
8
10
|
import { kleeneAnd, kleeneNot } from "./evaluation/kleene.js";
|
|
9
11
|
import { makeErrorIssue, makeValidationResult, } from "./evaluation/validation.js";
|
|
12
|
+
import { InvariantViolationError } from "./invariant-violation-error.js";
|
|
10
13
|
import { PremiseEngine } from "./premise-engine.js";
|
|
11
14
|
import { VariableManager } from "./variable-manager.js";
|
|
12
15
|
/**
|
|
@@ -27,6 +30,7 @@ export class ArgumentEngine {
|
|
|
27
30
|
checksumConfig;
|
|
28
31
|
positionConfig;
|
|
29
32
|
grammarConfig;
|
|
33
|
+
restoringFromSnapshot = false;
|
|
30
34
|
checksumDirty = true;
|
|
31
35
|
cachedMetaChecksum;
|
|
32
36
|
cachedDescendantChecksum;
|
|
@@ -104,6 +108,15 @@ export class ArgumentEngine {
|
|
|
104
108
|
return !boundPremise?.getRootExpressionId();
|
|
105
109
|
});
|
|
106
110
|
}
|
|
111
|
+
generateUniqueSymbol() {
|
|
112
|
+
let n = this.premises.size - 1;
|
|
113
|
+
let candidate = `P${n}`;
|
|
114
|
+
while (this.variables.getVariableBySymbol(candidate) !== undefined) {
|
|
115
|
+
n++;
|
|
116
|
+
candidate = `P${n}`;
|
|
117
|
+
}
|
|
118
|
+
return candidate;
|
|
119
|
+
}
|
|
107
120
|
subscribe = (listener) => {
|
|
108
121
|
this.listeners.add(listener);
|
|
109
122
|
return () => {
|
|
@@ -115,6 +128,47 @@ export class ArgumentEngine {
|
|
|
115
128
|
listener();
|
|
116
129
|
}
|
|
117
130
|
}
|
|
131
|
+
static skipValidationResult = {
|
|
132
|
+
ok: true,
|
|
133
|
+
violations: [],
|
|
134
|
+
};
|
|
135
|
+
suppressPremiseValidation() {
|
|
136
|
+
for (const pe of this.premises.values()) {
|
|
137
|
+
pe.setArgumentValidateCallback(() => ArgumentEngine.skipValidationResult);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
restorePremiseValidation() {
|
|
141
|
+
for (const pe of this.premises.values()) {
|
|
142
|
+
pe.setArgumentValidateCallback(() => this.validateAfterPremiseMutation());
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
withValidation(fn) {
|
|
146
|
+
if (this.restoringFromSnapshot) {
|
|
147
|
+
return fn();
|
|
148
|
+
}
|
|
149
|
+
const snap = this.snapshot();
|
|
150
|
+
// Suppress PremiseEngine-level validation during ArgumentEngine
|
|
151
|
+
// mutations. The ArgumentEngine will do its own validation at the end.
|
|
152
|
+
this.suppressPremiseValidation();
|
|
153
|
+
try {
|
|
154
|
+
const result = fn();
|
|
155
|
+
const validation = this.validate();
|
|
156
|
+
if (!validation.ok) {
|
|
157
|
+
this.rollbackInternal(snap);
|
|
158
|
+
throw new InvariantViolationError(validation.violations);
|
|
159
|
+
}
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
catch (e) {
|
|
163
|
+
if (!(e instanceof InvariantViolationError)) {
|
|
164
|
+
this.rollbackInternal(snap);
|
|
165
|
+
}
|
|
166
|
+
throw e;
|
|
167
|
+
}
|
|
168
|
+
finally {
|
|
169
|
+
this.restorePremiseValidation();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
118
172
|
getSnapshot = () => {
|
|
119
173
|
return this.buildReactiveSnapshot();
|
|
120
174
|
};
|
|
@@ -265,90 +319,115 @@ export class ArgumentEngine {
|
|
|
265
319
|
}
|
|
266
320
|
return lines.join("\n");
|
|
267
321
|
}
|
|
268
|
-
createPremise(extras) {
|
|
269
|
-
return this.createPremiseWithId(randomUUID(), extras);
|
|
322
|
+
createPremise(extras, symbol) {
|
|
323
|
+
return this.createPremiseWithId(randomUUID(), extras, symbol);
|
|
270
324
|
}
|
|
271
|
-
createPremiseWithId(id, extras) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
325
|
+
createPremiseWithId(id, extras, symbol) {
|
|
326
|
+
return this.withValidation(() => {
|
|
327
|
+
if (this.premises.has(id)) {
|
|
328
|
+
throw new Error(`Premise "${id}" already exists.`);
|
|
329
|
+
}
|
|
330
|
+
const premiseData = {
|
|
331
|
+
...extras,
|
|
332
|
+
id,
|
|
333
|
+
argumentId: this.argument.id,
|
|
334
|
+
argumentVersion: this.argument.version,
|
|
335
|
+
};
|
|
336
|
+
const pm = new PremiseEngine(premiseData, {
|
|
337
|
+
argument: this.argument,
|
|
338
|
+
variables: this.variables,
|
|
339
|
+
expressionIndex: this.expressionIndex,
|
|
340
|
+
}, {
|
|
341
|
+
checksumConfig: this.checksumConfig,
|
|
342
|
+
positionConfig: this.positionConfig,
|
|
343
|
+
grammarConfig: this.grammarConfig,
|
|
344
|
+
});
|
|
345
|
+
this.premises.set(id, pm);
|
|
346
|
+
this.wireCircularityCheck(pm);
|
|
347
|
+
this.wireEmptyBoundPremiseCheck(pm);
|
|
348
|
+
pm.setVariableIdsCallback(() => new Set(this.variables.toArray().map((v) => v.id)));
|
|
349
|
+
pm.setArgumentValidateCallback(() => this.validateAfterPremiseMutation());
|
|
350
|
+
pm.setOnMutate(() => {
|
|
351
|
+
this.markDirty();
|
|
352
|
+
this.reactiveDirty.premiseIds.add(id);
|
|
353
|
+
this.notifySubscribers();
|
|
354
|
+
});
|
|
355
|
+
const collector = new ChangeCollector();
|
|
356
|
+
collector.addedPremise(pm.toPremiseData());
|
|
294
357
|
this.markDirty();
|
|
295
|
-
this.
|
|
358
|
+
if (this.conclusionPremiseId === undefined) {
|
|
359
|
+
this.conclusionPremiseId = id;
|
|
360
|
+
collector.setRoles(this.getRoleState());
|
|
361
|
+
}
|
|
362
|
+
// Auto-create a premise-bound variable for this premise
|
|
363
|
+
if (!this.restoringFromSnapshot) {
|
|
364
|
+
const autoSymbol = symbol ?? this.generateUniqueSymbol();
|
|
365
|
+
const autoVariable = {
|
|
366
|
+
id: randomUUID(),
|
|
367
|
+
argumentId: this.argument.id,
|
|
368
|
+
argumentVersion: this.argument.version,
|
|
369
|
+
symbol: autoSymbol,
|
|
370
|
+
boundPremiseId: id,
|
|
371
|
+
boundArgumentId: this.argument.id,
|
|
372
|
+
boundArgumentVersion: this.argument.version,
|
|
373
|
+
};
|
|
374
|
+
const withChecksum = this.attachVariableChecksum({
|
|
375
|
+
...autoVariable,
|
|
376
|
+
});
|
|
377
|
+
this.variables.addVariable(withChecksum);
|
|
378
|
+
collector.addedVariable(withChecksum);
|
|
379
|
+
this.markAllPremisesDirty();
|
|
380
|
+
}
|
|
381
|
+
const changes = collector.toChangeset();
|
|
382
|
+
this.markReactiveDirty(changes);
|
|
296
383
|
this.notifySubscribers();
|
|
384
|
+
return {
|
|
385
|
+
result: pm,
|
|
386
|
+
changes,
|
|
387
|
+
};
|
|
297
388
|
});
|
|
298
|
-
const collector = new ChangeCollector();
|
|
299
|
-
collector.addedPremise(pm.toPremiseData());
|
|
300
|
-
this.markDirty();
|
|
301
|
-
if (this.conclusionPremiseId === undefined) {
|
|
302
|
-
this.conclusionPremiseId = id;
|
|
303
|
-
collector.setRoles(this.getRoleState());
|
|
304
|
-
}
|
|
305
|
-
const changes = collector.toChangeset();
|
|
306
|
-
this.markReactiveDirty(changes);
|
|
307
|
-
this.notifySubscribers();
|
|
308
|
-
return {
|
|
309
|
-
result: pm,
|
|
310
|
-
changes,
|
|
311
|
-
};
|
|
312
389
|
}
|
|
313
390
|
removePremise(premiseId) {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
this.conclusionPremiseId
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
// Cascade: remove variables bound to the deleted premise
|
|
330
|
-
const boundVars = this.getVariablesBoundToPremise(premiseId);
|
|
331
|
-
for (const v of boundVars) {
|
|
332
|
-
const removeResult = this.removeVariable(v.id);
|
|
333
|
-
if (removeResult.changes.variables) {
|
|
334
|
-
for (const rv of removeResult.changes.variables.removed) {
|
|
335
|
-
collector.removedVariable(rv);
|
|
336
|
-
}
|
|
391
|
+
return this.withValidation(() => {
|
|
392
|
+
const pm = this.premises.get(premiseId);
|
|
393
|
+
if (!pm)
|
|
394
|
+
return { result: undefined, changes: {} };
|
|
395
|
+
const data = pm.toPremiseData();
|
|
396
|
+
const collector = new ChangeCollector();
|
|
397
|
+
// Clean up expression index for removed premise's expressions
|
|
398
|
+
for (const expr of pm.getExpressions()) {
|
|
399
|
+
this.expressionIndex.delete(expr.id);
|
|
400
|
+
}
|
|
401
|
+
this.premises.delete(premiseId);
|
|
402
|
+
collector.removedPremise(data);
|
|
403
|
+
if (this.conclusionPremiseId === premiseId) {
|
|
404
|
+
this.conclusionPremiseId = undefined;
|
|
405
|
+
collector.setRoles(this.getRoleState());
|
|
337
406
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
407
|
+
// Cascade: remove variables bound to the deleted premise
|
|
408
|
+
const boundVars = this.getVariablesBoundToPremise(premiseId);
|
|
409
|
+
for (const v of boundVars) {
|
|
410
|
+
const removeResult = this.removeVariableCore(v.id);
|
|
411
|
+
if (removeResult.changes.variables) {
|
|
412
|
+
for (const rv of removeResult.changes.variables.removed) {
|
|
413
|
+
collector.removedVariable(rv);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (removeResult.changes.expressions) {
|
|
417
|
+
for (const re of removeResult.changes.expressions.removed) {
|
|
418
|
+
collector.removedExpression(re);
|
|
419
|
+
}
|
|
341
420
|
}
|
|
342
421
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
};
|
|
422
|
+
this.markDirty();
|
|
423
|
+
const changes = collector.toChangeset();
|
|
424
|
+
this.markReactiveDirty(changes);
|
|
425
|
+
this.notifySubscribers();
|
|
426
|
+
return {
|
|
427
|
+
result: data,
|
|
428
|
+
changes,
|
|
429
|
+
};
|
|
430
|
+
});
|
|
352
431
|
}
|
|
353
432
|
getPremise(premiseId) {
|
|
354
433
|
return this.premises.get(premiseId);
|
|
@@ -365,122 +444,90 @@ export class ArgumentEngine {
|
|
|
365
444
|
.filter((pm) => pm !== undefined);
|
|
366
445
|
}
|
|
367
446
|
addVariable(variable) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
447
|
+
return this.withValidation(() => {
|
|
448
|
+
// Only claim-bound variables may be added via addVariable.
|
|
449
|
+
// Premise-bound variables must use bindVariableToPremise.
|
|
450
|
+
if (!isClaimBound(variable)) {
|
|
451
|
+
throw new Error("addVariable only accepts claim-bound variables. Use bindVariableToPremise for premise-bound variables.");
|
|
452
|
+
}
|
|
453
|
+
if (variable.argumentId !== this.argument.id) {
|
|
454
|
+
throw new Error(`Variable argumentId "${variable.argumentId}" does not match engine argument ID "${this.argument.id}".`);
|
|
455
|
+
}
|
|
456
|
+
if (variable.argumentVersion !== this.argument.version) {
|
|
457
|
+
throw new Error(`Variable argumentVersion "${variable.argumentVersion}" does not match engine argument version "${this.argument.version}".`);
|
|
458
|
+
}
|
|
459
|
+
// Validate claim reference
|
|
460
|
+
if (!this.claimLibrary.get(variable.claimId, variable.claimVersion)) {
|
|
461
|
+
throw new Error(`Claim "${variable.claimId}" version ${variable.claimVersion} does not exist in the claim library.`);
|
|
462
|
+
}
|
|
463
|
+
const withChecksum = this.attachVariableChecksum({
|
|
464
|
+
...variable,
|
|
465
|
+
});
|
|
466
|
+
this.variables.addVariable(withChecksum);
|
|
467
|
+
const collector = new ChangeCollector();
|
|
468
|
+
collector.addedVariable(withChecksum);
|
|
469
|
+
this.markDirty();
|
|
470
|
+
this.markAllPremisesDirty();
|
|
471
|
+
const changes = collector.toChangeset();
|
|
472
|
+
this.markReactiveDirty(changes);
|
|
473
|
+
this.notifySubscribers();
|
|
474
|
+
return {
|
|
475
|
+
result: withChecksum,
|
|
476
|
+
changes,
|
|
477
|
+
};
|
|
385
478
|
});
|
|
386
|
-
this.variables.addVariable(withChecksum);
|
|
387
|
-
const collector = new ChangeCollector();
|
|
388
|
-
collector.addedVariable(withChecksum);
|
|
389
|
-
this.markDirty();
|
|
390
|
-
this.markAllPremisesDirty();
|
|
391
|
-
const changes = collector.toChangeset();
|
|
392
|
-
this.markReactiveDirty(changes);
|
|
393
|
-
this.notifySubscribers();
|
|
394
|
-
return {
|
|
395
|
-
result: withChecksum,
|
|
396
|
-
changes,
|
|
397
|
-
};
|
|
398
479
|
}
|
|
399
480
|
bindVariableToPremise(variable) {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
481
|
+
return this.withValidation(() => {
|
|
482
|
+
if (variable.argumentId !== this.argument.id) {
|
|
483
|
+
throw new Error(`Variable argumentId "${variable.argumentId}" does not match engine argument ID "${this.argument.id}".`);
|
|
484
|
+
}
|
|
485
|
+
if (variable.argumentVersion !== this.argument.version) {
|
|
486
|
+
throw new Error(`Variable argumentVersion "${variable.argumentVersion}" does not match engine argument version "${this.argument.version}".`);
|
|
487
|
+
}
|
|
488
|
+
if (variable.boundArgumentId !== this.argument.id) {
|
|
489
|
+
throw new Error(`Cross-argument bindings are not supported. boundArgumentId "${variable.boundArgumentId}" does not match engine argument ID "${this.argument.id}".`);
|
|
490
|
+
}
|
|
491
|
+
if (!this.premises.has(variable.boundPremiseId)) {
|
|
492
|
+
throw new Error(`Bound premise "${variable.boundPremiseId}" does not exist in this argument.`);
|
|
493
|
+
}
|
|
494
|
+
const withChecksum = this.attachVariableChecksum({
|
|
495
|
+
...variable,
|
|
496
|
+
});
|
|
497
|
+
this.variables.addVariable(withChecksum);
|
|
498
|
+
const collector = new ChangeCollector();
|
|
499
|
+
collector.addedVariable(withChecksum);
|
|
500
|
+
this.markDirty();
|
|
501
|
+
this.markAllPremisesDirty();
|
|
502
|
+
const changes = collector.toChangeset();
|
|
503
|
+
this.markReactiveDirty(changes);
|
|
504
|
+
this.notifySubscribers();
|
|
505
|
+
return {
|
|
506
|
+
result: withChecksum,
|
|
507
|
+
changes,
|
|
508
|
+
};
|
|
414
509
|
});
|
|
415
|
-
this.variables.addVariable(withChecksum);
|
|
416
|
-
const collector = new ChangeCollector();
|
|
417
|
-
collector.addedVariable(withChecksum);
|
|
418
|
-
this.markDirty();
|
|
419
|
-
this.markAllPremisesDirty();
|
|
420
|
-
const changes = collector.toChangeset();
|
|
421
|
-
this.markReactiveDirty(changes);
|
|
422
|
-
this.notifySubscribers();
|
|
423
|
-
return {
|
|
424
|
-
result: withChecksum,
|
|
425
|
-
changes,
|
|
426
|
-
};
|
|
427
510
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
}
|
|
433
|
-
const existingVar = existing;
|
|
434
|
-
const updatesObj = updates;
|
|
435
|
-
// Reject binding-type conversion
|
|
436
|
-
if (isClaimBound(existingVar)) {
|
|
437
|
-
const premiseBoundFields = [
|
|
438
|
-
"boundPremiseId",
|
|
439
|
-
"boundArgumentId",
|
|
440
|
-
"boundArgumentVersion",
|
|
441
|
-
];
|
|
442
|
-
for (const f of premiseBoundFields) {
|
|
443
|
-
if (updatesObj[f] !== undefined) {
|
|
444
|
-
throw new Error(`Cannot set "${f}" on a claim-bound variable. Delete and re-create to change binding type.`);
|
|
445
|
-
}
|
|
511
|
+
bindVariableToExternalPremise(variable) {
|
|
512
|
+
return this.withValidation(() => {
|
|
513
|
+
if (variable.argumentId !== this.argument.id) {
|
|
514
|
+
throw new Error(`Variable argumentId "${variable.argumentId}" does not match engine argument ID "${this.argument.id}".`);
|
|
446
515
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
const hasClaimVersion = updatesObj.claimVersion !== undefined;
|
|
450
|
-
if (hasClaimId !== hasClaimVersion) {
|
|
451
|
-
throw new Error("claimId and claimVersion must be provided together.");
|
|
516
|
+
if (variable.argumentVersion !== this.argument.version) {
|
|
517
|
+
throw new Error(`Variable argumentVersion "${variable.argumentVersion}" does not match engine argument version "${this.argument.version}".`);
|
|
452
518
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
if (!this.claimLibrary.get(updatesObj.claimId, updatesObj.claimVersion)) {
|
|
456
|
-
throw new Error(`Claim "${String(updatesObj.claimId)}" version ${String(updatesObj.claimVersion)} does not exist in the claim library.`);
|
|
457
|
-
}
|
|
519
|
+
if (variable.boundArgumentId === this.argument.id) {
|
|
520
|
+
throw new Error(`boundArgumentId matches this engine's argument — use bindVariableToPremise for internal bindings.`);
|
|
458
521
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
const claimBoundFields = ["claimId", "claimVersion"];
|
|
462
|
-
for (const f of claimBoundFields) {
|
|
463
|
-
if (updatesObj[f] !== undefined) {
|
|
464
|
-
throw new Error(`Cannot set "${f}" on a premise-bound variable. Delete and re-create to change binding type.`);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
// Validate boundPremiseId if provided
|
|
468
|
-
if (updatesObj.boundPremiseId !== undefined) {
|
|
469
|
-
const newPremiseId = updatesObj.boundPremiseId;
|
|
470
|
-
if (!this.premises.has(newPremiseId)) {
|
|
471
|
-
throw new Error(`Bound premise "${newPremiseId}" does not exist in this argument.`);
|
|
472
|
-
}
|
|
522
|
+
if (!this.canBind(variable.boundArgumentId, variable.boundArgumentVersion)) {
|
|
523
|
+
throw new Error(`Binding to argument "${variable.boundArgumentId}" version ${variable.boundArgumentVersion} is not allowed.`);
|
|
473
524
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
if (updated) {
|
|
478
|
-
const withChecksum = this.attachVariableChecksum({ ...updated });
|
|
479
|
-
// Re-store with updated checksum so VariableManager always holds
|
|
480
|
-
// variables with correct checksums.
|
|
481
|
-
this.variables.removeVariable(variableId);
|
|
525
|
+
const withChecksum = this.attachVariableChecksum({
|
|
526
|
+
...variable,
|
|
527
|
+
});
|
|
482
528
|
this.variables.addVariable(withChecksum);
|
|
483
|
-
collector
|
|
529
|
+
const collector = new ChangeCollector();
|
|
530
|
+
collector.addedVariable(withChecksum);
|
|
484
531
|
this.markDirty();
|
|
485
532
|
this.markAllPremisesDirty();
|
|
486
533
|
const changes = collector.toChangeset();
|
|
@@ -490,13 +537,90 @@ export class ArgumentEngine {
|
|
|
490
537
|
result: withChecksum,
|
|
491
538
|
changes,
|
|
492
539
|
};
|
|
493
|
-
}
|
|
494
|
-
return {
|
|
495
|
-
result: undefined,
|
|
496
|
-
changes: collector.toChangeset(),
|
|
497
|
-
};
|
|
540
|
+
});
|
|
498
541
|
}
|
|
499
|
-
|
|
542
|
+
bindVariableToArgument(variable, conclusionPremiseId) {
|
|
543
|
+
return this.bindVariableToExternalPremise({
|
|
544
|
+
...variable,
|
|
545
|
+
boundPremiseId: conclusionPremiseId,
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
updateVariable(variableId, updates) {
|
|
549
|
+
return this.withValidation(() => {
|
|
550
|
+
const existing = this.variables.getVariable(variableId);
|
|
551
|
+
if (!existing) {
|
|
552
|
+
return { result: undefined, changes: {} };
|
|
553
|
+
}
|
|
554
|
+
const existingVar = existing;
|
|
555
|
+
const updatesObj = updates;
|
|
556
|
+
// Reject binding-type conversion
|
|
557
|
+
if (isClaimBound(existingVar)) {
|
|
558
|
+
const premiseBoundFields = [
|
|
559
|
+
"boundPremiseId",
|
|
560
|
+
"boundArgumentId",
|
|
561
|
+
"boundArgumentVersion",
|
|
562
|
+
];
|
|
563
|
+
for (const f of premiseBoundFields) {
|
|
564
|
+
if (updatesObj[f] !== undefined) {
|
|
565
|
+
throw new Error(`Cannot set "${f}" on a claim-bound variable. Delete and re-create to change binding type.`);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
// Validate: claimId and claimVersion must be provided together
|
|
569
|
+
const hasClaimId = updatesObj.claimId !== undefined;
|
|
570
|
+
const hasClaimVersion = updatesObj.claimVersion !== undefined;
|
|
571
|
+
if (hasClaimId !== hasClaimVersion) {
|
|
572
|
+
throw new Error("claimId and claimVersion must be provided together.");
|
|
573
|
+
}
|
|
574
|
+
// Validate claim reference if provided
|
|
575
|
+
if (hasClaimId && hasClaimVersion) {
|
|
576
|
+
if (!this.claimLibrary.get(updatesObj.claimId, updatesObj.claimVersion)) {
|
|
577
|
+
throw new Error(`Claim "${String(updatesObj.claimId)}" version ${String(updatesObj.claimVersion)} does not exist in the claim library.`);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
else if (isPremiseBound(existingVar)) {
|
|
582
|
+
const claimBoundFields = ["claimId", "claimVersion"];
|
|
583
|
+
for (const f of claimBoundFields) {
|
|
584
|
+
if (updatesObj[f] !== undefined) {
|
|
585
|
+
throw new Error(`Cannot set "${f}" on a premise-bound variable. Delete and re-create to change binding type.`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
// Validate boundPremiseId if provided
|
|
589
|
+
if (updatesObj.boundPremiseId !== undefined) {
|
|
590
|
+
const newPremiseId = updatesObj.boundPremiseId;
|
|
591
|
+
if (!this.premises.has(newPremiseId)) {
|
|
592
|
+
throw new Error(`Bound premise "${newPremiseId}" does not exist in this argument.`);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
const updated = this.variables.updateVariable(variableId, updates);
|
|
597
|
+
const collector = new ChangeCollector();
|
|
598
|
+
if (updated) {
|
|
599
|
+
const withChecksum = this.attachVariableChecksum({
|
|
600
|
+
...updated,
|
|
601
|
+
});
|
|
602
|
+
// Re-store with updated checksum so VariableManager always holds
|
|
603
|
+
// variables with correct checksums.
|
|
604
|
+
this.variables.removeVariable(variableId);
|
|
605
|
+
this.variables.addVariable(withChecksum);
|
|
606
|
+
collector.modifiedVariable(withChecksum);
|
|
607
|
+
this.markDirty();
|
|
608
|
+
this.markAllPremisesDirty();
|
|
609
|
+
const changes = collector.toChangeset();
|
|
610
|
+
this.markReactiveDirty(changes);
|
|
611
|
+
this.notifySubscribers();
|
|
612
|
+
return {
|
|
613
|
+
result: withChecksum,
|
|
614
|
+
changes,
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
return {
|
|
618
|
+
result: undefined,
|
|
619
|
+
changes: collector.toChangeset(),
|
|
620
|
+
};
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
removeVariableCore(variableId) {
|
|
500
624
|
const variable = this.variables.getVariable(variableId);
|
|
501
625
|
if (!variable) {
|
|
502
626
|
return { result: undefined, changes: {} };
|
|
@@ -523,6 +647,11 @@ export class ArgumentEngine {
|
|
|
523
647
|
changes,
|
|
524
648
|
};
|
|
525
649
|
}
|
|
650
|
+
removeVariable(variableId) {
|
|
651
|
+
return this.withValidation(() => {
|
|
652
|
+
return this.removeVariableCore(variableId);
|
|
653
|
+
});
|
|
654
|
+
}
|
|
526
655
|
getVariables() {
|
|
527
656
|
return this.variables.toArray();
|
|
528
657
|
}
|
|
@@ -605,36 +734,40 @@ export class ArgumentEngine {
|
|
|
605
734
|
};
|
|
606
735
|
}
|
|
607
736
|
setConclusionPremise(premiseId) {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
737
|
+
return this.withValidation(() => {
|
|
738
|
+
const premise = this.premises.get(premiseId);
|
|
739
|
+
if (!premise) {
|
|
740
|
+
throw new Error(`Premise "${premiseId}" does not exist.`);
|
|
741
|
+
}
|
|
742
|
+
this.conclusionPremiseId = premiseId;
|
|
743
|
+
const roles = this.getRoleState();
|
|
744
|
+
const collector = new ChangeCollector();
|
|
745
|
+
collector.setRoles(roles);
|
|
746
|
+
this.markDirty();
|
|
747
|
+
const changes = collector.toChangeset();
|
|
748
|
+
this.markReactiveDirty(changes);
|
|
749
|
+
this.notifySubscribers();
|
|
750
|
+
return {
|
|
751
|
+
result: roles,
|
|
752
|
+
changes,
|
|
753
|
+
};
|
|
754
|
+
});
|
|
624
755
|
}
|
|
625
756
|
clearConclusionPremise() {
|
|
626
|
-
this.
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
757
|
+
return this.withValidation(() => {
|
|
758
|
+
this.conclusionPremiseId = undefined;
|
|
759
|
+
const roles = this.getRoleState();
|
|
760
|
+
const collector = new ChangeCollector();
|
|
761
|
+
collector.setRoles(roles);
|
|
762
|
+
this.markDirty();
|
|
763
|
+
const changes = collector.toChangeset();
|
|
764
|
+
this.markReactiveDirty(changes);
|
|
765
|
+
this.notifySubscribers();
|
|
766
|
+
return {
|
|
767
|
+
result: roles,
|
|
768
|
+
changes,
|
|
769
|
+
};
|
|
770
|
+
});
|
|
638
771
|
}
|
|
639
772
|
getConclusionPremise() {
|
|
640
773
|
if (this.conclusionPremiseId === undefined) {
|
|
@@ -674,12 +807,15 @@ export class ArgumentEngine {
|
|
|
674
807
|
checksumConfig: normalizeChecksumConfig(snapshot.config.checksumConfig),
|
|
675
808
|
}
|
|
676
809
|
: undefined);
|
|
810
|
+
engine.restoringFromSnapshot = true;
|
|
677
811
|
// Restore premises first (premise-bound variables reference them)
|
|
678
812
|
for (const premiseSnap of snapshot.premises) {
|
|
679
813
|
const pe = PremiseEngine.fromSnapshot(premiseSnap, snapshot.argument, engine.variables, engine.expressionIndex, grammarConfig);
|
|
680
814
|
engine.premises.set(pe.getId(), pe);
|
|
681
815
|
engine.wireCircularityCheck(pe);
|
|
682
816
|
engine.wireEmptyBoundPremiseCheck(pe);
|
|
817
|
+
pe.setVariableIdsCallback(() => new Set(engine.variables.toArray().map((v) => v.id)));
|
|
818
|
+
pe.setArgumentValidateCallback(() => engine.validateAfterPremiseMutation());
|
|
683
819
|
const premiseId = pe.getId();
|
|
684
820
|
pe.setOnMutate(() => {
|
|
685
821
|
engine.markDirty();
|
|
@@ -695,15 +831,26 @@ export class ArgumentEngine {
|
|
|
695
831
|
}
|
|
696
832
|
for (const v of snapshot.variables.variables) {
|
|
697
833
|
if (isPremiseBound(v)) {
|
|
698
|
-
|
|
834
|
+
const pbv = v;
|
|
835
|
+
if (pbv.boundArgumentId === engine.argument.id) {
|
|
836
|
+
engine.bindVariableToPremise(v);
|
|
837
|
+
}
|
|
838
|
+
else {
|
|
839
|
+
engine.bindVariableToExternalPremise(v);
|
|
840
|
+
}
|
|
699
841
|
}
|
|
700
842
|
}
|
|
701
843
|
// Restore conclusion role (don't use setConclusionPremise to avoid auto-assign logic)
|
|
702
844
|
engine.conclusionPremiseId = snapshot.conclusionPremiseId;
|
|
845
|
+
engine.restoringFromSnapshot = false;
|
|
703
846
|
if (checksumVerification === "strict") {
|
|
704
847
|
engine.flushChecksums();
|
|
705
848
|
ArgumentEngine.verifySnapshotChecksums(engine, snapshot);
|
|
706
849
|
}
|
|
850
|
+
const validation = engine.validate();
|
|
851
|
+
if (!validation.ok) {
|
|
852
|
+
throw new InvariantViolationError(validation.violations);
|
|
853
|
+
}
|
|
707
854
|
return engine;
|
|
708
855
|
}
|
|
709
856
|
/**
|
|
@@ -713,7 +860,7 @@ export class ArgumentEngine {
|
|
|
713
860
|
* of already-added nodes) to satisfy parent-existence requirements.
|
|
714
861
|
*/
|
|
715
862
|
static fromData(argument, claimLibrary, sourceLibrary, claimSourceLibrary, variables, premises, expressions, roles, config, grammarConfig, checksumVerification) {
|
|
716
|
-
const loadingGrammarConfig = grammarConfig ??
|
|
863
|
+
const loadingGrammarConfig = grammarConfig ?? config?.grammarConfig ?? DEFAULT_GRAMMAR_CONFIG;
|
|
717
864
|
const normalizedConfig = config
|
|
718
865
|
? {
|
|
719
866
|
...config,
|
|
@@ -725,6 +872,7 @@ export class ArgumentEngine {
|
|
|
725
872
|
grammarConfig: loadingGrammarConfig,
|
|
726
873
|
};
|
|
727
874
|
const engine = new ArgumentEngine(argument, claimLibrary, sourceLibrary, claimSourceLibrary, loadingConfig);
|
|
875
|
+
engine.restoringFromSnapshot = true;
|
|
728
876
|
// Register claim-bound variables first (no dependencies)
|
|
729
877
|
for (const v of variables) {
|
|
730
878
|
if (isClaimBound(v)) {
|
|
@@ -741,7 +889,13 @@ export class ArgumentEngine {
|
|
|
741
889
|
// Register premise-bound variables (depend on premises)
|
|
742
890
|
for (const v of variables) {
|
|
743
891
|
if (isPremiseBound(v)) {
|
|
744
|
-
|
|
892
|
+
const pbv = v;
|
|
893
|
+
if (pbv.boundArgumentId === engine.argument.id) {
|
|
894
|
+
engine.bindVariableToPremise(v);
|
|
895
|
+
}
|
|
896
|
+
else {
|
|
897
|
+
engine.bindVariableToExternalPremise(v);
|
|
898
|
+
}
|
|
745
899
|
}
|
|
746
900
|
}
|
|
747
901
|
// Group expressions by premiseId
|
|
@@ -767,10 +921,15 @@ export class ArgumentEngine {
|
|
|
767
921
|
}
|
|
768
922
|
// After loading: restore the caller's intended grammar config
|
|
769
923
|
engine.grammarConfig = config?.grammarConfig;
|
|
924
|
+
engine.restoringFromSnapshot = false;
|
|
770
925
|
if (checksumVerification === "strict") {
|
|
771
926
|
engine.flushChecksums();
|
|
772
927
|
ArgumentEngine.verifyDataChecksums(engine, argument, variables, premises);
|
|
773
928
|
}
|
|
929
|
+
const validation = engine.validate();
|
|
930
|
+
if (!validation.ok) {
|
|
931
|
+
throw new InvariantViolationError(validation.violations);
|
|
932
|
+
}
|
|
774
933
|
return engine;
|
|
775
934
|
}
|
|
776
935
|
/**
|
|
@@ -882,6 +1041,15 @@ export class ArgumentEngine {
|
|
|
882
1041
|
}
|
|
883
1042
|
}
|
|
884
1043
|
rollback(snapshot) {
|
|
1044
|
+
const preRollbackSnap = this.snapshot();
|
|
1045
|
+
this.rollbackInternal(snapshot);
|
|
1046
|
+
const validation = this.validate();
|
|
1047
|
+
if (!validation.ok) {
|
|
1048
|
+
this.rollbackInternal(preRollbackSnap);
|
|
1049
|
+
throw new InvariantViolationError(validation.violations);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
rollbackInternal(snapshot) {
|
|
885
1053
|
this.argument = { ...snapshot.argument };
|
|
886
1054
|
this.checksumConfig = normalizeChecksumConfig(snapshot.config?.checksumConfig);
|
|
887
1055
|
this.positionConfig = snapshot.config?.positionConfig;
|
|
@@ -897,6 +1065,8 @@ export class ArgumentEngine {
|
|
|
897
1065
|
for (const pe of this.premises.values()) {
|
|
898
1066
|
this.wireCircularityCheck(pe);
|
|
899
1067
|
this.wireEmptyBoundPremiseCheck(pe);
|
|
1068
|
+
pe.setVariableIdsCallback(() => new Set(this.variables.toArray().map((v) => v.id)));
|
|
1069
|
+
pe.setArgumentValidateCallback(() => this.validateAfterPremiseMutation());
|
|
900
1070
|
const premiseId = pe.getId();
|
|
901
1071
|
pe.setOnMutate(() => {
|
|
902
1072
|
this.markDirty();
|
|
@@ -999,6 +1169,11 @@ export class ArgumentEngine {
|
|
|
999
1169
|
}
|
|
1000
1170
|
markDirty() {
|
|
1001
1171
|
this.checksumDirty = true;
|
|
1172
|
+
this.cachedMetaChecksum = undefined;
|
|
1173
|
+
this.cachedDescendantChecksum = undefined;
|
|
1174
|
+
this.cachedCombinedChecksum = undefined;
|
|
1175
|
+
this.cachedPremisesCollectionChecksum = undefined;
|
|
1176
|
+
this.cachedVariablesCollectionChecksum = undefined;
|
|
1002
1177
|
}
|
|
1003
1178
|
/** Invalidate all premise checksums (e.g. after variable changes). */
|
|
1004
1179
|
markAllPremisesDirty() {
|
|
@@ -1060,6 +1235,207 @@ export class ArgumentEngine {
|
|
|
1060
1235
|
bySymbol,
|
|
1061
1236
|
};
|
|
1062
1237
|
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Validates after a PremiseEngine mutation. Identical to `validate()` but
|
|
1240
|
+
* clears cached argument-level checksums first so the checksum-stability
|
|
1241
|
+
* check is skipped (checksums are known to be dirty after a premise
|
|
1242
|
+
* mutation).
|
|
1243
|
+
*/
|
|
1244
|
+
/**
|
|
1245
|
+
* Lightweight validation triggered after a PremiseEngine mutation.
|
|
1246
|
+
* Skips per-premise deep validation (which is O(n) over all premises)
|
|
1247
|
+
* and argument-level checksum stability checks (checksums are known to
|
|
1248
|
+
* be dirty). Only checks argument-level cross-references that a
|
|
1249
|
+
* PremiseEngine mutation could affect.
|
|
1250
|
+
*/
|
|
1251
|
+
validateAfterPremiseMutation() {
|
|
1252
|
+
const violations = [];
|
|
1253
|
+
// Variable references: ensure all variable expressions in the
|
|
1254
|
+
// mutated premise still reference known variables (this is the main
|
|
1255
|
+
// cross-cutting invariant a premise mutation can break).
|
|
1256
|
+
for (const v of this.variables.toArray()) {
|
|
1257
|
+
const base = v;
|
|
1258
|
+
if (isPremiseBound(base)) {
|
|
1259
|
+
const pb = base;
|
|
1260
|
+
if (pb.boundArgumentId === this.argument.id) {
|
|
1261
|
+
if (!this.premises.has(pb.boundPremiseId)) {
|
|
1262
|
+
violations.push({
|
|
1263
|
+
code: ARG_PREMISE_REF_NOT_FOUND,
|
|
1264
|
+
message: `Premise-bound variable "${pb.id}" references non-existent premise "${pb.boundPremiseId}".`,
|
|
1265
|
+
entityType: "variable",
|
|
1266
|
+
entityId: pb.id,
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
// Conclusion premise reference
|
|
1273
|
+
if (this.conclusionPremiseId !== undefined &&
|
|
1274
|
+
!this.premises.has(this.conclusionPremiseId)) {
|
|
1275
|
+
violations.push({
|
|
1276
|
+
code: ARG_CONCLUSION_NOT_FOUND,
|
|
1277
|
+
message: `Conclusion premise "${this.conclusionPremiseId}" does not exist in this argument.`,
|
|
1278
|
+
entityType: "argument",
|
|
1279
|
+
entityId: this.argument.id,
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
return {
|
|
1283
|
+
ok: violations.length === 0,
|
|
1284
|
+
violations,
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
validate() {
|
|
1288
|
+
const violations = [];
|
|
1289
|
+
// 1. Schema check — flush checksums first so fields are populated
|
|
1290
|
+
const savedMeta = this.cachedMetaChecksum;
|
|
1291
|
+
const savedDescendant = this.cachedDescendantChecksum;
|
|
1292
|
+
const savedCombined = this.cachedCombinedChecksum;
|
|
1293
|
+
this.flushChecksums();
|
|
1294
|
+
const arg = this.getArgument();
|
|
1295
|
+
if (!Value.Check(CoreArgumentSchema, arg)) {
|
|
1296
|
+
violations.push({
|
|
1297
|
+
code: ARG_SCHEMA_INVALID,
|
|
1298
|
+
message: `Argument "${arg.id}" does not conform to CoreArgumentSchema.`,
|
|
1299
|
+
entityType: "argument",
|
|
1300
|
+
entityId: arg.id,
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
// 2. Delegate to VariableManager.validate()
|
|
1304
|
+
const varResult = this.variables.validate();
|
|
1305
|
+
violations.push(...varResult.violations);
|
|
1306
|
+
// 3. Delegate to each PremiseEngine.validate()
|
|
1307
|
+
for (const pe of this.listPremises()) {
|
|
1308
|
+
const premiseResult = pe.validate();
|
|
1309
|
+
violations.push(...premiseResult.violations);
|
|
1310
|
+
}
|
|
1311
|
+
// 4. Variable ownership: all variables must belong to this argument
|
|
1312
|
+
for (const v of this.variables.toArray()) {
|
|
1313
|
+
const base = v;
|
|
1314
|
+
if (base.argumentId !== this.argument.id ||
|
|
1315
|
+
base.argumentVersion !== this.argument.version) {
|
|
1316
|
+
violations.push({
|
|
1317
|
+
code: ARG_OWNERSHIP_MISMATCH,
|
|
1318
|
+
message: `Variable "${base.id}" has argumentId/version "${base.argumentId}/${base.argumentVersion}" but engine is "${this.argument.id}/${this.argument.version}".`,
|
|
1319
|
+
entityType: "variable",
|
|
1320
|
+
entityId: base.id,
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
// 5. Claim-bound variable references
|
|
1325
|
+
for (const v of this.variables.toArray()) {
|
|
1326
|
+
const base = v;
|
|
1327
|
+
if (isClaimBound(base)) {
|
|
1328
|
+
const cb = base;
|
|
1329
|
+
if (!this.claimLibrary.get(cb.claimId, cb.claimVersion)) {
|
|
1330
|
+
violations.push({
|
|
1331
|
+
code: ARG_CLAIM_REF_NOT_FOUND,
|
|
1332
|
+
message: `Variable "${cb.id}" references claim "${cb.claimId}" version ${cb.claimVersion} which does not exist in the claim library.`,
|
|
1333
|
+
entityType: "variable",
|
|
1334
|
+
entityId: cb.id,
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
// 6. Premise-bound internal variable references
|
|
1340
|
+
for (const v of this.variables.toArray()) {
|
|
1341
|
+
const base = v;
|
|
1342
|
+
if (isPremiseBound(base)) {
|
|
1343
|
+
const pb = base;
|
|
1344
|
+
if (pb.boundArgumentId === this.argument.id) {
|
|
1345
|
+
if (!this.premises.has(pb.boundPremiseId)) {
|
|
1346
|
+
violations.push({
|
|
1347
|
+
code: ARG_PREMISE_REF_NOT_FOUND,
|
|
1348
|
+
message: `Premise-bound variable "${pb.id}" references non-existent premise "${pb.boundPremiseId}".`,
|
|
1349
|
+
entityType: "variable",
|
|
1350
|
+
entityId: pb.id,
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
// 7. Circularity detection for internal premise-bound variables.
|
|
1357
|
+
// A cycle exists when a premise-bound variable's bound premise
|
|
1358
|
+
// transitively references back to itself through other
|
|
1359
|
+
// premise-bound variables.
|
|
1360
|
+
for (const v of this.variables.toArray()) {
|
|
1361
|
+
const base = v;
|
|
1362
|
+
if (isPremiseBound(base)) {
|
|
1363
|
+
const pb = base;
|
|
1364
|
+
if (pb.boundArgumentId === this.argument.id) {
|
|
1365
|
+
// Trace from the bound premise through expressions'
|
|
1366
|
+
// variable references to see if we reach back to the
|
|
1367
|
+
// same premise.
|
|
1368
|
+
const boundPremise = this.premises.get(pb.boundPremiseId);
|
|
1369
|
+
if (boundPremise) {
|
|
1370
|
+
let hasCycle = false;
|
|
1371
|
+
for (const expr of boundPremise.getExpressions()) {
|
|
1372
|
+
if (expr.type === "variable") {
|
|
1373
|
+
try {
|
|
1374
|
+
if (this.wouldCreateCycle(expr.variableId, pb.boundPremiseId, new Set())) {
|
|
1375
|
+
hasCycle = true;
|
|
1376
|
+
break;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
catch {
|
|
1380
|
+
hasCycle = true;
|
|
1381
|
+
break;
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
if (hasCycle) {
|
|
1386
|
+
violations.push({
|
|
1387
|
+
code: ARG_CIRCULARITY_DETECTED,
|
|
1388
|
+
message: `Premise-bound variable "${pb.id}" creates a circular dependency through premise "${pb.boundPremiseId}".`,
|
|
1389
|
+
entityType: "variable",
|
|
1390
|
+
entityId: pb.id,
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
// 8. Conclusion premise reference
|
|
1398
|
+
if (this.conclusionPremiseId !== undefined &&
|
|
1399
|
+
!this.premises.has(this.conclusionPremiseId)) {
|
|
1400
|
+
violations.push({
|
|
1401
|
+
code: ARG_CONCLUSION_NOT_FOUND,
|
|
1402
|
+
message: `Conclusion premise "${this.conclusionPremiseId}" does not exist in this argument.`,
|
|
1403
|
+
entityType: "argument",
|
|
1404
|
+
entityId: this.argument.id,
|
|
1405
|
+
});
|
|
1406
|
+
}
|
|
1407
|
+
// 9. Argument-level checksum verification
|
|
1408
|
+
if (savedMeta !== undefined && savedMeta !== this.cachedMetaChecksum) {
|
|
1409
|
+
violations.push({
|
|
1410
|
+
code: ARG_CHECKSUM_MISMATCH,
|
|
1411
|
+
message: `Argument "${this.argument.id}" meta checksum changed after flush: "${savedMeta}" → "${this.cachedMetaChecksum}".`,
|
|
1412
|
+
entityType: "argument",
|
|
1413
|
+
entityId: this.argument.id,
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
if (savedDescendant !== undefined &&
|
|
1417
|
+
savedDescendant !== this.cachedDescendantChecksum) {
|
|
1418
|
+
violations.push({
|
|
1419
|
+
code: ARG_CHECKSUM_MISMATCH,
|
|
1420
|
+
message: `Argument "${this.argument.id}" descendant checksum changed after flush: "${String(savedDescendant)}" → "${String(this.cachedDescendantChecksum)}".`,
|
|
1421
|
+
entityType: "argument",
|
|
1422
|
+
entityId: this.argument.id,
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1425
|
+
if (savedCombined !== undefined &&
|
|
1426
|
+
savedCombined !== this.cachedCombinedChecksum) {
|
|
1427
|
+
violations.push({
|
|
1428
|
+
code: ARG_CHECKSUM_MISMATCH,
|
|
1429
|
+
message: `Argument "${this.argument.id}" combined checksum changed after flush: "${savedCombined}" → "${this.cachedCombinedChecksum}".`,
|
|
1430
|
+
entityType: "argument",
|
|
1431
|
+
entityId: this.argument.id,
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
return {
|
|
1435
|
+
ok: violations.length === 0,
|
|
1436
|
+
violations,
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1063
1439
|
validateEvaluability() {
|
|
1064
1440
|
const issues = [];
|
|
1065
1441
|
if (this.conclusionPremiseId === undefined) {
|
|
@@ -1150,11 +1526,17 @@ export class ArgumentEngine {
|
|
|
1150
1526
|
.filter((expr) => expr.type === "variable")
|
|
1151
1527
|
.map((expr) => expr.variableId))),
|
|
1152
1528
|
].sort();
|
|
1153
|
-
//
|
|
1154
|
-
//
|
|
1529
|
+
// Claim-bound and externally-bound premise variables get truth-table columns;
|
|
1530
|
+
// internally-bound premise variables are resolved lazily.
|
|
1155
1531
|
const referencedVariableIds = allVariableIds.filter((vid) => {
|
|
1156
1532
|
const v = this.variables.getVariable(vid);
|
|
1157
|
-
|
|
1533
|
+
if (v == null)
|
|
1534
|
+
return false;
|
|
1535
|
+
if (isClaimBound(v))
|
|
1536
|
+
return true;
|
|
1537
|
+
if (isPremiseBound(v) && v.boundArgumentId !== this.argument.id)
|
|
1538
|
+
return true;
|
|
1539
|
+
return false;
|
|
1158
1540
|
});
|
|
1159
1541
|
try {
|
|
1160
1542
|
// Build a resolver that lazily evaluates premise-bound variables
|
|
@@ -1166,9 +1548,13 @@ export class ArgumentEngine {
|
|
|
1166
1548
|
return resolverCache.get(variableId);
|
|
1167
1549
|
}
|
|
1168
1550
|
const variable = this.variables.getVariable(variableId);
|
|
1169
|
-
if (!variable ||
|
|
1551
|
+
if (!variable ||
|
|
1552
|
+
!isPremiseBound(variable) ||
|
|
1553
|
+
variable.boundArgumentId !== this.argument.id) {
|
|
1554
|
+
// Claim-bound or externally-bound: read from assignment
|
|
1170
1555
|
return assignment.variables[variableId] ?? null;
|
|
1171
1556
|
}
|
|
1557
|
+
// Internal premise-bound: lazy resolution
|
|
1172
1558
|
const boundPremiseId = variable.boundPremiseId;
|
|
1173
1559
|
const boundPremise = this.premises.get(boundPremiseId);
|
|
1174
1560
|
if (!boundPremise) {
|
|
@@ -1274,11 +1660,17 @@ export class ArgumentEngine {
|
|
|
1274
1660
|
.filter((expr) => expr.type === "variable")
|
|
1275
1661
|
.map((expr) => expr.variableId))),
|
|
1276
1662
|
].sort();
|
|
1277
|
-
//
|
|
1278
|
-
//
|
|
1663
|
+
// Claim-bound and externally-bound premise variables get truth-table columns;
|
|
1664
|
+
// internally-bound premise variables are resolved lazily.
|
|
1279
1665
|
const checkedVariableIds = allVariableIdsForCheck.filter((vid) => {
|
|
1280
1666
|
const v = this.variables.getVariable(vid);
|
|
1281
|
-
|
|
1667
|
+
if (v == null)
|
|
1668
|
+
return false;
|
|
1669
|
+
if (isClaimBound(v))
|
|
1670
|
+
return true;
|
|
1671
|
+
if (isPremiseBound(v) && v.boundArgumentId !== this.argument.id)
|
|
1672
|
+
return true;
|
|
1673
|
+
return false;
|
|
1282
1674
|
});
|
|
1283
1675
|
if (options?.maxVariables !== undefined &&
|
|
1284
1676
|
checkedVariableIds.length > options.maxVariables) {
|
|
@@ -1355,5 +1747,22 @@ export class ArgumentEngine {
|
|
|
1355
1747
|
truncated,
|
|
1356
1748
|
};
|
|
1357
1749
|
}
|
|
1750
|
+
// -----------------------------------------------------------------
|
|
1751
|
+
// Forking
|
|
1752
|
+
// -----------------------------------------------------------------
|
|
1753
|
+
/**
|
|
1754
|
+
* Override point for subclasses to prevent forking. When this returns
|
|
1755
|
+
* `false`, `forkArgument` will throw.
|
|
1756
|
+
*/
|
|
1757
|
+
canFork() {
|
|
1758
|
+
return true;
|
|
1759
|
+
}
|
|
1760
|
+
/**
|
|
1761
|
+
* Override point for subclasses to restrict cross-argument bindings.
|
|
1762
|
+
* When this returns `false`, `bindVariableToExternalPremise` will throw.
|
|
1763
|
+
*/
|
|
1764
|
+
canBind(_boundArgumentId, _boundArgumentVersion) {
|
|
1765
|
+
return true;
|
|
1766
|
+
}
|
|
1358
1767
|
}
|
|
1359
1768
|
//# sourceMappingURL=argument-engine.js.map
|