@crediolabs/policy-synth 0.5.3 → 0.5.4

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.
@@ -107,12 +107,33 @@ function leftArgLabel(leaf) {
107
107
  }
108
108
  /** Render ONE constraint sentence for ONE interpreter predicate node. The
109
109
  * shape of the output is pinned by Task 7b so the test suite can assert
110
- * byte-for-byte equality. Returns `null` when the node is a structural
111
- * boolean (`and`) - that is not a constraint leaf. */
110
+ * byte-for-byte equality. Returns `null` only when a node's shape cannot be
111
+ * rendered at all. */
112
112
  function renderConstraint(node) {
113
113
  switch (node.op) {
114
- case 'and':
115
- return null;
114
+ case 'and': {
115
+ // Reached ONLY when an `and` sits BENEATH an `or`: `walkPredicate`
116
+ // descends into a top-level `and` and never calls this on one.
117
+ //
118
+ // This used to return null, on the reasoning that `and` is structural
119
+ // rather than a constraint leaf. That is true for the walk path and
120
+ // false for this one, and the combination silently dropped real
121
+ // policies: `or(and, and)` is the natural shape of a policy with
122
+ // alternative permitted forms - one conjunction of pins per branch -
123
+ // so the `or` case below always saw null children and withheld the
124
+ // entire disjunction. `describePredicate` then returned an EMPTY list
125
+ // for a restrictive policy, which any caller renders as "no
126
+ // constraints".
127
+ //
128
+ // That inverts the very guard it was protecting: the `or` case
129
+ // withholds to avoid reading STRICTER than reality, but withholding
130
+ // the only line reads as UNRESTRICTED, which is far worse. Compose
131
+ // instead, and keep the withhold for genuinely unrenderable children.
132
+ const parts = node.children.map(renderConstraint);
133
+ if (parts.some((p) => p === null))
134
+ return null;
135
+ return parts.join(' and ');
136
+ }
116
137
  case 'or': {
117
138
  // One line for the whole disjunction. If any branch is a shape the
118
139
  // card cannot render, the entire line is withheld rather than shown
@@ -111,12 +111,33 @@ function leftArgLabel(leaf) {
111
111
  }
112
112
  /** Render ONE constraint sentence for ONE interpreter predicate node. The
113
113
  * shape of the output is pinned by Task 7b so the test suite can assert
114
- * byte-for-byte equality. Returns `null` when the node is a structural
115
- * boolean (`and`) - that is not a constraint leaf. */
114
+ * byte-for-byte equality. Returns `null` only when a node's shape cannot be
115
+ * rendered at all. */
116
116
  function renderConstraint(node) {
117
117
  switch (node.op) {
118
- case 'and':
119
- return null;
118
+ case 'and': {
119
+ // Reached ONLY when an `and` sits BENEATH an `or`: `walkPredicate`
120
+ // descends into a top-level `and` and never calls this on one.
121
+ //
122
+ // This used to return null, on the reasoning that `and` is structural
123
+ // rather than a constraint leaf. That is true for the walk path and
124
+ // false for this one, and the combination silently dropped real
125
+ // policies: `or(and, and)` is the natural shape of a policy with
126
+ // alternative permitted forms - one conjunction of pins per branch -
127
+ // so the `or` case below always saw null children and withheld the
128
+ // entire disjunction. `describePredicate` then returned an EMPTY list
129
+ // for a restrictive policy, which any caller renders as "no
130
+ // constraints".
131
+ //
132
+ // That inverts the very guard it was protecting: the `or` case
133
+ // withholds to avoid reading STRICTER than reality, but withholding
134
+ // the only line reads as UNRESTRICTED, which is far worse. Compose
135
+ // instead, and keep the withhold for genuinely unrenderable children.
136
+ const parts = node.children.map(renderConstraint);
137
+ if (parts.some((p) => p === null))
138
+ return null;
139
+ return parts.join(' and ');
140
+ }
120
141
  case 'or': {
121
142
  // One line for the whole disjunction. If any branch is a shape the
122
143
  // card cannot render, the entire line is withheld rather than shown
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crediolabs/policy-synth",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "license": "MIT",
5
5
  "description": "Off-chain TypeScript synthesis core for the OZ Accounts Policy Builder. Records Soroban transactions, synthesises the minimal policy that permits exactly that flow, verifies it, and returns an unsigned install transaction.",
6
6
  "type": "module",
@@ -134,12 +134,32 @@ function leftArgLabel(leaf: PredicateLeaf): string {
134
134
 
135
135
  /** Render ONE constraint sentence for ONE interpreter predicate node. The
136
136
  * shape of the output is pinned by Task 7b so the test suite can assert
137
- * byte-for-byte equality. Returns `null` when the node is a structural
138
- * boolean (`and`) - that is not a constraint leaf. */
137
+ * byte-for-byte equality. Returns `null` only when a node's shape cannot be
138
+ * rendered at all. */
139
139
  function renderConstraint(node: PredicateNode): string | null {
140
140
  switch (node.op) {
141
- case 'and':
142
- return null
141
+ case 'and': {
142
+ // Reached ONLY when an `and` sits BENEATH an `or`: `walkPredicate`
143
+ // descends into a top-level `and` and never calls this on one.
144
+ //
145
+ // This used to return null, on the reasoning that `and` is structural
146
+ // rather than a constraint leaf. That is true for the walk path and
147
+ // false for this one, and the combination silently dropped real
148
+ // policies: `or(and, and)` is the natural shape of a policy with
149
+ // alternative permitted forms - one conjunction of pins per branch -
150
+ // so the `or` case below always saw null children and withheld the
151
+ // entire disjunction. `describePredicate` then returned an EMPTY list
152
+ // for a restrictive policy, which any caller renders as "no
153
+ // constraints".
154
+ //
155
+ // That inverts the very guard it was protecting: the `or` case
156
+ // withholds to avoid reading STRICTER than reality, but withholding
157
+ // the only line reads as UNRESTRICTED, which is far worse. Compose
158
+ // instead, and keep the withhold for genuinely unrenderable children.
159
+ const parts = node.children.map(renderConstraint)
160
+ if (parts.some((p) => p === null)) return null
161
+ return parts.join(' and ')
162
+ }
143
163
  case 'or': {
144
164
  // One line for the whole disjunction. If any branch is a shape the
145
165
  // card cannot render, the entire line is withheld rather than shown