@timeax/digital-service-engine 0.0.5 → 0.0.7

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.
@@ -1,7 +1,7 @@
1
1
  interface ButtonValue {
2
2
  id: string;
3
3
  value: string | number;
4
- service_id?: number;
4
+ service_id?: ServiceIdRef;
5
5
  pricing_role?: "base" | "utility";
6
6
  meta?: Record<string, unknown> & UtilityMark & WithQuantityDefault;
7
7
  }
@@ -10,16 +10,23 @@ type UtilityMode = "flat" | "per_quantity" | "per_value" | "percent";
10
10
  type QuantityRule = {
11
11
  valueBy: "value" | "length" | "eval";
12
12
  code?: string;
13
+ multiply?: number;
14
+ clamp?: {
15
+ min?: number;
16
+ max?: number;
17
+ };
18
+ fallback?: number;
13
19
  };
14
20
  type UtilityLineItem = {
15
21
  nodeId: string;
16
22
  mode: UtilityMode;
17
23
  rate: number;
24
+ percentBase?: "service_total" | "base_service" | "all";
25
+ label?: string;
18
26
  inputs: {
19
27
  quantity: number;
20
28
  value?: Scalar | Scalar[];
21
- valueBy?: "value" | "length" | "eval";
22
- evalCodeUsed?: boolean;
29
+ valueBy?: "value" | "length";
23
30
  };
24
31
  };
25
32
  type ServiceFallbacks = ServiceFallback;
@@ -45,8 +52,16 @@ type SnapshotContext = {
45
52
  /** Client pruning policy used (so server can mirror/compare). */
46
53
  policy: {
47
54
  ratePolicy: {
48
- kind: "lte_primary" | "none";
49
- thresholdPct?: number;
55
+ kind: "eq_primary";
56
+ } | {
57
+ kind: "lte_primary";
58
+ pct: number;
59
+ } | {
60
+ kind: "within_pct";
61
+ pct: number;
62
+ } | {
63
+ kind: "at_least_pct_lower";
64
+ pct: number;
50
65
  };
51
66
  requireConstraintFit: boolean;
52
67
  };
@@ -154,7 +169,7 @@ type NodeRef = {
154
169
  };
155
170
  type NodeMap = Map<string, NodeRef>;
156
171
 
157
- type ValidationCode = "root_missing" | "cycle_in_tags" | "bad_bind_reference" | "duplicate_id" | "duplicate_tag_label" | "duplicate_field_name" | "label_missing" | "duplicate_visible_label" | "bad_option_key" | "option_include_exclude_conflict" | "service_field_missing_service_id" | "user_input_field_has_service_option" | "rate_mismatch_across_base" | "utility_without_base" | "unsupported_constraint" | "constraint_contradiction" | "custom_component_missing" | "policy_violation" | "field_unbound" | "constraint_overridden" | "unsupported_constraint_option" | "custom_component_unresolvable" | "quantity_multiple_markers" | "utility_with_service_id" | "utility_missing_rate" | "utility_invalid_mode" | "fallback_bad_node" | "fallback_unknown_service" | "fallback_cycle" | "fallback_no_primary" | "fallback_rate_violation" | "fallback_constraint_mismatch" | "fallback_no_tag_context";
172
+ type ValidationCode = "root_missing" | "cycle_in_tags" | "bad_bind_reference" | "duplicate_id" | "duplicate_tag_label" | "duplicate_field_name" | "label_missing" | "duplicate_visible_label" | "bad_option_key" | "option_include_exclude_conflict" | "service_field_missing_service_id" | "user_input_field_has_service_option" | "rate_mismatch_across_base" | "rate_coherence_violation" | "utility_without_base" | "unsupported_constraint" | "constraint_contradiction" | "custom_component_missing" | "policy_violation" | "field_unbound" | "constraint_overridden" | "unsupported_constraint_option" | "custom_component_unresolvable" | "quantity_multiple_markers" | "utility_with_service_id" | "utility_missing_rate" | "utility_invalid_mode" | "fallback_bad_node" | "fallback_unknown_service" | "fallback_cycle" | "fallback_no_primary" | "fallback_rate_violation" | "fallback_constraint_mismatch" | "fallback_no_tag_context" | "field_validation_invalid_rule" | "field_validation_invalid_op" | "field_validation_eval_missing_code" | "field_validation_between_missing_bounds" | "field_validation_match_missing_pattern";
158
173
  type ValidationError = {
159
174
  code: ValidationCode;
160
175
  message: string;
@@ -210,7 +225,10 @@ type ValidatorOptions = {
210
225
  fallbackSettings?: FallbackSettings;
211
226
  };
212
227
  type RatePolicy = {
228
+ kind: "eq_primary";
229
+ } | {
213
230
  kind: "lte_primary";
231
+ pct: number;
214
232
  } | {
215
233
  kind: "within_pct";
216
234
  pct: number;
@@ -221,7 +239,7 @@ type RatePolicy = {
221
239
  type FallbackSettings = {
222
240
  /** Require fallbacks to satisfy tag constraints (dripfeed/refill/cancel) when a tag context is known. Default: true */
223
241
  requireConstraintFit?: boolean;
224
- /** Rate rule policy. Default: { kind: 'lte_primary' } i.e. candidate.rate <= primary.rate */
242
+ /** Rate rule policy. Default: { kind: 'lte_primary', pct: 5 }. */
225
243
  ratePolicy?: RatePolicy;
226
244
  /** When multiple candidates remain, choose first (priority) or cheapest. Default: 'priority' */
227
245
  selectionStrategy?: "priority" | "cheapest";
@@ -229,7 +247,6 @@ type FallbackSettings = {
229
247
  mode?: "strict" | "dev";
230
248
  };
231
249
 
232
- type ServiceIdRef = number | string;
233
250
  type NodeIdRef = string;
234
251
  type ServiceFallback = {
235
252
  /** Node-scoped fallbacks: prefer these when that node’s primary service fails */
@@ -322,6 +339,7 @@ type FallbackMutationOptions = {
322
339
  index?: number;
323
340
  };
324
341
 
342
+ type ServiceIdRef = IdType;
325
343
  type PricingRole = "base" | "utility";
326
344
  type FieldType = "custom" | (string & {});
327
345
  /** ── Marker types (live inside meta; non-breaking) ───────────────────── */
@@ -357,11 +375,25 @@ interface BaseFieldUI {
357
375
  /** Host-defined prop names → runtime default values (untyped base) */
358
376
  defaults?: Record<string, unknown>;
359
377
  }
378
+ type FieldValidationValueBy = "value" | "length" | "eval";
379
+ type FieldValidationOp = "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "between" | "in" | "nin" | "truthy" | "falsy" | "match";
380
+ type FieldValidationRule = {
381
+ valueBy?: FieldValidationValueBy;
382
+ op: FieldValidationOp;
383
+ value?: unknown;
384
+ min?: number;
385
+ max?: number;
386
+ values?: unknown[];
387
+ pattern?: string;
388
+ flags?: string;
389
+ code?: string;
390
+ message?: string;
391
+ };
360
392
  type FieldOption = {
361
393
  id: string;
362
394
  label: string;
363
395
  value?: string | number;
364
- service_id?: number;
396
+ service_id?: ServiceIdRef;
365
397
  pricing_role?: PricingRole;
366
398
  meta?: Record<string, unknown> & UtilityMark & WithQuantityDefault;
367
399
  };
@@ -374,6 +406,7 @@ type Field = BaseFieldUI & {
374
406
  description?: string;
375
407
  component?: string;
376
408
  pricing_role?: PricingRole;
409
+ validation?: FieldValidationRule[];
377
410
  meta?: Record<string, unknown> & QuantityMark & UtilityMark & {
378
411
  multi?: boolean;
379
412
  };
@@ -382,14 +415,14 @@ type Field = BaseFieldUI & {
382
415
  service_id?: undefined;
383
416
  } | ({
384
417
  button: true;
385
- service_id?: number;
418
+ service_id?: ServiceIdRef;
386
419
  } & WithQuantityDefault));
387
420
  type ConstraintKey = string;
388
421
  type Tag = {
389
422
  id: string;
390
423
  label: string;
391
424
  bind_id?: string;
392
- service_id?: number;
425
+ service_id?: ServiceIdRef;
393
426
  includes?: string[];
394
427
  excludes?: string[];
395
428
  meta?: Record<string, unknown> & WithQuantityDefault;
@@ -475,6 +508,7 @@ type NormaliseOptions = {
475
508
  constraints?: string[];
476
509
  };
477
510
  declare function normalise(input: unknown, opts?: NormaliseOptions): ServiceProps;
511
+ declare function normalizeFieldValidation(input: unknown): FieldValidationRule[] | undefined;
478
512
 
479
513
  /**
480
514
  * Core synchronous validate.
@@ -491,8 +525,6 @@ declare function validateAsync(props: ServiceProps, ctx?: ValidatorOptions): Pro
491
525
  /** Options you can set on the builder (used for validation/visibility) */
492
526
  type BuilderOptions = Omit<ValidatorOptions, "serviceMap"> & {
493
527
  serviceMap?: DgpServiceMap;
494
- /** max history entries for undo/redo */
495
- historyLimit?: number;
496
528
  /**
497
529
  * Field ids whose options should be shown as nodes in the graph.
498
530
  * If a field id is NOT in this set, its options are not materialized as nodes:
@@ -517,9 +549,6 @@ interface Builder {
517
549
  visibleFields(tagId: string, selectedOptionKeys?: string[]): string[];
518
550
  /** Update builder options (validator context etc.) */
519
551
  setOptions(patch: Partial<BuilderOptions>): void;
520
- /** History */
521
- undo(): boolean;
522
- redo(): boolean;
523
552
  /** Access the current props (already normalised) */
524
553
  getProps(): ServiceProps;
525
554
  /** Service map for validation/rules */
@@ -583,48 +612,50 @@ declare function getFallbackRegistrationInfo(props: ServiceProps, nodeId: NodeId
583
612
  tagContexts: string[];
584
613
  };
585
614
 
586
- type BaseCandidate = {
615
+ type SimulationAnchor = {
587
616
  kind: "field" | "option";
588
617
  id: string;
618
+ fieldId: string;
589
619
  label?: string;
590
- service_id: number;
591
- rate: number;
592
620
  };
593
- /** Result for each violation discovered during deep simulation. */
594
- type RateCoherenceDiagnostic = {
621
+ type SharedDiagnostic = {
595
622
  scope: "visible_group";
596
623
  tagId: string;
597
- /** The “primary” used for comparison in this simulation:
598
- * anchor service if present; otherwise, the first base service among simulated candidates.
599
- * (Tag service is never used as primary.)
600
- */
601
- primary: BaseCandidate;
602
- /** The item that violated the policy against the primary. */
603
- offender: {
604
- kind: "field" | "option";
605
- id: string;
624
+ nodeId: string;
625
+ message: string;
626
+ simulationAnchor?: SimulationAnchor;
627
+ invalidFieldIds: string[];
628
+ };
629
+ type RateCoherenceDiagnostic = (SharedDiagnostic & {
630
+ kind: "contextual";
631
+ primary: {
632
+ nodeId: string;
633
+ fieldId: string;
606
634
  label?: string;
607
- service_id: number;
635
+ refKind: "single" | "multi";
636
+ service_id?: ServiceIdRef;
608
637
  rate: number;
609
638
  };
610
- policy: RatePolicy["kind"];
611
- policyPct?: number;
612
- message: string;
613
- /** Which button triggered this simulation */
614
- simulationAnchor: {
615
- kind: "field" | "option";
616
- id: string;
639
+ offender: {
640
+ nodeId: string;
617
641
  fieldId: string;
618
642
  label?: string;
643
+ refKind: "single" | "multi";
644
+ service_id?: ServiceIdRef;
645
+ rate: number;
619
646
  };
620
- };
621
- /** Run deep rate-coherence validation by simulating each button selection in the active tag. */
647
+ policy: RatePolicy["kind"];
648
+ policyPct?: number;
649
+ }) | (SharedDiagnostic & {
650
+ kind: "internal_field";
651
+ fieldId: string;
652
+ });
622
653
  declare function validateRateCoherenceDeep(params: {
623
654
  builder: Builder;
624
655
  services: DgpServiceMap;
625
656
  tagId: string;
626
- /** Optional rate policy (defaults to { kind: 'lte_primary' }) */
627
657
  ratePolicy?: RatePolicy;
658
+ invalidFieldIds?: Iterable<string>;
628
659
  }): RateCoherenceDiagnostic[];
629
660
 
630
661
  type NodeKind = "tag" | "field" | "option" | "unknown";
@@ -704,6 +735,47 @@ interface NodeIndex {
704
735
  }
705
736
  declare function createNodeIndex(builder: Builder): NodeIndex;
706
737
 
738
+ type PolicyDiagnostic = {
739
+ ruleIndex: number;
740
+ ruleId?: string;
741
+ severity: "error" | "warning";
742
+ message: string;
743
+ path?: string;
744
+ };
745
+
746
+ type ServiceCheck = {
747
+ id: ServiceIdRef;
748
+ ok: boolean;
749
+ fitsConstraints: boolean;
750
+ passesRate: boolean;
751
+ passesPolicies: boolean;
752
+ policyErrors?: string[];
753
+ policyWarnings?: string[];
754
+ reasons: Array<"constraint_mismatch" | "rate_policy" | "policy_error" | "missing_capability">;
755
+ cap?: DgpServiceCapability;
756
+ rate?: number;
757
+ };
758
+ type FilterServicesForVisibleGroupInput = {
759
+ candidates: ServiceIdRef[];
760
+ context: {
761
+ tagId: string;
762
+ selectedButtons?: string[];
763
+ usedServiceIds: ServiceIdRef[];
764
+ effectiveConstraints?: Partial<Record<"refill" | "cancel" | "dripfeed", boolean>>;
765
+ policies?: unknown;
766
+ fallback?: FallbackSettings;
767
+ strictSafety?: boolean;
768
+ enforcePolicies?: boolean;
769
+ };
770
+ };
771
+ type FilterServicesForVisibleGroupResult = {
772
+ checks: ServiceCheck[];
773
+ diagnostics?: PolicyDiagnostic[];
774
+ };
775
+ declare function filterServicesForVisibleGroup(input: FilterServicesForVisibleGroupInput, deps: {
776
+ builder: Builder;
777
+ }): FilterServicesForVisibleGroupResult;
778
+
707
779
  type BuildOrderSnapshotSettings = {
708
780
  mode?: "prod" | "dev";
709
781
  hostDefaultQuantity?: number;
@@ -765,4 +837,4 @@ interface FallbackEditor {
765
837
  }
766
838
  declare function createFallbackEditor(options?: FallbackEditorOptions): FallbackEditor;
767
839
 
768
- export { type AncestryHit, type AnyNode, type Builder, type BuilderOptions, type FailedFallbackContext, type FallbackEditor, type FieldNode, type NodeIndex, type NodeKind, type NormaliseOptions, type OptionNode, type RateCoherenceDiagnostic, type RelKind, type TagNode, type UnknownNode, type WithAncestry, buildOrderSnapshot, collectFailedFallbacks, createBuilder, createFallbackEditor, createNodeIndex, getEligibleFallbacks, getFallbackRegistrationInfo, normalise, resolveServiceFallback, validate, validateAsync, validateRateCoherenceDeep };
840
+ export { type AncestryHit, type AnyNode, type Builder, type BuilderOptions, type FailedFallbackContext, type FallbackEditor, type FieldNode, type FilterServicesForVisibleGroupInput, type FilterServicesForVisibleGroupResult, type NodeIndex, type NodeKind, type NormaliseOptions, type OptionNode, type RateCoherenceDiagnostic, type RelKind, type ServiceCheck, type TagNode, type UnknownNode, type WithAncestry, buildOrderSnapshot, collectFailedFallbacks, createBuilder, createFallbackEditor, createNodeIndex, filterServicesForVisibleGroup, getEligibleFallbacks, getFallbackRegistrationInfo, normalise, normalizeFieldValidation, resolveServiceFallback, validate, validateAsync, validateRateCoherenceDeep };
@@ -1,7 +1,7 @@
1
1
  interface ButtonValue {
2
2
  id: string;
3
3
  value: string | number;
4
- service_id?: number;
4
+ service_id?: ServiceIdRef;
5
5
  pricing_role?: "base" | "utility";
6
6
  meta?: Record<string, unknown> & UtilityMark & WithQuantityDefault;
7
7
  }
@@ -10,16 +10,23 @@ type UtilityMode = "flat" | "per_quantity" | "per_value" | "percent";
10
10
  type QuantityRule = {
11
11
  valueBy: "value" | "length" | "eval";
12
12
  code?: string;
13
+ multiply?: number;
14
+ clamp?: {
15
+ min?: number;
16
+ max?: number;
17
+ };
18
+ fallback?: number;
13
19
  };
14
20
  type UtilityLineItem = {
15
21
  nodeId: string;
16
22
  mode: UtilityMode;
17
23
  rate: number;
24
+ percentBase?: "service_total" | "base_service" | "all";
25
+ label?: string;
18
26
  inputs: {
19
27
  quantity: number;
20
28
  value?: Scalar | Scalar[];
21
- valueBy?: "value" | "length" | "eval";
22
- evalCodeUsed?: boolean;
29
+ valueBy?: "value" | "length";
23
30
  };
24
31
  };
25
32
  type ServiceFallbacks = ServiceFallback;
@@ -45,8 +52,16 @@ type SnapshotContext = {
45
52
  /** Client pruning policy used (so server can mirror/compare). */
46
53
  policy: {
47
54
  ratePolicy: {
48
- kind: "lte_primary" | "none";
49
- thresholdPct?: number;
55
+ kind: "eq_primary";
56
+ } | {
57
+ kind: "lte_primary";
58
+ pct: number;
59
+ } | {
60
+ kind: "within_pct";
61
+ pct: number;
62
+ } | {
63
+ kind: "at_least_pct_lower";
64
+ pct: number;
50
65
  };
51
66
  requireConstraintFit: boolean;
52
67
  };
@@ -154,7 +169,7 @@ type NodeRef = {
154
169
  };
155
170
  type NodeMap = Map<string, NodeRef>;
156
171
 
157
- type ValidationCode = "root_missing" | "cycle_in_tags" | "bad_bind_reference" | "duplicate_id" | "duplicate_tag_label" | "duplicate_field_name" | "label_missing" | "duplicate_visible_label" | "bad_option_key" | "option_include_exclude_conflict" | "service_field_missing_service_id" | "user_input_field_has_service_option" | "rate_mismatch_across_base" | "utility_without_base" | "unsupported_constraint" | "constraint_contradiction" | "custom_component_missing" | "policy_violation" | "field_unbound" | "constraint_overridden" | "unsupported_constraint_option" | "custom_component_unresolvable" | "quantity_multiple_markers" | "utility_with_service_id" | "utility_missing_rate" | "utility_invalid_mode" | "fallback_bad_node" | "fallback_unknown_service" | "fallback_cycle" | "fallback_no_primary" | "fallback_rate_violation" | "fallback_constraint_mismatch" | "fallback_no_tag_context";
172
+ type ValidationCode = "root_missing" | "cycle_in_tags" | "bad_bind_reference" | "duplicate_id" | "duplicate_tag_label" | "duplicate_field_name" | "label_missing" | "duplicate_visible_label" | "bad_option_key" | "option_include_exclude_conflict" | "service_field_missing_service_id" | "user_input_field_has_service_option" | "rate_mismatch_across_base" | "rate_coherence_violation" | "utility_without_base" | "unsupported_constraint" | "constraint_contradiction" | "custom_component_missing" | "policy_violation" | "field_unbound" | "constraint_overridden" | "unsupported_constraint_option" | "custom_component_unresolvable" | "quantity_multiple_markers" | "utility_with_service_id" | "utility_missing_rate" | "utility_invalid_mode" | "fallback_bad_node" | "fallback_unknown_service" | "fallback_cycle" | "fallback_no_primary" | "fallback_rate_violation" | "fallback_constraint_mismatch" | "fallback_no_tag_context" | "field_validation_invalid_rule" | "field_validation_invalid_op" | "field_validation_eval_missing_code" | "field_validation_between_missing_bounds" | "field_validation_match_missing_pattern";
158
173
  type ValidationError = {
159
174
  code: ValidationCode;
160
175
  message: string;
@@ -210,7 +225,10 @@ type ValidatorOptions = {
210
225
  fallbackSettings?: FallbackSettings;
211
226
  };
212
227
  type RatePolicy = {
228
+ kind: "eq_primary";
229
+ } | {
213
230
  kind: "lte_primary";
231
+ pct: number;
214
232
  } | {
215
233
  kind: "within_pct";
216
234
  pct: number;
@@ -221,7 +239,7 @@ type RatePolicy = {
221
239
  type FallbackSettings = {
222
240
  /** Require fallbacks to satisfy tag constraints (dripfeed/refill/cancel) when a tag context is known. Default: true */
223
241
  requireConstraintFit?: boolean;
224
- /** Rate rule policy. Default: { kind: 'lte_primary' } i.e. candidate.rate <= primary.rate */
242
+ /** Rate rule policy. Default: { kind: 'lte_primary', pct: 5 }. */
225
243
  ratePolicy?: RatePolicy;
226
244
  /** When multiple candidates remain, choose first (priority) or cheapest. Default: 'priority' */
227
245
  selectionStrategy?: "priority" | "cheapest";
@@ -229,7 +247,6 @@ type FallbackSettings = {
229
247
  mode?: "strict" | "dev";
230
248
  };
231
249
 
232
- type ServiceIdRef = number | string;
233
250
  type NodeIdRef = string;
234
251
  type ServiceFallback = {
235
252
  /** Node-scoped fallbacks: prefer these when that node’s primary service fails */
@@ -322,6 +339,7 @@ type FallbackMutationOptions = {
322
339
  index?: number;
323
340
  };
324
341
 
342
+ type ServiceIdRef = IdType;
325
343
  type PricingRole = "base" | "utility";
326
344
  type FieldType = "custom" | (string & {});
327
345
  /** ── Marker types (live inside meta; non-breaking) ───────────────────── */
@@ -357,11 +375,25 @@ interface BaseFieldUI {
357
375
  /** Host-defined prop names → runtime default values (untyped base) */
358
376
  defaults?: Record<string, unknown>;
359
377
  }
378
+ type FieldValidationValueBy = "value" | "length" | "eval";
379
+ type FieldValidationOp = "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "between" | "in" | "nin" | "truthy" | "falsy" | "match";
380
+ type FieldValidationRule = {
381
+ valueBy?: FieldValidationValueBy;
382
+ op: FieldValidationOp;
383
+ value?: unknown;
384
+ min?: number;
385
+ max?: number;
386
+ values?: unknown[];
387
+ pattern?: string;
388
+ flags?: string;
389
+ code?: string;
390
+ message?: string;
391
+ };
360
392
  type FieldOption = {
361
393
  id: string;
362
394
  label: string;
363
395
  value?: string | number;
364
- service_id?: number;
396
+ service_id?: ServiceIdRef;
365
397
  pricing_role?: PricingRole;
366
398
  meta?: Record<string, unknown> & UtilityMark & WithQuantityDefault;
367
399
  };
@@ -374,6 +406,7 @@ type Field = BaseFieldUI & {
374
406
  description?: string;
375
407
  component?: string;
376
408
  pricing_role?: PricingRole;
409
+ validation?: FieldValidationRule[];
377
410
  meta?: Record<string, unknown> & QuantityMark & UtilityMark & {
378
411
  multi?: boolean;
379
412
  };
@@ -382,14 +415,14 @@ type Field = BaseFieldUI & {
382
415
  service_id?: undefined;
383
416
  } | ({
384
417
  button: true;
385
- service_id?: number;
418
+ service_id?: ServiceIdRef;
386
419
  } & WithQuantityDefault));
387
420
  type ConstraintKey = string;
388
421
  type Tag = {
389
422
  id: string;
390
423
  label: string;
391
424
  bind_id?: string;
392
- service_id?: number;
425
+ service_id?: ServiceIdRef;
393
426
  includes?: string[];
394
427
  excludes?: string[];
395
428
  meta?: Record<string, unknown> & WithQuantityDefault;
@@ -475,6 +508,7 @@ type NormaliseOptions = {
475
508
  constraints?: string[];
476
509
  };
477
510
  declare function normalise(input: unknown, opts?: NormaliseOptions): ServiceProps;
511
+ declare function normalizeFieldValidation(input: unknown): FieldValidationRule[] | undefined;
478
512
 
479
513
  /**
480
514
  * Core synchronous validate.
@@ -491,8 +525,6 @@ declare function validateAsync(props: ServiceProps, ctx?: ValidatorOptions): Pro
491
525
  /** Options you can set on the builder (used for validation/visibility) */
492
526
  type BuilderOptions = Omit<ValidatorOptions, "serviceMap"> & {
493
527
  serviceMap?: DgpServiceMap;
494
- /** max history entries for undo/redo */
495
- historyLimit?: number;
496
528
  /**
497
529
  * Field ids whose options should be shown as nodes in the graph.
498
530
  * If a field id is NOT in this set, its options are not materialized as nodes:
@@ -517,9 +549,6 @@ interface Builder {
517
549
  visibleFields(tagId: string, selectedOptionKeys?: string[]): string[];
518
550
  /** Update builder options (validator context etc.) */
519
551
  setOptions(patch: Partial<BuilderOptions>): void;
520
- /** History */
521
- undo(): boolean;
522
- redo(): boolean;
523
552
  /** Access the current props (already normalised) */
524
553
  getProps(): ServiceProps;
525
554
  /** Service map for validation/rules */
@@ -583,48 +612,50 @@ declare function getFallbackRegistrationInfo(props: ServiceProps, nodeId: NodeId
583
612
  tagContexts: string[];
584
613
  };
585
614
 
586
- type BaseCandidate = {
615
+ type SimulationAnchor = {
587
616
  kind: "field" | "option";
588
617
  id: string;
618
+ fieldId: string;
589
619
  label?: string;
590
- service_id: number;
591
- rate: number;
592
620
  };
593
- /** Result for each violation discovered during deep simulation. */
594
- type RateCoherenceDiagnostic = {
621
+ type SharedDiagnostic = {
595
622
  scope: "visible_group";
596
623
  tagId: string;
597
- /** The “primary” used for comparison in this simulation:
598
- * anchor service if present; otherwise, the first base service among simulated candidates.
599
- * (Tag service is never used as primary.)
600
- */
601
- primary: BaseCandidate;
602
- /** The item that violated the policy against the primary. */
603
- offender: {
604
- kind: "field" | "option";
605
- id: string;
624
+ nodeId: string;
625
+ message: string;
626
+ simulationAnchor?: SimulationAnchor;
627
+ invalidFieldIds: string[];
628
+ };
629
+ type RateCoherenceDiagnostic = (SharedDiagnostic & {
630
+ kind: "contextual";
631
+ primary: {
632
+ nodeId: string;
633
+ fieldId: string;
606
634
  label?: string;
607
- service_id: number;
635
+ refKind: "single" | "multi";
636
+ service_id?: ServiceIdRef;
608
637
  rate: number;
609
638
  };
610
- policy: RatePolicy["kind"];
611
- policyPct?: number;
612
- message: string;
613
- /** Which button triggered this simulation */
614
- simulationAnchor: {
615
- kind: "field" | "option";
616
- id: string;
639
+ offender: {
640
+ nodeId: string;
617
641
  fieldId: string;
618
642
  label?: string;
643
+ refKind: "single" | "multi";
644
+ service_id?: ServiceIdRef;
645
+ rate: number;
619
646
  };
620
- };
621
- /** Run deep rate-coherence validation by simulating each button selection in the active tag. */
647
+ policy: RatePolicy["kind"];
648
+ policyPct?: number;
649
+ }) | (SharedDiagnostic & {
650
+ kind: "internal_field";
651
+ fieldId: string;
652
+ });
622
653
  declare function validateRateCoherenceDeep(params: {
623
654
  builder: Builder;
624
655
  services: DgpServiceMap;
625
656
  tagId: string;
626
- /** Optional rate policy (defaults to { kind: 'lte_primary' }) */
627
657
  ratePolicy?: RatePolicy;
658
+ invalidFieldIds?: Iterable<string>;
628
659
  }): RateCoherenceDiagnostic[];
629
660
 
630
661
  type NodeKind = "tag" | "field" | "option" | "unknown";
@@ -704,6 +735,47 @@ interface NodeIndex {
704
735
  }
705
736
  declare function createNodeIndex(builder: Builder): NodeIndex;
706
737
 
738
+ type PolicyDiagnostic = {
739
+ ruleIndex: number;
740
+ ruleId?: string;
741
+ severity: "error" | "warning";
742
+ message: string;
743
+ path?: string;
744
+ };
745
+
746
+ type ServiceCheck = {
747
+ id: ServiceIdRef;
748
+ ok: boolean;
749
+ fitsConstraints: boolean;
750
+ passesRate: boolean;
751
+ passesPolicies: boolean;
752
+ policyErrors?: string[];
753
+ policyWarnings?: string[];
754
+ reasons: Array<"constraint_mismatch" | "rate_policy" | "policy_error" | "missing_capability">;
755
+ cap?: DgpServiceCapability;
756
+ rate?: number;
757
+ };
758
+ type FilterServicesForVisibleGroupInput = {
759
+ candidates: ServiceIdRef[];
760
+ context: {
761
+ tagId: string;
762
+ selectedButtons?: string[];
763
+ usedServiceIds: ServiceIdRef[];
764
+ effectiveConstraints?: Partial<Record<"refill" | "cancel" | "dripfeed", boolean>>;
765
+ policies?: unknown;
766
+ fallback?: FallbackSettings;
767
+ strictSafety?: boolean;
768
+ enforcePolicies?: boolean;
769
+ };
770
+ };
771
+ type FilterServicesForVisibleGroupResult = {
772
+ checks: ServiceCheck[];
773
+ diagnostics?: PolicyDiagnostic[];
774
+ };
775
+ declare function filterServicesForVisibleGroup(input: FilterServicesForVisibleGroupInput, deps: {
776
+ builder: Builder;
777
+ }): FilterServicesForVisibleGroupResult;
778
+
707
779
  type BuildOrderSnapshotSettings = {
708
780
  mode?: "prod" | "dev";
709
781
  hostDefaultQuantity?: number;
@@ -765,4 +837,4 @@ interface FallbackEditor {
765
837
  }
766
838
  declare function createFallbackEditor(options?: FallbackEditorOptions): FallbackEditor;
767
839
 
768
- export { type AncestryHit, type AnyNode, type Builder, type BuilderOptions, type FailedFallbackContext, type FallbackEditor, type FieldNode, type NodeIndex, type NodeKind, type NormaliseOptions, type OptionNode, type RateCoherenceDiagnostic, type RelKind, type TagNode, type UnknownNode, type WithAncestry, buildOrderSnapshot, collectFailedFallbacks, createBuilder, createFallbackEditor, createNodeIndex, getEligibleFallbacks, getFallbackRegistrationInfo, normalise, resolveServiceFallback, validate, validateAsync, validateRateCoherenceDeep };
840
+ export { type AncestryHit, type AnyNode, type Builder, type BuilderOptions, type FailedFallbackContext, type FallbackEditor, type FieldNode, type FilterServicesForVisibleGroupInput, type FilterServicesForVisibleGroupResult, type NodeIndex, type NodeKind, type NormaliseOptions, type OptionNode, type RateCoherenceDiagnostic, type RelKind, type ServiceCheck, type TagNode, type UnknownNode, type WithAncestry, buildOrderSnapshot, collectFailedFallbacks, createBuilder, createFallbackEditor, createNodeIndex, filterServicesForVisibleGroup, getEligibleFallbacks, getFallbackRegistrationInfo, normalise, normalizeFieldValidation, resolveServiceFallback, validate, validateAsync, validateRateCoherenceDeep };