@proposit/shared 0.5.0 → 0.6.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/dist/api-client/argument/batch.d.ts +12 -12
- package/dist/api-client/argument/claims.d.ts +8 -10
- package/dist/api-client/argument/claims.d.ts.map +1 -1
- package/dist/api-client/argument/index.d.ts +13 -16
- package/dist/api-client/argument/index.d.ts.map +1 -1
- package/dist/api-client/argument/logic/index.d.ts +10 -10
- package/dist/api-client/argument/logic/repair.d.ts +5 -5
- package/dist/api-client/user/citations.d.ts +4 -4
- package/dist/api-client/user/claims.d.ts +2 -2
- package/dist/checksum.d.ts.map +1 -1
- package/dist/checksum.js +7 -8
- package/dist/checksum.js.map +1 -1
- package/dist/engine/mutations/index.d.ts +1 -1
- package/dist/engine/mutations/index.d.ts.map +1 -1
- package/dist/engine/mutations/index.js +1 -1
- package/dist/engine/mutations/index.js.map +1 -1
- package/dist/engine/mutations/premises.d.ts +56 -1
- package/dist/engine/mutations/premises.d.ts.map +1 -1
- package/dist/engine/mutations/premises.js +500 -0
- package/dist/engine/mutations/premises.js.map +1 -1
- package/dist/engine/text-tree.d.ts.map +1 -1
- package/dist/engine/text-tree.js +5 -0
- package/dist/engine/text-tree.js.map +1 -1
- package/dist/schemas/api/argument/batch/change-edge-operator.d.ts +5 -5
- package/dist/schemas/api/argument/batch/create-expression-with-operator.d.ts +5 -5
- package/dist/schemas/api/argument/batch/provision.d.ts +2 -2
- package/dist/schemas/api/argument/claims.d.ts +6 -8
- package/dist/schemas/api/argument/claims.d.ts.map +1 -1
- package/dist/schemas/api/argument/index.d.ts +7 -8
- package/dist/schemas/api/argument/index.d.ts.map +1 -1
- package/dist/schemas/api/argument/repair.d.ts +5 -5
- package/dist/schemas/api/citations.d.ts +10 -10
- package/dist/schemas/api/claims.d.ts +4 -4
- package/dist/schemas/model/arguments.d.ts +6 -8
- package/dist/schemas/model/arguments.d.ts.map +1 -1
- package/dist/schemas/model/citations.d.ts +0 -1
- package/dist/schemas/model/citations.d.ts.map +1 -1
- package/dist/schemas/model/citations.js +0 -1
- package/dist/schemas/model/citations.js.map +1 -1
- package/dist/schemas/model/claims.d.ts +4 -4
- package/dist/schemas/model/claims.js +1 -1
- package/dist/schemas/model/claims.js.map +1 -1
- package/dist/schemas/snapshot.d.ts +5 -5
- package/dist/schemas/snapshot.d.ts.map +1 -1
- package/dist/schemas/snapshot.js +6 -0
- package/dist/schemas/snapshot.js.map +1 -1
- package/package.json +1 -1
package/dist/checksum.js
CHANGED
|
@@ -10,14 +10,13 @@ export const CHECKSUM_CONFIG = createChecksumConfig({
|
|
|
10
10
|
"createdOn",
|
|
11
11
|
"creatorId",
|
|
12
12
|
]),
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
]),
|
|
13
|
+
// In proposit-core@0.11+, the engine's internal PremiseEngine strips
|
|
14
|
+
// extra fields (role, title, creatorId, createdOn) before computing
|
|
15
|
+
// premise checksums — only the 4 core fields (argumentId, argumentVersion,
|
|
16
|
+
// type, derivedClaimId) are hashed. Matching the DEFAULT_CHECKSUM_CONFIG
|
|
17
|
+
// premise fields here ensures client (PropositArgumentEngine) and server
|
|
18
|
+
// agree on premise checksums regardless of what extra fields are present.
|
|
19
|
+
// premiseFields intentionally omitted: falls back to DEFAULT (4 core fields)
|
|
21
20
|
argumentFields: new Set([
|
|
22
21
|
"title",
|
|
23
22
|
"published",
|
package/dist/checksum.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checksum.js","sourceRoot":"","sources":["../src/checksum.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAA;AAE9D,MAAM,CAAC,MAAM,eAAe,GAAG,oBAAoB,CAAC;IAChD,gBAAgB,EAAE,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,WAAW,CAAC,CAAC;IAClE,cAAc,EAAE,IAAI,GAAG,CAAC;QACpB,SAAS;QACT,cAAc;QACd,gBAAgB;QAChB,iBAAiB;QACjB,sBAAsB;QACtB,WAAW;QACX,WAAW;KACd,CAAC;IACF,
|
|
1
|
+
{"version":3,"file":"checksum.js","sourceRoot":"","sources":["../src/checksum.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAA;AAE9D,MAAM,CAAC,MAAM,eAAe,GAAG,oBAAoB,CAAC;IAChD,gBAAgB,EAAE,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,WAAW,CAAC,CAAC;IAClE,cAAc,EAAE,IAAI,GAAG,CAAC;QACpB,SAAS;QACT,cAAc;QACd,gBAAgB;QAChB,iBAAiB;QACjB,sBAAsB;QACtB,WAAW;QACX,WAAW;KACd,CAAC;IACF,qEAAqE;IACrE,oEAAoE;IACpE,2EAA2E;IAC3E,yEAAyE;IACzE,yEAAyE;IACzE,0EAA0E;IAC1E,6EAA6E;IAC7E,cAAc,EAAE,IAAI,GAAG,CAAC;QACpB,OAAO;QACP,WAAW;QACX,WAAW;QACX,WAAW;QACX,aAAa;QACb,QAAQ;QACR,QAAQ;QACR,aAAa;KAChB,CAAC;CACL,CAAC,CAAA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { mergeChangesets } from "./types.js";
|
|
2
2
|
export type { ProjectEngine, ProjectChangeset, ProjectMutationResult, } from "./types.js";
|
|
3
|
-
export { mutateCreatePremise, mutateUpdatePremiseRole, mutateUpdatePremiseExtras, mutateDeletePremise, } from "./premises.js";
|
|
3
|
+
export { mutateCreatePremise, mutateUpdatePremiseRole, mutateUpdatePremiseExtras, mutateDeletePremise, mutateCreateDerivationPremise, clearDerivationAntecedent, populateDerivationFromCitations, } from "./premises.js";
|
|
4
4
|
export { mutateCreateExpression, mutateUpdateExpression, mutateDeleteExpression, mutateToggleNegation, mutateChangeOperator, mutateAddSiblingExpression, mutateWrapExpression, mutateCreateExpressionWithOperator, } from "./expressions.js";
|
|
5
5
|
export { mutateCreateVariable, mutateUpdateVariable, mutateDeleteVariable, } from "./variables.js";
|
|
6
6
|
export { mutateSetClaim, mutateUpdateClaim, mutateRemoveClaimWithCascade, } from "./claims.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/engine/mutations/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAC5C,YAAY,EACR,aAAa,EACb,gBAAgB,EAChB,qBAAqB,GACxB,MAAM,YAAY,CAAA;AAEnB,OAAO,EACH,mBAAmB,EACnB,uBAAuB,EACvB,yBAAyB,EACzB,mBAAmB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/engine/mutations/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAC5C,YAAY,EACR,aAAa,EACb,gBAAgB,EAChB,qBAAqB,GACxB,MAAM,YAAY,CAAA;AAEnB,OAAO,EACH,mBAAmB,EACnB,uBAAuB,EACvB,yBAAyB,EACzB,mBAAmB,EACnB,6BAA6B,EAC7B,yBAAyB,EACzB,+BAA+B,GAClC,MAAM,eAAe,CAAA;AAEtB,OAAO,EACH,sBAAsB,EACtB,sBAAsB,EACtB,sBAAsB,EACtB,oBAAoB,EACpB,oBAAoB,EACpB,0BAA0B,EAC1B,oBAAoB,EACpB,kCAAkC,GACrC,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EACH,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,GACvB,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EACH,cAAc,EACd,iBAAiB,EACjB,4BAA4B,GAC/B,MAAM,aAAa,CAAA"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { mergeChangesets } from "./types.js";
|
|
2
|
-
export { mutateCreatePremise, mutateUpdatePremiseRole, mutateUpdatePremiseExtras, mutateDeletePremise, } from "./premises.js";
|
|
2
|
+
export { mutateCreatePremise, mutateUpdatePremiseRole, mutateUpdatePremiseExtras, mutateDeletePremise, mutateCreateDerivationPremise, clearDerivationAntecedent, populateDerivationFromCitations, } from "./premises.js";
|
|
3
3
|
export { mutateCreateExpression, mutateUpdateExpression, mutateDeleteExpression, mutateToggleNegation, mutateChangeOperator, mutateAddSiblingExpression, mutateWrapExpression, mutateCreateExpressionWithOperator, } from "./expressions.js";
|
|
4
4
|
export { mutateCreateVariable, mutateUpdateVariable, mutateDeleteVariable, } from "./variables.js";
|
|
5
5
|
export { mutateSetClaim, mutateUpdateClaim, mutateRemoveClaimWithCascade, } from "./claims.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/engine/mutations/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAO5C,OAAO,EACH,mBAAmB,EACnB,uBAAuB,EACvB,yBAAyB,EACzB,mBAAmB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/engine/mutations/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAO5C,OAAO,EACH,mBAAmB,EACnB,uBAAuB,EACvB,yBAAyB,EACzB,mBAAmB,EACnB,6BAA6B,EAC7B,yBAAyB,EACzB,+BAA+B,GAClC,MAAM,eAAe,CAAA;AAEtB,OAAO,EACH,sBAAsB,EACtB,sBAAsB,EACtB,sBAAsB,EACtB,oBAAoB,EACpB,oBAAoB,EACpB,0BAA0B,EAC1B,oBAAoB,EACpB,kCAAkC,GACrC,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EACH,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,GACvB,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EACH,cAAc,EACd,iBAAiB,EACjB,4BAA4B,GAC/B,MAAM,aAAa,CAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TPremiseRoleType, TPropositionalPremise } from "../../schemas/logic.js";
|
|
1
|
+
import type { TPremiseRoleType, TPropositionalPremise, TPropositionalVariable, TPropositionalExpressionCombined } from "../../schemas/logic.js";
|
|
2
2
|
import type { ProjectEngine, ProjectChangeset } from "./types.js";
|
|
3
3
|
export declare function mutateCreatePremise(engine: ProjectEngine, premiseId: string, data: {
|
|
4
4
|
argumentId: string;
|
|
@@ -22,4 +22,59 @@ export declare function mutateDeletePremise(engine: ProjectEngine, premiseId: st
|
|
|
22
22
|
removed: boolean;
|
|
23
23
|
changes: ProjectChangeset;
|
|
24
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* Atomically creates a derivation premise + claim-bound consequent variable +
|
|
27
|
+
* naked-Q variable expression. The naked-Q form (single variable expression at
|
|
28
|
+
* the root) is the only valid empty antecedent shape; populate the antecedent
|
|
29
|
+
* via `populateDerivationFromCitations` once citations exist.
|
|
30
|
+
*
|
|
31
|
+
* All three IDs are caller-controlled so the live engine path produces the same
|
|
32
|
+
* row IDs as the server's raw-SQL migration backfill, simplifying post-migration
|
|
33
|
+
* verification.
|
|
34
|
+
*
|
|
35
|
+
* @throws InvariantViolationError(CREATE_DERIVATION_CLAIM_NOT_FOUND) when
|
|
36
|
+
* `derivedClaimId` does not resolve in the engine's claim library.
|
|
37
|
+
*/
|
|
38
|
+
export declare function mutateCreateDerivationPremise(engine: ProjectEngine, premiseId: string, data: {
|
|
39
|
+
argumentId: string;
|
|
40
|
+
argumentVersion: number;
|
|
41
|
+
creatorId: string;
|
|
42
|
+
createdOn: Date;
|
|
43
|
+
derivedClaimId: string;
|
|
44
|
+
consequentVariableId: string;
|
|
45
|
+
consequentExpressionId: string;
|
|
46
|
+
}): {
|
|
47
|
+
premise: TPropositionalPremise;
|
|
48
|
+
consequentVariable: TPropositionalVariable;
|
|
49
|
+
consequentExpression: TPropositionalExpressionCombined;
|
|
50
|
+
changes: ProjectChangeset;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Removes the IMPLIES/OR antecedent subtree from a populated derivation
|
|
54
|
+
* premise, leaving the premise in the naked-Q form (just the consequent
|
|
55
|
+
* variable as root). Idempotent: a no-op when the premise is already naked.
|
|
56
|
+
*
|
|
57
|
+
* @throws InvariantViolationError(DERIVATION_TYPE_MISMATCH) when `premiseId`
|
|
58
|
+
* resolves to a non-derivation premise.
|
|
59
|
+
*/
|
|
60
|
+
export declare function clearDerivationAntecedent(engine: ProjectEngine, premiseId: string): {
|
|
61
|
+
changes: ProjectChangeset;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Builds the `IMPLIES(OR(citation_var_1, …, citation_var_n), Q)` tree on a
|
|
65
|
+
* naked derivation premise. For `n=1`, produces `IMPLIES(citation_var_1, Q)`.
|
|
66
|
+
* For `n=0`, returns an empty changeset.
|
|
67
|
+
*
|
|
68
|
+
* Caller-supplied `sourceClaimIds` ordering determines the OR's child positions
|
|
69
|
+
* (or the single-source antecedent for n=1).
|
|
70
|
+
*
|
|
71
|
+
* @throws InvariantViolationError(DERIVATION_TYPE_MISMATCH) when `premiseId`
|
|
72
|
+
* resolves to a non-derivation premise.
|
|
73
|
+
* @throws InvariantViolationError(DERIVATION_ANTECEDENT_NON_EMPTY) when the
|
|
74
|
+
* premise's root expression is already an IMPLIES (caller forgot to
|
|
75
|
+
* call `clearDerivationAntecedent` first).
|
|
76
|
+
*/
|
|
77
|
+
export declare function populateDerivationFromCitations(engine: ProjectEngine, premiseId: string, sourceClaimIds: string[]): {
|
|
78
|
+
changes: ProjectChangeset;
|
|
79
|
+
};
|
|
25
80
|
//# sourceMappingURL=premises.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"premises.d.ts","sourceRoot":"","sources":["../../../src/engine/mutations/premises.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"premises.d.ts","sourceRoot":"","sources":["../../../src/engine/mutations/premises.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACR,gBAAgB,EAChB,qBAAqB,EACrB,sBAAsB,EACtB,gCAAgC,EACnC,MAAM,wBAAwB,CAAA;AAC/B,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAQjE,wBAAgB,mBAAmB,CAC/B,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE;IACF,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,IAAI,CAAA;IACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,IAAI,EAAE,gBAAgB,CAAA;CACzB,GACF;IAAE,OAAO,EAAE,qBAAqB,CAAC;IAAC,OAAO,EAAE,gBAAgB,CAAA;CAAE,CA+B/D;AAED,wBAAgB,uBAAuB,CACnC,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,gBAAgB,GAC1B;IAAE,OAAO,EAAE,gBAAgB,CAAA;CAAE,CAa/B;AAED,wBAAgB,yBAAyB,CACrC,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC;IAAE,OAAO,EAAE,qBAAqB,CAAC;IAAC,OAAO,EAAE,gBAAgB,CAAA;CAAE,CAO/D;AAED,wBAAgB,mBAAmB,CAC/B,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,MAAM,GAClB;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,gBAAgB,CAAA;CAAE,CAGjD;AAID;;;;;;;;;;;;GAYG;AACH,wBAAgB,6BAA6B,CACzC,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE;IACF,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,IAAI,CAAA;IACf,cAAc,EAAE,MAAM,CAAA;IACtB,oBAAoB,EAAE,MAAM,CAAA;IAC5B,sBAAsB,EAAE,MAAM,CAAA;CACjC,GACF;IACC,OAAO,EAAE,qBAAqB,CAAA;IAC9B,kBAAkB,EAAE,sBAAsB,CAAA;IAC1C,oBAAoB,EAAE,gCAAgC,CAAA;IACtD,OAAO,EAAE,gBAAgB,CAAA;CAC5B,CA+GA;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CACrC,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,MAAM,GAClB;IAAE,OAAO,EAAE,gBAAgB,CAAA;CAAE,CA2G/B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,+BAA+B,CAC3C,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EAAE,GACzB;IAAE,OAAO,EAAE,gBAAgB,CAAA;CAAE,CAoJ/B"}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
import { InvariantViolationError, PERMISSIVE_GRAMMAR_CONFIG, } from "@proposit/proposit-core";
|
|
1
2
|
import { mergeChangesets } from "./types.js";
|
|
3
|
+
// Error codes mirrored from `@proposit/proposit-core`. Surfaced in messages so
|
|
4
|
+
// callers (server, tests) can match on string codes without importing core.
|
|
5
|
+
const DERIVATION_TYPE_MISMATCH = "DERIVATION_TYPE_MISMATCH";
|
|
6
|
+
const DERIVATION_ANTECEDENT_NON_EMPTY = "DERIVATION_ANTECEDENT_NON_EMPTY";
|
|
2
7
|
export function mutateCreatePremise(engine, premiseId, data) {
|
|
3
8
|
if (data.role === "conclusion" && engine.getConclusionPremise()) {
|
|
4
9
|
throw new Error("A conclusion premise already exists for this argument version");
|
|
@@ -51,4 +56,499 @@ export function mutateDeletePremise(engine, premiseId) {
|
|
|
51
56
|
const { result, changes } = engine.removePremise(premiseId);
|
|
52
57
|
return { removed: result !== undefined, changes };
|
|
53
58
|
}
|
|
59
|
+
// ──── Derivation premise mutations ─────────────────────────────────────────
|
|
60
|
+
/**
|
|
61
|
+
* Atomically creates a derivation premise + claim-bound consequent variable +
|
|
62
|
+
* naked-Q variable expression. The naked-Q form (single variable expression at
|
|
63
|
+
* the root) is the only valid empty antecedent shape; populate the antecedent
|
|
64
|
+
* via `populateDerivationFromCitations` once citations exist.
|
|
65
|
+
*
|
|
66
|
+
* All three IDs are caller-controlled so the live engine path produces the same
|
|
67
|
+
* row IDs as the server's raw-SQL migration backfill, simplifying post-migration
|
|
68
|
+
* verification.
|
|
69
|
+
*
|
|
70
|
+
* @throws InvariantViolationError(CREATE_DERIVATION_CLAIM_NOT_FOUND) when
|
|
71
|
+
* `derivedClaimId` does not resolve in the engine's claim library.
|
|
72
|
+
*/
|
|
73
|
+
export function mutateCreateDerivationPremise(engine, premiseId, data) {
|
|
74
|
+
// 1. Create the derivation premise. Core auto-creates: a premise-bound
|
|
75
|
+
// variable for the new premise, a claim-bound consequent variable
|
|
76
|
+
// (engine-generated id), and a naked-Q variable expression rooted on
|
|
77
|
+
// that consequent variable (engine-generated id).
|
|
78
|
+
const { result: pm, changes: premiseChanges } = engine.createPremiseWithId(premiseId, {
|
|
79
|
+
type: "derivation",
|
|
80
|
+
derivedClaimId: data.derivedClaimId,
|
|
81
|
+
extras: {
|
|
82
|
+
creatorId: data.creatorId,
|
|
83
|
+
createdOn: data.createdOn,
|
|
84
|
+
role: "supporting",
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
// 2. Identify the auto-generated entities so we can swap their IDs for the
|
|
88
|
+
// caller-minted ones.
|
|
89
|
+
const autoExpressions = pm.getExpressions();
|
|
90
|
+
if (autoExpressions.length !== 1) {
|
|
91
|
+
throw new Error(`mutateCreateDerivationPremise: expected exactly one auto-created expression, got ${autoExpressions.length}`);
|
|
92
|
+
}
|
|
93
|
+
const autoExpr = autoExpressions[0];
|
|
94
|
+
if (autoExpr.type !== "variable" || autoExpr.variableId == null) {
|
|
95
|
+
throw new Error("mutateCreateDerivationPremise: auto-created naked-Q expression is not a variable expression");
|
|
96
|
+
}
|
|
97
|
+
const autoVarId = autoExpr.variableId;
|
|
98
|
+
const autoVar = engine.getVariable(autoVarId);
|
|
99
|
+
if (!autoVar || !("claimId" in autoVar)) {
|
|
100
|
+
throw new Error("mutateCreateDerivationPremise: auto-created consequent variable is not claim-bound");
|
|
101
|
+
}
|
|
102
|
+
// 3. Update the engine's internal state to use caller-minted IDs. The
|
|
103
|
+
// intermediate changesets from these calls are discarded; we filter
|
|
104
|
+
// the auto entities out of `premiseChanges` (step 4) so the merged
|
|
105
|
+
// result never references them.
|
|
106
|
+
pm.removeExpression(autoExpr.id, true);
|
|
107
|
+
engine.removeVariable(autoVarId);
|
|
108
|
+
const { result: consequentVariable, changes: addVarChanges } = engine.addVariable({
|
|
109
|
+
id: data.consequentVariableId,
|
|
110
|
+
argumentId: data.argumentId,
|
|
111
|
+
argumentVersion: data.argumentVersion,
|
|
112
|
+
claimId: data.derivedClaimId,
|
|
113
|
+
claimVersion: autoVar.claimVersion,
|
|
114
|
+
symbol: autoVar.symbol,
|
|
115
|
+
creatorId: data.creatorId,
|
|
116
|
+
createdOn: data.createdOn,
|
|
117
|
+
});
|
|
118
|
+
const { result: consequentExpression, changes: addExprChanges } = pm.appendExpression(null, {
|
|
119
|
+
id: data.consequentExpressionId,
|
|
120
|
+
argumentId: data.argumentId,
|
|
121
|
+
argumentVersion: data.argumentVersion,
|
|
122
|
+
premiseId,
|
|
123
|
+
parentId: null,
|
|
124
|
+
type: "variable",
|
|
125
|
+
variableId: data.consequentVariableId,
|
|
126
|
+
operator: null,
|
|
127
|
+
creatorId: data.creatorId,
|
|
128
|
+
createdOn: data.createdOn,
|
|
129
|
+
});
|
|
130
|
+
// 4. Strip the auto entities from the create-premise changeset so the
|
|
131
|
+
// merged result reflects only: the premise, the auto-created
|
|
132
|
+
// premise-bound variable (engine-managed; not caller-minted), and the
|
|
133
|
+
// caller-minted consequent variable + naked-Q expression.
|
|
134
|
+
//
|
|
135
|
+
// Also normalize the changeset so the premise doesn't appear in both
|
|
136
|
+
// `added` and `modified`. Core's createPremiseWithId for derivation
|
|
137
|
+
// emits both because the auto naked-Q expression's `appendExpression`
|
|
138
|
+
// triggers a checksum-change `modifiedPremise` entry that gets merged
|
|
139
|
+
// alongside the outer `addedPremise`. Subsequent mutations
|
|
140
|
+
// (addVariable, appendExpression on the same premise) emit further
|
|
141
|
+
// `modifiedPremise` entries that we also drop.
|
|
142
|
+
const filteredPremiseChanges = sanitizeAddedThenModified(withoutEntities(premiseChanges, {
|
|
143
|
+
variableIds: new Set([autoVarId]),
|
|
144
|
+
expressionIds: new Set([autoExpr.id]),
|
|
145
|
+
}));
|
|
146
|
+
const droppedModifiedAddVarChanges = withoutPremiseModifications(addVarChanges, premiseId);
|
|
147
|
+
const droppedModifiedAddExprChanges = withoutPremiseModifications(addExprChanges, premiseId);
|
|
148
|
+
const allChanges = mergeChangesets(mergeChangesets(filteredPremiseChanges, droppedModifiedAddVarChanges), droppedModifiedAddExprChanges);
|
|
149
|
+
return {
|
|
150
|
+
premise: pm.toPremiseData(),
|
|
151
|
+
consequentVariable,
|
|
152
|
+
consequentExpression,
|
|
153
|
+
changes: allChanges,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Removes the IMPLIES/OR antecedent subtree from a populated derivation
|
|
158
|
+
* premise, leaving the premise in the naked-Q form (just the consequent
|
|
159
|
+
* variable as root). Idempotent: a no-op when the premise is already naked.
|
|
160
|
+
*
|
|
161
|
+
* @throws InvariantViolationError(DERIVATION_TYPE_MISMATCH) when `premiseId`
|
|
162
|
+
* resolves to a non-derivation premise.
|
|
163
|
+
*/
|
|
164
|
+
export function clearDerivationAntecedent(engine, premiseId) {
|
|
165
|
+
const pm = engine.getPremise(premiseId);
|
|
166
|
+
if (!pm) {
|
|
167
|
+
throw new Error(`Premise ${premiseId} not found`);
|
|
168
|
+
}
|
|
169
|
+
const premiseData = pm.toPremiseData();
|
|
170
|
+
if (premiseData.type !== "derivation") {
|
|
171
|
+
throw new InvariantViolationError([
|
|
172
|
+
{
|
|
173
|
+
code: DERIVATION_TYPE_MISMATCH,
|
|
174
|
+
message: `${DERIVATION_TYPE_MISMATCH}: premise ${premiseId} is not a derivation premise (type=${premiseData.type ?? "freeform"})`,
|
|
175
|
+
entityType: "premise",
|
|
176
|
+
entityId: premiseId,
|
|
177
|
+
premiseId,
|
|
178
|
+
},
|
|
179
|
+
]);
|
|
180
|
+
}
|
|
181
|
+
const rootExprId = pm.getRootExpressionId();
|
|
182
|
+
if (rootExprId === undefined)
|
|
183
|
+
return { changes: {} };
|
|
184
|
+
const root = pm.getExpression(rootExprId);
|
|
185
|
+
if (!root)
|
|
186
|
+
return { changes: {} };
|
|
187
|
+
// Naked-Q: root is the consequent variable expression. Nothing to clear.
|
|
188
|
+
if (root.type === "variable")
|
|
189
|
+
return { changes: {} };
|
|
190
|
+
// Populated form: root must be IMPLIES with antecedent at position 0 and
|
|
191
|
+
// the consequent variable at position 1.
|
|
192
|
+
if (root.type !== "operator" || root.operator !== "implies") {
|
|
193
|
+
throw new Error(`clearDerivationAntecedent: unexpected root type for premise ${premiseId} — expected variable or implies, got ${root.type}/${root.type === "operator" ? root.operator : ""}`);
|
|
194
|
+
}
|
|
195
|
+
const rootChildren = pm
|
|
196
|
+
.getChildExpressions(rootExprId)
|
|
197
|
+
.sort((a, b) => a.position - b.position);
|
|
198
|
+
if (rootChildren.length !== 2) {
|
|
199
|
+
throw new Error(`clearDerivationAntecedent: IMPLIES root has ${rootChildren.length} children, expected 2`);
|
|
200
|
+
}
|
|
201
|
+
const antecedent = rootChildren[0];
|
|
202
|
+
const consequent = rootChildren[1];
|
|
203
|
+
if (consequent.type !== "variable") {
|
|
204
|
+
throw new Error("clearDerivationAntecedent: expected consequent (position 1 child of IMPLIES) to be a variable expression");
|
|
205
|
+
}
|
|
206
|
+
// Collect citation-bound variable IDs so we can cascade-remove them after
|
|
207
|
+
// the antecedent expressions are gone. n=1: antecedent is the lone
|
|
208
|
+
// citation variable expression. n≥2: antecedent is an OR with one citation
|
|
209
|
+
// variable child per source.
|
|
210
|
+
const citationVariableIds = [];
|
|
211
|
+
if (antecedent.type === "variable") {
|
|
212
|
+
if (antecedent.variableId != null) {
|
|
213
|
+
citationVariableIds.push(antecedent.variableId);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
else if (antecedent.type === "operator" && antecedent.operator === "or") {
|
|
217
|
+
for (const child of pm.getChildExpressions(antecedent.id)) {
|
|
218
|
+
if (child.type === "variable" && child.variableId != null) {
|
|
219
|
+
citationVariableIds.push(child.variableId);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
throw new Error(`clearDerivationAntecedent: unexpected antecedent shape for premise ${premiseId}`);
|
|
225
|
+
}
|
|
226
|
+
const collected = [];
|
|
227
|
+
// 1. Remove the antecedent subtree (cascade=true). The cascade removes the
|
|
228
|
+
// OR (if present) and all citation variable expressions in one shot.
|
|
229
|
+
// The IMPLIES root is unaffected; the consequent variable expression is
|
|
230
|
+
// unaffected (it's the position-1 child).
|
|
231
|
+
const { changes: removeAntecedentChanges } = pm.removeExpression(antecedent.id, true);
|
|
232
|
+
collected.push(removeAntecedentChanges);
|
|
233
|
+
// 2. Remove IMPLIES with cascade=false, which "promotes" the remaining
|
|
234
|
+
// child (the consequent variable expression) to take IMPLIES's slot —
|
|
235
|
+
// its parentId becomes null, position becomes IMPLIES's old position.
|
|
236
|
+
// The consequent expression appears as `modified` in the changeset.
|
|
237
|
+
withGrammarConfig(pm, PERMISSIVE_GRAMMAR_CONFIG, () => {
|
|
238
|
+
const { changes: removeImpliesChanges } = pm.removeExpression(rootExprId, false);
|
|
239
|
+
collected.push(removeImpliesChanges);
|
|
240
|
+
});
|
|
241
|
+
// 3. Cascade citation-bound variable removals. By this point the
|
|
242
|
+
// expressions referencing them are already gone, so removeVariable's
|
|
243
|
+
// expression-cascade is a no-op and only the variable add/remove
|
|
244
|
+
// bookkeeping shows up in the changeset.
|
|
245
|
+
for (const varId of citationVariableIds) {
|
|
246
|
+
const { changes: removeVarChanges } = engine.removeVariable(varId);
|
|
247
|
+
collected.push(removeVarChanges);
|
|
248
|
+
}
|
|
249
|
+
return { changes: mergeChangesetSequence(collected) };
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Builds the `IMPLIES(OR(citation_var_1, …, citation_var_n), Q)` tree on a
|
|
253
|
+
* naked derivation premise. For `n=1`, produces `IMPLIES(citation_var_1, Q)`.
|
|
254
|
+
* For `n=0`, returns an empty changeset.
|
|
255
|
+
*
|
|
256
|
+
* Caller-supplied `sourceClaimIds` ordering determines the OR's child positions
|
|
257
|
+
* (or the single-source antecedent for n=1).
|
|
258
|
+
*
|
|
259
|
+
* @throws InvariantViolationError(DERIVATION_TYPE_MISMATCH) when `premiseId`
|
|
260
|
+
* resolves to a non-derivation premise.
|
|
261
|
+
* @throws InvariantViolationError(DERIVATION_ANTECEDENT_NON_EMPTY) when the
|
|
262
|
+
* premise's root expression is already an IMPLIES (caller forgot to
|
|
263
|
+
* call `clearDerivationAntecedent` first).
|
|
264
|
+
*/
|
|
265
|
+
export function populateDerivationFromCitations(engine, premiseId, sourceClaimIds) {
|
|
266
|
+
const pm = engine.getPremise(premiseId);
|
|
267
|
+
if (!pm) {
|
|
268
|
+
throw new Error(`Premise ${premiseId} not found`);
|
|
269
|
+
}
|
|
270
|
+
const premiseData = pm.toPremiseData();
|
|
271
|
+
if (premiseData.type !== "derivation") {
|
|
272
|
+
throw new InvariantViolationError([
|
|
273
|
+
{
|
|
274
|
+
code: DERIVATION_TYPE_MISMATCH,
|
|
275
|
+
message: `${DERIVATION_TYPE_MISMATCH}: premise ${premiseId} is not a derivation premise (type=${premiseData.type ?? "freeform"})`,
|
|
276
|
+
entityType: "premise",
|
|
277
|
+
entityId: premiseId,
|
|
278
|
+
premiseId,
|
|
279
|
+
},
|
|
280
|
+
]);
|
|
281
|
+
}
|
|
282
|
+
const rootExprId = pm.getRootExpressionId();
|
|
283
|
+
if (rootExprId === undefined) {
|
|
284
|
+
throw new Error(`populateDerivationFromCitations: premise ${premiseId} has no root expression`);
|
|
285
|
+
}
|
|
286
|
+
const root = pm.getExpression(rootExprId);
|
|
287
|
+
if (!root) {
|
|
288
|
+
throw new Error(`populateDerivationFromCitations: root expression ${rootExprId} not found in premise ${premiseId}`);
|
|
289
|
+
}
|
|
290
|
+
// The antecedent must be empty — root is the bare consequent variable.
|
|
291
|
+
if (root.type !== "variable") {
|
|
292
|
+
throw new InvariantViolationError([
|
|
293
|
+
{
|
|
294
|
+
code: DERIVATION_ANTECEDENT_NON_EMPTY,
|
|
295
|
+
message: `${DERIVATION_ANTECEDENT_NON_EMPTY}: derivation premise ${premiseId} already has a non-empty antecedent`,
|
|
296
|
+
entityType: "premise",
|
|
297
|
+
entityId: premiseId,
|
|
298
|
+
premiseId,
|
|
299
|
+
},
|
|
300
|
+
]);
|
|
301
|
+
}
|
|
302
|
+
if (sourceClaimIds.length === 0)
|
|
303
|
+
return { changes: {} };
|
|
304
|
+
const consequentExpr = root;
|
|
305
|
+
if (consequentExpr.variableId == null) {
|
|
306
|
+
throw new Error("populateDerivationFromCitations: consequent variable expression has no variableId");
|
|
307
|
+
}
|
|
308
|
+
const sharedMeta = {
|
|
309
|
+
argumentId: consequentExpr.argumentId,
|
|
310
|
+
argumentVersion: consequentExpr.argumentVersion,
|
|
311
|
+
premiseId,
|
|
312
|
+
creatorId: consequentExpr.creatorId,
|
|
313
|
+
createdOn: consequentExpr.createdOn,
|
|
314
|
+
};
|
|
315
|
+
const collected = [];
|
|
316
|
+
// 1. Materialize a claim-bound variable for each source claim. Capture the
|
|
317
|
+
// newly-added ones (ensureClaimBoundVariable bypasses the standard
|
|
318
|
+
// mutation surface and produces no changeset of its own).
|
|
319
|
+
const variablesBefore = new Set(engine.getVariables().map((v) => v.id));
|
|
320
|
+
const sourceVariables = [];
|
|
321
|
+
for (const sourceClaimId of sourceClaimIds) {
|
|
322
|
+
const v = engine.ensureClaimBoundVariable(sourceClaimId);
|
|
323
|
+
sourceVariables.push(v);
|
|
324
|
+
}
|
|
325
|
+
const newlyAddedVariables = sourceVariables.filter((v) => !variablesBefore.has(v.id));
|
|
326
|
+
if (newlyAddedVariables.length > 0) {
|
|
327
|
+
collected.push({
|
|
328
|
+
variables: {
|
|
329
|
+
added: newlyAddedVariables,
|
|
330
|
+
modified: [],
|
|
331
|
+
removed: [],
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
// 2. Build the IMPLIES + antecedent tree by wrapping the consequent
|
|
336
|
+
// expression. wrapExpression with `rightNodeId=consequentExpr.id` makes
|
|
337
|
+
// the existing consequent the right (consequent) child and the new
|
|
338
|
+
// sibling the left (antecedent) child of the new IMPLIES root. The
|
|
339
|
+
// consequent appears in the wrap's changeset as `modified` (its
|
|
340
|
+
// parentId/position changed).
|
|
341
|
+
withGrammarConfig(pm, PERMISSIVE_GRAMMAR_CONFIG, () => {
|
|
342
|
+
const impliesId = generateId();
|
|
343
|
+
const impliesOp = {
|
|
344
|
+
id: impliesId,
|
|
345
|
+
...sharedMeta,
|
|
346
|
+
parentId: null,
|
|
347
|
+
type: "operator",
|
|
348
|
+
operator: "implies",
|
|
349
|
+
variableId: null,
|
|
350
|
+
};
|
|
351
|
+
if (sourceVariables.length === 1) {
|
|
352
|
+
const sibling = {
|
|
353
|
+
id: generateId(),
|
|
354
|
+
...sharedMeta,
|
|
355
|
+
parentId: null, // wrapExpression sets the real parentId
|
|
356
|
+
type: "variable",
|
|
357
|
+
variableId: sourceVariables[0].id,
|
|
358
|
+
operator: null,
|
|
359
|
+
};
|
|
360
|
+
const { changes: wrap } = pm.wrapExpression(impliesOp, sibling, undefined, consequentExpr.id);
|
|
361
|
+
collected.push(wrap);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
// n ≥ 2: wrap with IMPLIES(OR, Q) and append citation children to OR.
|
|
365
|
+
const orId = generateId();
|
|
366
|
+
const orSibling = {
|
|
367
|
+
id: orId,
|
|
368
|
+
...sharedMeta,
|
|
369
|
+
parentId: null,
|
|
370
|
+
type: "operator",
|
|
371
|
+
operator: "or",
|
|
372
|
+
variableId: null,
|
|
373
|
+
};
|
|
374
|
+
const { changes: wrap } = pm.wrapExpression(impliesOp, orSibling, undefined, consequentExpr.id);
|
|
375
|
+
collected.push(wrap);
|
|
376
|
+
for (const sourceVar of sourceVariables) {
|
|
377
|
+
const { changes: appendChanges } = pm.appendExpression(orId, {
|
|
378
|
+
id: generateId(),
|
|
379
|
+
...sharedMeta,
|
|
380
|
+
parentId: orId,
|
|
381
|
+
type: "variable",
|
|
382
|
+
variableId: sourceVar.id,
|
|
383
|
+
operator: null,
|
|
384
|
+
});
|
|
385
|
+
collected.push(appendChanges);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
return { changes: mergeChangesetSequence(collected) };
|
|
389
|
+
}
|
|
390
|
+
// ──── Internal helpers ─────────────────────────────────────────────────────
|
|
391
|
+
/**
|
|
392
|
+
* Strips entries with the given IDs from the `added`/`modified`/`removed`
|
|
393
|
+
* arrays of a changeset. Used to drop engine-auto-generated entities so the
|
|
394
|
+
* caller-facing changeset only references caller-minted IDs.
|
|
395
|
+
*/
|
|
396
|
+
function withoutEntities(changes, omit) {
|
|
397
|
+
const result = { ...changes };
|
|
398
|
+
if (changes.expressions &&
|
|
399
|
+
omit.expressionIds &&
|
|
400
|
+
omit.expressionIds.size > 0) {
|
|
401
|
+
const ids = omit.expressionIds;
|
|
402
|
+
result.expressions = {
|
|
403
|
+
added: changes.expressions.added.filter((e) => !ids.has(e.id)),
|
|
404
|
+
modified: changes.expressions.modified.filter((e) => !ids.has(e.id)),
|
|
405
|
+
removed: changes.expressions.removed.filter((e) => !ids.has(e.id)),
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
if (changes.variables && omit.variableIds && omit.variableIds.size > 0) {
|
|
409
|
+
const ids = omit.variableIds;
|
|
410
|
+
result.variables = {
|
|
411
|
+
added: changes.variables.added.filter((v) => !ids.has(v.id)),
|
|
412
|
+
modified: changes.variables.modified.filter((v) => !ids.has(v.id)),
|
|
413
|
+
removed: changes.variables.removed.filter((v) => !ids.has(v.id)),
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
return result;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Runs `fn` with `pm.grammarConfig` swapped to `nextConfig`, restoring the
|
|
420
|
+
* original config after `fn` returns or throws. Mirrors the technique used by
|
|
421
|
+
* core's `ManagedDerivationPremiseEngine.populateFromCitations`, which needs
|
|
422
|
+
* permissive grammar to place an `OR` operator directly under `IMPLIES`
|
|
423
|
+
* without a formula buffer.
|
|
424
|
+
*/
|
|
425
|
+
function withGrammarConfig(pm, nextConfig, fn) {
|
|
426
|
+
// grammarConfig is `protected` on PremiseEngine. Casting to access it is
|
|
427
|
+
// intentional: the alternative — exposing it publicly in core — would be a
|
|
428
|
+
// larger surface change for a single use-case.
|
|
429
|
+
const target = pm;
|
|
430
|
+
const saved = target.grammarConfig;
|
|
431
|
+
pm.setGrammarConfig(nextConfig);
|
|
432
|
+
try {
|
|
433
|
+
return fn();
|
|
434
|
+
}
|
|
435
|
+
finally {
|
|
436
|
+
pm.setGrammarConfig(saved);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
/** Generates a fresh UUID for engine-auto-generated antecedent expressions. */
|
|
440
|
+
function generateId() {
|
|
441
|
+
return crypto.randomUUID();
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Drops a premise id from the `modified` bucket of a changeset. Used when
|
|
445
|
+
* combining a changeset that *added* a premise with later changesets that
|
|
446
|
+
* report the same premise as `modified` (e.g. an `appendExpression` call
|
|
447
|
+
* marks the parent premise as modified). The merged result needs only the
|
|
448
|
+
* `added` entry — the `modified` entry is redundant and `mergeChangesets`
|
|
449
|
+
* rejects ids appearing in both buckets.
|
|
450
|
+
*/
|
|
451
|
+
function withoutPremiseModifications(changes, premiseId) {
|
|
452
|
+
if (!changes.premises)
|
|
453
|
+
return changes;
|
|
454
|
+
const filteredModified = changes.premises.modified.filter((p) => p.id !== premiseId);
|
|
455
|
+
if (filteredModified.length === changes.premises.modified.length) {
|
|
456
|
+
return changes;
|
|
457
|
+
}
|
|
458
|
+
return {
|
|
459
|
+
...changes,
|
|
460
|
+
premises: {
|
|
461
|
+
...changes.premises,
|
|
462
|
+
modified: filteredModified,
|
|
463
|
+
},
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Drops every entity that appears in `added` from the `modified` bucket of
|
|
468
|
+
* the same category. Required because core's `createPremiseWithId` for
|
|
469
|
+
* derivation type emits the new premise in BOTH `added` (outer collector) and
|
|
470
|
+
* `modified` (the inner naked-Q expression's `flushAndBuildChangeset` pushes
|
|
471
|
+
* a `modifiedPremise` entry as the premise's checksum changes). Both entries
|
|
472
|
+
* describe the same row; the `added` entry's data is the latest snapshot, so
|
|
473
|
+
* the `modified` entry is redundant.
|
|
474
|
+
*/
|
|
475
|
+
function sanitizeAddedThenModified(changes) {
|
|
476
|
+
const result = { ...changes };
|
|
477
|
+
if (changes.premises) {
|
|
478
|
+
const addedIds = new Set(changes.premises.added.map((p) => p.id));
|
|
479
|
+
result.premises = {
|
|
480
|
+
...changes.premises,
|
|
481
|
+
modified: changes.premises.modified.filter((p) => !addedIds.has(p.id)),
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
if (changes.variables) {
|
|
485
|
+
const addedIds = new Set(changes.variables.added.map((v) => v.id));
|
|
486
|
+
result.variables = {
|
|
487
|
+
...changes.variables,
|
|
488
|
+
modified: changes.variables.modified.filter((v) => !addedIds.has(v.id)),
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
if (changes.expressions) {
|
|
492
|
+
const addedIds = new Set(changes.expressions.added.map((e) => e.id));
|
|
493
|
+
result.expressions = {
|
|
494
|
+
...changes.expressions,
|
|
495
|
+
modified: changes.expressions.modified.filter((e) => !addedIds.has(e.id)),
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
return result;
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Merges a list of changesets, treating later `added` entries for the same id
|
|
502
|
+
* as superseding earlier `modified` entries (and vice versa). This is needed
|
|
503
|
+
* when the engine reports the same entity in different buckets across
|
|
504
|
+
* sequential mutations — e.g. an `appendExpression` adds an OR and its
|
|
505
|
+
* subsequent `appendExpression` reports OR's descendant-checksum change as a
|
|
506
|
+
* `modified` entry, which would otherwise trip `mergeChangesets`'s
|
|
507
|
+
* single-bucket invariant.
|
|
508
|
+
*
|
|
509
|
+
* Resolution rule per id:
|
|
510
|
+
* - If the id is `added` in ANY changeset, drop all `modified` entries for
|
|
511
|
+
* that id (the `added` row will reflect the final post-merge state).
|
|
512
|
+
* - `removed` entries are preserved; if `added` and `removed` for the same
|
|
513
|
+
* id appear, that's a logic error and `mergeChangesets` will surface it.
|
|
514
|
+
*/
|
|
515
|
+
function mergeChangesetSequence(changesets) {
|
|
516
|
+
const addedIds = {
|
|
517
|
+
premises: new Set(),
|
|
518
|
+
variables: new Set(),
|
|
519
|
+
expressions: new Set(),
|
|
520
|
+
};
|
|
521
|
+
for (const c of changesets) {
|
|
522
|
+
for (const p of c.premises?.added ?? [])
|
|
523
|
+
addedIds.premises.add(p.id);
|
|
524
|
+
for (const v of c.variables?.added ?? [])
|
|
525
|
+
addedIds.variables.add(v.id);
|
|
526
|
+
for (const e of c.expressions?.added ?? [])
|
|
527
|
+
addedIds.expressions.add(e.id);
|
|
528
|
+
}
|
|
529
|
+
let result = {};
|
|
530
|
+
for (const c of changesets) {
|
|
531
|
+
const sanitized = { ...c };
|
|
532
|
+
if (c.premises) {
|
|
533
|
+
sanitized.premises = {
|
|
534
|
+
...c.premises,
|
|
535
|
+
modified: c.premises.modified.filter((p) => !addedIds.premises.has(p.id)),
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
if (c.variables) {
|
|
539
|
+
sanitized.variables = {
|
|
540
|
+
...c.variables,
|
|
541
|
+
modified: c.variables.modified.filter((v) => !addedIds.variables.has(v.id)),
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
if (c.expressions) {
|
|
545
|
+
sanitized.expressions = {
|
|
546
|
+
...c.expressions,
|
|
547
|
+
modified: c.expressions.modified.filter((e) => !addedIds.expressions.has(e.id)),
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
result = mergeChangesets(result, sanitized);
|
|
551
|
+
}
|
|
552
|
+
return result;
|
|
553
|
+
}
|
|
54
554
|
//# sourceMappingURL=premises.js.map
|