@timeax/digital-service-engine 0.2.7 → 0.2.9
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/core/index.cjs +1688 -1218
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +21 -1
- package/dist/core/index.d.ts +21 -1
- package/dist/core/index.js +1686 -1218
- package/dist/core/index.js.map +1 -1
- package/dist/react/index.cjs +586 -376
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +8 -0
- package/dist/react/index.d.ts +8 -0
- package/dist/react/index.js +586 -376
- package/dist/react/index.js.map +1 -1
- package/dist/workspace/index.cjs +595 -389
- package/dist/workspace/index.cjs.map +1 -1
- package/dist/workspace/index.d.cts +8 -0
- package/dist/workspace/index.d.ts +8 -0
- package/dist/workspace/index.js +595 -389
- package/dist/workspace/index.js.map +1 -1
- package/package.json +1 -1
package/dist/core/index.js
CHANGED
|
@@ -607,13 +607,7 @@ function resolveRootTags(tags) {
|
|
|
607
607
|
const roots = tags.filter((t) => !t.bind_id);
|
|
608
608
|
return roots.length ? roots : tags.slice(0, 1);
|
|
609
609
|
}
|
|
610
|
-
function
|
|
611
|
-
var _a, _b, _c, _d, _e, _f;
|
|
612
|
-
const inc = (_a = v.props.includes_for_buttons) != null ? _a : {};
|
|
613
|
-
const exc = (_b = v.props.excludes_for_buttons) != null ? _b : {};
|
|
614
|
-
return ((_d = (_c = inc[trigger]) == null ? void 0 : _c.length) != null ? _d : 0) > 0 || ((_f = (_e = exc[trigger]) == null ? void 0 : _e.length) != null ? _f : 0) > 0;
|
|
615
|
-
}
|
|
616
|
-
function collectSelectableTriggersInContext(v, tagId, selectedKeys, onlyEffectful) {
|
|
610
|
+
function collectSelectableTriggersInContext(v, tagId, selectedKeys, effectfulKeys) {
|
|
617
611
|
var _a;
|
|
618
612
|
const visible = visibleFieldsUnder(v.props, tagId, {
|
|
619
613
|
selectedKeys
|
|
@@ -622,11 +616,11 @@ function collectSelectableTriggersInContext(v, tagId, selectedKeys, onlyEffectfu
|
|
|
622
616
|
for (const f of visible) {
|
|
623
617
|
if (f.button === true) {
|
|
624
618
|
const t = f.id;
|
|
625
|
-
if (
|
|
619
|
+
if (effectfulKeys.has(t)) triggers.push(t);
|
|
626
620
|
}
|
|
627
621
|
for (const o of (_a = f.options) != null ? _a : []) {
|
|
628
|
-
const t =
|
|
629
|
-
if (
|
|
622
|
+
const t = o.id;
|
|
623
|
+
if (effectfulKeys.has(t)) triggers.push(t);
|
|
630
624
|
}
|
|
631
625
|
}
|
|
632
626
|
triggers.sort();
|
|
@@ -726,20 +720,38 @@ function dedupeErrorsInPlace(v, startIndex) {
|
|
|
726
720
|
v.errors.splice(startIndex, v.errors.length - startIndex, ...kept);
|
|
727
721
|
}
|
|
728
722
|
function validateVisibility(v, options = {}) {
|
|
729
|
-
var _a, _b, _c;
|
|
723
|
+
var _a, _b, _c, _d, _e;
|
|
724
|
+
v.simulatedVisibilityContexts = [];
|
|
730
725
|
const simulate = options.simulate === true;
|
|
731
726
|
if (!simulate) {
|
|
732
727
|
runVisibilityRulesOnce(v);
|
|
728
|
+
for (const tag of v.tags) {
|
|
729
|
+
v.simulatedVisibilityContexts.push({
|
|
730
|
+
tagId: tag.id,
|
|
731
|
+
selectedKeys: Array.from(v.selectedKeys),
|
|
732
|
+
visibleFieldIds: v.fieldsVisibleUnder(tag.id).map((f) => f.id)
|
|
733
|
+
});
|
|
734
|
+
}
|
|
733
735
|
return;
|
|
734
736
|
}
|
|
735
737
|
const maxStates = Math.max(1, (_a = options.maxStates) != null ? _a : 500);
|
|
736
738
|
const maxDepth = Math.max(0, (_b = options.maxDepth) != null ? _b : 6);
|
|
737
739
|
const onlyEffectful = options.onlyEffectfulTriggers !== false;
|
|
740
|
+
const effectfulKeys = /* @__PURE__ */ new Set();
|
|
741
|
+
if (onlyEffectful) {
|
|
742
|
+
for (const key of Object.keys((_c = v.props.includes_for_buttons) != null ? _c : {})) {
|
|
743
|
+
effectfulKeys.add(key);
|
|
744
|
+
}
|
|
745
|
+
for (const key of Object.keys((_d = v.props.excludes_for_buttons) != null ? _d : {})) {
|
|
746
|
+
effectfulKeys.add(key);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
738
749
|
const roots = resolveRootTags(v.tags);
|
|
739
750
|
const rootTags = options.simulateAllRoots ? roots : roots.slice(0, 1);
|
|
740
|
-
const originalSelected = new Set((
|
|
751
|
+
const originalSelected = new Set((_e = v.selectedKeys) != null ? _e : []);
|
|
741
752
|
const errorsStart = v.errors.length;
|
|
742
753
|
const visited = /* @__PURE__ */ new Set();
|
|
754
|
+
const seenContexts = /* @__PURE__ */ new Set();
|
|
743
755
|
const stack = [];
|
|
744
756
|
for (const rt of rootTags) {
|
|
745
757
|
stack.push({
|
|
@@ -752,10 +764,27 @@ function validateVisibility(v, options = {}) {
|
|
|
752
764
|
while (stack.length) {
|
|
753
765
|
if (validatedStates >= maxStates) break;
|
|
754
766
|
const state = stack.pop();
|
|
755
|
-
const sig = stableKeyOfSelection(state.selected)
|
|
767
|
+
const sig = `${state.rootTagId}::${stableKeyOfSelection(state.selected)}`;
|
|
756
768
|
if (visited.has(sig)) continue;
|
|
757
769
|
visited.add(sig);
|
|
758
770
|
v.selectedKeys = state.selected;
|
|
771
|
+
const visibleNow = visibleFieldsUnder(v.props, state.rootTagId, {
|
|
772
|
+
selectedKeys: state.selected
|
|
773
|
+
}).map((f) => f.id);
|
|
774
|
+
const context = {
|
|
775
|
+
tagId: state.rootTagId,
|
|
776
|
+
selectedKeys: Array.from(state.selected),
|
|
777
|
+
visibleFieldIds: visibleNow
|
|
778
|
+
};
|
|
779
|
+
const contextKey = [
|
|
780
|
+
context.tagId,
|
|
781
|
+
[...context.selectedKeys].sort().join("|"),
|
|
782
|
+
[...context.visibleFieldIds].sort().join("|")
|
|
783
|
+
].join("::");
|
|
784
|
+
if (!seenContexts.has(contextKey)) {
|
|
785
|
+
seenContexts.add(contextKey);
|
|
786
|
+
v.simulatedVisibilityContexts.push(context);
|
|
787
|
+
}
|
|
759
788
|
validatedStates++;
|
|
760
789
|
runVisibilityRulesOnce(v);
|
|
761
790
|
if (state.depth >= maxDepth) continue;
|
|
@@ -763,7 +792,7 @@ function validateVisibility(v, options = {}) {
|
|
|
763
792
|
v,
|
|
764
793
|
state.rootTagId,
|
|
765
794
|
state.selected,
|
|
766
|
-
|
|
795
|
+
effectfulKeys
|
|
767
796
|
);
|
|
768
797
|
for (let i = triggers.length - 1; i >= 0; i--) {
|
|
769
798
|
const trig = triggers[i];
|
|
@@ -1535,1273 +1564,1335 @@ function validateRates(v) {
|
|
|
1535
1564
|
}
|
|
1536
1565
|
}
|
|
1537
1566
|
|
|
1538
|
-
// src/core/
|
|
1539
|
-
function
|
|
1540
|
-
const
|
|
1541
|
-
const
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
while (cur && !seenTags.has(cur)) {
|
|
1545
|
-
seenTags.add(cur);
|
|
1546
|
-
const t = v.tagById.get(cur);
|
|
1547
|
-
const c = t == null ? void 0 : t.constraints;
|
|
1548
|
-
if (c && typeof c === "object") {
|
|
1549
|
-
for (const k of Object.keys(c)) {
|
|
1550
|
-
if (!seenKeys.has(k)) {
|
|
1551
|
-
seenKeys.add(k);
|
|
1552
|
-
keys.push(k);
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
1555
|
-
}
|
|
1556
|
-
cur = t == null ? void 0 : t.bind_id;
|
|
1557
|
-
}
|
|
1558
|
-
return keys;
|
|
1559
|
-
}
|
|
1560
|
-
function effectiveConstraints(v, tagId) {
|
|
1561
|
-
var _a;
|
|
1562
|
-
const out = {};
|
|
1563
|
-
const keys = constraintKeysInChain(v, tagId);
|
|
1564
|
-
for (const key of keys) {
|
|
1565
|
-
let cur = tagId;
|
|
1566
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1567
|
-
while (cur && !seen.has(cur)) {
|
|
1568
|
-
seen.add(cur);
|
|
1569
|
-
const t = v.tagById.get(cur);
|
|
1570
|
-
const val = (_a = t == null ? void 0 : t.constraints) == null ? void 0 : _a[key];
|
|
1571
|
-
if (val === true || val === false) {
|
|
1572
|
-
out[key] = val;
|
|
1573
|
-
break;
|
|
1574
|
-
}
|
|
1575
|
-
cur = t == null ? void 0 : t.bind_id;
|
|
1576
|
-
}
|
|
1567
|
+
// src/core/rate-coherence.ts
|
|
1568
|
+
function uniqueStrings(values) {
|
|
1569
|
+
const out = /* @__PURE__ */ new Set();
|
|
1570
|
+
for (const value of values) {
|
|
1571
|
+
if (!value) continue;
|
|
1572
|
+
out.add(value);
|
|
1577
1573
|
}
|
|
1578
|
-
return out;
|
|
1574
|
+
return Array.from(out);
|
|
1579
1575
|
}
|
|
1580
|
-
function
|
|
1576
|
+
function buildTriggerEffectMap(props) {
|
|
1581
1577
|
var _a, _b;
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
const visible = v.fieldsVisibleUnder(t.id);
|
|
1589
|
-
for (const f of visible) {
|
|
1590
|
-
for (const o of (_a = f.options) != null ? _a : []) {
|
|
1591
|
-
if (!isServiceIdRef(o.service_id)) continue;
|
|
1592
|
-
const svc = getServiceCapability(v.serviceMap, o.service_id);
|
|
1593
|
-
if (!svc || typeof svc !== "object") continue;
|
|
1594
|
-
for (const [k, val] of Object.entries(eff)) {
|
|
1595
|
-
if (val === true && !isServiceFlagEnabled(svc, k)) {
|
|
1596
|
-
v.errors.push({
|
|
1597
|
-
code: "unsupported_constraint",
|
|
1598
|
-
severity: "error",
|
|
1599
|
-
message: `Service option "${o.id}" under tag "${t.id}" does not support required constraint "${k}".`,
|
|
1600
|
-
nodeId: t.id,
|
|
1601
|
-
details: withAffected(
|
|
1602
|
-
{
|
|
1603
|
-
flag: k,
|
|
1604
|
-
serviceId: o.service_id,
|
|
1605
|
-
fieldId: f.id,
|
|
1606
|
-
optionId: o.id
|
|
1607
|
-
},
|
|
1608
|
-
[t.id, f.id, o.id]
|
|
1609
|
-
)
|
|
1610
|
-
});
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1613
|
-
}
|
|
1614
|
-
}
|
|
1615
|
-
}
|
|
1616
|
-
for (const t of v.tags) {
|
|
1617
|
-
const sid = t.service_id;
|
|
1618
|
-
if (!isServiceIdRef(sid)) continue;
|
|
1619
|
-
const svc = getServiceCapability(v.serviceMap, sid);
|
|
1620
|
-
if (!svc || typeof svc !== "object") continue;
|
|
1621
|
-
const eff = effectiveConstraints(v, t.id);
|
|
1622
|
-
for (const [k, val] of Object.entries(eff)) {
|
|
1623
|
-
if (val === true && !isServiceFlagEnabled(svc, k)) {
|
|
1624
|
-
v.errors.push({
|
|
1625
|
-
code: "unsupported_constraint",
|
|
1626
|
-
severity: "error",
|
|
1627
|
-
message: `Tag "${t.id}" maps to service "${String(
|
|
1628
|
-
sid
|
|
1629
|
-
)}" which does not support required constraint "${k}".`,
|
|
1630
|
-
nodeId: t.id,
|
|
1631
|
-
details: { flag: k, serviceId: sid }
|
|
1632
|
-
});
|
|
1633
|
-
}
|
|
1578
|
+
const map = /* @__PURE__ */ new Map();
|
|
1579
|
+
const ensure = (key) => {
|
|
1580
|
+
let item = map.get(key);
|
|
1581
|
+
if (!item) {
|
|
1582
|
+
item = { includes: /* @__PURE__ */ new Set(), excludes: /* @__PURE__ */ new Set() };
|
|
1583
|
+
map.set(key, item);
|
|
1634
1584
|
}
|
|
1585
|
+
return item;
|
|
1586
|
+
};
|
|
1587
|
+
for (const [key, ids] of Object.entries((_a = props.includes_for_buttons) != null ? _a : {})) {
|
|
1588
|
+
const item = ensure(key);
|
|
1589
|
+
for (const id of ids != null ? ids : []) item.includes.add(id);
|
|
1635
1590
|
}
|
|
1636
|
-
for (const
|
|
1637
|
-
const
|
|
1638
|
-
|
|
1639
|
-
for (const k of Object.keys(ov)) {
|
|
1640
|
-
const row = ov[k];
|
|
1641
|
-
if (!row) continue;
|
|
1642
|
-
const from = row.from === true;
|
|
1643
|
-
const to = row.to === true;
|
|
1644
|
-
const origin = String((_b = row.origin) != null ? _b : "");
|
|
1645
|
-
v.errors.push({
|
|
1646
|
-
code: "constraint_overridden",
|
|
1647
|
-
severity: "warning",
|
|
1648
|
-
message: origin ? `Constraint "${k}" on tag "${t.id}" was overridden by ancestor "${origin}" (${String(from)} \u2192 ${String(
|
|
1649
|
-
to
|
|
1650
|
-
)}).` : `Constraint "${k}" on tag "${t.id}" was overridden by an ancestor (${String(from)} \u2192 ${String(to)}).`,
|
|
1651
|
-
nodeId: t.id,
|
|
1652
|
-
details: withAffected(
|
|
1653
|
-
{ flag: k, from, to, origin },
|
|
1654
|
-
origin ? [t.id, origin] : void 0
|
|
1655
|
-
)
|
|
1656
|
-
});
|
|
1657
|
-
}
|
|
1591
|
+
for (const [key, ids] of Object.entries((_b = props.excludes_for_buttons) != null ? _b : {})) {
|
|
1592
|
+
const item = ensure(key);
|
|
1593
|
+
for (const id of ids != null ? ids : []) item.excludes.add(id);
|
|
1658
1594
|
}
|
|
1595
|
+
return map;
|
|
1659
1596
|
}
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
if (
|
|
1665
|
-
|
|
1666
|
-
v.errors.push({
|
|
1667
|
-
code: "custom_component_missing",
|
|
1668
|
-
severity: "error",
|
|
1669
|
-
message: `Custom field "${f.id}" is missing a valid component reference.`,
|
|
1670
|
-
nodeId: f.id
|
|
1671
|
-
});
|
|
1597
|
+
function isRefExcludedBySelectedKeys(ref, selectedKeys, effectMap) {
|
|
1598
|
+
for (const key of selectedKeys) {
|
|
1599
|
+
const effects = effectMap.get(key);
|
|
1600
|
+
if (!effects) continue;
|
|
1601
|
+
if (ref.fieldId && effects.excludes.has(ref.fieldId) || effects.excludes.has(ref.nodeId)) {
|
|
1602
|
+
return true;
|
|
1672
1603
|
}
|
|
1673
1604
|
}
|
|
1605
|
+
return false;
|
|
1674
1606
|
}
|
|
1675
|
-
|
|
1676
|
-
// src/core/validate/steps/global-utility-guard.ts
|
|
1677
|
-
function validateGlobalUtilityGuard(v) {
|
|
1607
|
+
function validateRateCoherenceDeep(params) {
|
|
1678
1608
|
var _a, _b, _c;
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1609
|
+
const { builder, services, tagId } = params;
|
|
1610
|
+
const ratePolicy = normalizeRatePolicy(params.ratePolicy);
|
|
1611
|
+
const props = builder.getProps();
|
|
1612
|
+
const invalidFieldIds = new Set((_a = params.invalidFieldIds) != null ? _a : []);
|
|
1613
|
+
const fields = (_b = props.fields) != null ? _b : [];
|
|
1614
|
+
const fieldById = new Map(fields.map((f) => [f.id, f]));
|
|
1615
|
+
const tagById = new Map(((_c = props.filters) != null ? _c : []).map((t) => [t.id, t]));
|
|
1616
|
+
const tag = tagById.get(tagId);
|
|
1617
|
+
const baselineFieldIds = builder.visibleFields(tagId, []);
|
|
1618
|
+
const baselineFields = baselineFieldIds.map((fid) => fieldById.get(fid)).filter(Boolean);
|
|
1619
|
+
const anchors = collectAnchors(baselineFields);
|
|
1620
|
+
const diagnostics = [];
|
|
1621
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1622
|
+
for (const anchor of anchors) {
|
|
1623
|
+
const selectedKeys = anchor.kind === "option" ? [anchor.id] : [anchor.fieldId];
|
|
1624
|
+
const visibleFields = builder.visibleFields(tagId, selectedKeys).map((fid) => fieldById.get(fid)).filter(Boolean);
|
|
1625
|
+
const visibleInvalidFieldIds = visibleFields.map((field) => field.id).filter((fieldId) => invalidFieldIds.has(fieldId));
|
|
1626
|
+
for (const fieldId of visibleInvalidFieldIds) {
|
|
1627
|
+
const key = `internal|${tagId}|${fieldId}`;
|
|
1628
|
+
if (seen.has(key)) continue;
|
|
1629
|
+
seen.add(key);
|
|
1630
|
+
diagnostics.push({
|
|
1631
|
+
kind: "internal_field",
|
|
1632
|
+
scope: "visible_group",
|
|
1633
|
+
tagId,
|
|
1634
|
+
fieldId,
|
|
1635
|
+
nodeId: fieldId,
|
|
1636
|
+
message: `Field "${fieldId}" is internally invalid under rate policy "${ratePolicy.kind}".`,
|
|
1637
|
+
simulationAnchor: {
|
|
1638
|
+
kind: anchor.kind,
|
|
1639
|
+
id: anchor.id,
|
|
1640
|
+
fieldId: anchor.fieldId,
|
|
1641
|
+
label: anchor.label
|
|
1642
|
+
},
|
|
1643
|
+
invalidFieldIds: [fieldId],
|
|
1644
|
+
affectedIds: uniqueStrings([
|
|
1645
|
+
tagId,
|
|
1646
|
+
anchor.id,
|
|
1647
|
+
anchor.fieldId,
|
|
1648
|
+
fieldId
|
|
1649
|
+
])
|
|
1650
|
+
});
|
|
1689
1651
|
}
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1652
|
+
const references = visibleFields.flatMap(
|
|
1653
|
+
(field) => collectFieldReferences(field, services)
|
|
1654
|
+
);
|
|
1655
|
+
if (references.length <= 1) continue;
|
|
1656
|
+
const primary = references.reduce((best, current) => {
|
|
1657
|
+
if (current.rate !== best.rate) {
|
|
1658
|
+
return current.rate > best.rate ? current : best;
|
|
1659
|
+
}
|
|
1660
|
+
const bestKey = `${best.fieldId}|${best.nodeId}`;
|
|
1661
|
+
const currentKey = `${current.fieldId}|${current.nodeId}`;
|
|
1662
|
+
return currentKey < bestKey ? current : best;
|
|
1699
1663
|
});
|
|
1664
|
+
for (const candidate of references) {
|
|
1665
|
+
if (candidate.nodeId === primary.nodeId) continue;
|
|
1666
|
+
if (candidate.fieldId === primary.fieldId) continue;
|
|
1667
|
+
if (passesRatePolicy(ratePolicy, primary.rate, candidate.rate)) {
|
|
1668
|
+
continue;
|
|
1669
|
+
}
|
|
1670
|
+
const key = contextualKey(tagId, primary, candidate, ratePolicy);
|
|
1671
|
+
if (seen.has(key)) continue;
|
|
1672
|
+
seen.add(key);
|
|
1673
|
+
diagnostics.push({
|
|
1674
|
+
kind: "contextual",
|
|
1675
|
+
scope: "visible_group",
|
|
1676
|
+
tagId,
|
|
1677
|
+
nodeId: candidate.nodeId,
|
|
1678
|
+
primary: toDiagnosticRef(primary),
|
|
1679
|
+
offender: toDiagnosticRef(candidate),
|
|
1680
|
+
policy: ratePolicy.kind,
|
|
1681
|
+
policyPct: "pct" in ratePolicy ? ratePolicy.pct : void 0,
|
|
1682
|
+
message: explainRateMismatch(
|
|
1683
|
+
ratePolicy,
|
|
1684
|
+
primary,
|
|
1685
|
+
candidate,
|
|
1686
|
+
describeLabel(tag)
|
|
1687
|
+
),
|
|
1688
|
+
simulationAnchor: {
|
|
1689
|
+
kind: anchor.kind,
|
|
1690
|
+
id: anchor.id,
|
|
1691
|
+
fieldId: anchor.fieldId,
|
|
1692
|
+
label: anchor.label
|
|
1693
|
+
},
|
|
1694
|
+
invalidFieldIds: visibleInvalidFieldIds,
|
|
1695
|
+
affectedIds: uniqueStrings([
|
|
1696
|
+
tagId,
|
|
1697
|
+
...selectedKeys,
|
|
1698
|
+
anchor.id,
|
|
1699
|
+
anchor.fieldId,
|
|
1700
|
+
primary.nodeId,
|
|
1701
|
+
primary.fieldId,
|
|
1702
|
+
candidate.nodeId,
|
|
1703
|
+
candidate.fieldId
|
|
1704
|
+
]),
|
|
1705
|
+
affectedServiceIds: uniqueStrings([
|
|
1706
|
+
primary.service_id == null ? void 0 : String(primary.service_id),
|
|
1707
|
+
candidate.service_id == null ? void 0 : String(candidate.service_id)
|
|
1708
|
+
])
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1700
1711
|
}
|
|
1712
|
+
return diagnostics;
|
|
1701
1713
|
}
|
|
1702
|
-
|
|
1703
|
-
// src/core/validate/steps/unbound.ts
|
|
1704
|
-
function validateUnboundFields(v) {
|
|
1714
|
+
function collectAnchors(fields) {
|
|
1705
1715
|
var _a, _b;
|
|
1706
|
-
const
|
|
1707
|
-
for (const
|
|
1708
|
-
if (
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
if (!boundFieldIds.has(f.id) && !includedByTag.has(f.id) && !includedByOption.has(f.id)) {
|
|
1720
|
-
v.errors.push({
|
|
1721
|
-
code: "field_unbound",
|
|
1722
|
-
severity: "error",
|
|
1723
|
-
message: `Field "${f.id}" is unbound: it is not bound to any tag and not included by tags or option maps.`,
|
|
1724
|
-
nodeId: f.id,
|
|
1725
|
-
details: withAffected(
|
|
1726
|
-
{
|
|
1727
|
-
fieldId: f.id,
|
|
1728
|
-
bound: false,
|
|
1729
|
-
// exposing these helps editors explain "why"
|
|
1730
|
-
includedByTag: includedByTag.has(f.id),
|
|
1731
|
-
includedByOption: includedByOption.has(f.id)
|
|
1732
|
-
},
|
|
1733
|
-
[f.id]
|
|
1734
|
-
)
|
|
1735
|
-
});
|
|
1716
|
+
const anchors = [];
|
|
1717
|
+
for (const field of fields) {
|
|
1718
|
+
if (!isButton(field)) continue;
|
|
1719
|
+
if (Array.isArray(field.options) && field.options.length > 0) {
|
|
1720
|
+
for (const option of field.options) {
|
|
1721
|
+
anchors.push({
|
|
1722
|
+
kind: "option",
|
|
1723
|
+
id: option.id,
|
|
1724
|
+
fieldId: field.id,
|
|
1725
|
+
label: (_a = option.label) != null ? _a : option.id
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1728
|
+
continue;
|
|
1736
1729
|
}
|
|
1730
|
+
anchors.push({
|
|
1731
|
+
kind: "field",
|
|
1732
|
+
id: field.id,
|
|
1733
|
+
fieldId: field.id,
|
|
1734
|
+
label: (_b = field.label) != null ? _b : field.id
|
|
1735
|
+
});
|
|
1737
1736
|
}
|
|
1737
|
+
return anchors;
|
|
1738
1738
|
}
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1739
|
+
function collectFieldReferences(field, services) {
|
|
1740
|
+
var _a;
|
|
1741
|
+
const members = collectBaseMembers(field, services);
|
|
1742
|
+
if (members.length === 0) return [];
|
|
1743
|
+
if (isMultiField(field)) {
|
|
1744
|
+
const averageRate = members.reduce((sum, member) => sum + member.rate, 0) / members.length;
|
|
1745
|
+
return [
|
|
1746
|
+
{
|
|
1747
|
+
refKind: "multi",
|
|
1748
|
+
nodeId: field.id,
|
|
1749
|
+
fieldId: field.id,
|
|
1750
|
+
label: (_a = field.label) != null ? _a : field.id,
|
|
1751
|
+
rate: averageRate,
|
|
1752
|
+
members
|
|
1753
|
+
}
|
|
1754
|
+
];
|
|
1755
1755
|
}
|
|
1756
|
+
return members.map((member) => ({
|
|
1757
|
+
refKind: "single",
|
|
1758
|
+
nodeId: member.id,
|
|
1759
|
+
fieldId: field.id,
|
|
1760
|
+
label: member.label,
|
|
1761
|
+
rate: member.rate,
|
|
1762
|
+
service_id: member.service_id,
|
|
1763
|
+
members: [member]
|
|
1764
|
+
}));
|
|
1756
1765
|
}
|
|
1757
|
-
function
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1766
|
+
function collectBaseMembers(field, services) {
|
|
1767
|
+
var _a, _b, _c;
|
|
1768
|
+
const members = [];
|
|
1769
|
+
if (Array.isArray(field.options) && field.options.length > 0) {
|
|
1770
|
+
for (const option of field.options) {
|
|
1771
|
+
const role2 = normalizeRole((_a = option.pricing_role) != null ? _a : field.pricing_role, "base");
|
|
1772
|
+
if (role2 !== "base") continue;
|
|
1773
|
+
if (option.service_id === void 0 || option.service_id === null) {
|
|
1774
|
+
continue;
|
|
1775
|
+
}
|
|
1776
|
+
const cap2 = getServiceCapability(services, option.service_id);
|
|
1777
|
+
if (!cap2 || typeof cap2.rate !== "number" || !Number.isFinite(cap2.rate)) {
|
|
1778
|
+
continue;
|
|
1779
|
+
}
|
|
1780
|
+
members.push({
|
|
1781
|
+
kind: "option",
|
|
1782
|
+
id: option.id,
|
|
1783
|
+
fieldId: field.id,
|
|
1784
|
+
label: (_b = option.label) != null ? _b : option.id,
|
|
1785
|
+
service_id: option.service_id,
|
|
1786
|
+
rate: cap2.rate
|
|
1787
|
+
});
|
|
1788
|
+
}
|
|
1789
|
+
return members;
|
|
1778
1790
|
}
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
const
|
|
1783
|
-
if (!
|
|
1784
|
-
|
|
1785
|
-
...v.options.fallbackSettings,
|
|
1786
|
-
mode: "dev"
|
|
1787
|
-
});
|
|
1788
|
-
if (mode !== "strict") return;
|
|
1789
|
-
for (const d of diags) {
|
|
1790
|
-
if (d.scope === "global") continue;
|
|
1791
|
-
const code = codeForReason(
|
|
1792
|
-
String((_d = d.reason) != null ? _d : "fallback_bad_node")
|
|
1793
|
-
);
|
|
1794
|
-
const nodeId = d.nodeId ? String(d.nodeId) : void 0;
|
|
1795
|
-
const tagContext = d.tagContext;
|
|
1796
|
-
const affectedIds = [];
|
|
1797
|
-
if (nodeId) affectedIds.push(nodeId);
|
|
1798
|
-
if (typeof tagContext === "string" && tagContext && tagContext !== nodeId)
|
|
1799
|
-
affectedIds.push(tagContext);
|
|
1800
|
-
v.errors.push({
|
|
1801
|
-
code,
|
|
1802
|
-
severity: "error",
|
|
1803
|
-
message: messageFor(code, {
|
|
1804
|
-
nodeId,
|
|
1805
|
-
primary: d.primary,
|
|
1806
|
-
candidate: d.candidate,
|
|
1807
|
-
tagContext,
|
|
1808
|
-
scope: d.scope
|
|
1809
|
-
}),
|
|
1810
|
-
nodeId,
|
|
1811
|
-
details: withAffected(
|
|
1812
|
-
{
|
|
1813
|
-
primary: d.primary,
|
|
1814
|
-
candidate: d.candidate,
|
|
1815
|
-
tagContext,
|
|
1816
|
-
scope: d.scope
|
|
1817
|
-
},
|
|
1818
|
-
affectedIds.length > 1 ? affectedIds : void 0
|
|
1819
|
-
)
|
|
1820
|
-
});
|
|
1791
|
+
const role = normalizeRole(field.pricing_role, "base");
|
|
1792
|
+
if (role !== "base") return members;
|
|
1793
|
+
if (field.service_id === void 0 || field.service_id === null) return members;
|
|
1794
|
+
const cap = getServiceCapability(services, field.service_id);
|
|
1795
|
+
if (!cap || typeof cap.rate !== "number" || !Number.isFinite(cap.rate)) {
|
|
1796
|
+
return members;
|
|
1821
1797
|
}
|
|
1798
|
+
members.push({
|
|
1799
|
+
kind: "field",
|
|
1800
|
+
id: field.id,
|
|
1801
|
+
fieldId: field.id,
|
|
1802
|
+
label: (_c = field.label) != null ? _c : field.id,
|
|
1803
|
+
service_id: field.service_id,
|
|
1804
|
+
rate: cap.rate
|
|
1805
|
+
});
|
|
1806
|
+
return members;
|
|
1822
1807
|
}
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
if (v === void 0) return void 0;
|
|
1827
|
-
return Array.isArray(v) ? v : [v];
|
|
1808
|
+
function isButton(field) {
|
|
1809
|
+
if (field.button === true) return true;
|
|
1810
|
+
return Array.isArray(field.options) && field.options.length > 0;
|
|
1828
1811
|
}
|
|
1829
|
-
function
|
|
1830
|
-
return
|
|
1812
|
+
function normalizeRole(role, fallback) {
|
|
1813
|
+
return role === "base" || role === "utility" ? role : fallback;
|
|
1831
1814
|
}
|
|
1832
|
-
function
|
|
1833
|
-
const svc = serviceMap[sid];
|
|
1834
|
-
if (!svc) return { id: sid };
|
|
1835
|
-
const meta = svc.meta && typeof svc.meta === "object" ? svc.meta : {};
|
|
1815
|
+
function toDiagnosticRef(reference) {
|
|
1836
1816
|
return {
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1817
|
+
nodeId: reference.nodeId,
|
|
1818
|
+
fieldId: reference.fieldId,
|
|
1819
|
+
label: reference.label,
|
|
1820
|
+
refKind: reference.refKind,
|
|
1821
|
+
service_id: reference.service_id,
|
|
1822
|
+
rate: reference.rate
|
|
1840
1823
|
};
|
|
1841
1824
|
}
|
|
1842
|
-
function
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
role: next.role,
|
|
1854
|
-
service: next.service,
|
|
1855
|
-
affectedIds: Array.from(new Set(next.affectedIds))
|
|
1856
|
-
});
|
|
1857
|
-
return;
|
|
1858
|
-
}
|
|
1859
|
-
const mergedIds = Array.from(
|
|
1860
|
-
/* @__PURE__ */ new Set([...existing.affectedIds, ...next.affectedIds])
|
|
1861
|
-
);
|
|
1862
|
-
out.set(key, {
|
|
1863
|
-
...existing,
|
|
1864
|
-
tagId: (_a = existing.tagId) != null ? _a : next.tagId,
|
|
1865
|
-
affectedIds: mergedIds
|
|
1866
|
-
});
|
|
1825
|
+
function contextualKey(tagId, primary, candidate, ratePolicy) {
|
|
1826
|
+
const pctKey = "pct" in ratePolicy ? `:${ratePolicy.pct}` : "";
|
|
1827
|
+
return [
|
|
1828
|
+
"contextual",
|
|
1829
|
+
tagId,
|
|
1830
|
+
primary.fieldId,
|
|
1831
|
+
primary.nodeId,
|
|
1832
|
+
candidate.fieldId,
|
|
1833
|
+
candidate.nodeId,
|
|
1834
|
+
`${ratePolicy.kind}${pctKey}`
|
|
1835
|
+
].join("|");
|
|
1867
1836
|
}
|
|
1868
|
-
function
|
|
1837
|
+
function describeLabel(tag) {
|
|
1869
1838
|
var _a, _b;
|
|
1870
|
-
|
|
1871
|
-
return roleRaw === "utility" ? "utility" : "base";
|
|
1839
|
+
return (_b = (_a = tag == null ? void 0 : tag.label) != null ? _a : tag == null ? void 0 : tag.id) != null ? _b : "tag";
|
|
1872
1840
|
}
|
|
1873
|
-
function
|
|
1874
|
-
|
|
1875
|
-
const
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1841
|
+
function explainRateMismatch(policy, primary, candidate, where) {
|
|
1842
|
+
var _a, _b;
|
|
1843
|
+
const primaryLabel = `${(_a = primary.label) != null ? _a : primary.nodeId} (${primary.rate})`;
|
|
1844
|
+
const candidateLabel = `${(_b = candidate.label) != null ? _b : candidate.nodeId} (${candidate.rate})`;
|
|
1845
|
+
switch (policy.kind) {
|
|
1846
|
+
case "eq_primary":
|
|
1847
|
+
return `Rate coherence failed (${where}): ${candidateLabel} must exactly match ${primaryLabel}.`;
|
|
1848
|
+
case "lte_primary":
|
|
1849
|
+
return `Rate coherence failed (${where}): ${candidateLabel} must stay within ${policy.pct}% below and never above ${primaryLabel}.`;
|
|
1850
|
+
case "within_pct":
|
|
1851
|
+
return `Rate coherence failed (${where}): ${candidateLabel} must be within ${policy.pct}% of ${primaryLabel}.`;
|
|
1852
|
+
case "at_least_pct_lower":
|
|
1853
|
+
return `Rate coherence failed (${where}): ${candidateLabel} must be at least ${policy.pct}% lower than ${primaryLabel}.`;
|
|
1879
1854
|
}
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
// src/core/validate/steps/rate-coherence.ts
|
|
1858
|
+
function normalizeRole2(role, fallback) {
|
|
1859
|
+
return role === "base" || role === "utility" ? role : fallback;
|
|
1860
|
+
}
|
|
1861
|
+
function uniqueStrings2(values) {
|
|
1862
|
+
const out = /* @__PURE__ */ new Set();
|
|
1863
|
+
for (const value of values) {
|
|
1864
|
+
if (!value) continue;
|
|
1865
|
+
out.add(value);
|
|
1883
1866
|
}
|
|
1884
|
-
return
|
|
1867
|
+
return Array.from(out);
|
|
1885
1868
|
}
|
|
1886
|
-
function
|
|
1869
|
+
function getRate(serviceMap, serviceId) {
|
|
1870
|
+
const cap = getServiceCapability(serviceMap, serviceId);
|
|
1871
|
+
const rate = cap == null ? void 0 : cap.rate;
|
|
1872
|
+
if (typeof rate !== "number" || !Number.isFinite(rate)) return void 0;
|
|
1873
|
+
return rate;
|
|
1874
|
+
}
|
|
1875
|
+
function collectContextRefs(tag, visibleFields, serviceMap) {
|
|
1887
1876
|
var _a, _b, _c, _d, _e;
|
|
1888
|
-
const
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
if (args.mode === "global") {
|
|
1903
|
-
for (const t of (_b = args.tags) != null ? _b : []) {
|
|
1904
|
-
const sid = t.service_id;
|
|
1905
|
-
if (!isServiceIdRef2(sid)) continue;
|
|
1906
|
-
addServiceRef({
|
|
1907
|
-
tagId: t.id,
|
|
1908
|
-
serviceId: sid,
|
|
1909
|
-
role: "base",
|
|
1910
|
-
affectedIds: [`tag:${t.id}`, `service:${String(sid)}`]
|
|
1911
|
-
});
|
|
1912
|
-
}
|
|
1913
|
-
} else if (args.mode === "visible_group") {
|
|
1914
|
-
const t = args.tag;
|
|
1915
|
-
const sid = t ? t.service_id : void 0;
|
|
1916
|
-
if (t && isServiceIdRef2(sid)) {
|
|
1917
|
-
addServiceRef({
|
|
1918
|
-
tagId: t.id,
|
|
1919
|
-
serviceId: sid,
|
|
1920
|
-
role: "base",
|
|
1921
|
-
affectedIds: [`tag:${t.id}`, `service:${String(sid)}`]
|
|
1922
|
-
});
|
|
1877
|
+
const serviceRefs = [];
|
|
1878
|
+
let tagDefault;
|
|
1879
|
+
if (tag.service_id !== void 0 && tag.service_id !== null) {
|
|
1880
|
+
const tagRate = getRate(serviceMap, tag.service_id);
|
|
1881
|
+
if (tagRate != null) {
|
|
1882
|
+
tagDefault = {
|
|
1883
|
+
key: tag.id,
|
|
1884
|
+
nodeId: tag.id,
|
|
1885
|
+
nodeKind: "tag",
|
|
1886
|
+
serviceId: tag.service_id,
|
|
1887
|
+
rate: tagRate,
|
|
1888
|
+
label: (_a = tag.label) != null ? _a : tag.id,
|
|
1889
|
+
pricingRole: "base"
|
|
1890
|
+
};
|
|
1923
1891
|
}
|
|
1924
1892
|
}
|
|
1925
|
-
const
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1893
|
+
for (const field of visibleFields) {
|
|
1894
|
+
const fieldRole = normalizeRole2(field.pricing_role, "base");
|
|
1895
|
+
if (field.service_id !== void 0 && field.service_id !== null) {
|
|
1896
|
+
const rate = getRate(serviceMap, field.service_id);
|
|
1897
|
+
if (rate != null) {
|
|
1898
|
+
serviceRefs.push({
|
|
1899
|
+
key: field.id,
|
|
1900
|
+
nodeId: field.id,
|
|
1901
|
+
fieldId: field.id,
|
|
1902
|
+
nodeKind: "button",
|
|
1903
|
+
serviceId: field.service_id,
|
|
1904
|
+
rate,
|
|
1905
|
+
label: (_b = field.label) != null ? _b : field.id,
|
|
1906
|
+
pricingRole: fieldRole
|
|
1907
|
+
});
|
|
1908
|
+
}
|
|
1936
1909
|
}
|
|
1937
|
-
for (const
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
`service:${String(oSid)}`
|
|
1951
|
-
]
|
|
1910
|
+
for (const option of (_c = field.options) != null ? _c : []) {
|
|
1911
|
+
if (option.service_id === void 0 || option.service_id === null) continue;
|
|
1912
|
+
const rate = getRate(serviceMap, option.service_id);
|
|
1913
|
+
if (rate == null) continue;
|
|
1914
|
+
serviceRefs.push({
|
|
1915
|
+
key: option.id,
|
|
1916
|
+
nodeId: option.id,
|
|
1917
|
+
fieldId: field.id,
|
|
1918
|
+
nodeKind: "option",
|
|
1919
|
+
serviceId: option.service_id,
|
|
1920
|
+
rate,
|
|
1921
|
+
label: (_d = option.label) != null ? _d : option.id,
|
|
1922
|
+
pricingRole: normalizeRole2((_e = option.pricing_role) != null ? _e : field.pricing_role, "base")
|
|
1952
1923
|
});
|
|
1953
1924
|
}
|
|
1954
1925
|
}
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
const
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1926
|
+
return { tagDefault, serviceRefs };
|
|
1927
|
+
}
|
|
1928
|
+
function pickHighestRatePrimary(refs) {
|
|
1929
|
+
return refs.reduce((best, cur) => {
|
|
1930
|
+
if (!best) return cur;
|
|
1931
|
+
if (cur.rate > best.rate) return cur;
|
|
1932
|
+
if (cur.rate < best.rate) return best;
|
|
1933
|
+
return cur.nodeId < best.nodeId ? cur : best;
|
|
1934
|
+
}, void 0);
|
|
1935
|
+
}
|
|
1936
|
+
function validateRateCoherenceForVisibleContext(params) {
|
|
1937
|
+
const { v, tagId, selectedKeys, visibleFieldIds, effectMap, seen } = params;
|
|
1938
|
+
const tag = v.tagById.get(tagId);
|
|
1939
|
+
if (!tag) return;
|
|
1940
|
+
const visibleFields = visibleFieldIds.map((id) => v.fieldById.get(id)).filter(Boolean);
|
|
1941
|
+
const { tagDefault, serviceRefs: allServiceRefs } = collectContextRefs(
|
|
1942
|
+
tag,
|
|
1943
|
+
visibleFields,
|
|
1944
|
+
v.serviceMap
|
|
1945
|
+
);
|
|
1946
|
+
const baseRefs = allServiceRefs.filter((ref) => ref.pricingRole === "base");
|
|
1947
|
+
if (baseRefs.length === 0 && !tagDefault) return;
|
|
1948
|
+
const ratePolicy = normalizeRatePolicy(v.options.ratePolicy);
|
|
1949
|
+
const visibleInvalidFieldIds = visibleFieldIds.filter(
|
|
1950
|
+
(fieldId) => v.invalidRateFieldIds.has(fieldId)
|
|
1951
|
+
);
|
|
1952
|
+
for (const fieldId of visibleInvalidFieldIds) {
|
|
1953
|
+
const internalKey = [
|
|
1954
|
+
"rate-coherence-internal",
|
|
1955
|
+
tagId,
|
|
1956
|
+
[...selectedKeys].sort().join("|"),
|
|
1957
|
+
fieldId
|
|
1958
|
+
].join("::");
|
|
1959
|
+
if (seen.has(internalKey)) continue;
|
|
1960
|
+
seen.add(internalKey);
|
|
1961
|
+
v.errors.push({
|
|
1962
|
+
code: "rate_coherence_violation",
|
|
1963
|
+
severity: "error",
|
|
1964
|
+
nodeId: fieldId,
|
|
1965
|
+
message: `Field "${fieldId}" is internally invalid under rate policy "${ratePolicy.kind}".`,
|
|
1966
|
+
details: {
|
|
1967
|
+
kind: "internal_field",
|
|
1968
|
+
tagId,
|
|
1969
|
+
selectedKeys: [...selectedKeys],
|
|
1970
|
+
visibleFieldIds: [...visibleFieldIds],
|
|
1971
|
+
fieldId,
|
|
1972
|
+
invalidFieldIds: [fieldId],
|
|
1973
|
+
affectedIds: uniqueStrings2([tagId, ...selectedKeys, fieldId])
|
|
1974
|
+
}
|
|
1981
1975
|
});
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
if (includeAllFallbacks) {
|
|
2002
|
-
for (const [nodeId, list] of Object.entries(nodes)) {
|
|
2003
|
-
addFallbackNode(nodeId, list);
|
|
1976
|
+
}
|
|
1977
|
+
const selectedSet = new Set(selectedKeys);
|
|
1978
|
+
const selectedServiceRefs = baseRefs.filter((ref) => selectedSet.has(ref.key));
|
|
1979
|
+
if (baseRefs.length === 0) return;
|
|
1980
|
+
for (let i = 0; i < baseRefs.length; i++) {
|
|
1981
|
+
for (let j = i + 1; j < baseRefs.length; j++) {
|
|
1982
|
+
const left = baseRefs[i];
|
|
1983
|
+
const right = baseRefs[j];
|
|
1984
|
+
const hypotheticalKeys = [...selectedKeys, left.key, right.key];
|
|
1985
|
+
const survivingRefs = baseRefs.filter(
|
|
1986
|
+
(ref) => !isRefExcludedBySelectedKeys(
|
|
1987
|
+
{ fieldId: ref.fieldId, nodeId: ref.nodeId },
|
|
1988
|
+
hypotheticalKeys,
|
|
1989
|
+
effectMap
|
|
1990
|
+
)
|
|
1991
|
+
);
|
|
1992
|
+
const survivingSet = new Set(survivingRefs.map((ref) => ref.nodeId));
|
|
1993
|
+
if (!survivingSet.has(left.nodeId) || !survivingSet.has(right.nodeId)) {
|
|
1994
|
+
continue;
|
|
2004
1995
|
}
|
|
2005
|
-
|
|
2006
|
-
const
|
|
2007
|
-
|
|
1996
|
+
if (survivingRefs.length <= 1) continue;
|
|
1997
|
+
const survivingSelected = survivingRefs.filter(
|
|
1998
|
+
(ref) => selectedSet.has(ref.key)
|
|
2008
1999
|
);
|
|
2009
|
-
|
|
2010
|
-
|
|
2000
|
+
const tagIsCompeting = survivingSelected.length === 0;
|
|
2001
|
+
const primary = pickHighestRatePrimary(survivingRefs);
|
|
2002
|
+
if (!primary) continue;
|
|
2003
|
+
const comparePool = survivingRefs.filter((ref) => ref.nodeId !== primary.nodeId);
|
|
2004
|
+
for (const candidate of comparePool) {
|
|
2005
|
+
if (passesRatePolicy(ratePolicy, primary.rate, candidate.rate)) continue;
|
|
2006
|
+
const issueKey = [
|
|
2007
|
+
"rate-coherence-context",
|
|
2008
|
+
tagId,
|
|
2009
|
+
[...selectedKeys].sort().join("|"),
|
|
2010
|
+
[...survivingRefs.map((r) => r.nodeId).sort()].join("|"),
|
|
2011
|
+
primary.nodeId,
|
|
2012
|
+
candidate.nodeId,
|
|
2013
|
+
ratePolicy.kind,
|
|
2014
|
+
"pct" in ratePolicy ? String(ratePolicy.pct) : ""
|
|
2015
|
+
].join("::");
|
|
2016
|
+
if (seen.has(issueKey)) continue;
|
|
2017
|
+
seen.add(issueKey);
|
|
2018
|
+
v.errors.push({
|
|
2019
|
+
code: "rate_coherence_violation",
|
|
2020
|
+
severity: "error",
|
|
2021
|
+
nodeId: candidate.nodeId,
|
|
2022
|
+
message: "Visible service context contains incompatible base service rates.",
|
|
2023
|
+
details: {
|
|
2024
|
+
kind: "selected_context",
|
|
2025
|
+
tagId,
|
|
2026
|
+
selectedKeys: [...selectedKeys],
|
|
2027
|
+
visibleFieldIds: [...visibleFieldIds],
|
|
2028
|
+
primary: {
|
|
2029
|
+
nodeId: primary.nodeId,
|
|
2030
|
+
fieldId: primary.fieldId,
|
|
2031
|
+
service_id: primary.serviceId,
|
|
2032
|
+
serviceId: primary.serviceId,
|
|
2033
|
+
rate: primary.rate
|
|
2034
|
+
},
|
|
2035
|
+
candidate: {
|
|
2036
|
+
nodeId: candidate.nodeId,
|
|
2037
|
+
fieldId: candidate.fieldId,
|
|
2038
|
+
service_id: candidate.serviceId,
|
|
2039
|
+
serviceId: candidate.serviceId,
|
|
2040
|
+
rate: candidate.rate
|
|
2041
|
+
},
|
|
2042
|
+
policy: ratePolicy.kind,
|
|
2043
|
+
policyPct: "pct" in ratePolicy ? ratePolicy.pct : void 0,
|
|
2044
|
+
invalidFieldIds: visibleInvalidFieldIds,
|
|
2045
|
+
affectedIds: uniqueStrings2([
|
|
2046
|
+
tagId,
|
|
2047
|
+
...selectedKeys,
|
|
2048
|
+
primary.nodeId,
|
|
2049
|
+
primary.fieldId,
|
|
2050
|
+
candidate.nodeId,
|
|
2051
|
+
candidate.fieldId,
|
|
2052
|
+
tagIsCompeting ? tagDefault == null ? void 0 : tagDefault.nodeId : void 0
|
|
2053
|
+
]),
|
|
2054
|
+
affectedServiceIds: uniqueStrings2([
|
|
2055
|
+
String(primary.serviceId),
|
|
2056
|
+
String(candidate.serviceId)
|
|
2057
|
+
])
|
|
2058
|
+
}
|
|
2059
|
+
});
|
|
2011
2060
|
}
|
|
2012
2061
|
}
|
|
2013
2062
|
}
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
);
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2063
|
+
if (selectedServiceRefs.length === 0 && tagDefault && baseRefs.length > 0) {
|
|
2064
|
+
const survivingByDefault = baseRefs.filter(
|
|
2065
|
+
(ref) => !isRefExcludedBySelectedKeys(
|
|
2066
|
+
{ fieldId: ref.fieldId, nodeId: ref.nodeId },
|
|
2067
|
+
selectedKeys,
|
|
2068
|
+
effectMap
|
|
2069
|
+
)
|
|
2070
|
+
);
|
|
2071
|
+
for (const candidate of survivingByDefault) {
|
|
2072
|
+
if (passesRatePolicy(ratePolicy, tagDefault.rate, candidate.rate)) continue;
|
|
2073
|
+
const issueKey = [
|
|
2074
|
+
"rate-coherence-default",
|
|
2075
|
+
tagId,
|
|
2076
|
+
[...selectedKeys].sort().join("|"),
|
|
2077
|
+
tagDefault.nodeId,
|
|
2078
|
+
candidate.nodeId,
|
|
2079
|
+
ratePolicy.kind,
|
|
2080
|
+
"pct" in ratePolicy ? String(ratePolicy.pct) : ""
|
|
2081
|
+
].join("::");
|
|
2082
|
+
if (seen.has(issueKey)) continue;
|
|
2083
|
+
seen.add(issueKey);
|
|
2084
|
+
v.errors.push({
|
|
2085
|
+
code: "rate_coherence_violation",
|
|
2086
|
+
severity: "error",
|
|
2087
|
+
nodeId: candidate.nodeId,
|
|
2088
|
+
message: "Visible service context contains incompatible base service rates.",
|
|
2089
|
+
details: {
|
|
2090
|
+
kind: "selected_context",
|
|
2091
|
+
tagId,
|
|
2092
|
+
selectedKeys: [...selectedKeys],
|
|
2093
|
+
visibleFieldIds: [...visibleFieldIds],
|
|
2094
|
+
primary: {
|
|
2095
|
+
nodeId: tagDefault.nodeId,
|
|
2096
|
+
service_id: tagDefault.serviceId,
|
|
2097
|
+
serviceId: tagDefault.serviceId,
|
|
2098
|
+
rate: tagDefault.rate
|
|
2099
|
+
},
|
|
2100
|
+
candidate: {
|
|
2101
|
+
nodeId: candidate.nodeId,
|
|
2102
|
+
fieldId: candidate.fieldId,
|
|
2103
|
+
service_id: candidate.serviceId,
|
|
2104
|
+
serviceId: candidate.serviceId,
|
|
2105
|
+
rate: candidate.rate
|
|
2106
|
+
},
|
|
2107
|
+
policy: ratePolicy.kind,
|
|
2108
|
+
policyPct: "pct" in ratePolicy ? ratePolicy.pct : void 0,
|
|
2109
|
+
invalidFieldIds: visibleInvalidFieldIds,
|
|
2110
|
+
affectedIds: uniqueStrings2([
|
|
2111
|
+
tagId,
|
|
2112
|
+
...selectedKeys,
|
|
2113
|
+
tagDefault.nodeId,
|
|
2114
|
+
candidate.nodeId,
|
|
2115
|
+
candidate.fieldId
|
|
2116
|
+
]),
|
|
2117
|
+
affectedServiceIds: uniqueStrings2([
|
|
2118
|
+
String(tagDefault.serviceId),
|
|
2119
|
+
String(candidate.serviceId)
|
|
2120
|
+
])
|
|
2121
|
+
}
|
|
2122
|
+
});
|
|
2029
2123
|
}
|
|
2030
2124
|
}
|
|
2031
|
-
return Array.from(out.values());
|
|
2032
2125
|
}
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
);
|
|
2047
|
-
return set.size <= 1;
|
|
2048
|
-
}
|
|
2049
|
-
case "unique": {
|
|
2050
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2051
|
-
for (const v of values) {
|
|
2052
|
-
const k = JSON.stringify(v);
|
|
2053
|
-
if (seen.has(k)) return false;
|
|
2054
|
-
seen.add(k);
|
|
2055
|
-
}
|
|
2056
|
-
return true;
|
|
2057
|
-
}
|
|
2058
|
-
case "all_true": {
|
|
2059
|
-
return values.every((v) => v === true);
|
|
2060
|
-
}
|
|
2061
|
-
case "any_true": {
|
|
2062
|
-
return values.some((v) => v === true);
|
|
2063
|
-
}
|
|
2064
|
-
case "max_count": {
|
|
2065
|
-
const limit = typeof rule.value === "number" ? rule.value : Infinity;
|
|
2066
|
-
return values.length <= limit;
|
|
2067
|
-
}
|
|
2068
|
-
case "min_count": {
|
|
2069
|
-
const min = typeof rule.value === "number" ? rule.value : 0;
|
|
2070
|
-
return values.length >= min;
|
|
2071
|
-
}
|
|
2072
|
-
default:
|
|
2073
|
-
return true;
|
|
2126
|
+
function validateRateCoherence(v) {
|
|
2127
|
+
if (Object.keys(v.serviceMap).length === 0 || v.tags.length === 0) return;
|
|
2128
|
+
const effectMap = buildTriggerEffectMap(v.props);
|
|
2129
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2130
|
+
for (const context of v.simulatedVisibilityContexts) {
|
|
2131
|
+
validateRateCoherenceForVisibleContext({
|
|
2132
|
+
v,
|
|
2133
|
+
tagId: context.tagId,
|
|
2134
|
+
selectedKeys: context.selectedKeys,
|
|
2135
|
+
visibleFieldIds: context.visibleFieldIds,
|
|
2136
|
+
effectMap,
|
|
2137
|
+
seen
|
|
2138
|
+
});
|
|
2074
2139
|
}
|
|
2075
2140
|
}
|
|
2076
2141
|
|
|
2077
|
-
// src/core/validate/
|
|
2078
|
-
function
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
}
|
|
2093
|
-
|
|
2094
|
-
var _a;
|
|
2095
|
-
const ids = [];
|
|
2096
|
-
for (const it of items) {
|
|
2097
|
-
for (const x of (_a = it.affectedIds) != null ? _a : []) ids.push(x);
|
|
2098
|
-
ids.push(`service:${String(it.serviceId)}`);
|
|
2099
|
-
}
|
|
2100
|
-
return uniq(ids);
|
|
2101
|
-
}
|
|
2102
|
-
function visibleGroupNodeIds(tag, fields) {
|
|
2103
|
-
var _a;
|
|
2104
|
-
const ids = [tag.id];
|
|
2105
|
-
for (const f of fields) {
|
|
2106
|
-
for (const o of (_a = f.options) != null ? _a : []) {
|
|
2107
|
-
ids.push(o.id);
|
|
2142
|
+
// src/core/validate/steps/constraints.ts
|
|
2143
|
+
function constraintKeysInChain(v, tagId) {
|
|
2144
|
+
const keys = [];
|
|
2145
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
2146
|
+
let cur = tagId;
|
|
2147
|
+
const seenTags = /* @__PURE__ */ new Set();
|
|
2148
|
+
while (cur && !seenTags.has(cur)) {
|
|
2149
|
+
seenTags.add(cur);
|
|
2150
|
+
const t = v.tagById.get(cur);
|
|
2151
|
+
const c = t == null ? void 0 : t.constraints;
|
|
2152
|
+
if (c && typeof c === "object") {
|
|
2153
|
+
for (const k of Object.keys(c)) {
|
|
2154
|
+
if (!seenKeys.has(k)) {
|
|
2155
|
+
seenKeys.add(k);
|
|
2156
|
+
keys.push(k);
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2108
2159
|
}
|
|
2160
|
+
cur = t == null ? void 0 : t.bind_id;
|
|
2109
2161
|
}
|
|
2110
|
-
return
|
|
2162
|
+
return keys;
|
|
2111
2163
|
}
|
|
2112
|
-
function
|
|
2164
|
+
function effectiveConstraints(v, tagId) {
|
|
2113
2165
|
var _a;
|
|
2114
|
-
const
|
|
2115
|
-
const
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
if (typeof osid === "string" || typeof osid === "number" && Number.isFinite(osid)) {
|
|
2127
|
-
prim.push(osid);
|
|
2166
|
+
const out = {};
|
|
2167
|
+
const keys = constraintKeysInChain(v, tagId);
|
|
2168
|
+
for (const key of keys) {
|
|
2169
|
+
let cur = tagId;
|
|
2170
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2171
|
+
while (cur && !seen.has(cur)) {
|
|
2172
|
+
seen.add(cur);
|
|
2173
|
+
const t = v.tagById.get(cur);
|
|
2174
|
+
const val = (_a = t == null ? void 0 : t.constraints) == null ? void 0 : _a[key];
|
|
2175
|
+
if (val === true || val === false) {
|
|
2176
|
+
out[key] = val;
|
|
2177
|
+
break;
|
|
2128
2178
|
}
|
|
2179
|
+
cur = t == null ? void 0 : t.bind_id;
|
|
2129
2180
|
}
|
|
2130
2181
|
}
|
|
2131
|
-
return
|
|
2182
|
+
return out;
|
|
2132
2183
|
}
|
|
2133
|
-
function
|
|
2134
|
-
var _a, _b
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
const projPath = (_a = rule.projection) != null ? _a : "service.id";
|
|
2140
|
-
const severity = stableSeverity(
|
|
2141
|
-
rule.severity
|
|
2184
|
+
function validateConstraints(v) {
|
|
2185
|
+
var _a, _b;
|
|
2186
|
+
for (const t of v.tags) {
|
|
2187
|
+
const eff = effectiveConstraints(v, t.id);
|
|
2188
|
+
const hasAnyRequired = Object.values(eff).some(
|
|
2189
|
+
(x) => x === true
|
|
2142
2190
|
);
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
const
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
tag: t,
|
|
2168
|
-
tagId: t.id,
|
|
2169
|
-
fields: visibleFields,
|
|
2170
|
-
filter: rule.filter,
|
|
2171
|
-
visibleNodeIds: nodeIds,
|
|
2172
|
-
visiblePrimaries: primaries
|
|
2173
|
-
});
|
|
2174
|
-
for (const it of sub) {
|
|
2175
|
-
const k = `${String(it.serviceId)}|${it.role}`;
|
|
2176
|
-
const existing = merged.get(k);
|
|
2177
|
-
if (!existing) {
|
|
2178
|
-
merged.set(k, it);
|
|
2179
|
-
} else {
|
|
2180
|
-
merged.set(k, {
|
|
2181
|
-
...existing,
|
|
2182
|
-
affectedIds: uniq([
|
|
2183
|
-
...existing.affectedIds,
|
|
2184
|
-
...it.affectedIds
|
|
2185
|
-
])
|
|
2186
|
-
});
|
|
2187
|
-
}
|
|
2191
|
+
if (!hasAnyRequired) continue;
|
|
2192
|
+
const visible = v.fieldsVisibleUnder(t.id);
|
|
2193
|
+
for (const f of visible) {
|
|
2194
|
+
for (const o of (_a = f.options) != null ? _a : []) {
|
|
2195
|
+
if (!isServiceIdRef(o.service_id)) continue;
|
|
2196
|
+
const svc = getServiceCapability(v.serviceMap, o.service_id);
|
|
2197
|
+
if (!svc || typeof svc !== "object") continue;
|
|
2198
|
+
for (const [k, val] of Object.entries(eff)) {
|
|
2199
|
+
if (val === true && !isServiceFlagEnabled(svc, k)) {
|
|
2200
|
+
v.errors.push({
|
|
2201
|
+
code: "unsupported_constraint",
|
|
2202
|
+
severity: "error",
|
|
2203
|
+
message: `Service option "${o.id}" under tag "${t.id}" does not support required constraint "${k}".`,
|
|
2204
|
+
nodeId: t.id,
|
|
2205
|
+
details: withAffected(
|
|
2206
|
+
{
|
|
2207
|
+
flag: k,
|
|
2208
|
+
serviceId: o.service_id,
|
|
2209
|
+
fieldId: f.id,
|
|
2210
|
+
optionId: o.id
|
|
2211
|
+
},
|
|
2212
|
+
[t.id, f.id, o.id]
|
|
2213
|
+
)
|
|
2214
|
+
});
|
|
2188
2215
|
}
|
|
2189
2216
|
}
|
|
2190
|
-
items = Array.from(merged.values());
|
|
2191
|
-
} else {
|
|
2192
|
-
const allFields = (_e = props.fields) != null ? _e : [];
|
|
2193
|
-
items = collectServiceItems({
|
|
2194
|
-
mode: "global",
|
|
2195
|
-
props,
|
|
2196
|
-
serviceMap,
|
|
2197
|
-
tags,
|
|
2198
|
-
fields: allFields,
|
|
2199
|
-
filter: rule.filter
|
|
2200
|
-
});
|
|
2201
|
-
}
|
|
2202
|
-
const values = items.map(
|
|
2203
|
-
(it) => getByPath(it, projPath)
|
|
2204
|
-
);
|
|
2205
|
-
if (!evalPolicyOp(rule.op, values, rule)) {
|
|
2206
|
-
errors.push({
|
|
2207
|
-
code: "policy_violation",
|
|
2208
|
-
severity,
|
|
2209
|
-
message,
|
|
2210
|
-
nodeId: "global",
|
|
2211
|
-
details: {
|
|
2212
|
-
ruleId: rule.id,
|
|
2213
|
-
scope: "global",
|
|
2214
|
-
op: rule.op,
|
|
2215
|
-
projection: projPath,
|
|
2216
|
-
count: items.length,
|
|
2217
|
-
affectedIds: affectedFromItems(items)
|
|
2218
|
-
}
|
|
2219
|
-
});
|
|
2220
2217
|
}
|
|
2221
|
-
continue;
|
|
2222
2218
|
}
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
if (!items.length) continue;
|
|
2239
|
-
const values = items.map(
|
|
2240
|
-
(it) => getByPath(it, projPath)
|
|
2241
|
-
);
|
|
2242
|
-
if (!evalPolicyOp(rule.op, values, rule)) {
|
|
2243
|
-
errors.push({
|
|
2244
|
-
code: "policy_violation",
|
|
2245
|
-
severity,
|
|
2246
|
-
message,
|
|
2219
|
+
}
|
|
2220
|
+
for (const t of v.tags) {
|
|
2221
|
+
const sid = t.service_id;
|
|
2222
|
+
if (!isServiceIdRef(sid)) continue;
|
|
2223
|
+
const svc = getServiceCapability(v.serviceMap, sid);
|
|
2224
|
+
if (!svc || typeof svc !== "object") continue;
|
|
2225
|
+
const eff = effectiveConstraints(v, t.id);
|
|
2226
|
+
for (const [k, val] of Object.entries(eff)) {
|
|
2227
|
+
if (val === true && !isServiceFlagEnabled(svc, k)) {
|
|
2228
|
+
v.errors.push({
|
|
2229
|
+
code: "unsupported_constraint",
|
|
2230
|
+
severity: "error",
|
|
2231
|
+
message: `Tag "${t.id}" maps to service "${String(
|
|
2232
|
+
sid
|
|
2233
|
+
)}" which does not support required constraint "${k}".`,
|
|
2247
2234
|
nodeId: t.id,
|
|
2248
|
-
details: {
|
|
2249
|
-
ruleId: rule.id,
|
|
2250
|
-
scope: "visible_group",
|
|
2251
|
-
op: rule.op,
|
|
2252
|
-
projection: projPath,
|
|
2253
|
-
count: items.length,
|
|
2254
|
-
affectedIds: affectedFromItems(items)
|
|
2255
|
-
}
|
|
2235
|
+
details: { flag: k, serviceId: sid }
|
|
2256
2236
|
});
|
|
2257
2237
|
}
|
|
2258
2238
|
}
|
|
2259
2239
|
}
|
|
2240
|
+
for (const t of v.tags) {
|
|
2241
|
+
const ov = t.constraints_overrides;
|
|
2242
|
+
if (!ov || typeof ov !== "object") continue;
|
|
2243
|
+
for (const k of Object.keys(ov)) {
|
|
2244
|
+
const row = ov[k];
|
|
2245
|
+
if (!row) continue;
|
|
2246
|
+
const from = row.from === true;
|
|
2247
|
+
const to = row.to === true;
|
|
2248
|
+
const origin = String((_b = row.origin) != null ? _b : "");
|
|
2249
|
+
v.errors.push({
|
|
2250
|
+
code: "constraint_overridden",
|
|
2251
|
+
severity: "warning",
|
|
2252
|
+
message: origin ? `Constraint "${k}" on tag "${t.id}" was overridden by ancestor "${origin}" (${String(from)} \u2192 ${String(
|
|
2253
|
+
to
|
|
2254
|
+
)}).` : `Constraint "${k}" on tag "${t.id}" was overridden by an ancestor (${String(from)} \u2192 ${String(to)}).`,
|
|
2255
|
+
nodeId: t.id,
|
|
2256
|
+
details: withAffected(
|
|
2257
|
+
{ flag: k, from, to, origin },
|
|
2258
|
+
origin ? [t.id, origin] : void 0
|
|
2259
|
+
)
|
|
2260
|
+
});
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2260
2263
|
}
|
|
2261
2264
|
|
|
2262
|
-
// src/core/
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
return {
|
|
2275
|
-
...DEFAULT_FALLBACK_SETTINGS,
|
|
2276
|
-
...(_a = options.fallbackSettings) != null ? _a : {}
|
|
2277
|
-
};
|
|
2278
|
-
}
|
|
2279
|
-
function mergeValidatorOptions(defaults = {}, overrides = {}) {
|
|
2280
|
-
var _a, _b, _c, _d;
|
|
2281
|
-
const mergedFallbackSettings = {
|
|
2282
|
-
...(_a = defaults.fallbackSettings) != null ? _a : {},
|
|
2283
|
-
...(_b = overrides.fallbackSettings) != null ? _b : {}
|
|
2284
|
-
};
|
|
2285
|
-
return {
|
|
2286
|
-
...defaults,
|
|
2287
|
-
...overrides,
|
|
2288
|
-
policies: (_c = overrides.policies) != null ? _c : defaults.policies,
|
|
2289
|
-
ratePolicy: (_d = overrides.ratePolicy) != null ? _d : defaults.ratePolicy,
|
|
2290
|
-
fallbackSettings: Object.keys(mergedFallbackSettings).length > 0 ? mergedFallbackSettings : void 0
|
|
2291
|
-
};
|
|
2292
|
-
}
|
|
2293
|
-
|
|
2294
|
-
// src/core/builder.ts
|
|
2295
|
-
import { cloneDeep as cloneDeep2 } from "lodash-es";
|
|
2296
|
-
function createBuilder(opts = {}) {
|
|
2297
|
-
return new BuilderImpl(opts);
|
|
2298
|
-
}
|
|
2299
|
-
var BuilderImpl = class {
|
|
2300
|
-
constructor(opts = {}) {
|
|
2301
|
-
this.props = {
|
|
2302
|
-
filters: [],
|
|
2303
|
-
fields: [],
|
|
2304
|
-
schema_version: "1.0"
|
|
2305
|
-
};
|
|
2306
|
-
this.tagById = /* @__PURE__ */ new Map();
|
|
2307
|
-
this.fieldById = /* @__PURE__ */ new Map();
|
|
2308
|
-
this.optionOwnerById = /* @__PURE__ */ new Map();
|
|
2309
|
-
this._nodemap = null;
|
|
2310
|
-
this.options = { ...opts };
|
|
2311
|
-
}
|
|
2312
|
-
/* ───── lifecycle ─────────────────────────────────────────────────────── */
|
|
2313
|
-
isTagId(id) {
|
|
2314
|
-
return this.tagById.has(id);
|
|
2315
|
-
}
|
|
2316
|
-
isFieldId(id) {
|
|
2317
|
-
return this.fieldById.has(id);
|
|
2265
|
+
// src/core/validate/steps/custom.ts
|
|
2266
|
+
function validateCustomFields(v) {
|
|
2267
|
+
for (const f of v.fields) {
|
|
2268
|
+
if (f.type !== "custom") continue;
|
|
2269
|
+
if (!f.component || !String(f.component).trim()) {
|
|
2270
|
+
v.errors.push({
|
|
2271
|
+
code: "custom_component_missing",
|
|
2272
|
+
severity: "error",
|
|
2273
|
+
message: `Custom field "${f.id}" is missing a valid component reference.`,
|
|
2274
|
+
nodeId: f.id
|
|
2275
|
+
});
|
|
2276
|
+
}
|
|
2318
2277
|
}
|
|
2319
|
-
|
|
2320
|
-
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
// src/core/validate/steps/global-utility-guard.ts
|
|
2281
|
+
function validateGlobalUtilityGuard(v) {
|
|
2282
|
+
var _a, _b, _c;
|
|
2283
|
+
if (!v.options.globalUtilityGuard) return;
|
|
2284
|
+
let hasUtility = false;
|
|
2285
|
+
let hasBase = false;
|
|
2286
|
+
for (const f of v.fields) {
|
|
2287
|
+
for (const o of (_a = f.options) != null ? _a : []) {
|
|
2288
|
+
if (!isServiceIdRef(o.service_id)) continue;
|
|
2289
|
+
const role = (_c = (_b = o.pricing_role) != null ? _b : f.pricing_role) != null ? _c : "base";
|
|
2290
|
+
if (role === "base") hasBase = true;
|
|
2291
|
+
else if (role === "utility") hasUtility = true;
|
|
2292
|
+
if (hasUtility && hasBase) break;
|
|
2293
|
+
}
|
|
2294
|
+
if (hasUtility && hasBase) break;
|
|
2321
2295
|
}
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2296
|
+
if (hasUtility && !hasBase) {
|
|
2297
|
+
v.errors.push({
|
|
2298
|
+
code: "utility_without_base",
|
|
2299
|
+
severity: "warning",
|
|
2300
|
+
message: "Global utility guard: utility-priced options exist but no base-priced options were found.",
|
|
2301
|
+
nodeId: "global",
|
|
2302
|
+
details: { scope: "global" }
|
|
2326
2303
|
});
|
|
2327
|
-
this.props = next;
|
|
2328
|
-
this.rebuildIndexes();
|
|
2329
|
-
}
|
|
2330
|
-
getProps() {
|
|
2331
|
-
return this.props;
|
|
2332
2304
|
}
|
|
2333
|
-
|
|
2334
|
-
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
// src/core/validate/steps/unbound.ts
|
|
2308
|
+
function validateUnboundFields(v) {
|
|
2309
|
+
var _a, _b;
|
|
2310
|
+
const boundFieldIds = /* @__PURE__ */ new Set();
|
|
2311
|
+
for (const f of v.fields) {
|
|
2312
|
+
if (f.bind_id) boundFieldIds.add(f.id);
|
|
2335
2313
|
}
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2314
|
+
const includedByTag = /* @__PURE__ */ new Set();
|
|
2315
|
+
for (const t of v.tags) {
|
|
2316
|
+
for (const id of (_a = t.includes) != null ? _a : []) includedByTag.add(id);
|
|
2339
2317
|
}
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
const
|
|
2343
|
-
const out = /* @__PURE__ */ new Set();
|
|
2344
|
-
const guard = /* @__PURE__ */ new Set();
|
|
2345
|
-
for (const svc of Object.values(serviceMap)) {
|
|
2346
|
-
const flags = (_a = svc.flags) != null ? _a : {};
|
|
2347
|
-
for (const flagId of Object.keys(flags)) {
|
|
2348
|
-
if (guard.has(flagId)) continue;
|
|
2349
|
-
guard.add(flagId);
|
|
2350
|
-
out.add({
|
|
2351
|
-
id: flagId,
|
|
2352
|
-
value: flagId,
|
|
2353
|
-
label: flagId,
|
|
2354
|
-
description: flags[flagId].description
|
|
2355
|
-
});
|
|
2356
|
-
}
|
|
2357
|
-
}
|
|
2358
|
-
return Array.from(out);
|
|
2318
|
+
const includedByOption = /* @__PURE__ */ new Set();
|
|
2319
|
+
for (const arr of Object.values((_b = v.props.includes_for_buttons) != null ? _b : {})) {
|
|
2320
|
+
for (const id of arr != null ? arr : []) includedByOption.add(id);
|
|
2359
2321
|
}
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
}
|
|
2378
|
-
for (const f of this.props.fields) {
|
|
2379
|
-
nodes.push({
|
|
2380
|
-
id: f.id,
|
|
2381
|
-
kind: "field",
|
|
2382
|
-
label: f.label,
|
|
2383
|
-
bind_type: f.pricing_role === "utility" ? "utility" : f.bind_id ? "bound" : null
|
|
2322
|
+
for (const f of v.fields) {
|
|
2323
|
+
if (!boundFieldIds.has(f.id) && !includedByTag.has(f.id) && !includedByOption.has(f.id)) {
|
|
2324
|
+
v.errors.push({
|
|
2325
|
+
code: "field_unbound",
|
|
2326
|
+
severity: "error",
|
|
2327
|
+
message: `Field "${f.id}" is unbound: it is not bound to any tag and not included by tags or option maps.`,
|
|
2328
|
+
nodeId: f.id,
|
|
2329
|
+
details: withAffected(
|
|
2330
|
+
{
|
|
2331
|
+
fieldId: f.id,
|
|
2332
|
+
bound: false,
|
|
2333
|
+
// exposing these helps editors explain "why"
|
|
2334
|
+
includedByTag: includedByTag.has(f.id),
|
|
2335
|
+
includedByOption: includedByOption.has(f.id)
|
|
2336
|
+
},
|
|
2337
|
+
[f.id]
|
|
2338
|
+
)
|
|
2384
2339
|
});
|
|
2385
2340
|
}
|
|
2386
|
-
for (const f of this.props.fields) {
|
|
2387
|
-
const b = f.bind_id;
|
|
2388
|
-
if (Array.isArray(b)) {
|
|
2389
|
-
for (const tagId of b)
|
|
2390
|
-
edges.push({
|
|
2391
|
-
from: tagId,
|
|
2392
|
-
to: f.id,
|
|
2393
|
-
kind: "bind"
|
|
2394
|
-
});
|
|
2395
|
-
} else if (typeof b === "string") {
|
|
2396
|
-
edges.push({ from: b, to: f.id, kind: "bind" });
|
|
2397
|
-
}
|
|
2398
|
-
}
|
|
2399
|
-
for (const f of this.props.fields) {
|
|
2400
|
-
const showOptions = showSet.has(f.id);
|
|
2401
|
-
if (!showOptions) continue;
|
|
2402
|
-
if (!Array.isArray(f.options)) continue;
|
|
2403
|
-
for (const o of f.options) {
|
|
2404
|
-
nodes.push({
|
|
2405
|
-
id: o.id,
|
|
2406
|
-
kind: "option",
|
|
2407
|
-
label: o.label
|
|
2408
|
-
});
|
|
2409
|
-
const e = {
|
|
2410
|
-
from: f.id,
|
|
2411
|
-
to: o.id,
|
|
2412
|
-
kind: "option",
|
|
2413
|
-
meta: { ownerField: f.id }
|
|
2414
|
-
};
|
|
2415
|
-
edges.push(e);
|
|
2416
|
-
}
|
|
2417
|
-
}
|
|
2418
|
-
for (const t of this.props.filters) {
|
|
2419
|
-
for (const id of (_a = t.includes) != null ? _a : []) {
|
|
2420
|
-
edges.push({ from: t.id, to: id, kind: "include" });
|
|
2421
|
-
}
|
|
2422
|
-
for (const id of (_b = t.excludes) != null ? _b : []) {
|
|
2423
|
-
edges.push({ from: t.id, to: id, kind: "exclude" });
|
|
2424
|
-
}
|
|
2425
|
-
}
|
|
2426
|
-
const incMap = (_c = this.props.includes_for_buttons) != null ? _c : {};
|
|
2427
|
-
const excMap = (_d = this.props.excludes_for_buttons) != null ? _d : {};
|
|
2428
|
-
const pushButtonEdge = (keyId, targetFieldId, kind) => {
|
|
2429
|
-
var _a2;
|
|
2430
|
-
const owner = this.optionOwnerById.get(keyId);
|
|
2431
|
-
const ownerFieldId = (_a2 = owner == null ? void 0 : owner.fieldId) != null ? _a2 : this.fieldById.has(keyId) ? keyId : void 0;
|
|
2432
|
-
if (!ownerFieldId) return;
|
|
2433
|
-
const fromNode = owner && showSet.has(owner.fieldId) ? keyId : ownerFieldId;
|
|
2434
|
-
const meta = owner ? showSet.has(owner.fieldId) ? {
|
|
2435
|
-
via: "option-visible",
|
|
2436
|
-
ownerField: owner.fieldId,
|
|
2437
|
-
sourceOption: keyId
|
|
2438
|
-
} : {
|
|
2439
|
-
via: "option-hidden",
|
|
2440
|
-
ownerField: owner.fieldId,
|
|
2441
|
-
sourceOption: keyId
|
|
2442
|
-
} : { via: "field-button" };
|
|
2443
|
-
const e = { from: fromNode, to: targetFieldId, kind, meta };
|
|
2444
|
-
edges.push(e);
|
|
2445
|
-
};
|
|
2446
|
-
for (const [keyId, arr] of Object.entries(incMap)) {
|
|
2447
|
-
for (const fid of arr != null ? arr : [])
|
|
2448
|
-
pushButtonEdge(keyId, fid, "include");
|
|
2449
|
-
}
|
|
2450
|
-
for (const [keyId, arr] of Object.entries(excMap)) {
|
|
2451
|
-
for (const fid of arr != null ? arr : [])
|
|
2452
|
-
pushButtonEdge(keyId, fid, "exclude");
|
|
2453
|
-
}
|
|
2454
|
-
return { nodes, edges };
|
|
2455
2341
|
}
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
referencedKeys.add(key);
|
|
2474
|
-
const owner = this.optionOwnerById.get(key);
|
|
2475
|
-
if (owner) referencedOwnerFields.add(owner.fieldId);
|
|
2476
|
-
for (const fid of arr != null ? arr : []) {
|
|
2477
|
-
includedByButtons.add(fid);
|
|
2478
|
-
}
|
|
2479
|
-
}
|
|
2480
|
-
for (const [key, arr] of Object.entries(excMap)) {
|
|
2481
|
-
referencedKeys.add(key);
|
|
2482
|
-
const owner = this.optionOwnerById.get(key);
|
|
2483
|
-
if (owner) referencedOwnerFields.add(owner.fieldId);
|
|
2484
|
-
for (const fid of arr != null ? arr : []) {
|
|
2485
|
-
void fid;
|
|
2486
|
-
}
|
|
2487
|
-
}
|
|
2488
|
-
const boundIds = /* @__PURE__ */ new Set();
|
|
2489
|
-
for (const f of this.props.fields) {
|
|
2490
|
-
const b = f.bind_id;
|
|
2491
|
-
if (Array.isArray(b)) b.forEach((id) => boundIds.add(id));
|
|
2492
|
-
else if (typeof b === "string") boundIds.add(b);
|
|
2493
|
-
}
|
|
2494
|
-
const fields = this.props.fields.filter((f) => {
|
|
2495
|
-
var _a2;
|
|
2496
|
-
const isUtility = ((_a2 = f.pricing_role) != null ? _a2 : "base") === "utility";
|
|
2497
|
-
if (!isUtility) return true;
|
|
2498
|
-
const bound = !!f.bind_id;
|
|
2499
|
-
const included = includedByTag.has(f.id) || includedByButtons.has(f.id);
|
|
2500
|
-
const referenced = referencedOwnerFields.has(f.id) || referencedKeys.has(f.id);
|
|
2501
|
-
const excluded = excludedAnywhere.has(f.id);
|
|
2502
|
-
return bound || included || referenced || !excluded;
|
|
2503
|
-
});
|
|
2504
|
-
const allowedTargets = new Set(fields.map((f) => f.id));
|
|
2505
|
-
const pruneButtons = (src) => {
|
|
2506
|
-
if (!src) return void 0;
|
|
2507
|
-
const out2 = {};
|
|
2508
|
-
for (const [key, arr] of Object.entries(src)) {
|
|
2509
|
-
const keyIsValid = optionIds.has(key) || fieldIds.has(key);
|
|
2510
|
-
if (!keyIsValid) continue;
|
|
2511
|
-
const cleaned = (arr != null ? arr : []).filter(
|
|
2512
|
-
(fid) => allowedTargets.has(fid)
|
|
2513
|
-
);
|
|
2514
|
-
if (cleaned.length) out2[key] = Array.from(new Set(cleaned));
|
|
2515
|
-
}
|
|
2516
|
-
return Object.keys(out2).length ? out2 : void 0;
|
|
2517
|
-
};
|
|
2518
|
-
const includes_for_buttons = pruneButtons(
|
|
2519
|
-
this.props.includes_for_buttons
|
|
2520
|
-
);
|
|
2521
|
-
const excludes_for_buttons = pruneButtons(
|
|
2522
|
-
this.props.excludes_for_buttons
|
|
2523
|
-
);
|
|
2524
|
-
const out = {
|
|
2525
|
-
filters: this.props.filters.slice(),
|
|
2526
|
-
fields,
|
|
2527
|
-
...this.props.orderKinds ? { orderKinds: this.props.orderKinds } : {},
|
|
2528
|
-
...includes_for_buttons && { includes_for_buttons },
|
|
2529
|
-
...excludes_for_buttons && { excludes_for_buttons },
|
|
2530
|
-
schema_version: (_e = this.props.schema_version) != null ? _e : "1.0",
|
|
2531
|
-
// keep fallbacks & other maps as-is
|
|
2532
|
-
...this.props.fallbacks ? { fallbacks: this.props.fallbacks } : {}
|
|
2533
|
-
};
|
|
2534
|
-
return out;
|
|
2535
|
-
}
|
|
2536
|
-
errors() {
|
|
2537
|
-
return validate(this.props, mergeValidatorOptions({}, this.options));
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
// src/core/validate/steps/fallbacks.ts
|
|
2345
|
+
function codeForReason(reason) {
|
|
2346
|
+
switch (reason) {
|
|
2347
|
+
case "unknown_service":
|
|
2348
|
+
return "fallback_unknown_service";
|
|
2349
|
+
case "no_primary":
|
|
2350
|
+
return "fallback_no_primary";
|
|
2351
|
+
case "rate_violation":
|
|
2352
|
+
return "fallback_rate_violation";
|
|
2353
|
+
case "constraint_mismatch":
|
|
2354
|
+
return "fallback_constraint_mismatch";
|
|
2355
|
+
case "cycle":
|
|
2356
|
+
return "fallback_cycle";
|
|
2357
|
+
default:
|
|
2358
|
+
return "fallback_bad_node";
|
|
2538
2359
|
}
|
|
2539
|
-
|
|
2540
|
-
|
|
2360
|
+
}
|
|
2361
|
+
function messageFor(code, d) {
|
|
2362
|
+
const n = d.nodeId ? `node "${String(d.nodeId)}"` : "node";
|
|
2363
|
+
switch (code) {
|
|
2364
|
+
case "fallback_unknown_service":
|
|
2365
|
+
return `Fallback candidate "${String(
|
|
2366
|
+
d.candidate
|
|
2367
|
+
)}" is unknown for ${n}.`;
|
|
2368
|
+
case "fallback_no_primary":
|
|
2369
|
+
return `Fallback rule has no primary service for ${n}.`;
|
|
2370
|
+
case "fallback_rate_violation":
|
|
2371
|
+
return `Fallback candidate "${String(
|
|
2372
|
+
d.candidate
|
|
2373
|
+
)}" violates the base-rate rules for ${n}.`;
|
|
2374
|
+
case "fallback_constraint_mismatch":
|
|
2375
|
+
return `Fallback candidate "${String(
|
|
2376
|
+
d.candidate
|
|
2377
|
+
)}" does not satisfy required constraints for ${n}.`;
|
|
2378
|
+
case "fallback_cycle":
|
|
2379
|
+
return `Fallback rules contain a cycle for ${n}.`;
|
|
2380
|
+
default:
|
|
2381
|
+
return `Fallback rule is invalid for ${n}.`;
|
|
2541
2382
|
}
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2383
|
+
}
|
|
2384
|
+
function validateFallbacks(v) {
|
|
2385
|
+
var _a, _b, _c, _d;
|
|
2386
|
+
const mode = (_b = (_a = v.options.fallbackSettings) == null ? void 0 : _a.mode) != null ? _b : "strict";
|
|
2387
|
+
if (!v.props.fallbacks) return;
|
|
2388
|
+
const diags = collectFailedFallbacks(v.props, (_c = v.options.serviceMap) != null ? _c : {}, {
|
|
2389
|
+
...v.options.fallbackSettings,
|
|
2390
|
+
mode: "dev"
|
|
2391
|
+
});
|
|
2392
|
+
if (mode !== "strict") return;
|
|
2393
|
+
for (const d of diags) {
|
|
2394
|
+
if (d.scope === "global") continue;
|
|
2395
|
+
const code = codeForReason(
|
|
2396
|
+
String((_d = d.reason) != null ? _d : "fallback_bad_node")
|
|
2397
|
+
);
|
|
2398
|
+
const nodeId = d.nodeId ? String(d.nodeId) : void 0;
|
|
2399
|
+
const tagContext = d.tagContext;
|
|
2400
|
+
const affectedIds = [];
|
|
2401
|
+
if (nodeId) affectedIds.push(nodeId);
|
|
2402
|
+
if (typeof tagContext === "string" && tagContext && tagContext !== nodeId)
|
|
2403
|
+
affectedIds.push(tagContext);
|
|
2404
|
+
v.errors.push({
|
|
2405
|
+
code,
|
|
2406
|
+
severity: "error",
|
|
2407
|
+
message: messageFor(code, {
|
|
2408
|
+
nodeId,
|
|
2409
|
+
primary: d.primary,
|
|
2410
|
+
candidate: d.candidate,
|
|
2411
|
+
tagContext,
|
|
2412
|
+
scope: d.scope
|
|
2413
|
+
}),
|
|
2414
|
+
nodeId,
|
|
2415
|
+
details: withAffected(
|
|
2416
|
+
{
|
|
2417
|
+
primary: d.primary,
|
|
2418
|
+
candidate: d.candidate,
|
|
2419
|
+
tagContext,
|
|
2420
|
+
scope: d.scope
|
|
2421
|
+
},
|
|
2422
|
+
affectedIds.length > 1 ? affectedIds : void 0
|
|
2547
2423
|
)
|
|
2548
2424
|
});
|
|
2549
2425
|
}
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
// src/core/validate/policies/collect-service-items.ts
|
|
2429
|
+
function asArray(v) {
|
|
2430
|
+
if (v === void 0) return void 0;
|
|
2431
|
+
return Array.isArray(v) ? v : [v];
|
|
2432
|
+
}
|
|
2433
|
+
function isServiceIdRef2(v) {
|
|
2434
|
+
return typeof v === "string" || typeof v === "number" && Number.isFinite(v);
|
|
2435
|
+
}
|
|
2436
|
+
function svcSnapshot(serviceMap, sid) {
|
|
2437
|
+
const svc = serviceMap[sid];
|
|
2438
|
+
if (!svc) return { id: sid };
|
|
2439
|
+
const meta = svc.meta && typeof svc.meta === "object" ? svc.meta : {};
|
|
2440
|
+
return {
|
|
2441
|
+
...svc,
|
|
2442
|
+
id: sid,
|
|
2443
|
+
...meta
|
|
2444
|
+
};
|
|
2445
|
+
}
|
|
2446
|
+
function pushItem(out, next) {
|
|
2447
|
+
var _a;
|
|
2448
|
+
const key = `${String(next.serviceId)}|${next.role}`;
|
|
2449
|
+
const existing = out.get(key);
|
|
2450
|
+
if (!existing) {
|
|
2451
|
+
out.set(key, {
|
|
2452
|
+
tagId: next.tagId,
|
|
2453
|
+
fieldId: next.fieldId,
|
|
2454
|
+
optionId: next.optionId,
|
|
2455
|
+
nodeId: next.nodeId,
|
|
2456
|
+
serviceId: next.serviceId,
|
|
2457
|
+
role: next.role,
|
|
2458
|
+
service: next.service,
|
|
2459
|
+
affectedIds: Array.from(new Set(next.affectedIds))
|
|
2460
|
+
});
|
|
2461
|
+
return;
|
|
2553
2462
|
}
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2463
|
+
const mergedIds = Array.from(
|
|
2464
|
+
/* @__PURE__ */ new Set([...existing.affectedIds, ...next.affectedIds])
|
|
2465
|
+
);
|
|
2466
|
+
out.set(key, {
|
|
2467
|
+
...existing,
|
|
2468
|
+
tagId: (_a = existing.tagId) != null ? _a : next.tagId,
|
|
2469
|
+
affectedIds: mergedIds
|
|
2470
|
+
});
|
|
2471
|
+
}
|
|
2472
|
+
function fieldRoleOf(f, o) {
|
|
2473
|
+
var _a, _b;
|
|
2474
|
+
const roleRaw = (_b = (_a = o == null ? void 0 : o.pricing_role) != null ? _a : f.pricing_role) != null ? _b : "base";
|
|
2475
|
+
return roleRaw === "utility" ? "utility" : "base";
|
|
2476
|
+
}
|
|
2477
|
+
function applyFilterAllowLists(tagId, fieldId, filter) {
|
|
2478
|
+
const tagAllow = asArray(filter == null ? void 0 : filter.tag_id);
|
|
2479
|
+
const fieldAllow = asArray(filter == null ? void 0 : filter.field_id);
|
|
2480
|
+
if (tagAllow) {
|
|
2481
|
+
if (!tagId) return false;
|
|
2482
|
+
if (!tagAllow.includes(tagId)) return false;
|
|
2568
2483
|
}
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
return
|
|
2484
|
+
if (fieldAllow) {
|
|
2485
|
+
if (!fieldId) return false;
|
|
2486
|
+
if (!fieldAllow.includes(fieldId)) return false;
|
|
2487
|
+
}
|
|
2488
|
+
return true;
|
|
2574
2489
|
}
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
const
|
|
2580
|
-
const
|
|
2581
|
-
const
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
diagnostics.push({
|
|
2601
|
-
kind: "internal_field",
|
|
2602
|
-
scope: "visible_group",
|
|
2603
|
-
tagId,
|
|
2604
|
-
fieldId,
|
|
2605
|
-
nodeId: fieldId,
|
|
2606
|
-
message: `Field "${fieldId}" is internally invalid under rate policy "${ratePolicy.kind}".`,
|
|
2607
|
-
simulationAnchor: {
|
|
2608
|
-
kind: anchor.kind,
|
|
2609
|
-
id: anchor.id,
|
|
2610
|
-
fieldId: anchor.fieldId,
|
|
2611
|
-
label: anchor.label
|
|
2612
|
-
},
|
|
2613
|
-
invalidFieldIds: [fieldId]
|
|
2490
|
+
function collectServiceItems(args) {
|
|
2491
|
+
var _a, _b, _c, _d, _e;
|
|
2492
|
+
const filter = args.filter;
|
|
2493
|
+
const roleFilter = (_a = filter == null ? void 0 : filter.role) != null ? _a : "both";
|
|
2494
|
+
const where = filter == null ? void 0 : filter.where;
|
|
2495
|
+
const out = /* @__PURE__ */ new Map();
|
|
2496
|
+
const addServiceRef = (ref) => {
|
|
2497
|
+
if (roleFilter !== "both" && ref.role !== roleFilter) return;
|
|
2498
|
+
if (!applyFilterAllowLists(ref.tagId, ref.fieldId, filter)) return;
|
|
2499
|
+
const svc = args.serviceMap[ref.serviceId];
|
|
2500
|
+
if (where && svc && !matchesWhere(svc, where)) return;
|
|
2501
|
+
pushItem(out, {
|
|
2502
|
+
...ref,
|
|
2503
|
+
service: svcSnapshot(args.serviceMap, ref.serviceId)
|
|
2504
|
+
});
|
|
2505
|
+
};
|
|
2506
|
+
if (args.mode === "global") {
|
|
2507
|
+
for (const t of (_b = args.tags) != null ? _b : []) {
|
|
2508
|
+
const sid = t.service_id;
|
|
2509
|
+
if (!isServiceIdRef2(sid)) continue;
|
|
2510
|
+
addServiceRef({
|
|
2511
|
+
tagId: t.id,
|
|
2512
|
+
serviceId: sid,
|
|
2513
|
+
role: "base",
|
|
2514
|
+
affectedIds: [`tag:${t.id}`, `service:${String(sid)}`]
|
|
2614
2515
|
});
|
|
2615
2516
|
}
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
if (
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2517
|
+
} else if (args.mode === "visible_group") {
|
|
2518
|
+
const t = args.tag;
|
|
2519
|
+
const sid = t ? t.service_id : void 0;
|
|
2520
|
+
if (t && isServiceIdRef2(sid)) {
|
|
2521
|
+
addServiceRef({
|
|
2522
|
+
tagId: t.id,
|
|
2523
|
+
serviceId: sid,
|
|
2524
|
+
role: "base",
|
|
2525
|
+
affectedIds: [`tag:${t.id}`, `service:${String(sid)}`]
|
|
2526
|
+
});
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
const fields = (_c = args.fields) != null ? _c : [];
|
|
2530
|
+
for (const f of fields) {
|
|
2531
|
+
const fSid = f.service_id;
|
|
2532
|
+
if (isServiceIdRef2(fSid)) {
|
|
2533
|
+
addServiceRef({
|
|
2534
|
+
tagId: args.tagId,
|
|
2535
|
+
fieldId: f.id,
|
|
2536
|
+
serviceId: fSid,
|
|
2537
|
+
role: "base",
|
|
2538
|
+
affectedIds: [`field:${f.id}`, `service:${String(fSid)}`]
|
|
2539
|
+
});
|
|
2540
|
+
}
|
|
2541
|
+
for (const o of (_d = f.options) != null ? _d : []) {
|
|
2542
|
+
const oSid = o.service_id;
|
|
2543
|
+
if (!isServiceIdRef2(oSid)) continue;
|
|
2544
|
+
const role = fieldRoleOf(f, o);
|
|
2545
|
+
addServiceRef({
|
|
2546
|
+
tagId: args.tagId,
|
|
2547
|
+
fieldId: f.id,
|
|
2548
|
+
optionId: o.id,
|
|
2549
|
+
serviceId: oSid,
|
|
2550
|
+
role,
|
|
2551
|
+
affectedIds: [
|
|
2552
|
+
`field:${f.id}`,
|
|
2553
|
+
`option:${o.id}`,
|
|
2554
|
+
`service:${String(oSid)}`
|
|
2555
|
+
]
|
|
2556
|
+
});
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
const fb = args.props.fallbacks;
|
|
2560
|
+
if (!fb) return Array.from(out.values());
|
|
2561
|
+
const addFallbackNode = (nodeId, list) => {
|
|
2562
|
+
const arr = Array.isArray(list) ? list : [];
|
|
2563
|
+
for (const cand of arr) {
|
|
2564
|
+
if (!isServiceIdRef2(cand)) continue;
|
|
2565
|
+
addServiceRef({
|
|
2566
|
+
tagId: args.tagId,
|
|
2567
|
+
nodeId,
|
|
2568
|
+
serviceId: cand,
|
|
2569
|
+
role: "base",
|
|
2570
|
+
affectedIds: [`fallback-node:${nodeId}`, `service:${String(cand)}`]
|
|
2571
|
+
});
|
|
2572
|
+
}
|
|
2573
|
+
};
|
|
2574
|
+
const addFallbackGlobal = (primaryKey, list) => {
|
|
2575
|
+
const primaryId = primaryKey;
|
|
2576
|
+
addServiceRef({
|
|
2577
|
+
tagId: args.tagId,
|
|
2578
|
+
nodeId: primaryKey,
|
|
2579
|
+
serviceId: primaryId,
|
|
2580
|
+
role: "base",
|
|
2581
|
+
affectedIds: [
|
|
2582
|
+
`fallback-global-primary:${primaryKey}`,
|
|
2583
|
+
`service:${String(primaryId)}`
|
|
2584
|
+
]
|
|
2585
|
+
});
|
|
2586
|
+
const arr = Array.isArray(list) ? list : [];
|
|
2587
|
+
for (const cand of arr) {
|
|
2588
|
+
if (!isServiceIdRef2(cand)) continue;
|
|
2589
|
+
addServiceRef({
|
|
2590
|
+
tagId: args.tagId,
|
|
2591
|
+
nodeId: primaryKey,
|
|
2592
|
+
serviceId: cand,
|
|
2593
|
+
role: "base",
|
|
2594
|
+
affectedIds: [
|
|
2595
|
+
`fallback-global:${primaryKey}`,
|
|
2596
|
+
`service:${String(cand)}`
|
|
2597
|
+
]
|
|
2598
|
+
});
|
|
2599
|
+
}
|
|
2600
|
+
};
|
|
2601
|
+
const includeAllFallbacks = args.mode === "global";
|
|
2602
|
+
const includeGroupFallbacks = args.mode === "visible_group";
|
|
2603
|
+
const nodes = fb.nodes && typeof fb.nodes === "object" ? fb.nodes : void 0;
|
|
2604
|
+
if (nodes) {
|
|
2605
|
+
if (includeAllFallbacks) {
|
|
2606
|
+
for (const [nodeId, list] of Object.entries(nodes)) {
|
|
2607
|
+
addFallbackNode(nodeId, list);
|
|
2608
|
+
}
|
|
2609
|
+
} else if (includeGroupFallbacks) {
|
|
2610
|
+
const allowNodes = new Set(
|
|
2611
|
+
Array.isArray(args.visibleNodeIds) ? args.visibleNodeIds : []
|
|
2612
|
+
);
|
|
2613
|
+
for (const nodeId of allowNodes) {
|
|
2614
|
+
addFallbackNode(nodeId, nodes[nodeId]);
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
const globalFb = fb.global && typeof fb.global === "object" ? fb.global : void 0;
|
|
2619
|
+
if (globalFb) {
|
|
2620
|
+
if (includeAllFallbacks) {
|
|
2621
|
+
for (const [primaryKey, list] of Object.entries(globalFb)) {
|
|
2622
|
+
addFallbackGlobal(primaryKey, list);
|
|
2623
2623
|
}
|
|
2624
|
-
|
|
2625
|
-
const
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
continue;
|
|
2624
|
+
} else if (includeGroupFallbacks) {
|
|
2625
|
+
const allowPrimaries = new Set(
|
|
2626
|
+
((_e = args.visiblePrimaries) != null ? _e : []).map((x) => String(x))
|
|
2627
|
+
);
|
|
2628
|
+
for (const primaryKey of allowPrimaries) {
|
|
2629
|
+
const list = globalFb[primaryKey];
|
|
2630
|
+
if (list === void 0) continue;
|
|
2631
|
+
addFallbackGlobal(primaryKey, list);
|
|
2633
2632
|
}
|
|
2634
|
-
const key = contextualKey(tagId, primary, candidate, ratePolicy);
|
|
2635
|
-
if (seen.has(key)) continue;
|
|
2636
|
-
seen.add(key);
|
|
2637
|
-
diagnostics.push({
|
|
2638
|
-
kind: "contextual",
|
|
2639
|
-
scope: "visible_group",
|
|
2640
|
-
tagId,
|
|
2641
|
-
nodeId: candidate.nodeId,
|
|
2642
|
-
primary: toDiagnosticRef(primary),
|
|
2643
|
-
offender: toDiagnosticRef(candidate),
|
|
2644
|
-
policy: ratePolicy.kind,
|
|
2645
|
-
policyPct: "pct" in ratePolicy ? ratePolicy.pct : void 0,
|
|
2646
|
-
message: explainRateMismatch(
|
|
2647
|
-
ratePolicy,
|
|
2648
|
-
primary,
|
|
2649
|
-
candidate,
|
|
2650
|
-
describeLabel(tag)
|
|
2651
|
-
),
|
|
2652
|
-
simulationAnchor: {
|
|
2653
|
-
kind: anchor.kind,
|
|
2654
|
-
id: anchor.id,
|
|
2655
|
-
fieldId: anchor.fieldId,
|
|
2656
|
-
label: anchor.label
|
|
2657
|
-
},
|
|
2658
|
-
invalidFieldIds: visibleInvalidFieldIds
|
|
2659
|
-
});
|
|
2660
2633
|
}
|
|
2661
2634
|
}
|
|
2662
|
-
return
|
|
2635
|
+
return Array.from(out.values());
|
|
2663
2636
|
}
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2637
|
+
|
|
2638
|
+
// src/core/validate/policies/ops.ts
|
|
2639
|
+
function evalPolicyOp(op, values, rule) {
|
|
2640
|
+
switch (op) {
|
|
2641
|
+
case "all_equal": {
|
|
2642
|
+
const set = new Set(
|
|
2643
|
+
values.map((v) => JSON.stringify(v))
|
|
2644
|
+
);
|
|
2645
|
+
return set.size <= 1;
|
|
2646
|
+
}
|
|
2647
|
+
case "no_mix": {
|
|
2648
|
+
const set = new Set(
|
|
2649
|
+
values.map((v) => JSON.stringify(v))
|
|
2650
|
+
);
|
|
2651
|
+
return set.size <= 1;
|
|
2652
|
+
}
|
|
2653
|
+
case "unique": {
|
|
2654
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2655
|
+
for (const v of values) {
|
|
2656
|
+
const k = JSON.stringify(v);
|
|
2657
|
+
if (seen.has(k)) return false;
|
|
2658
|
+
seen.add(k);
|
|
2677
2659
|
}
|
|
2678
|
-
|
|
2660
|
+
return true;
|
|
2679
2661
|
}
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
}
|
|
2662
|
+
case "all_true": {
|
|
2663
|
+
return values.every((v) => v === true);
|
|
2664
|
+
}
|
|
2665
|
+
case "any_true": {
|
|
2666
|
+
return values.some((v) => v === true);
|
|
2667
|
+
}
|
|
2668
|
+
case "max_count": {
|
|
2669
|
+
const limit = typeof rule.value === "number" ? rule.value : Infinity;
|
|
2670
|
+
return values.length <= limit;
|
|
2671
|
+
}
|
|
2672
|
+
case "min_count": {
|
|
2673
|
+
const min = typeof rule.value === "number" ? rule.value : 0;
|
|
2674
|
+
return values.length >= min;
|
|
2675
|
+
}
|
|
2676
|
+
default:
|
|
2677
|
+
return true;
|
|
2686
2678
|
}
|
|
2687
|
-
return anchors;
|
|
2688
2679
|
}
|
|
2689
|
-
|
|
2680
|
+
|
|
2681
|
+
// src/core/validate/policies/apply-policies.ts
|
|
2682
|
+
function uniq(arr) {
|
|
2683
|
+
return Array.from(new Set(arr));
|
|
2684
|
+
}
|
|
2685
|
+
function stableSeverity(s) {
|
|
2686
|
+
if (s === "warning") return "warning";
|
|
2687
|
+
if (s === "error") return "error";
|
|
2688
|
+
return "error";
|
|
2689
|
+
}
|
|
2690
|
+
function defaultPolicyMessage(rule) {
|
|
2691
|
+
if (typeof rule.message === "string" && rule.message.trim())
|
|
2692
|
+
return rule.message;
|
|
2693
|
+
if (typeof rule.label === "string" && rule.label.trim())
|
|
2694
|
+
return rule.label.trim();
|
|
2695
|
+
return `Policy "${rule.id}" violated`;
|
|
2696
|
+
}
|
|
2697
|
+
function affectedFromItems(items) {
|
|
2690
2698
|
var _a;
|
|
2691
|
-
const
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2699
|
+
const ids = [];
|
|
2700
|
+
for (const it of items) {
|
|
2701
|
+
for (const x of (_a = it.affectedIds) != null ? _a : []) ids.push(x);
|
|
2702
|
+
ids.push(`service:${String(it.serviceId)}`);
|
|
2703
|
+
}
|
|
2704
|
+
return uniq(ids);
|
|
2705
|
+
}
|
|
2706
|
+
function visibleGroupNodeIds(tag, fields) {
|
|
2707
|
+
var _a;
|
|
2708
|
+
const ids = [tag.id];
|
|
2709
|
+
for (const f of fields) {
|
|
2710
|
+
for (const o of (_a = f.options) != null ? _a : []) {
|
|
2711
|
+
ids.push(o.id);
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
return uniq(ids);
|
|
2715
|
+
}
|
|
2716
|
+
function visibleGroupPrimaries(tag, fields) {
|
|
2717
|
+
var _a;
|
|
2718
|
+
const prim = [];
|
|
2719
|
+
const tagSid = tag.service_id;
|
|
2720
|
+
if (typeof tagSid === "string" || typeof tagSid === "number" && Number.isFinite(tagSid)) {
|
|
2721
|
+
prim.push(tagSid);
|
|
2722
|
+
}
|
|
2723
|
+
for (const f of fields) {
|
|
2724
|
+
const fsid = f.service_id;
|
|
2725
|
+
if (typeof fsid === "string" || typeof fsid === "number" && Number.isFinite(fsid)) {
|
|
2726
|
+
prim.push(fsid);
|
|
2727
|
+
}
|
|
2728
|
+
for (const o of (_a = f.options) != null ? _a : []) {
|
|
2729
|
+
const osid = o.service_id;
|
|
2730
|
+
if (typeof osid === "string" || typeof osid === "number" && Number.isFinite(osid)) {
|
|
2731
|
+
prim.push(osid);
|
|
2703
2732
|
}
|
|
2704
|
-
|
|
2733
|
+
}
|
|
2705
2734
|
}
|
|
2706
|
-
return
|
|
2707
|
-
refKind: "single",
|
|
2708
|
-
nodeId: member.id,
|
|
2709
|
-
fieldId: field.id,
|
|
2710
|
-
label: member.label,
|
|
2711
|
-
rate: member.rate,
|
|
2712
|
-
service_id: member.service_id,
|
|
2713
|
-
members: [member]
|
|
2714
|
-
}));
|
|
2735
|
+
return uniq(prim);
|
|
2715
2736
|
}
|
|
2716
|
-
function
|
|
2717
|
-
var _a, _b, _c;
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2737
|
+
function applyPolicies(errors, props, serviceMap, policies, fieldsVisibleUnder, tags) {
|
|
2738
|
+
var _a, _b, _c, _d, _e;
|
|
2739
|
+
if (!(policies == null ? void 0 : policies.length)) return;
|
|
2740
|
+
const tagById = /* @__PURE__ */ new Map();
|
|
2741
|
+
for (const t of tags) tagById.set(t.id, t);
|
|
2742
|
+
for (const rule of policies) {
|
|
2743
|
+
const projPath = (_a = rule.projection) != null ? _a : "service.id";
|
|
2744
|
+
const severity = stableSeverity(
|
|
2745
|
+
rule.severity
|
|
2746
|
+
);
|
|
2747
|
+
const message = defaultPolicyMessage(rule);
|
|
2748
|
+
if (rule.scope === "global") {
|
|
2749
|
+
const tagAllow = Array.isArray(
|
|
2750
|
+
(_b = rule.filter) == null ? void 0 : _b.tag_id
|
|
2751
|
+
) ? (_c = rule.filter) == null ? void 0 : _c.tag_id : ((_d = rule.filter) == null ? void 0 : _d.tag_id) ? [rule.filter.tag_id] : void 0;
|
|
2752
|
+
let items = [];
|
|
2753
|
+
if (tagAllow && tagAllow.length) {
|
|
2754
|
+
const merged = /* @__PURE__ */ new Map();
|
|
2755
|
+
for (const id of tagAllow) {
|
|
2756
|
+
const t = tagById.get(id);
|
|
2757
|
+
if (!t) continue;
|
|
2758
|
+
const visibleFields = fieldsVisibleUnder(t.id);
|
|
2759
|
+
const nodeIds = visibleGroupNodeIds(
|
|
2760
|
+
t,
|
|
2761
|
+
visibleFields
|
|
2762
|
+
);
|
|
2763
|
+
const primaries = visibleGroupPrimaries(
|
|
2764
|
+
t,
|
|
2765
|
+
visibleFields
|
|
2766
|
+
);
|
|
2767
|
+
const sub = collectServiceItems({
|
|
2768
|
+
mode: "visible_group",
|
|
2769
|
+
props,
|
|
2770
|
+
serviceMap,
|
|
2771
|
+
tag: t,
|
|
2772
|
+
tagId: t.id,
|
|
2773
|
+
fields: visibleFields,
|
|
2774
|
+
filter: rule.filter,
|
|
2775
|
+
visibleNodeIds: nodeIds,
|
|
2776
|
+
visiblePrimaries: primaries
|
|
2777
|
+
});
|
|
2778
|
+
for (const it of sub) {
|
|
2779
|
+
const k = `${String(it.serviceId)}|${it.role}`;
|
|
2780
|
+
const existing = merged.get(k);
|
|
2781
|
+
if (!existing) {
|
|
2782
|
+
merged.set(k, it);
|
|
2783
|
+
} else {
|
|
2784
|
+
merged.set(k, {
|
|
2785
|
+
...existing,
|
|
2786
|
+
affectedIds: uniq([
|
|
2787
|
+
...existing.affectedIds,
|
|
2788
|
+
...it.affectedIds
|
|
2789
|
+
])
|
|
2790
|
+
});
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
items = Array.from(merged.values());
|
|
2795
|
+
} else {
|
|
2796
|
+
const allFields = (_e = props.fields) != null ? _e : [];
|
|
2797
|
+
items = collectServiceItems({
|
|
2798
|
+
mode: "global",
|
|
2799
|
+
props,
|
|
2800
|
+
serviceMap,
|
|
2801
|
+
tags,
|
|
2802
|
+
fields: allFields,
|
|
2803
|
+
filter: rule.filter
|
|
2804
|
+
});
|
|
2725
2805
|
}
|
|
2726
|
-
const
|
|
2727
|
-
|
|
2728
|
-
|
|
2806
|
+
const values = items.map(
|
|
2807
|
+
(it) => getByPath(it, projPath)
|
|
2808
|
+
);
|
|
2809
|
+
if (!evalPolicyOp(rule.op, values, rule)) {
|
|
2810
|
+
errors.push({
|
|
2811
|
+
code: "policy_violation",
|
|
2812
|
+
severity,
|
|
2813
|
+
message,
|
|
2814
|
+
nodeId: "global",
|
|
2815
|
+
details: {
|
|
2816
|
+
ruleId: rule.id,
|
|
2817
|
+
scope: "global",
|
|
2818
|
+
op: rule.op,
|
|
2819
|
+
projection: projPath,
|
|
2820
|
+
count: items.length,
|
|
2821
|
+
affectedIds: affectedFromItems(items)
|
|
2822
|
+
}
|
|
2823
|
+
});
|
|
2729
2824
|
}
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2825
|
+
continue;
|
|
2826
|
+
}
|
|
2827
|
+
for (const t of tags) {
|
|
2828
|
+
const visibleFields = fieldsVisibleUnder(t.id);
|
|
2829
|
+
const nodeIds = visibleGroupNodeIds(t, visibleFields);
|
|
2830
|
+
const primaries = visibleGroupPrimaries(t, visibleFields);
|
|
2831
|
+
const items = collectServiceItems({
|
|
2832
|
+
mode: "visible_group",
|
|
2833
|
+
props,
|
|
2834
|
+
serviceMap,
|
|
2835
|
+
tag: t,
|
|
2836
|
+
tagId: t.id,
|
|
2837
|
+
fields: visibleFields,
|
|
2838
|
+
filter: rule.filter,
|
|
2839
|
+
visibleNodeIds: nodeIds,
|
|
2840
|
+
visiblePrimaries: primaries
|
|
2737
2841
|
});
|
|
2842
|
+
if (!items.length) continue;
|
|
2843
|
+
const values = items.map(
|
|
2844
|
+
(it) => getByPath(it, projPath)
|
|
2845
|
+
);
|
|
2846
|
+
if (!evalPolicyOp(rule.op, values, rule)) {
|
|
2847
|
+
errors.push({
|
|
2848
|
+
code: "policy_violation",
|
|
2849
|
+
severity,
|
|
2850
|
+
message,
|
|
2851
|
+
nodeId: t.id,
|
|
2852
|
+
details: {
|
|
2853
|
+
ruleId: rule.id,
|
|
2854
|
+
scope: "visible_group",
|
|
2855
|
+
op: rule.op,
|
|
2856
|
+
projection: projPath,
|
|
2857
|
+
count: items.length,
|
|
2858
|
+
affectedIds: affectedFromItems(items)
|
|
2859
|
+
}
|
|
2860
|
+
});
|
|
2861
|
+
}
|
|
2738
2862
|
}
|
|
2739
|
-
return members;
|
|
2740
2863
|
}
|
|
2741
|
-
const role = normalizeRole(field.pricing_role, "base");
|
|
2742
|
-
if (role !== "base") return members;
|
|
2743
|
-
if (field.service_id === void 0 || field.service_id === null) return members;
|
|
2744
|
-
const cap = getServiceCapability(services, field.service_id);
|
|
2745
|
-
if (!cap || typeof cap.rate !== "number" || !Number.isFinite(cap.rate)) {
|
|
2746
|
-
return members;
|
|
2747
|
-
}
|
|
2748
|
-
members.push({
|
|
2749
|
-
kind: "field",
|
|
2750
|
-
id: field.id,
|
|
2751
|
-
fieldId: field.id,
|
|
2752
|
-
label: (_c = field.label) != null ? _c : field.id,
|
|
2753
|
-
service_id: field.service_id,
|
|
2754
|
-
rate: cap.rate
|
|
2755
|
-
});
|
|
2756
|
-
return members;
|
|
2757
2864
|
}
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2865
|
+
|
|
2866
|
+
// src/core/governance.ts
|
|
2867
|
+
var DEFAULT_FALLBACK_SETTINGS = {
|
|
2868
|
+
requireConstraintFit: true,
|
|
2869
|
+
ratePolicy: { kind: "lte_primary", pct: 5 },
|
|
2870
|
+
selectionStrategy: "priority",
|
|
2871
|
+
mode: "strict"
|
|
2872
|
+
};
|
|
2873
|
+
function resolveGlobalRatePolicy(options) {
|
|
2874
|
+
return normalizeRatePolicy(options.ratePolicy);
|
|
2764
2875
|
}
|
|
2765
|
-
function
|
|
2876
|
+
function resolveFallbackSettings(options) {
|
|
2877
|
+
var _a;
|
|
2766
2878
|
return {
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
label: reference.label,
|
|
2770
|
-
refKind: reference.refKind,
|
|
2771
|
-
service_id: reference.service_id,
|
|
2772
|
-
rate: reference.rate
|
|
2879
|
+
...DEFAULT_FALLBACK_SETTINGS,
|
|
2880
|
+
...(_a = options.fallbackSettings) != null ? _a : {}
|
|
2773
2881
|
};
|
|
2774
2882
|
}
|
|
2775
|
-
function
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
var _a, _b;
|
|
2789
|
-
return (_b = (_a = tag == null ? void 0 : tag.label) != null ? _a : tag == null ? void 0 : tag.id) != null ? _b : "tag";
|
|
2790
|
-
}
|
|
2791
|
-
function explainRateMismatch(policy, primary, candidate, where) {
|
|
2792
|
-
var _a, _b;
|
|
2793
|
-
const primaryLabel = `${(_a = primary.label) != null ? _a : primary.nodeId} (${primary.rate})`;
|
|
2794
|
-
const candidateLabel = `${(_b = candidate.label) != null ? _b : candidate.nodeId} (${candidate.rate})`;
|
|
2795
|
-
switch (policy.kind) {
|
|
2796
|
-
case "eq_primary":
|
|
2797
|
-
return `Rate coherence failed (${where}): ${candidateLabel} must exactly match ${primaryLabel}.`;
|
|
2798
|
-
case "lte_primary":
|
|
2799
|
-
return `Rate coherence failed (${where}): ${candidateLabel} must stay within ${policy.pct}% below and never above ${primaryLabel}.`;
|
|
2800
|
-
case "within_pct":
|
|
2801
|
-
return `Rate coherence failed (${where}): ${candidateLabel} must be within ${policy.pct}% of ${primaryLabel}.`;
|
|
2802
|
-
case "at_least_pct_lower":
|
|
2803
|
-
return `Rate coherence failed (${where}): ${candidateLabel} must be at least ${policy.pct}% lower than ${primaryLabel}.`;
|
|
2804
|
-
}
|
|
2883
|
+
function mergeValidatorOptions(defaults = {}, overrides = {}) {
|
|
2884
|
+
var _a, _b, _c, _d;
|
|
2885
|
+
const mergedFallbackSettings = {
|
|
2886
|
+
...(_a = defaults.fallbackSettings) != null ? _a : {},
|
|
2887
|
+
...(_b = overrides.fallbackSettings) != null ? _b : {}
|
|
2888
|
+
};
|
|
2889
|
+
return {
|
|
2890
|
+
...defaults,
|
|
2891
|
+
...overrides,
|
|
2892
|
+
policies: (_c = overrides.policies) != null ? _c : defaults.policies,
|
|
2893
|
+
ratePolicy: (_d = overrides.ratePolicy) != null ? _d : defaults.ratePolicy,
|
|
2894
|
+
fallbackSettings: Object.keys(mergedFallbackSettings).length > 0 ? mergedFallbackSettings : void 0
|
|
2895
|
+
};
|
|
2805
2896
|
}
|
|
2806
2897
|
|
|
2807
2898
|
// src/core/validate/index.ts
|
|
@@ -2852,7 +2943,8 @@ function validate(props, ctx = {}) {
|
|
|
2852
2943
|
invalidRateFieldIds: /* @__PURE__ */ new Set(),
|
|
2853
2944
|
tagById,
|
|
2854
2945
|
fieldById,
|
|
2855
|
-
fieldsVisibleUnder: (_tagId) => []
|
|
2946
|
+
fieldsVisibleUnder: (_tagId) => [],
|
|
2947
|
+
simulatedVisibilityContexts: []
|
|
2856
2948
|
};
|
|
2857
2949
|
validateStructure(v);
|
|
2858
2950
|
validateIdentity(v);
|
|
@@ -2872,54 +2964,306 @@ function validate(props, ctx = {}) {
|
|
|
2872
2964
|
validateServiceVsUserInput(v);
|
|
2873
2965
|
validateUtilityMarkers(v);
|
|
2874
2966
|
validateRates(v);
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2967
|
+
validateRateCoherence(v);
|
|
2968
|
+
validateConstraints(v);
|
|
2969
|
+
validateCustomFields(v);
|
|
2970
|
+
validateGlobalUtilityGuard(v);
|
|
2971
|
+
validateUnboundFields(v);
|
|
2972
|
+
validateFallbacks(v);
|
|
2973
|
+
return v.errors;
|
|
2974
|
+
}
|
|
2975
|
+
async function validateAsync(props, ctx = {}) {
|
|
2976
|
+
await Promise.resolve();
|
|
2977
|
+
if (typeof requestAnimationFrame === "function") {
|
|
2978
|
+
await new Promise(
|
|
2979
|
+
(resolve) => requestAnimationFrame(() => resolve())
|
|
2980
|
+
);
|
|
2981
|
+
} else {
|
|
2982
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
2983
|
+
}
|
|
2984
|
+
return validate(props, ctx);
|
|
2985
|
+
}
|
|
2986
|
+
|
|
2987
|
+
// src/core/builder.ts
|
|
2988
|
+
import { cloneDeep as cloneDeep2 } from "lodash-es";
|
|
2989
|
+
function createBuilder(opts = {}) {
|
|
2990
|
+
return new BuilderImpl(opts);
|
|
2991
|
+
}
|
|
2992
|
+
var BuilderImpl = class {
|
|
2993
|
+
constructor(opts = {}) {
|
|
2994
|
+
this.props = {
|
|
2995
|
+
filters: [],
|
|
2996
|
+
fields: [],
|
|
2997
|
+
schema_version: "1.0"
|
|
2998
|
+
};
|
|
2999
|
+
this.tagById = /* @__PURE__ */ new Map();
|
|
3000
|
+
this.fieldById = /* @__PURE__ */ new Map();
|
|
3001
|
+
this.optionOwnerById = /* @__PURE__ */ new Map();
|
|
3002
|
+
this._nodemap = null;
|
|
3003
|
+
this.options = { ...opts };
|
|
3004
|
+
}
|
|
3005
|
+
/* ───── lifecycle ─────────────────────────────────────────────────────── */
|
|
3006
|
+
isTagId(id) {
|
|
3007
|
+
return this.tagById.has(id);
|
|
3008
|
+
}
|
|
3009
|
+
isFieldId(id) {
|
|
3010
|
+
return this.fieldById.has(id);
|
|
3011
|
+
}
|
|
3012
|
+
isOptionId(id) {
|
|
3013
|
+
return this.optionOwnerById.has(id);
|
|
3014
|
+
}
|
|
3015
|
+
load(raw) {
|
|
3016
|
+
const next = normalise(raw, {
|
|
3017
|
+
defaultPricingRole: "base",
|
|
3018
|
+
constraints: this.getConstraints().map((item) => item.label)
|
|
3019
|
+
});
|
|
3020
|
+
this.props = next;
|
|
3021
|
+
this.rebuildIndexes();
|
|
3022
|
+
}
|
|
3023
|
+
getProps() {
|
|
3024
|
+
return this.props;
|
|
3025
|
+
}
|
|
3026
|
+
setOptions(patch) {
|
|
3027
|
+
this.options = { ...this.options, ...patch };
|
|
3028
|
+
}
|
|
3029
|
+
getServiceMap() {
|
|
3030
|
+
var _a;
|
|
3031
|
+
return (_a = this.options.serviceMap) != null ? _a : {};
|
|
3032
|
+
}
|
|
3033
|
+
getConstraints() {
|
|
3034
|
+
var _a;
|
|
3035
|
+
const serviceMap = this.getServiceMap();
|
|
3036
|
+
const out = /* @__PURE__ */ new Set();
|
|
3037
|
+
const guard = /* @__PURE__ */ new Set();
|
|
3038
|
+
for (const svc of Object.values(serviceMap)) {
|
|
3039
|
+
const flags = (_a = svc.flags) != null ? _a : {};
|
|
3040
|
+
for (const flagId of Object.keys(flags)) {
|
|
3041
|
+
if (guard.has(flagId)) continue;
|
|
3042
|
+
guard.add(flagId);
|
|
3043
|
+
out.add({
|
|
3044
|
+
id: flagId,
|
|
3045
|
+
value: flagId,
|
|
3046
|
+
label: flagId,
|
|
3047
|
+
description: flags[flagId].description
|
|
3048
|
+
});
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
return Array.from(out);
|
|
3052
|
+
}
|
|
3053
|
+
/* ───── querying ─────────────────────────────────────────────────────── */
|
|
3054
|
+
tree() {
|
|
3055
|
+
var _a, _b, _c, _d;
|
|
3056
|
+
const nodes = [];
|
|
3057
|
+
const edges = [];
|
|
3058
|
+
const showSet = toStringSet(this.options.showOptionNodes);
|
|
3059
|
+
for (const t of this.props.filters) {
|
|
3060
|
+
nodes.push({ id: t.id, kind: "tag", label: t.label });
|
|
3061
|
+
}
|
|
3062
|
+
for (const t of this.props.filters) {
|
|
3063
|
+
if (t.bind_id) {
|
|
3064
|
+
edges.push({
|
|
3065
|
+
from: t.bind_id,
|
|
3066
|
+
to: t.id,
|
|
3067
|
+
kind: "child"
|
|
3068
|
+
});
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
for (const f of this.props.fields) {
|
|
3072
|
+
nodes.push({
|
|
3073
|
+
id: f.id,
|
|
3074
|
+
kind: "field",
|
|
3075
|
+
label: f.label,
|
|
3076
|
+
bind_type: f.pricing_role === "utility" ? "utility" : f.bind_id ? "bound" : null
|
|
2885
3077
|
});
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
3078
|
+
}
|
|
3079
|
+
for (const f of this.props.fields) {
|
|
3080
|
+
const b = f.bind_id;
|
|
3081
|
+
if (Array.isArray(b)) {
|
|
3082
|
+
for (const tagId of b)
|
|
3083
|
+
edges.push({
|
|
3084
|
+
from: tagId,
|
|
3085
|
+
to: f.id,
|
|
3086
|
+
kind: "bind"
|
|
3087
|
+
});
|
|
3088
|
+
} else if (typeof b === "string") {
|
|
3089
|
+
edges.push({ from: b, to: f.id, kind: "bind" });
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
for (const f of this.props.fields) {
|
|
3093
|
+
const showOptions = showSet.has(f.id);
|
|
3094
|
+
if (!showOptions) continue;
|
|
3095
|
+
if (!Array.isArray(f.options)) continue;
|
|
3096
|
+
for (const o of f.options) {
|
|
3097
|
+
nodes.push({
|
|
3098
|
+
id: o.id,
|
|
3099
|
+
kind: "option",
|
|
3100
|
+
label: o.label
|
|
2902
3101
|
});
|
|
3102
|
+
const e = {
|
|
3103
|
+
from: f.id,
|
|
3104
|
+
to: o.id,
|
|
3105
|
+
kind: "option",
|
|
3106
|
+
meta: { ownerField: f.id }
|
|
3107
|
+
};
|
|
3108
|
+
edges.push(e);
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
for (const t of this.props.filters) {
|
|
3112
|
+
for (const id of (_a = t.includes) != null ? _a : []) {
|
|
3113
|
+
edges.push({ from: t.id, to: id, kind: "include" });
|
|
2903
3114
|
}
|
|
3115
|
+
for (const id of (_b = t.excludes) != null ? _b : []) {
|
|
3116
|
+
edges.push({ from: t.id, to: id, kind: "exclude" });
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
const incMap = (_c = this.props.includes_for_buttons) != null ? _c : {};
|
|
3120
|
+
const excMap = (_d = this.props.excludes_for_buttons) != null ? _d : {};
|
|
3121
|
+
const pushButtonEdge = (keyId, targetFieldId, kind) => {
|
|
3122
|
+
var _a2;
|
|
3123
|
+
const owner = this.optionOwnerById.get(keyId);
|
|
3124
|
+
const ownerFieldId = (_a2 = owner == null ? void 0 : owner.fieldId) != null ? _a2 : this.fieldById.has(keyId) ? keyId : void 0;
|
|
3125
|
+
if (!ownerFieldId) return;
|
|
3126
|
+
const fromNode = owner && showSet.has(owner.fieldId) ? keyId : ownerFieldId;
|
|
3127
|
+
const meta = owner ? showSet.has(owner.fieldId) ? {
|
|
3128
|
+
via: "option-visible",
|
|
3129
|
+
ownerField: owner.fieldId,
|
|
3130
|
+
sourceOption: keyId
|
|
3131
|
+
} : {
|
|
3132
|
+
via: "option-hidden",
|
|
3133
|
+
ownerField: owner.fieldId,
|
|
3134
|
+
sourceOption: keyId
|
|
3135
|
+
} : { via: "field-button" };
|
|
3136
|
+
const e = { from: fromNode, to: targetFieldId, kind, meta };
|
|
3137
|
+
edges.push(e);
|
|
3138
|
+
};
|
|
3139
|
+
for (const [keyId, arr] of Object.entries(incMap)) {
|
|
3140
|
+
for (const fid of arr != null ? arr : [])
|
|
3141
|
+
pushButtonEdge(keyId, fid, "include");
|
|
3142
|
+
}
|
|
3143
|
+
for (const [keyId, arr] of Object.entries(excMap)) {
|
|
3144
|
+
for (const fid of arr != null ? arr : [])
|
|
3145
|
+
pushButtonEdge(keyId, fid, "exclude");
|
|
2904
3146
|
}
|
|
3147
|
+
return { nodes, edges };
|
|
2905
3148
|
}
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
3149
|
+
cleanedProps() {
|
|
3150
|
+
var _a, _b, _c, _d, _e;
|
|
3151
|
+
const fieldIds = new Set(this.props.fields.map((f) => f.id));
|
|
3152
|
+
const optionIds = /* @__PURE__ */ new Set();
|
|
3153
|
+
this.optionOwnerById.forEach((_v, oid) => optionIds.add(oid));
|
|
3154
|
+
const includedByTag = /* @__PURE__ */ new Set();
|
|
3155
|
+
const excludedAnywhere = /* @__PURE__ */ new Set();
|
|
3156
|
+
for (const t of this.props.filters) {
|
|
3157
|
+
for (const id of (_a = t.includes) != null ? _a : []) includedByTag.add(id);
|
|
3158
|
+
for (const id of (_b = t.excludes) != null ? _b : []) excludedAnywhere.add(id);
|
|
3159
|
+
}
|
|
3160
|
+
const incMap = (_c = this.props.includes_for_buttons) != null ? _c : {};
|
|
3161
|
+
const excMap = (_d = this.props.excludes_for_buttons) != null ? _d : {};
|
|
3162
|
+
const includedByButtons = /* @__PURE__ */ new Set();
|
|
3163
|
+
const referencedKeys = /* @__PURE__ */ new Set();
|
|
3164
|
+
const referencedOwnerFields = /* @__PURE__ */ new Set();
|
|
3165
|
+
for (const [key, arr] of Object.entries(incMap)) {
|
|
3166
|
+
referencedKeys.add(key);
|
|
3167
|
+
const owner = this.optionOwnerById.get(key);
|
|
3168
|
+
if (owner) referencedOwnerFields.add(owner.fieldId);
|
|
3169
|
+
for (const fid of arr != null ? arr : []) {
|
|
3170
|
+
includedByButtons.add(fid);
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
for (const [key, arr] of Object.entries(excMap)) {
|
|
3174
|
+
referencedKeys.add(key);
|
|
3175
|
+
const owner = this.optionOwnerById.get(key);
|
|
3176
|
+
if (owner) referencedOwnerFields.add(owner.fieldId);
|
|
3177
|
+
for (const fid of arr != null ? arr : []) {
|
|
3178
|
+
void fid;
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
const boundIds = /* @__PURE__ */ new Set();
|
|
3182
|
+
for (const f of this.props.fields) {
|
|
3183
|
+
const b = f.bind_id;
|
|
3184
|
+
if (Array.isArray(b)) b.forEach((id) => boundIds.add(id));
|
|
3185
|
+
else if (typeof b === "string") boundIds.add(b);
|
|
3186
|
+
}
|
|
3187
|
+
const fields = this.props.fields.filter((f) => {
|
|
3188
|
+
var _a2;
|
|
3189
|
+
const isUtility = ((_a2 = f.pricing_role) != null ? _a2 : "base") === "utility";
|
|
3190
|
+
if (!isUtility) return true;
|
|
3191
|
+
const bound = !!f.bind_id;
|
|
3192
|
+
const included = includedByTag.has(f.id) || includedByButtons.has(f.id);
|
|
3193
|
+
const referenced = referencedOwnerFields.has(f.id) || referencedKeys.has(f.id);
|
|
3194
|
+
const excluded = excludedAnywhere.has(f.id);
|
|
3195
|
+
return bound || included || referenced || !excluded;
|
|
3196
|
+
});
|
|
3197
|
+
const allowedTargets = new Set(fields.map((f) => f.id));
|
|
3198
|
+
const pruneButtons = (src) => {
|
|
3199
|
+
if (!src) return void 0;
|
|
3200
|
+
const out2 = {};
|
|
3201
|
+
for (const [key, arr] of Object.entries(src)) {
|
|
3202
|
+
const keyIsValid = optionIds.has(key) || fieldIds.has(key);
|
|
3203
|
+
if (!keyIsValid) continue;
|
|
3204
|
+
const cleaned = (arr != null ? arr : []).filter(
|
|
3205
|
+
(fid) => allowedTargets.has(fid)
|
|
3206
|
+
);
|
|
3207
|
+
if (cleaned.length) out2[key] = Array.from(new Set(cleaned));
|
|
3208
|
+
}
|
|
3209
|
+
return Object.keys(out2).length ? out2 : void 0;
|
|
3210
|
+
};
|
|
3211
|
+
const includes_for_buttons = pruneButtons(
|
|
3212
|
+
this.props.includes_for_buttons
|
|
2918
3213
|
);
|
|
2919
|
-
|
|
2920
|
-
|
|
3214
|
+
const excludes_for_buttons = pruneButtons(
|
|
3215
|
+
this.props.excludes_for_buttons
|
|
3216
|
+
);
|
|
3217
|
+
const out = {
|
|
3218
|
+
filters: this.props.filters.slice(),
|
|
3219
|
+
fields,
|
|
3220
|
+
...this.props.orderKinds ? { orderKinds: this.props.orderKinds } : {},
|
|
3221
|
+
...includes_for_buttons && { includes_for_buttons },
|
|
3222
|
+
...excludes_for_buttons && { excludes_for_buttons },
|
|
3223
|
+
schema_version: (_e = this.props.schema_version) != null ? _e : "1.0",
|
|
3224
|
+
// keep fallbacks & other maps as-is
|
|
3225
|
+
...this.props.fallbacks ? { fallbacks: this.props.fallbacks } : {}
|
|
3226
|
+
};
|
|
3227
|
+
return out;
|
|
2921
3228
|
}
|
|
2922
|
-
|
|
3229
|
+
errors() {
|
|
3230
|
+
return validate(this.props, mergeValidatorOptions({}, this.options));
|
|
3231
|
+
}
|
|
3232
|
+
getOptions() {
|
|
3233
|
+
return cloneDeep2(this.options);
|
|
3234
|
+
}
|
|
3235
|
+
visibleFields(tagId, selectedKeys) {
|
|
3236
|
+
var _a;
|
|
3237
|
+
return visibleFieldIdsUnder(this.props, tagId, {
|
|
3238
|
+
selectedKeys: new Set(
|
|
3239
|
+
(_a = selectedKeys != null ? selectedKeys : this.options.selectedOptionKeys) != null ? _a : []
|
|
3240
|
+
)
|
|
3241
|
+
});
|
|
3242
|
+
}
|
|
3243
|
+
getNodeMap() {
|
|
3244
|
+
if (!this._nodemap) this._nodemap = buildNodeMap(this.getProps());
|
|
3245
|
+
return this._nodemap;
|
|
3246
|
+
}
|
|
3247
|
+
/* ───── internals ──────────────────────────────────────────────────── */
|
|
3248
|
+
rebuildIndexes() {
|
|
3249
|
+
this.tagById.clear();
|
|
3250
|
+
this.fieldById.clear();
|
|
3251
|
+
this.optionOwnerById.clear();
|
|
3252
|
+
this._nodemap = null;
|
|
3253
|
+
for (const t of this.props.filters) this.tagById.set(t.id, t);
|
|
3254
|
+
for (const f of this.props.fields) {
|
|
3255
|
+
this.fieldById.set(f.id, f);
|
|
3256
|
+
if (Array.isArray(f.options)) {
|
|
3257
|
+
for (const o of f.options)
|
|
3258
|
+
this.optionOwnerById.set(o.id, { fieldId: f.id });
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
};
|
|
3263
|
+
function toStringSet(v) {
|
|
3264
|
+
if (!v) return /* @__PURE__ */ new Set();
|
|
3265
|
+
if (v instanceof Set) return new Set(Array.from(v).map(String));
|
|
3266
|
+
return new Set(v.map(String));
|
|
2923
3267
|
}
|
|
2924
3268
|
|
|
2925
3269
|
// src/core/fallback.ts
|
|
@@ -3917,23 +4261,20 @@ function compilePolicies(raw) {
|
|
|
3917
4261
|
|
|
3918
4262
|
// src/core/service-filter.ts
|
|
3919
4263
|
function filterServicesForVisibleGroup(input, deps) {
|
|
3920
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
4264
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
3921
4265
|
const svcMap = (_c = (_b = (_a = deps.builder).getServiceMap) == null ? void 0 : _b.call(_a)) != null ? _c : {};
|
|
3922
4266
|
const builderOptions = (_e = (_d = deps.builder).getOptions) == null ? void 0 : _e.call(_d);
|
|
3923
4267
|
const { context } = input;
|
|
3924
4268
|
const usedSet = new Set(context.usedServiceIds.map(String));
|
|
3925
|
-
const primary = context.usedServiceIds[0];
|
|
3926
4269
|
const explicitFallbackSettings = (_f = context.fallbackSettings) != null ? _f : context.fallback;
|
|
3927
4270
|
const resolvedRatePolicy = normalizeRatePolicy(
|
|
3928
4271
|
(_h = (_g = context.ratePolicy) != null ? _g : explicitFallbackSettings == null ? void 0 : explicitFallbackSettings.ratePolicy) != null ? _h : builderOptions == null ? void 0 : builderOptions.ratePolicy
|
|
3929
4272
|
);
|
|
3930
|
-
const fallbackSettingsSource = explicitFallbackSettings != null ? explicitFallbackSettings : builderOptions == null ? void 0 : builderOptions.fallbackSettings;
|
|
3931
|
-
const fb = {
|
|
3932
|
-
...DEFAULT_FALLBACK_SETTINGS,
|
|
3933
|
-
...fallbackSettingsSource != null ? fallbackSettingsSource : {},
|
|
3934
|
-
ratePolicy: resolvedRatePolicy
|
|
3935
|
-
};
|
|
3936
4273
|
const policySource = (_j = (_i = context.policies) != null ? _i : builderOptions == null ? void 0 : builderOptions.policies) != null ? _j : [];
|
|
4274
|
+
const resolvedCustomPrimaryRate = resolveCustomPrimaryRate(
|
|
4275
|
+
context.rateContext,
|
|
4276
|
+
svcMap
|
|
4277
|
+
);
|
|
3937
4278
|
const visibleServiceIds = context.selectedButtons === void 0 ? void 0 : collectVisibleServiceIds(
|
|
3938
4279
|
deps.builder,
|
|
3939
4280
|
context.tagId,
|
|
@@ -3960,7 +4301,19 @@ function filterServicesForVisibleGroup(input, deps) {
|
|
|
3960
4301
|
cap.id,
|
|
3961
4302
|
(_k = context.effectiveConstraints) != null ? _k : {}
|
|
3962
4303
|
);
|
|
3963
|
-
const passesRate2 =
|
|
4304
|
+
const passesRate2 = resolvedCustomPrimaryRate != null ? passesRatePolicy(
|
|
4305
|
+
resolvedRatePolicy,
|
|
4306
|
+
resolvedCustomPrimaryRate,
|
|
4307
|
+
toFiniteNumber(cap.rate)
|
|
4308
|
+
) : candidatePassesRateCoherence(
|
|
4309
|
+
deps.builder,
|
|
4310
|
+
svcMap,
|
|
4311
|
+
context.tagId,
|
|
4312
|
+
(_l = context.selectedButtons) != null ? _l : [],
|
|
4313
|
+
context.usedServiceIds,
|
|
4314
|
+
id,
|
|
4315
|
+
resolvedRatePolicy
|
|
4316
|
+
);
|
|
3964
4317
|
const polRes = evaluatePoliciesRaw(
|
|
3965
4318
|
policySource,
|
|
3966
4319
|
[...context.usedServiceIds, id],
|
|
@@ -3992,6 +4345,17 @@ function filterServicesForVisibleGroup(input, deps) {
|
|
|
3992
4345
|
diagnostics: lastDiagnostics && lastDiagnostics.length ? lastDiagnostics : void 0
|
|
3993
4346
|
};
|
|
3994
4347
|
}
|
|
4348
|
+
function resolveCustomPrimaryRate(rateContext, serviceMap) {
|
|
4349
|
+
if (!rateContext || rateContext.mode !== "custom_primary_rate") {
|
|
4350
|
+
return void 0;
|
|
4351
|
+
}
|
|
4352
|
+
if (rateContext.source === "manual") {
|
|
4353
|
+
return toFiniteNumber(rateContext.primaryRate);
|
|
4354
|
+
}
|
|
4355
|
+
if (rateContext.primaryServiceId == null) return void 0;
|
|
4356
|
+
const cap = getServiceCapability(serviceMap, rateContext.primaryServiceId);
|
|
4357
|
+
return toFiniteNumber(cap == null ? void 0 : cap.rate);
|
|
4358
|
+
}
|
|
3995
4359
|
function evaluatePoliciesRaw(raw, serviceIds, svcMap, tagId, visibleServiceIds) {
|
|
3996
4360
|
const compiled = compilePolicies(raw);
|
|
3997
4361
|
const evaluated = evaluateServicePolicies(
|
|
@@ -4079,7 +4443,9 @@ function collectVisibleServiceIds(builder, tagId, selectedButtons) {
|
|
|
4079
4443
|
const fields = (_b = props.fields) != null ? _b : [];
|
|
4080
4444
|
const tag = tags.find((t) => t.id === tagId);
|
|
4081
4445
|
if ((tag == null ? void 0 : tag.service_id) != null) out.add(String(tag.service_id));
|
|
4082
|
-
const visibleFieldIds = new Set(
|
|
4446
|
+
const visibleFieldIds = new Set(
|
|
4447
|
+
builder.visibleFields(tagId, selectedButtons)
|
|
4448
|
+
);
|
|
4083
4449
|
for (const field of fields) {
|
|
4084
4450
|
if (!visibleFieldIds.has(field.id)) continue;
|
|
4085
4451
|
if (field.service_id != null) {
|
|
@@ -4102,8 +4468,7 @@ function matchesRuleFilter(cap, rule, tagId) {
|
|
|
4102
4468
|
if (!cap) return false;
|
|
4103
4469
|
const f = rule.filter;
|
|
4104
4470
|
if (!f) return true;
|
|
4105
|
-
|
|
4106
|
-
return true;
|
|
4471
|
+
return !(f.tag_id && !toStrSet(f.tag_id).has(String(tagId)));
|
|
4107
4472
|
}
|
|
4108
4473
|
function toStrSet(v) {
|
|
4109
4474
|
const arr = Array.isArray(v) ? v : [v];
|
|
@@ -4111,6 +4476,107 @@ function toStrSet(v) {
|
|
|
4111
4476
|
for (const x of arr) s.add(String(x));
|
|
4112
4477
|
return s;
|
|
4113
4478
|
}
|
|
4479
|
+
function candidatePassesRateCoherence(builder, serviceMap, tagId, selectedKeys, usedServiceIds, candidateId, ratePolicy) {
|
|
4480
|
+
var _a, _b, _c, _d;
|
|
4481
|
+
if (usedServiceIds.length === 0) return true;
|
|
4482
|
+
const props = builder.getProps();
|
|
4483
|
+
const baseFields = (_a = props.fields) != null ? _a : [];
|
|
4484
|
+
const candidateFieldId = syntheticServiceFieldId("candidate", candidateId, 0);
|
|
4485
|
+
const syntheticFields = [
|
|
4486
|
+
...usedServiceIds.map((serviceId, index) => ({
|
|
4487
|
+
id: syntheticServiceFieldId("used", serviceId, index),
|
|
4488
|
+
label: `Used service ${String(serviceId)}`,
|
|
4489
|
+
type: "custom",
|
|
4490
|
+
button: true,
|
|
4491
|
+
service_id: serviceId,
|
|
4492
|
+
pricing_role: "base"
|
|
4493
|
+
})),
|
|
4494
|
+
{
|
|
4495
|
+
id: candidateFieldId,
|
|
4496
|
+
label: `Candidate ${String(candidateId)}`,
|
|
4497
|
+
type: "custom",
|
|
4498
|
+
button: true,
|
|
4499
|
+
service_id: candidateId,
|
|
4500
|
+
pricing_role: "base"
|
|
4501
|
+
}
|
|
4502
|
+
];
|
|
4503
|
+
const fields = [...baseFields, ...syntheticFields];
|
|
4504
|
+
const visibleFieldIds = [
|
|
4505
|
+
...builder.visibleFields(tagId, selectedKeys),
|
|
4506
|
+
...syntheticFields.map((field) => field.id)
|
|
4507
|
+
];
|
|
4508
|
+
const anchoredFilters = ((_b = props.filters) != null ? _b : []).map(
|
|
4509
|
+
(tag) => tag.id === tagId && usedServiceIds[0] != null ? { ...tag, service_id: usedServiceIds[0] } : tag
|
|
4510
|
+
);
|
|
4511
|
+
const validationProps = {
|
|
4512
|
+
...props,
|
|
4513
|
+
filters: anchoredFilters,
|
|
4514
|
+
fields
|
|
4515
|
+
};
|
|
4516
|
+
const errors = [];
|
|
4517
|
+
const tags = (_c = validationProps.filters) != null ? _c : [];
|
|
4518
|
+
const fieldById = new Map(fields.map((field) => [field.id, field]));
|
|
4519
|
+
const tagById = new Map(tags.map((tag) => [tag.id, tag]));
|
|
4520
|
+
const v = {
|
|
4521
|
+
props: validationProps,
|
|
4522
|
+
nodeMap: buildNodeMap(validationProps),
|
|
4523
|
+
options: {
|
|
4524
|
+
...(_d = builder.getOptions) == null ? void 0 : _d.call(builder),
|
|
4525
|
+
serviceMap,
|
|
4526
|
+
ratePolicy
|
|
4527
|
+
},
|
|
4528
|
+
errors,
|
|
4529
|
+
serviceMap,
|
|
4530
|
+
selectedKeys: new Set(selectedKeys),
|
|
4531
|
+
tags,
|
|
4532
|
+
fields,
|
|
4533
|
+
invalidRateFieldIds: /* @__PURE__ */ new Set(),
|
|
4534
|
+
tagById,
|
|
4535
|
+
fieldById,
|
|
4536
|
+
fieldsVisibleUnder: () => [],
|
|
4537
|
+
simulatedVisibilityContexts: []
|
|
4538
|
+
};
|
|
4539
|
+
validateRateCoherenceForVisibleContext({
|
|
4540
|
+
v,
|
|
4541
|
+
tagId,
|
|
4542
|
+
selectedKeys,
|
|
4543
|
+
visibleFieldIds,
|
|
4544
|
+
effectMap: buildTriggerEffectMap(validationProps),
|
|
4545
|
+
seen: /* @__PURE__ */ new Set()
|
|
4546
|
+
});
|
|
4547
|
+
return !errors.some(
|
|
4548
|
+
(error) => rateIssueAffectsCandidate(
|
|
4549
|
+
error,
|
|
4550
|
+
candidateId,
|
|
4551
|
+
candidateFieldId,
|
|
4552
|
+
usedServiceIds[0]
|
|
4553
|
+
)
|
|
4554
|
+
);
|
|
4555
|
+
}
|
|
4556
|
+
function syntheticServiceFieldId(kind, serviceId, index) {
|
|
4557
|
+
return `__service_filter_${kind}__:${index}:${String(serviceId)}`;
|
|
4558
|
+
}
|
|
4559
|
+
function rateIssueAffectsCandidate(error, candidateId, candidateFieldId, primaryAnchorId) {
|
|
4560
|
+
var _a, _b, _c, _d;
|
|
4561
|
+
if (error.code !== "rate_coherence_violation") return false;
|
|
4562
|
+
const candidateKey = String(candidateId);
|
|
4563
|
+
const details = (_a = error.details) != null ? _a : {};
|
|
4564
|
+
const anchorKey = primaryAnchorId == null ? void 0 : String(primaryAnchorId);
|
|
4565
|
+
const primaryMatchesAnchor = anchorKey == null || String((_b = details.primary) == null ? void 0 : _b.serviceId) === anchorKey || String((_c = details.primary) == null ? void 0 : _c.service_id) === anchorKey;
|
|
4566
|
+
if (primaryMatchesAnchor && ((_d = details.affectedServiceIds) == null ? void 0 : _d.some(
|
|
4567
|
+
(serviceId) => String(serviceId) === candidateKey
|
|
4568
|
+
))) {
|
|
4569
|
+
return true;
|
|
4570
|
+
}
|
|
4571
|
+
if (primaryMatchesAnchor && String(error.nodeId) === candidateFieldId) {
|
|
4572
|
+
return true;
|
|
4573
|
+
}
|
|
4574
|
+
return [details.primary, details.candidate].some((ref) => {
|
|
4575
|
+
if (!ref) return false;
|
|
4576
|
+
if (!primaryMatchesAnchor) return false;
|
|
4577
|
+
return String(ref.serviceId) === candidateKey || String(ref.service_id) === candidateKey || String(ref.fieldId) === candidateFieldId || String(ref.nodeId) === candidateFieldId;
|
|
4578
|
+
});
|
|
4579
|
+
}
|
|
4114
4580
|
|
|
4115
4581
|
// src/utils/prune-fallbacks.ts
|
|
4116
4582
|
function pruneInvalidNodeFallbacks(props, services, settings) {
|
|
@@ -5214,6 +5680,7 @@ function mapDiagReason(reason) {
|
|
|
5214
5680
|
}
|
|
5215
5681
|
export {
|
|
5216
5682
|
buildOrderSnapshot,
|
|
5683
|
+
buildTriggerEffectMap,
|
|
5217
5684
|
collectFailedFallbacks,
|
|
5218
5685
|
createBuilder,
|
|
5219
5686
|
createFallbackEditor,
|
|
@@ -5222,6 +5689,7 @@ export {
|
|
|
5222
5689
|
getAssignedServiceIds,
|
|
5223
5690
|
getEligibleFallbacks,
|
|
5224
5691
|
getFallbackRegistrationInfo,
|
|
5692
|
+
isRefExcludedBySelectedKeys,
|
|
5225
5693
|
normalise,
|
|
5226
5694
|
normalizeFieldValidation,
|
|
5227
5695
|
resolveServiceFallback,
|