@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.
@@ -13,7 +13,7 @@ declare class EventBus<E extends EventMap> {
13
13
  interface ButtonValue {
14
14
  id: string;
15
15
  value: string | number;
16
- service_id?: number;
16
+ service_id?: ServiceIdRef;
17
17
  pricing_role?: "base" | "utility";
18
18
  meta?: Record<string, unknown> & UtilityMark & WithQuantityDefault;
19
19
  }
@@ -22,16 +22,23 @@ type UtilityMode = "flat" | "per_quantity" | "per_value" | "percent";
22
22
  type QuantityRule$1 = {
23
23
  valueBy: "value" | "length" | "eval";
24
24
  code?: string;
25
+ multiply?: number;
26
+ clamp?: {
27
+ min?: number;
28
+ max?: number;
29
+ };
30
+ fallback?: number;
25
31
  };
26
32
  type UtilityLineItem = {
27
33
  nodeId: string;
28
34
  mode: UtilityMode;
29
35
  rate: number;
36
+ percentBase?: "service_total" | "base_service" | "all";
37
+ label?: string;
30
38
  inputs: {
31
39
  quantity: number;
32
40
  value?: Scalar | Scalar[];
33
- valueBy?: "value" | "length" | "eval";
34
- evalCodeUsed?: boolean;
41
+ valueBy?: "value" | "length";
35
42
  };
36
43
  };
37
44
  type ServiceFallbacks = ServiceFallback;
@@ -57,8 +64,16 @@ type SnapshotContext = {
57
64
  /** Client pruning policy used (so server can mirror/compare). */
58
65
  policy: {
59
66
  ratePolicy: {
60
- kind: "lte_primary" | "none";
61
- thresholdPct?: number;
67
+ kind: "eq_primary";
68
+ } | {
69
+ kind: "lte_primary";
70
+ pct: number;
71
+ } | {
72
+ kind: "within_pct";
73
+ pct: number;
74
+ } | {
75
+ kind: "at_least_pct_lower";
76
+ pct: number;
62
77
  };
63
78
  requireConstraintFit: boolean;
64
79
  };
@@ -166,7 +181,7 @@ type NodeRef$1 = {
166
181
  };
167
182
  type NodeMap = Map<string, NodeRef$1>;
168
183
 
169
- 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";
184
+ 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";
170
185
  type ValidationError = {
171
186
  code: ValidationCode;
172
187
  message: string;
@@ -222,7 +237,10 @@ type ValidatorOptions = {
222
237
  fallbackSettings?: FallbackSettings;
223
238
  };
224
239
  type RatePolicy = {
240
+ kind: "eq_primary";
241
+ } | {
225
242
  kind: "lte_primary";
243
+ pct: number;
226
244
  } | {
227
245
  kind: "within_pct";
228
246
  pct: number;
@@ -233,7 +251,7 @@ type RatePolicy = {
233
251
  type FallbackSettings = {
234
252
  /** Require fallbacks to satisfy tag constraints (dripfeed/refill/cancel) when a tag context is known. Default: true */
235
253
  requireConstraintFit?: boolean;
236
- /** Rate rule policy. Default: { kind: 'lte_primary' } i.e. candidate.rate <= primary.rate */
254
+ /** Rate rule policy. Default: { kind: 'lte_primary', pct: 5 }. */
237
255
  ratePolicy?: RatePolicy;
238
256
  /** When multiple candidates remain, choose first (priority) or cheapest. Default: 'priority' */
239
257
  selectionStrategy?: "priority" | "cheapest";
@@ -241,7 +259,6 @@ type FallbackSettings = {
241
259
  mode?: "strict" | "dev";
242
260
  };
243
261
 
244
- type ServiceIdRef = number | string;
245
262
  type NodeIdRef = string;
246
263
  type ServiceFallback = {
247
264
  /** Node-scoped fallbacks: prefer these when that node’s primary service fails */
@@ -308,6 +325,7 @@ type FallbackMutationOptions = {
308
325
  index?: number;
309
326
  };
310
327
 
328
+ type ServiceIdRef = IdType;
311
329
  type PricingRole = "base" | "utility";
312
330
  type FieldType = "custom" | (string & {});
313
331
  /** ── Marker types (live inside meta; non-breaking) ───────────────────── */
@@ -412,11 +430,25 @@ interface UiObject {
412
430
  required?: string[];
413
431
  order?: string[];
414
432
  }
433
+ type FieldValidationValueBy = "value" | "length" | "eval";
434
+ type FieldValidationOp = "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "between" | "in" | "nin" | "truthy" | "falsy" | "match";
435
+ type FieldValidationRule = {
436
+ valueBy?: FieldValidationValueBy;
437
+ op: FieldValidationOp;
438
+ value?: unknown;
439
+ min?: number;
440
+ max?: number;
441
+ values?: unknown[];
442
+ pattern?: string;
443
+ flags?: string;
444
+ code?: string;
445
+ message?: string;
446
+ };
415
447
  type FieldOption = {
416
448
  id: string;
417
449
  label: string;
418
450
  value?: string | number;
419
- service_id?: number;
451
+ service_id?: ServiceIdRef;
420
452
  pricing_role?: PricingRole;
421
453
  meta?: Record<string, unknown> & UtilityMark & WithQuantityDefault;
422
454
  };
@@ -429,6 +461,7 @@ type Field = BaseFieldUI & {
429
461
  description?: string;
430
462
  component?: string;
431
463
  pricing_role?: PricingRole;
464
+ validation?: FieldValidationRule[];
432
465
  meta?: Record<string, unknown> & QuantityMark & UtilityMark & {
433
466
  multi?: boolean;
434
467
  };
@@ -437,14 +470,14 @@ type Field = BaseFieldUI & {
437
470
  service_id?: undefined;
438
471
  } | ({
439
472
  button: true;
440
- service_id?: number;
473
+ service_id?: ServiceIdRef;
441
474
  } & WithQuantityDefault));
442
475
  type ConstraintKey = string;
443
476
  type Tag = {
444
477
  id: string;
445
478
  label: string;
446
479
  bind_id?: string;
447
- service_id?: number;
480
+ service_id?: ServiceIdRef;
448
481
  includes?: string[];
449
482
  excludes?: string[];
450
483
  meta?: Record<string, unknown> & WithQuantityDefault;
@@ -565,140 +598,9 @@ type CommentThread = {
565
598
  _sync?: "pending" | "synced" | "error";
566
599
  };
567
600
 
568
- type Viewport = {
569
- x: number;
570
- y: number;
571
- zoom: number;
572
- };
573
- type NodePos = {
574
- x: number;
575
- y: number;
576
- };
577
- type NodePositions = Record<string, NodePos>;
578
- type DraftWire = {
579
- from: string;
580
- kind: EdgeKind;
581
- };
582
- type CanvasState = {
583
- graph: GraphSnapshot;
584
- positions: NodePositions;
585
- selection: Set<string>;
586
- highlighted: Set<string>;
587
- hoverId?: string;
588
- viewport: Viewport;
589
- draftWire?: DraftWire;
590
- version: number;
591
- };
592
- type CanvasEvents = {
593
- "graph:update": GraphSnapshot;
594
- "state:change": CanvasState;
595
- "selection:change": {
596
- ids: string[];
597
- };
598
- "viewport:change": Viewport;
599
- "hover:change": {
600
- id?: string;
601
- };
602
- "wire:preview": {
603
- from: string;
604
- to?: string;
605
- kind: EdgeKind;
606
- };
607
- "wire:commit": {
608
- from: string;
609
- to: string;
610
- kind: EdgeKind;
611
- };
612
- "wire:cancel": {
613
- from: string;
614
- };
615
- error: {
616
- message: string;
617
- code?: string;
618
- meta?: any;
619
- };
620
- "comment:thread:create": {
621
- thread: CommentThread;
622
- };
623
- "comment:thread:update": {
624
- thread: CommentThread;
625
- };
626
- "comment:thread:delete": {
627
- threadId: string;
628
- };
629
- "comment:message:create": {
630
- threadId: string;
631
- message: CommentMessage;
632
- };
633
- "comment:resolve": {
634
- thread: CommentThread;
635
- resolved: boolean;
636
- };
637
- "comment:move": {
638
- thread: CommentThread;
639
- };
640
- "comment:select": {
641
- threadId?: string;
642
- };
643
- "edge:change": EdgeKind;
644
- "comment:sync": {
645
- op: "create_thread" | "add_message" | "edit_message" | "delete_message" | "move_thread" | "resolve_thread" | "delete_thread";
646
- threadId: string;
647
- messageId?: string;
648
- status: "scheduled" | "retrying" | "succeeded" | "failed" | "cancelled";
649
- attempt: number;
650
- nextDelayMs?: number;
651
- error?: any;
652
- };
653
- };
654
- type CanvasOptions = {
655
- initialViewport?: Partial<Viewport>;
656
- autoEmitState?: boolean;
657
- };
658
-
659
- type CommentNode = {
660
- id: string;
661
- text: string;
662
- status: "open" | "resolved";
663
- anchor?: {
664
- kind: "tag" | "field" | "option";
665
- id: string;
666
- };
667
- replies?: Array<{
668
- id: string;
669
- text: string;
670
- created_at: string;
671
- author?: string;
672
- }>;
673
- xy?: {
674
- x: number;
675
- y: number;
676
- };
677
- meta?: Record<string, unknown>;
678
- };
679
- type EdgeRoute = {
680
- id: string;
681
- points: Array<{
682
- x: number;
683
- y: number;
684
- }>;
685
- };
686
- type LayoutState = {
687
- canvas: CanvasState;
688
- edges?: EdgeRoute[];
689
- };
690
- type EditorSnapshot = {
691
- props: ServiceProps;
692
- layout?: LayoutState;
693
- comments?: CommentNode[];
694
- meta?: Record<string, unknown>;
695
- };
696
-
697
601
  /** Options you can set on the builder (used for validation/visibility) */
698
602
  type BuilderOptions = Omit<ValidatorOptions, "serviceMap"> & {
699
603
  serviceMap?: DgpServiceMap;
700
- /** max history entries for undo/redo */
701
- historyLimit?: number;
702
604
  /**
703
605
  * Field ids whose options should be shown as nodes in the graph.
704
606
  * If a field id is NOT in this set, its options are not materialized as nodes:
@@ -723,9 +625,6 @@ interface Builder {
723
625
  visibleFields(tagId: string, selectedOptionKeys?: string[]): string[];
724
626
  /** Update builder options (validator context etc.) */
725
627
  setOptions(patch: Partial<BuilderOptions>): void;
726
- /** History */
727
- undo(): boolean;
728
- redo(): boolean;
729
628
  /** Access the current props (already normalised) */
730
629
  getProps(): ServiceProps;
731
630
  /** Service map for validation/rules */
@@ -742,6 +641,27 @@ interface Builder {
742
641
  getNodeMap(): NodeMap;
743
642
  }
744
643
 
644
+ type PolicyDiagnostic = {
645
+ ruleIndex: number;
646
+ ruleId?: string;
647
+ severity: "error" | "warning";
648
+ message: string;
649
+ path?: string;
650
+ };
651
+
652
+ type ServiceCheck = {
653
+ id: ServiceIdRef;
654
+ ok: boolean;
655
+ fitsConstraints: boolean;
656
+ passesRate: boolean;
657
+ passesPolicies: boolean;
658
+ policyErrors?: string[];
659
+ policyWarnings?: string[];
660
+ reasons: Array<"constraint_mismatch" | "rate_policy" | "policy_error" | "missing_capability">;
661
+ cap?: DgpServiceCapability;
662
+ rate?: number;
663
+ };
664
+
745
665
  /**
746
666
  * Keep the editor contract exactly as discussed:
747
667
  * - mutates only ServiceFallback (internal clone)
@@ -857,6 +777,92 @@ declare class Selection {
857
777
  private findOptionById;
858
778
  }
859
779
 
780
+ type CatalogId = string;
781
+ type CatalogServiceId = string | number;
782
+ type CatalogSmartRule = {
783
+ type: "service-field";
784
+ field: string;
785
+ op: "eq" | "neq" | "in" | "contains" | "startsWith" | "endsWith" | "gt" | "gte" | "lt" | "lte" | "between" | "exists";
786
+ value?: unknown;
787
+ min?: number;
788
+ max?: number;
789
+ } | {
790
+ type: "policy-family";
791
+ key: string;
792
+ value?: unknown;
793
+ } | {
794
+ type: "compatibility";
795
+ scope: "tag" | "field" | "option" | "visible-group";
796
+ targetId?: string;
797
+ mode: "safe" | "assignable" | "same-family" | "conflicts";
798
+ };
799
+ type CatalogNodeBase = {
800
+ id: CatalogId;
801
+ label: string;
802
+ parentId?: CatalogId;
803
+ description?: string;
804
+ order?: number;
805
+ color?: string;
806
+ icon?: string;
807
+ collapsed?: boolean;
808
+ meta?: Record<string, unknown>;
809
+ };
810
+ type CatalogGroupNode = CatalogNodeBase & {
811
+ kind: "group";
812
+ serviceIds: CatalogServiceId[];
813
+ };
814
+ type CatalogSmartGroupNode = CatalogNodeBase & {
815
+ kind: "smart-group";
816
+ rules: CatalogSmartRule[];
817
+ match: "all" | "any";
818
+ resolvedServiceIds?: CatalogServiceId[];
819
+ resolvedAt?: number;
820
+ };
821
+ type CatalogNode = CatalogGroupNode | CatalogSmartGroupNode;
822
+ type CatalogViewMode = "all" | "grouped" | "smart" | "assigned";
823
+ interface ServiceCatalogState {
824
+ version: 1;
825
+ nodes: CatalogNode[];
826
+ activeNodeId?: CatalogId;
827
+ expandedIds?: CatalogId[];
828
+ pinnedNodeIds?: CatalogId[];
829
+ selectedServiceId?: CatalogServiceId;
830
+ viewMode?: CatalogViewMode;
831
+ meta?: Record<string, unknown>;
832
+ }
833
+
834
+ type EditorEvents = {
835
+ "editor:command": {
836
+ name: string;
837
+ payload?: any;
838
+ };
839
+ "editor:change": {
840
+ props: ServiceProps;
841
+ reason: string;
842
+ command?: string;
843
+ snapshot: EditorSnapshot;
844
+ };
845
+ "editor:undo": {
846
+ stackSize: number;
847
+ index: number;
848
+ };
849
+ "editor:redo": {
850
+ stackSize: number;
851
+ index: number;
852
+ };
853
+ "editor:error": {
854
+ message: string;
855
+ code?: string;
856
+ meta?: any;
857
+ };
858
+ "catalog:change": {
859
+ catalog?: ServiceCatalogState;
860
+ reason: string;
861
+ };
862
+ "catalog:active-change": {
863
+ activeNodeId?: string;
864
+ };
865
+ };
860
866
  type Command = {
861
867
  name: string;
862
868
  do(): void;
@@ -874,12 +880,134 @@ type EditorOptions = {
874
880
  selectionProps?: SelectionOptions;
875
881
  };
876
882
 
877
- type PolicyDiagnostic = {
878
- ruleIndex: number;
879
- ruleId?: string;
880
- severity: "error" | "warning";
881
- message: string;
882
- path?: string;
883
+ type Viewport = {
884
+ x: number;
885
+ y: number;
886
+ zoom: number;
887
+ };
888
+ type NodePos = {
889
+ x: number;
890
+ y: number;
891
+ };
892
+ type NodePositions = Record<string, NodePos>;
893
+ type DraftWire = {
894
+ from: string;
895
+ kind: EdgeKind;
896
+ };
897
+ type CanvasState = {
898
+ graph: GraphSnapshot;
899
+ positions: NodePositions;
900
+ selection: Set<string>;
901
+ highlighted: Set<string>;
902
+ hoverId?: string;
903
+ viewport: Viewport;
904
+ draftWire?: DraftWire;
905
+ version: number;
906
+ };
907
+ type CanvasEvents = {
908
+ "graph:update": GraphSnapshot;
909
+ "state:change": CanvasState;
910
+ "selection:change": {
911
+ ids: string[];
912
+ };
913
+ "viewport:change": Viewport;
914
+ "hover:change": {
915
+ id?: string;
916
+ };
917
+ "wire:preview": {
918
+ from: string;
919
+ to?: string;
920
+ kind: EdgeKind;
921
+ };
922
+ "wire:commit": {
923
+ from: string;
924
+ to: string;
925
+ kind: EdgeKind;
926
+ };
927
+ "wire:cancel": {
928
+ from: string;
929
+ };
930
+ error: {
931
+ message: string;
932
+ code?: string;
933
+ meta?: any;
934
+ };
935
+ "comment:thread:create": {
936
+ thread: CommentThread;
937
+ };
938
+ "comment:thread:update": {
939
+ thread: CommentThread;
940
+ };
941
+ "comment:thread:delete": {
942
+ threadId: string;
943
+ };
944
+ "comment:message:create": {
945
+ threadId: string;
946
+ message: CommentMessage;
947
+ };
948
+ "comment:resolve": {
949
+ thread: CommentThread;
950
+ resolved: boolean;
951
+ };
952
+ "comment:move": {
953
+ thread: CommentThread;
954
+ };
955
+ "comment:select": {
956
+ threadId?: string;
957
+ };
958
+ "edge:change": EdgeKind;
959
+ "comment:sync": {
960
+ op: "create_thread" | "add_message" | "edit_message" | "delete_message" | "move_thread" | "resolve_thread" | "delete_thread";
961
+ threadId: string;
962
+ messageId?: string;
963
+ status: "scheduled" | "retrying" | "succeeded" | "failed" | "cancelled";
964
+ attempt: number;
965
+ nextDelayMs?: number;
966
+ error?: any;
967
+ };
968
+ } & EditorEvents;
969
+ type CanvasOptions = {
970
+ initialViewport?: Partial<Viewport>;
971
+ autoEmitState?: boolean;
972
+ };
973
+
974
+ type CommentNode = {
975
+ id: string;
976
+ text: string;
977
+ status: "open" | "resolved";
978
+ anchor?: {
979
+ kind: "tag" | "field" | "option";
980
+ id: string;
981
+ };
982
+ replies?: Array<{
983
+ id: string;
984
+ text: string;
985
+ created_at: string;
986
+ author?: string;
987
+ }>;
988
+ xy?: {
989
+ x: number;
990
+ y: number;
991
+ };
992
+ meta?: Record<string, unknown>;
993
+ };
994
+ type EdgeRoute = {
995
+ id: string;
996
+ points: Array<{
997
+ x: number;
998
+ y: number;
999
+ }>;
1000
+ };
1001
+ type LayoutState = {
1002
+ canvas: CanvasState;
1003
+ edges?: EdgeRoute[];
1004
+ };
1005
+ type EditorSnapshot = {
1006
+ props: ServiceProps;
1007
+ layout?: LayoutState;
1008
+ comments?: CommentNode[];
1009
+ catalog?: ServiceCatalogState;
1010
+ meta?: Record<string, unknown>;
883
1011
  };
884
1012
 
885
1013
  interface BackendError {
@@ -1332,9 +1460,39 @@ type DuplicateOptions = {
1332
1460
  nameStrategy?: (old?: string) => string | undefined;
1333
1461
  optionIdStrategy?: (old: string) => string;
1334
1462
  };
1463
+ type EditorNodeLookup = {
1464
+ kind: "tag";
1465
+ data?: Tag;
1466
+ owners: {
1467
+ parentTagId?: string;
1468
+ };
1469
+ } | {
1470
+ kind: "field";
1471
+ data?: Field;
1472
+ owners: {
1473
+ bindTagIds: string[];
1474
+ };
1475
+ } | {
1476
+ kind: "option";
1477
+ data?: any;
1478
+ owners: {
1479
+ fieldId?: string;
1480
+ };
1481
+ };
1482
+ type QuantityRule = {
1483
+ valueBy: "value" | "length" | "eval";
1484
+ code?: string;
1485
+ multiply?: number;
1486
+ clamp?: {
1487
+ min?: number;
1488
+ max?: number;
1489
+ };
1490
+ fallback?: number;
1491
+ };
1492
+
1335
1493
  declare class Editor {
1336
- private builder;
1337
- private api;
1494
+ private readonly builder;
1495
+ private readonly api;
1338
1496
  private readonly opts;
1339
1497
  private history;
1340
1498
  private index;
@@ -1342,6 +1500,7 @@ declare class Editor {
1342
1500
  private txnLabel?;
1343
1501
  private stagedBefore?;
1344
1502
  private _lastPolicyDiagnostics?;
1503
+ private catalog?;
1345
1504
  constructor(builder: Builder, api: CanvasAPI, opts?: EditorOptions);
1346
1505
  isTagId(id: string): boolean;
1347
1506
  isFieldId(id: string): boolean;
@@ -1353,31 +1512,9 @@ declare class Editor {
1353
1512
  redo(): boolean;
1354
1513
  clearService(id: string): void;
1355
1514
  duplicate(ref: NodeRef, opts?: DuplicateOptions): string;
1356
- /**
1357
- * Update the display label for a node and refresh the graph so node labels stay in sync.
1358
- * Supports: tag ("t:*"), field ("f:*"), option ("o:*").
1359
- * IDs are NOT changed; only the human-readable label.
1360
- */
1361
1515
  reLabel(id: string, nextLabel: string): void;
1362
- /**
1363
- * Assign or change a field's `name`. Only allowed when the field (and its options) have NO service mapping.
1364
- * - If `nextName` is empty/blank → removes the `name`.
1365
- * - Emits an error if the field or any of its options carry a `service_id`.
1366
- * - Emits an error if `nextName` collides with an existing field's name (case-sensitive).
1367
- */
1368
1516
  setFieldName(fieldId: string, nextName: string | null | undefined): void;
1369
1517
  getLastPolicyDiagnostics(): PolicyDiagnostic[] | undefined;
1370
- private duplicateTag;
1371
- private duplicateField;
1372
- private duplicateOption;
1373
- private uniqueId;
1374
- private uniqueOptionId;
1375
- /**
1376
- * Reorder a node:
1377
- * - Tag: among its siblings (same bind_id) inside filters[]
1378
- * - Field: inside order_for_tags[scopeTagId] (you must pass scopeTagId)
1379
- * - Option: use placeOption() instead
1380
- */
1381
1518
  placeNode(id: string, opts: {
1382
1519
  scopeTagId?: string;
1383
1520
  beforeId?: string;
@@ -1392,20 +1529,20 @@ declare class Editor {
1392
1529
  addOption(fieldId: string, input: {
1393
1530
  id?: string;
1394
1531
  label: string;
1395
- service_id?: number;
1532
+ service_id?: ServiceIdRef;
1396
1533
  pricing_role?: "base" | "utility" | "addon";
1397
1534
  [k: string]: any;
1398
1535
  }): string;
1399
1536
  updateOption(optionId: string, patch: Partial<{
1400
1537
  label: string;
1401
- service_id: number;
1538
+ service_id: ServiceIdRef;
1402
1539
  pricing_role: "base" | "utility" | "addon";
1403
1540
  } & Record<string, any>>): void;
1404
1541
  removeOption(optionId: string): void;
1405
1542
  editLabel(id: string, label: string): void;
1406
1543
  editName(fieldId: string, name: string | undefined): void;
1407
1544
  setService(id: string, input: {
1408
- service_id?: number;
1545
+ service_id?: ServiceIdRef;
1409
1546
  pricing_role?: "base" | "utility";
1410
1547
  }): void;
1411
1548
  addTag(partial: Omit<Tag, "id" | "label"> & {
@@ -1414,6 +1551,11 @@ declare class Editor {
1414
1551
  }): void;
1415
1552
  updateTag(id: string, patch: Partial<Tag>): void;
1416
1553
  removeTag(id: string): void;
1554
+ addNotice(input: Omit<ServicePropsNotice, "id"> & {
1555
+ id?: string;
1556
+ }): string;
1557
+ updateNotice(id: string, patch: Partial<ServicePropsNotice>): void;
1558
+ removeNotice(id: string): void;
1417
1559
  addField(partial: Omit<Field, "id" | "label" | "type"> & {
1418
1560
  id?: string;
1419
1561
  label: string;
@@ -1422,45 +1564,76 @@ declare class Editor {
1422
1564
  updateField(id: string, patch: Partial<Field>): void;
1423
1565
  removeField(id: string): void;
1424
1566
  remove(id: string): void;
1425
- getNode(id: string): {
1426
- kind: "tag";
1427
- data?: Tag;
1428
- owners: {
1429
- parentTagId?: string;
1430
- };
1431
- } | {
1432
- kind: "field";
1433
- data?: Field;
1434
- owners: {
1435
- bindTagIds: string[];
1436
- };
1437
- } | {
1438
- kind: "option";
1439
- data?: any;
1440
- owners: {
1441
- fieldId?: string;
1442
- };
1443
- };
1567
+ getNode(id: string): EditorNodeLookup;
1444
1568
  getFieldQuantityRule(id: string): QuantityRule | undefined;
1445
1569
  setFieldQuantityRule(id: string, rule: unknown): void;
1446
1570
  clearFieldQuantityRule(id: string): void;
1447
- /** Walk ancestors for a tag and detect if parent→child would create a cycle */
1448
- private wouldCreateTagCycle;
1449
- private wouldCreateIncludeExcludeCycle;
1450
1571
  include(receiverId: string, idOrIds: string | string[]): void;
1451
1572
  exclude(receiverId: string, idOrIds: string | string[]): void;
1452
1573
  connect(kind: WireKind, fromId: string, toId: string): void;
1453
1574
  disconnect(kind: WireKind, fromId: string, toId: string): void;
1454
1575
  setConstraint(tagId: string, flag: string, value: boolean | undefined): void;
1455
- /**
1456
- * Clear a constraint override by removing the local constraint that conflicts with an ancestor.
1457
- */
1458
1576
  clearConstraintOverride(tagId: string, flag: string): void;
1459
- /**
1460
- * Clear a constraint from a tag and its descendants.
1461
- * If a descendant has an override, it assigns that override's value as local.
1462
- */
1463
1577
  clearConstraint(tagId: string, flag: string): void;
1578
+ getFieldValidation(id: string): FieldValidationRule[] | undefined;
1579
+ setFieldValidation(id: string, rules: unknown): void;
1580
+ clearFieldValidation(id: string): void;
1581
+ getCatalog(): ServiceCatalogState | undefined;
1582
+ setCatalog(next?: ServiceCatalogState): void;
1583
+ clearCatalog(): void;
1584
+ ensureCatalog(): ServiceCatalogState;
1585
+ createCatalogGroup(input: {
1586
+ id?: string;
1587
+ label: string;
1588
+ parentId?: string;
1589
+ description?: string;
1590
+ serviceIds?: CatalogServiceId[];
1591
+ collapsed?: boolean;
1592
+ order?: number;
1593
+ color?: string;
1594
+ icon?: string;
1595
+ }): string;
1596
+ createSmartCatalogGroup(input: {
1597
+ id?: string;
1598
+ label: string;
1599
+ parentId?: string;
1600
+ description?: string;
1601
+ rules: CatalogSmartRule[];
1602
+ match?: "all" | "any";
1603
+ collapsed?: boolean;
1604
+ order?: number;
1605
+ color?: string;
1606
+ icon?: string;
1607
+ }): string;
1608
+ updateCatalogNode(id: CatalogId, patch: Partial<Omit<CatalogNode, "id" | "kind">>): void;
1609
+ removeCatalogNode(id: CatalogId, opts?: {
1610
+ cascade?: boolean;
1611
+ }): void;
1612
+ moveCatalogNode(nodeId: CatalogId, opts: {
1613
+ parentId?: CatalogId;
1614
+ beforeId?: CatalogId;
1615
+ afterId?: CatalogId;
1616
+ index?: number;
1617
+ }): void;
1618
+ assignServicesToCatalogGroup(nodeId: CatalogId, serviceIds: CatalogServiceId[], mode?: "append" | "replace" | "remove"): void;
1619
+ setActiveCatalogNode(id?: CatalogId): void;
1620
+ setCatalogViewMode(mode: ServiceCatalogState["viewMode"]): void;
1621
+ setSelectedCatalogService(serviceId?: CatalogServiceId): void;
1622
+ toggleCatalogExpanded(id: CatalogId): void;
1623
+ setCatalogExpanded(id: CatalogId, expanded: boolean): void;
1624
+ toggleCatalogPinned(id: CatalogId): void;
1625
+ resolveSmartCatalogGroup(nodeId: CatalogId, candidates: CatalogServiceId[], matchers: {
1626
+ serviceField?: (candidate: CatalogServiceId, rule: Extract<CatalogSmartRule, {
1627
+ type: "service-field";
1628
+ }>) => boolean;
1629
+ policyFamily?: (candidate: CatalogServiceId, rule: Extract<CatalogSmartRule, {
1630
+ type: "policy-family";
1631
+ }>) => boolean;
1632
+ compatibility?: (candidate: CatalogServiceId, rule: Extract<CatalogSmartRule, {
1633
+ type: "compatibility";
1634
+ }>) => boolean;
1635
+ }): CatalogServiceId[];
1636
+ private replaceCatalog;
1464
1637
  private replaceProps;
1465
1638
  private patchProps;
1466
1639
  private afterMutation;
@@ -1468,47 +1641,17 @@ declare class Editor {
1468
1641
  private makeSnapshot;
1469
1642
  private loadSnapshot;
1470
1643
  private pushHistory;
1471
- private genId;
1472
1644
  private emit;
1473
- /**
1474
- * Suggest/filter candidate services against the current visible-group
1475
- * (single tag) context.
1476
- *
1477
- * - Excludes services already used in this group.
1478
- * - Applies capability presence, tag constraints, rate policy, and compiled policies.
1479
- *
1480
- * @param candidates service ids to evaluate
1481
- * @param ctx
1482
- * @param ctx.tagId active visible-group tag id
1483
- * @param ctx.usedServiceIds services already selected for this visible group (first is treated as "primary" for rate policy)
1484
- * @param ctx.effectiveConstraints effective constraints for the active tag (dripfeed/refill/cancel)
1485
- * @param ctx.policies raw JSON policies (will be compiled via compilePolicies)
1486
- * @param ctx.fallback fallback/rate settings (defaults applied if omitted)
1487
- */
1488
1645
  filterServicesForVisibleGroup(candidates: Array<number | string>, ctx: {
1489
1646
  tagId: string;
1647
+ selectedButtons?: string[];
1490
1648
  usedServiceIds: Array<number | string>;
1491
1649
  effectiveConstraints?: Partial<Record<"refill" | "cancel" | "dripfeed", boolean>>;
1492
1650
  policies?: unknown;
1493
1651
  fallback?: FallbackSettings;
1494
1652
  }): ServiceCheck[];
1653
+ private moduleCtx;
1495
1654
  }
1496
- type QuantityRule = {
1497
- valueBy: "value" | "length" | "eval";
1498
- code?: string;
1499
- };
1500
- type ServiceCheck = {
1501
- id: number | string;
1502
- ok: boolean;
1503
- fitsConstraints: boolean;
1504
- passesRate: boolean;
1505
- passesPolicies: boolean;
1506
- policyErrors?: string[];
1507
- policyWarnings?: string[];
1508
- reasons: Array<"constraint_mismatch" | "rate_policy" | "policy_error" | "missing_capability">;
1509
- cap?: DgpServiceCapability;
1510
- rate?: number;
1511
- };
1512
1655
 
1513
1656
  declare class CanvasAPI {
1514
1657
  private bus;
@@ -1519,8 +1662,8 @@ declare class CanvasAPI {
1519
1662
  readonly comments: CommentsAPI;
1520
1663
  readonly selection: Selection;
1521
1664
  constructor(builder: Builder, opts?: CanvasOptions & CanvasBackendOptions);
1522
- on: <K extends keyof CanvasEvents>(event: K, handler: (payload: CanvasEvents[K]) => void) => () => void;
1523
- once: <K extends keyof CanvasEvents>(event: K, handler: (payload: CanvasEvents[K]) => void) => () => void;
1665
+ on: <K extends "error" | "graph:update" | "state:change" | "selection:change" | "viewport:change" | "hover:change" | "wire:preview" | "wire:commit" | "wire:cancel" | "comment:thread:create" | "comment:thread:update" | "comment:thread:delete" | "comment:message:create" | "comment:resolve" | "comment:move" | "comment:select" | "edge:change" | "comment:sync" | keyof EditorEvents>(event: K, handler: (payload: CanvasEvents[K]) => void) => () => void;
1666
+ once: <K extends "error" | "graph:update" | "state:change" | "selection:change" | "viewport:change" | "hover:change" | "wire:preview" | "wire:commit" | "wire:cancel" | "comment:thread:create" | "comment:thread:update" | "comment:thread:delete" | "comment:message:create" | "comment:resolve" | "comment:move" | "comment:select" | "edge:change" | "comment:sync" | keyof EditorEvents>(event: K, handler: (payload: CanvasEvents[K]) => void) => () => void;
1524
1667
  emit<K extends keyof CanvasEvents>(event: K, payload: CanvasEvents[K]): void;
1525
1668
  snapshot(): CanvasState;
1526
1669
  getGraph(): GraphSnapshot;
@@ -1529,7 +1672,7 @@ declare class CanvasAPI {
1529
1672
  refreshGraph(): void;
1530
1673
  setPositions(pos: NodePositions): void;
1531
1674
  setPosition(id: string, x: number, y: number): void;
1532
- select(ids: string[] | Set<string>): void;
1675
+ select(ids: string[] | Set<string>, primary?: string): void;
1533
1676
  addToSelection(ids: string[] | Set<string>): void;
1534
1677
  toggleSelection(id: string): void;
1535
1678
  clearSelection(): void;
@@ -1543,7 +1686,6 @@ declare class CanvasAPI {
1543
1686
  cancelWire(): void;
1544
1687
  private bump;
1545
1688
  dispose(): void;
1546
- undo(): void;
1547
1689
  private edgeRel;
1548
1690
  getEdgeRel(): EdgeKind;
1549
1691
  setEdgeRel(rel: EdgeKind): void;
@@ -1603,6 +1745,8 @@ type FormApi = {
1603
1745
  values: Dict;
1604
1746
  valid: boolean;
1605
1747
  };
1748
+ setFieldError(id: any, message: string): void;
1749
+ setErrors(errors: Record<string, string>): void;
1606
1750
  };
1607
1751
  declare function useFormApi(): FormApi;
1608
1752
  declare function useOptionalFormApi(): FormApi | null;
@@ -1845,6 +1989,7 @@ type UseOrderFlowReturn = {
1845
1989
  }) => void;
1846
1990
  /** VALIDATES via form.submit() */
1847
1991
  buildSnapshot: () => OrderSnapshot | undefined;
1992
+ notices: ServicePropsNotice[];
1848
1993
  fallbackPolicy: FallbackSettings;
1849
1994
  setFallbackPolicy: (next: FallbackSettings) => void;
1850
1995
  };
@@ -1937,7 +2082,18 @@ declare function VirtualServiceList({ items, selected, onToggle, height, rowHeig
1937
2082
 
1938
2083
  type EditorSettings = {
1939
2084
  requireConstraintFit: boolean;
1940
- ratePolicy: "lte_primary" | "ignore";
2085
+ ratePolicy: {
2086
+ kind: "eq_primary";
2087
+ } | {
2088
+ kind: "lte_primary";
2089
+ pct: number;
2090
+ } | {
2091
+ kind: "within_pct";
2092
+ pct: number;
2093
+ } | {
2094
+ kind: "at_least_pct_lower";
2095
+ pct: number;
2096
+ };
1941
2097
  selectionStrategy: "priority" | "cheapest";
1942
2098
  mode: "strict" | "dev";
1943
2099
  };