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