@proposit/proposit-core 0.12.3 → 1.0.1

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 (125) hide show
  1. package/README.md +104 -93
  2. package/dist/cli/commands/premises.d.ts.map +1 -1
  3. package/dist/cli/commands/premises.js +28 -24
  4. package/dist/cli/commands/premises.js.map +1 -1
  5. package/dist/cli/commands/repair.d.ts.map +1 -1
  6. package/dist/cli/commands/repair.js +4 -2
  7. package/dist/cli/commands/repair.js.map +1 -1
  8. package/dist/cli/commands/validate.d.ts.map +1 -1
  9. package/dist/cli/commands/validate.js +7 -1
  10. package/dist/cli/commands/validate.js.map +1 -1
  11. package/dist/cli/engine.d.ts.map +1 -1
  12. package/dist/cli/engine.js +7 -25
  13. package/dist/cli/engine.js.map +1 -1
  14. package/dist/cli/import.d.ts.map +1 -1
  15. package/dist/cli/import.js +66 -28
  16. package/dist/cli/import.js.map +1 -1
  17. package/dist/lib/core/argument-engine.d.ts +275 -10
  18. package/dist/lib/core/argument-engine.d.ts.map +1 -1
  19. package/dist/lib/core/argument-engine.js +442 -82
  20. package/dist/lib/core/argument-engine.js.map +1 -1
  21. package/dist/lib/core/argument-library.d.ts +5 -1
  22. package/dist/lib/core/argument-library.d.ts.map +1 -1
  23. package/dist/lib/core/argument-library.js +7 -3
  24. package/dist/lib/core/argument-library.js.map +1 -1
  25. package/dist/lib/core/expression-manager.d.ts +68 -73
  26. package/dist/lib/core/expression-manager.d.ts.map +1 -1
  27. package/dist/lib/core/expression-manager.js +272 -771
  28. package/dist/lib/core/expression-manager.js.map +1 -1
  29. package/dist/lib/core/fork.d.ts +5 -1
  30. package/dist/lib/core/fork.d.ts.map +1 -1
  31. package/dist/lib/core/fork.js +16 -6
  32. package/dist/lib/core/fork.js.map +1 -1
  33. package/dist/lib/core/interfaces/argument-engine.interfaces.d.ts +68 -7
  34. package/dist/lib/core/interfaces/argument-engine.interfaces.d.ts.map +1 -1
  35. package/dist/lib/core/interfaces/library.interfaces.d.ts +8 -3
  36. package/dist/lib/core/interfaces/library.interfaces.d.ts.map +1 -1
  37. package/dist/lib/core/interfaces/premise-engine.interfaces.d.ts +50 -47
  38. package/dist/lib/core/interfaces/premise-engine.interfaces.d.ts.map +1 -1
  39. package/dist/lib/core/premise-engine.d.ts +80 -11
  40. package/dist/lib/core/premise-engine.d.ts.map +1 -1
  41. package/dist/lib/core/premise-engine.js +245 -83
  42. package/dist/lib/core/premise-engine.js.map +1 -1
  43. package/dist/lib/core/proposit-core.d.ts.map +1 -1
  44. package/dist/lib/core/proposit-core.js +13 -3
  45. package/dist/lib/core/proposit-core.js.map +1 -1
  46. package/dist/lib/grammar/an-rules.d.ts +158 -0
  47. package/dist/lib/grammar/an-rules.d.ts.map +1 -0
  48. package/dist/lib/grammar/an-rules.js +778 -0
  49. package/dist/lib/grammar/an-rules.js.map +1 -0
  50. package/dist/lib/grammar/auto-normalize.d.ts +14 -0
  51. package/dist/lib/grammar/auto-normalize.d.ts.map +1 -0
  52. package/dist/lib/grammar/auto-normalize.js +35 -0
  53. package/dist/lib/grammar/auto-normalize.js.map +1 -0
  54. package/dist/lib/grammar/bounded-subtree.d.ts +30 -0
  55. package/dist/lib/grammar/bounded-subtree.d.ts.map +1 -0
  56. package/dist/lib/grammar/bounded-subtree.js +74 -0
  57. package/dist/lib/grammar/bounded-subtree.js.map +1 -0
  58. package/dist/lib/grammar/naked-q.d.ts +20 -0
  59. package/dist/lib/grammar/naked-q.d.ts.map +1 -0
  60. package/dist/lib/grammar/naked-q.js +58 -0
  61. package/dist/lib/grammar/naked-q.js.map +1 -0
  62. package/dist/lib/grammar/normalize.d.ts +12 -0
  63. package/dist/lib/grammar/normalize.d.ts.map +1 -0
  64. package/dist/lib/grammar/normalize.js +45 -0
  65. package/dist/lib/grammar/normalize.js.map +1 -0
  66. package/dist/lib/grammar/populate-from.d.ts +25 -0
  67. package/dist/lib/grammar/populate-from.d.ts.map +1 -0
  68. package/dist/lib/grammar/populate-from.js +252 -0
  69. package/dist/lib/grammar/populate-from.js.map +1 -0
  70. package/dist/lib/grammar/repair.d.ts +65 -0
  71. package/dist/lib/grammar/repair.d.ts.map +1 -0
  72. package/dist/lib/grammar/repair.js +251 -0
  73. package/dist/lib/grammar/repair.js.map +1 -0
  74. package/dist/lib/grammar/types.d.ts +17 -0
  75. package/dist/lib/grammar/types.d.ts.map +1 -0
  76. package/dist/lib/grammar/types.js +82 -0
  77. package/dist/lib/grammar/types.js.map +1 -0
  78. package/dist/lib/grammar/validate.d.ts +4 -0
  79. package/dist/lib/grammar/validate.d.ts.map +1 -0
  80. package/dist/lib/grammar/validate.js +28 -0
  81. package/dist/lib/grammar/validate.js.map +1 -0
  82. package/dist/lib/grammar/validators/context.d.ts +11 -0
  83. package/dist/lib/grammar/validators/context.d.ts.map +1 -0
  84. package/dist/lib/grammar/validators/context.js +5 -0
  85. package/dist/lib/grammar/validators/context.js.map +1 -0
  86. package/dist/lib/grammar/validators/derivable.d.ts +63 -0
  87. package/dist/lib/grammar/validators/derivable.d.ts.map +1 -0
  88. package/dist/lib/grammar/validators/derivable.js +502 -0
  89. package/dist/lib/grammar/validators/derivable.js.map +1 -0
  90. package/dist/lib/grammar/validators/evaluable.d.ts +48 -0
  91. package/dist/lib/grammar/validators/evaluable.d.ts.map +1 -0
  92. package/dist/lib/grammar/validators/evaluable.js +226 -0
  93. package/dist/lib/grammar/validators/evaluable.js.map +1 -0
  94. package/dist/lib/grammar/validators/presentable.d.ts +45 -0
  95. package/dist/lib/grammar/validators/presentable.d.ts.map +1 -0
  96. package/dist/lib/grammar/validators/presentable.js +231 -0
  97. package/dist/lib/grammar/validators/presentable.js.map +1 -0
  98. package/dist/lib/grammar/validators/structural.d.ts +103 -0
  99. package/dist/lib/grammar/validators/structural.d.ts.map +1 -0
  100. package/dist/lib/grammar/validators/structural.js +602 -0
  101. package/dist/lib/grammar/validators/structural.js.map +1 -0
  102. package/dist/lib/index.d.ts +4 -3
  103. package/dist/lib/index.d.ts.map +1 -1
  104. package/dist/lib/index.js +2 -2
  105. package/dist/lib/index.js.map +1 -1
  106. package/dist/lib/parsing/argument-parser.d.ts.map +1 -1
  107. package/dist/lib/parsing/argument-parser.js +52 -10
  108. package/dist/lib/parsing/argument-parser.js.map +1 -1
  109. package/dist/lib/types/evaluation.d.ts +1 -1
  110. package/dist/lib/types/evaluation.d.ts.map +1 -1
  111. package/dist/lib/types/fork.d.ts +12 -3
  112. package/dist/lib/types/fork.d.ts.map +1 -1
  113. package/dist/lib/types/validation.d.ts +0 -6
  114. package/dist/lib/types/validation.d.ts.map +1 -1
  115. package/dist/lib/types/validation.js +23 -6
  116. package/dist/lib/types/validation.js.map +1 -1
  117. package/package.json +1 -1
  118. package/dist/lib/core/managed-derivation-premise-engine.d.ts +0 -172
  119. package/dist/lib/core/managed-derivation-premise-engine.d.ts.map +0 -1
  120. package/dist/lib/core/managed-derivation-premise-engine.js +0 -550
  121. package/dist/lib/core/managed-derivation-premise-engine.js.map +0 -1
  122. package/dist/lib/types/grammar.d.ts +0 -83
  123. package/dist/lib/types/grammar.d.ts.map +0 -1
  124. package/dist/lib/types/grammar.js +0 -24
  125. package/dist/lib/types/grammar.js.map +0 -1
@@ -1,13 +1,12 @@
1
1
  import { CorePremiseSchema, isExternallyBound, isPremiseBound, } from "../schemata/index.js";
2
2
  import { DefaultMap } from "../utils/default-map.js";
3
- import { midpoint, POSITION_INITIAL, POSITION_MAX } from "../utils/position.js";
3
+ import { midpoint, POSITION_INITIAL, POSITION_MAX, } from "../utils/position.js";
4
4
  import { sortedCopyById, sortedUnique } from "../utils/collections.js";
5
5
  import { kleeneAnd, kleeneIff, kleeneImplies, kleeneNot, kleeneOr, } from "./evaluation/kleene.js";
6
6
  import { buildDirectionalVacuity, makeErrorIssue, makeValidationResult, } from "./evaluation/validation.js";
7
7
  import { Value } from "typebox/value";
8
8
  import { PREMISE_SCHEMA_INVALID, PREMISE_ROOT_EXPRESSION_INVALID, PREMISE_VARIABLE_REF_NOT_FOUND, } from "../types/validation.js";
9
9
  import { defaultGenerateId, } from "./argument-engine.js";
10
- import { DEFAULT_GRAMMAR_CONFIG, resolveAutoNormalize, } from "../types/grammar.js";
11
10
  import { DEFAULT_CHECKSUM_CONFIG, normalizeChecksumConfig, serializeChecksumConfig, } from "../consts.js";
12
11
  import { ChangeCollector } from "./change-collector.js";
13
12
  import { computeHash, entityChecksum } from "./checksum.js";
@@ -22,7 +21,6 @@ export class PremiseEngine {
22
21
  expressionsByVariableId;
23
22
  argument;
24
23
  checksumConfig;
25
- grammarConfig;
26
24
  checksumDirty = true;
27
25
  cachedMetaChecksum;
28
26
  cachedDescendantChecksum;
@@ -39,7 +37,6 @@ export class PremiseEngine {
39
37
  this.premise = { ...premise };
40
38
  this.argument = deps.argument;
41
39
  this.checksumConfig = config?.checksumConfig;
42
- this.grammarConfig = config?.grammarConfig ?? DEFAULT_GRAMMAR_CONFIG;
43
40
  this.rootExpressionId = undefined;
44
41
  this.variables = deps.variables;
45
42
  this.expressions = new ExpressionManager(config);
@@ -48,13 +45,16 @@ export class PremiseEngine {
48
45
  this.generateId = config?.generateId ?? defaultGenerateId;
49
46
  }
50
47
  /**
51
- * Overrides the grammar config for both this premise engine and its
52
- * internal expression manager. Used by restoration paths when the
53
- * caller's grammar config should override the snapshot's config.
48
+ * Returns the position config in effect for this premise engine.
49
+ * Used by in-package helpers (notably the native AN-4 absorption
50
+ * pass in `src/lib/grammar/an-rules.ts`) that need the position
51
+ * range boundaries when computing target positions for absorbed
52
+ * children.
53
+ *
54
+ * @internal
54
55
  */
55
- setGrammarConfig(grammarConfig) {
56
- this.grammarConfig = grammarConfig;
57
- this.expressions.setGrammarConfig(grammarConfig);
56
+ getPositionConfig() {
57
+ return this.expressions.getPositionConfig();
58
58
  }
59
59
  setOnMutate(callback) {
60
60
  this.onMutate = callback;
@@ -184,6 +184,16 @@ export class PremiseEngine {
184
184
  if (this.rootExpressionId !== undefined) {
185
185
  throw new Error(`Premise "${this.premise.id}" already has a root expression.`);
186
186
  }
187
+ // S-14: derivation premise root must be one of variable,
188
+ // implies, or iff. Enforced at mutation time regardless
189
+ // of engine `behavior` — Structural rules throw in both
190
+ // modes (spec §4).
191
+ if (this.premise.type === "derivation" &&
192
+ expression.type === "operator" &&
193
+ expression.operator !== "implies" &&
194
+ expression.operator !== "iff") {
195
+ throw new Error(`S-14: derivation premise "${this.premise.id}" root must be variable, implies, or iff (got operator "${expression.operator}").`);
196
+ }
187
197
  }
188
198
  else {
189
199
  if (!this.expressions.getExpression(expression.parentId)) {
@@ -418,24 +428,199 @@ export class PremiseEngine {
418
428
  });
419
429
  }
420
430
  /**
421
- * Performs a full normalization sweep on this premise's expression tree.
422
- * Collapses unjustified formulas, operators with 0/1 children, and inserts
423
- * formula buffers where needed. Works regardless of `autoNormalize` setting.
431
+ * Reparent an existing expression onto a new parent at the given
432
+ * position. Bundled-composite mutation per spec §8 the parent
433
+ * reference, position field, and checksum-dirty propagation update
434
+ * atomically in a single call. No transient orphan state is
435
+ * externally observable.
436
+ *
437
+ * Enforces Structural rules only (S-1 FK soundness, S-4 no-cycles,
438
+ * entity-not-found, and S-9 logical sibling-position uniqueness at
439
+ * the bundled-composite level — same-position collisions with the
440
+ * moved expression's own prior slot are tolerated as transient,
441
+ * since the move atomically frees that slot). Higher-tier violations
442
+ * never throw here — Evaluable/Derivable/Presentable issues surface
443
+ * through `validate(tier)` per spec §7.1.
444
+ *
445
+ * Used by the native AN-1 (formula-buffer insertion) and AN-4
446
+ * (same-operator absorption) passes in `src/lib/grammar/an-rules.ts`,
447
+ * and available to repair primitives and future composite ops that
448
+ * need a public reparent surface (e.g. `removeOrphanOperators`).
449
+ *
450
+ * @throws If `expressionId` or `newParentId` does not exist in this
451
+ * premise.
452
+ * @throws S-1: if `newParent` is not an `operator` or `formula` (a
453
+ * variable cannot be a parent — parity with `addExpression`
454
+ * at em.ts:418-422).
455
+ * @throws S-1: arity — if reparenting would push `newParent`'s
456
+ * child count past its operator-specific limit (unary `not`
457
+ * max 1; binary `implies`/`iff` max 2). Same-parent moves
458
+ * leave count unchanged and bypass this check.
459
+ * @throws S-4: if `newParentId === expressionId` or `newParentId` is
460
+ * a descendant of `expressionId`.
461
+ * @throws S-9: if another sibling (NOT the expression being moved)
462
+ * already occupies `newPosition` under `newParentId`.
463
+ *
464
+ * @since 1.0.0
424
465
  */
425
- normalizeExpressions() {
466
+ reparentExpression(expressionId, newParentId, newPosition) {
426
467
  return this.withValidation(() => {
468
+ const expression = this.expressions.getExpression(expressionId);
469
+ if (!expression) {
470
+ throw new Error(`Expression "${expressionId}" not found in premise "${this.premise.id}".`);
471
+ }
472
+ const newParent = this.expressions.getExpression(newParentId);
473
+ if (!newParent) {
474
+ throw new Error(`Parent expression "${newParentId}" not found in premise "${this.premise.id}".`);
475
+ }
476
+ // S-1 parent-type: only operators and formulas accept
477
+ // children. Without this guard a caller could reparent under
478
+ // a variable (or any other non-container) and produce a
479
+ // malformed AST that no validator catches. Parity with
480
+ // `addExpression` at em.ts:418-422.
481
+ if (newParent.type !== "operator" && newParent.type !== "formula") {
482
+ throw new Error(`S-1: cannot reparent under non-operator/formula parent "${newParentId}" (type=${newParent.type}).`);
483
+ }
484
+ // S-4 no-cycles: newParent cannot be expressionId itself nor
485
+ // a descendant of expressionId.
486
+ if (newParentId === expressionId) {
487
+ throw new Error(`S-4: cannot reparent expression "${expressionId}" under itself.`);
488
+ }
489
+ if (this.isDescendantOf(newParentId, expressionId)) {
490
+ throw new Error(`S-4: cannot reparent expression "${expressionId}" under its descendant "${newParentId}" (would create a cycle).`);
491
+ }
492
+ // S-9 sibling-position uniqueness — only fires if a sibling
493
+ // OTHER than the moved expression occupies the target slot.
494
+ // Same-parent move with newPosition === expression.position
495
+ // is a no-op-position case and tolerated.
496
+ const isSameParentSamePosition = expression.parentId === newParentId &&
497
+ expression.position === newPosition;
498
+ if (!isSameParentSamePosition) {
499
+ const siblings = this.expressions.getChildExpressions(newParentId);
500
+ const collision = siblings.find((s) => s.id !== expressionId && s.position === newPosition);
501
+ if (collision) {
502
+ throw new Error(`S-9: position ${newPosition} is already occupied by sibling "${collision.id}" under parent "${newParentId}".`);
503
+ }
504
+ }
505
+ // S-1 arity: when source is moving OUT of one parent and
506
+ // INTO `newParent`, the new parent's child count increases
507
+ // by 1. Reuse `addExpression`'s `assertChildLimit` parity
508
+ // (which only enforces caps for `not`/`implies`/`iff`).
509
+ // Same-parent moves bypass: count is unchanged. Formula
510
+ // parents enforce their 1-child cap separately.
511
+ const isSameParent = expression.parentId === newParentId;
512
+ if (!isSameParent) {
513
+ if (newParent.type === "operator") {
514
+ // assertChildLimit lives in EM — gate via the public
515
+ // reparent call's pre-check shape: replicate its
516
+ // logic here so we don't widen EM's surface.
517
+ const childCount = this.expressions.getChildExpressions(newParentId).length;
518
+ if (newParent.operator === "not" && childCount >= 1) {
519
+ throw new Error(`Operator expression "${newParentId}" with "not" can only have one child.`);
520
+ }
521
+ if ((newParent.operator === "implies" ||
522
+ newParent.operator === "iff") &&
523
+ childCount >= 2) {
524
+ throw new Error(`Operator expression "${newParentId}" with "${newParent.operator}" can only have two children.`);
525
+ }
526
+ }
527
+ else if (newParent.type === "formula") {
528
+ const childCount = this.expressions.getChildExpressions(newParentId).length;
529
+ if (childCount >= 1) {
530
+ throw new Error(`Formula expression "${newParentId}" can only have one child.`);
531
+ }
532
+ }
533
+ }
427
534
  const collector = new ChangeCollector();
428
535
  this.expressions.setCollector(collector);
429
536
  try {
430
- this.expressions.normalize();
537
+ this.expressions.reparentExpression(expressionId, newParentId, newPosition);
431
538
  const changes = this.finalizeExpressionMutation(collector);
432
- return { result: undefined, changes };
539
+ return {
540
+ result: this.expressions.getExpression(expressionId),
541
+ changes,
542
+ };
433
543
  }
434
544
  finally {
435
545
  this.expressions.setCollector(null);
436
546
  }
437
547
  });
438
548
  }
549
+ /**
550
+ * Wrap an existing expression in a freshly-minted `formula` node
551
+ * atomically. The formula takes the child's original parent slot
552
+ * (parentId + position); the child becomes the formula's sole
553
+ * child at position 0. Bundled-composite mutation per spec §8.
554
+ *
555
+ * Used by the native AN-1 (formula-buffer insertion) pass in
556
+ * `src/lib/grammar/an-rules.ts`. Composing this from
557
+ * `addExpression` + `reparentExpression` is not possible: the
558
+ * intermediate state would either (a) violate S-9 with the child
559
+ * still occupying the formula's target slot, or (b) trip the
560
+ * parent's `assertChildLimit` (unary `not`, binary
561
+ * `implies`/`iff`) even though the *net* child count of the parent
562
+ * is unchanged after the wrap.
563
+ *
564
+ * The new formula's id is minted via the engine's `idGenerator`
565
+ * accessor and returned in the mutation result. Argument fields
566
+ * (`argumentId`, `argumentVersion`, `premiseId`) are inherited
567
+ * from the source child.
568
+ *
569
+ * @throws If `childId` does not exist in this premise.
570
+ * @throws If `childId` is at the root (no parent to insert a
571
+ * buffer beneath).
572
+ * @throws S-10: if `formulaId` is already used by an existing
573
+ * expression in this premise (entity-ID uniqueness).
574
+ *
575
+ * @since 1.0.0
576
+ */
577
+ wrapInFormula(childId, formulaId) {
578
+ return this.withValidation(() => {
579
+ const child = this.expressions.getExpression(childId);
580
+ if (!child) {
581
+ throw new Error(`Expression "${childId}" not found in premise "${this.premise.id}".`);
582
+ }
583
+ if (child.parentId === null) {
584
+ throw new Error(`Cannot wrap root expression "${childId}" in a formula — no parent operator above it.`);
585
+ }
586
+ const collector = new ChangeCollector();
587
+ this.expressions.setCollector(collector);
588
+ try {
589
+ this.expressions.wrapInFormula(childId, formulaId);
590
+ const changes = this.finalizeExpressionMutation(collector);
591
+ return {
592
+ result: this.expressions.getExpression(formulaId),
593
+ changes,
594
+ };
595
+ }
596
+ finally {
597
+ this.expressions.setCollector(null);
598
+ }
599
+ });
600
+ }
601
+ /**
602
+ * Returns true iff `candidateId` is a descendant of `ancestorId` in
603
+ * this premise's expression tree. Used by `reparentExpression` for
604
+ * the S-4 no-cycles check.
605
+ */
606
+ isDescendantOf(candidateId, ancestorId) {
607
+ const stack = [ancestorId];
608
+ while (stack.length > 0) {
609
+ const cursor = stack.pop();
610
+ for (const child of this.expressions.getChildExpressions(cursor)) {
611
+ if (child.id === candidateId)
612
+ return true;
613
+ stack.push(child.id);
614
+ }
615
+ }
616
+ return false;
617
+ }
618
+ // D2 — `pe.normalizeExpressions()` was the per-premise wrapper for
619
+ // the legacy `ExpressionManager.normalize()` 5-pass sweep. Both
620
+ // methods are deleted in D2. The replacement is
621
+ // `engine.normalize(tier?)` (Phase C3) which routes through the
622
+ // native AN-1..AN-4 passes in `src/lib/grammar/an-rules.ts`. The
623
+ // post-mutation assistive hook covers the per-mutation use case.
439
624
  toggleNegation(expressionId, extraFields) {
440
625
  return this.withValidation(() => {
441
626
  const target = this.expressions.getExpression(expressionId);
@@ -473,71 +658,42 @@ export class PremiseEngine {
473
658
  return { result: null, changes };
474
659
  }
475
660
  else if (target.type === "operator" &&
476
- target.operator === "not" &&
477
- resolveAutoNormalize(this.grammarConfig, "collapseDoubleNegation")) {
478
- // Target is already NOT wrapping would create NOT(NOT(x)).
479
- // Collapse instead: remove the existing NOT, promoting its child.
661
+ target.operator === "not") {
662
+ // Target is already NOT — toggling adds a second NOT and
663
+ // immediately collapses to the inner child. We express
664
+ // this directly by removing the existing NOT (promotes
665
+ // its child into its slot). D2: the pre-v1.0 gate on
666
+ // `collapseDoubleNegation` was deleted — `toggleNegation`
667
+ // unconditionally toggles.
480
668
  this.expressions.removeExpression(expressionId, false);
481
669
  const changes = this.finalizeExpressionMutation(collector);
482
670
  return { result: null, changes };
483
671
  }
484
672
  else {
485
- // When the target is a non-not operator, a formula buffer
486
- // is needed between the new NOT and the target to satisfy
487
- // the operator nesting restriction.
488
- const needsFormula = this.grammarConfig.enforceFormulaBetweenOperators &&
489
- target.type === "operator" &&
490
- target.operator !== "not";
491
- let notExprId;
492
- if (needsFormula) {
493
- if (!resolveAutoNormalize(this.grammarConfig, "negationInsertFormula")) {
494
- throw new Error(`Cannot negate operator expression "${expressionId}" — would place a non-not operator as a direct child of NOT. Enable negationInsertFormula or wrap in a formula node first.`);
495
- }
496
- // Build not → formula → target
497
- const formulaExpr = {
498
- ...extraFields,
499
- id: this.generateId(),
500
- argumentId: target.argumentId,
501
- argumentVersion: target.argumentVersion,
502
- premiseId: target.premiseId,
503
- type: "formula",
504
- parentId: target.parentId,
505
- position: target.position,
506
- };
507
- this.expressions.insertExpression(formulaExpr, expressionId);
508
- const notExpr = {
509
- ...extraFields,
510
- id: this.generateId(),
511
- argumentId: target.argumentId,
512
- argumentVersion: target.argumentVersion,
513
- premiseId: target.premiseId,
514
- type: "operator",
515
- operator: "not",
516
- parentId: target.parentId,
517
- position: target.position,
518
- };
519
- this.expressions.insertExpression(notExpr, formulaExpr.id);
520
- notExprId = notExpr.id;
521
- }
522
- else {
523
- // Wrap target with a new NOT operator
524
- const notExpr = {
525
- ...extraFields,
526
- id: this.generateId(),
527
- argumentId: target.argumentId,
528
- argumentVersion: target.argumentVersion,
529
- premiseId: target.premiseId,
530
- type: "operator",
531
- operator: "not",
532
- parentId: target.parentId,
533
- position: target.position,
534
- };
535
- this.expressions.insertExpression(notExpr, expressionId);
536
- notExprId = notExpr.id;
537
- }
673
+ // D2: the pre-v1.0 P-1 inline buffer-insertion branch
674
+ // (gated on `grammarConfig.enforceFormulaBetweenOperators`
675
+ // + `resolveAutoNormalize(_, 'negationInsertFormula')`,
676
+ // which built `NOT(formula(target))` inline) is gone.
677
+ // Always wrap with just NOT. AN-1 (post-mutation hook in
678
+ // assistive mode) inserts the formula buffer if the
679
+ // target is a non-not operator; permissive mode leaves
680
+ // the un-buffered state and `validate('presentable')`
681
+ // flags it.
682
+ const notExpr = {
683
+ ...extraFields,
684
+ id: this.generateId(),
685
+ argumentId: target.argumentId,
686
+ argumentVersion: target.argumentVersion,
687
+ premiseId: target.premiseId,
688
+ type: "operator",
689
+ operator: "not",
690
+ parentId: target.parentId,
691
+ position: target.position,
692
+ };
693
+ this.expressions.insertExpression(notExpr, expressionId);
538
694
  const changes = this.finalizeExpressionMutation(collector);
539
695
  return {
540
- result: this.expressions.getExpression(notExprId),
696
+ result: this.expressions.getExpression(notExpr.id),
541
697
  changes,
542
698
  };
543
699
  }
@@ -699,9 +855,19 @@ export class PremiseEngine {
699
855
  position: POSITION_INITIAL,
700
856
  };
701
857
  this.expressions.addExpression(newOpExpr);
702
- // Now reparent the children under the new sub-operator
703
- this.expressions.reparentExpression(firstChild.id, newOpId, POSITION_INITIAL);
704
- this.expressions.reparentExpression(secondChild.id, newOpId, midpoint(POSITION_INITIAL, POSITION_MAX));
858
+ // Now reparent the children under the new sub-operator.
859
+ // For `implies`/`iff` S-8 pins positions to exact [0, 1];
860
+ // for `and`/`or` keep the midpoint-spaced pattern so
861
+ // future inserts can bisect. (S-5 throws on non-root
862
+ // implies/iff at addExpression above, so this branch is
863
+ // unreachable for the binary case today — the explicit
864
+ // [0, 1] is defense-in-depth against future loosening
865
+ // of S-5 enforcement at the mutation surface.)
866
+ const isBinaryOp = newOperator === "implies" || newOperator === "iff";
867
+ this.expressions.reparentExpression(firstChild.id, newOpId, isBinaryOp ? 0 : POSITION_INITIAL);
868
+ this.expressions.reparentExpression(secondChild.id, newOpId, isBinaryOp
869
+ ? 1
870
+ : midpoint(POSITION_INITIAL, POSITION_MAX));
705
871
  const changes = this.finalizeExpressionMutation(collector);
706
872
  return {
707
873
  result: this.expressions.getExpression(newOpId),
@@ -1361,7 +1527,7 @@ export class PremiseEngine {
1361
1527
  };
1362
1528
  }
1363
1529
  /** Creates a new PremiseEngine from a previously captured snapshot. */
1364
- static fromSnapshot(snapshot, argument, variables, expressionIndex, grammarConfig, generateId) {
1530
+ static fromSnapshot(snapshot, argument, variables, expressionIndex, generateId) {
1365
1531
  // Normalize checksumConfig in case the snapshot went through a JSON
1366
1532
  // round-trip that converted Sets to arrays or empty objects.
1367
1533
  const normalizedConfig = snapshot.config
@@ -1374,12 +1540,8 @@ export class PremiseEngine {
1374
1540
  ? { generateId }
1375
1541
  : snapshot.config;
1376
1542
  const pe = new PremiseEngine(snapshot.premise, { argument, variables, expressionIndex }, normalizedConfig);
1377
- // Override grammar config if the caller specified one.
1378
- if (grammarConfig) {
1379
- pe.grammarConfig = grammarConfig;
1380
- }
1381
1543
  // Restore expressions from the snapshot
1382
- pe.expressions = ExpressionManager.fromSnapshot(snapshot.expressions, grammarConfig, generateId);
1544
+ pe.expressions = ExpressionManager.fromSnapshot(snapshot.expressions, generateId);
1383
1545
  // Restore rootExpressionId from snapshot
1384
1546
  pe.rootExpressionId = snapshot.rootExpressionId;
1385
1547
  // Rebuild the expressionsByVariableId index