@timeax/digital-service-engine 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,146 @@ import * as React from 'react';
2
2
  import React__default, { ReactNode } from 'react';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
4
 
5
+ type TimeRangeEstimate = {
6
+ min_seconds?: number;
7
+ max_seconds?: number;
8
+ label?: string;
9
+ meta?: Record<string, unknown>;
10
+ };
11
+ type SpeedEstimate = {
12
+ amount?: number;
13
+ per?: "minute" | "hour" | "day" | "week" | "month";
14
+ unit?: string;
15
+ label?: string;
16
+ meta?: Record<string, unknown>;
17
+ };
18
+ type ServiceEstimates = {
19
+ start?: TimeRangeEstimate;
20
+ speed?: SpeedEstimate;
21
+ average?: TimeRangeEstimate;
22
+ meta?: Record<string, unknown>;
23
+ };
24
+ type ServiceFlag = {
25
+ enabled: boolean;
26
+ description: string;
27
+ meta?: Record<string, unknown>;
28
+ };
29
+ type IdType = string | number;
30
+ type ServiceFlags = Record<string, ServiceFlag>;
31
+ type DgpServiceCapability = {
32
+ id: IdType;
33
+ name?: string;
34
+ rate: number;
35
+ min?: number;
36
+ max?: number;
37
+ category?: string;
38
+ flags?: ServiceFlags;
39
+ estimates?: ServiceEstimates;
40
+ meta?: Record<string, unknown>;
41
+ [x: string]: any;
42
+ };
43
+ type DgpServiceMap = Record<string, DgpServiceCapability> & Record<number, DgpServiceCapability>;
44
+
45
+ type NodeRef$1 = {
46
+ kind: "tag";
47
+ id: string;
48
+ node: Tag;
49
+ } | {
50
+ kind: "field";
51
+ id: string;
52
+ node: Field;
53
+ } | {
54
+ kind: "option";
55
+ id: string;
56
+ node: FieldOption;
57
+ fieldId: string;
58
+ };
59
+ type NodeMap = Map<string, NodeRef$1>;
60
+
61
+ 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";
62
+ type ValidationError = {
63
+ code: ValidationCode;
64
+ message: string;
65
+ severity: "error" | "warning" | "info";
66
+ nodeId?: string;
67
+ details?: Record<string, unknown> & {
68
+ affectedIds?: string[];
69
+ };
70
+ };
71
+ type ServiceWhereOp = "eq" | "neq" | "in" | "nin" | "exists" | "truthy" | "falsy" | "sw";
72
+ /**
73
+ * Host-extensible service filter clause.
74
+ * `path` should usually be "service.<prop>" or "service.meta.<prop>" etc.
75
+ */
76
+ type ServiceWhereClause = {
77
+ path: string;
78
+ op?: ServiceWhereOp;
79
+ value?: unknown;
80
+ };
81
+ type DynamicRule = {
82
+ id: string;
83
+ label?: string;
84
+ scope: "global" | "visible_group";
85
+ subject: "services";
86
+ /**
87
+ * Package-level filter surface:
88
+ * - role/tag/field are core to the builder schema
89
+ * - where[] allows hosts to filter against extra service properties (handler_id/platform_id/type/key/category_id/etc.)
90
+ */
91
+ filter?: {
92
+ role?: "base" | "utility" | "both";
93
+ tag_id?: string | string[];
94
+ field_id?: string | string[];
95
+ where?: ServiceWhereClause[];
96
+ };
97
+ /**
98
+ * Projection is intentionally open:
99
+ * hosts may project custom service properties.
100
+ */
101
+ projection?: "service.id" | "service.name" | "service.rate" | "service.min" | "service.max" | "service.category" | string;
102
+ op: "all_equal" | "unique" | "no_mix" | "all_true" | "any_true" | "max_count" | "min_count";
103
+ value?: number | boolean;
104
+ severity?: "error" | "warning";
105
+ message?: string;
106
+ };
107
+ type ValidatorOptions = {
108
+ serviceMap?: DgpServiceMap;
109
+ nodeMap?: NodeMap;
110
+ allowUnsafe?: boolean;
111
+ selectedOptionKeys?: string[];
112
+ globalUtilityGuard?: boolean;
113
+ policies?: DynamicRule[];
114
+ fallbackSettings?: FallbackSettings;
115
+ };
116
+ type RatePolicy = {
117
+ kind: "lte_primary";
118
+ } | {
119
+ kind: "within_pct";
120
+ pct: number;
121
+ } | {
122
+ kind: "at_least_pct_lower";
123
+ pct: number;
124
+ };
125
+ type FallbackSettings = {
126
+ /** Require fallbacks to satisfy tag constraints (dripfeed/refill/cancel) when a tag context is known. Default: true */
127
+ requireConstraintFit?: boolean;
128
+ /** Rate rule policy. Default: { kind: 'lte_primary' } i.e. candidate.rate <= primary.rate */
129
+ ratePolicy?: RatePolicy;
130
+ /** When multiple candidates remain, choose first (priority) or cheapest. Default: 'priority' */
131
+ selectionStrategy?: "priority" | "cheapest";
132
+ /** Validation mode: 'strict' → node-scoped violations reported as ValidationError; 'dev' → only collect diagnostics. Default: 'strict' */
133
+ mode?: "strict" | "dev";
134
+ };
135
+
136
+ type ServiceIdRef = number | string;
137
+ type NodeIdRef = string;
138
+ type ServiceFallback = {
139
+ /** Node-scoped fallbacks: prefer these when that node’s primary service fails */
140
+ nodes?: Record<NodeIdRef, ServiceIdRef[]>;
141
+ /** Primary→fallback list used when no node-scoped entry is present */
142
+ global?: Record<ServiceIdRef, ServiceIdRef[]>;
143
+ };
144
+
5
145
  type PricingRole = "base" | "utility";
6
146
  type FieldType = "custom" | (string & {});
7
147
  /** ── Marker types (live inside meta; non-breaking) ───────────────────── */
@@ -100,14 +240,6 @@ type ServiceProps = {
100
240
  name?: string;
101
241
  notices?: ServicePropsNotice[];
102
242
  };
103
- type ServiceIdRef = number | string;
104
- type NodeIdRef = string;
105
- type ServiceFallback = {
106
- /** Node-scoped fallbacks: prefer these when that node’s primary service fails */
107
- nodes?: Record<NodeIdRef, ServiceIdRef[]>;
108
- /** Primary→fallback list used when no node-scoped entry is present */
109
- global?: Record<ServiceIdRef, ServiceIdRef[]>;
110
- };
111
243
  type NoticeType = "public" | "private";
112
244
  type NoticeSeverity = "info" | "warning" | "error";
113
245
  /**
@@ -327,137 +459,6 @@ type EditorSnapshot = {
327
459
  meta?: Record<string, unknown>;
328
460
  };
329
461
 
330
- type TimeRangeEstimate = {
331
- min_seconds?: number;
332
- max_seconds?: number;
333
- label?: string;
334
- meta?: Record<string, unknown>;
335
- };
336
- type SpeedEstimate = {
337
- amount?: number;
338
- per?: "minute" | "hour" | "day" | "week" | "month";
339
- unit?: string;
340
- label?: string;
341
- meta?: Record<string, unknown>;
342
- };
343
- type ServiceEstimates = {
344
- start?: TimeRangeEstimate;
345
- speed?: SpeedEstimate;
346
- average?: TimeRangeEstimate;
347
- meta?: Record<string, unknown>;
348
- };
349
- type ServiceFlag = {
350
- enabled: boolean;
351
- description: string;
352
- meta?: Record<string, unknown>;
353
- };
354
- type IdType = string | number;
355
- type ServiceFlags = Record<string, ServiceFlag>;
356
- type DgpServiceCapability = {
357
- id: IdType;
358
- name?: string;
359
- rate: number;
360
- min?: number;
361
- max?: number;
362
- category?: string;
363
- flags?: ServiceFlags;
364
- estimates?: ServiceEstimates;
365
- meta?: Record<string, unknown>;
366
- [x: string]: any;
367
- };
368
- type DgpServiceMap = Record<string, DgpServiceCapability> & Record<number, DgpServiceCapability>;
369
-
370
- type NodeRef$1 = {
371
- kind: "tag";
372
- id: string;
373
- node: Tag;
374
- } | {
375
- kind: "field";
376
- id: string;
377
- node: Field;
378
- } | {
379
- kind: "option";
380
- id: string;
381
- node: FieldOption;
382
- fieldId: string;
383
- };
384
- type NodeMap = Map<string, NodeRef$1>;
385
-
386
- 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";
387
- type ValidationError = {
388
- code: ValidationCode;
389
- message: string;
390
- severity: "error" | "warning" | "info";
391
- nodeId?: string;
392
- details?: Record<string, unknown> & {
393
- affectedIds?: string[];
394
- };
395
- };
396
- type ServiceWhereOp = "eq" | "neq" | "in" | "nin" | "exists" | "truthy" | "falsy" | "sw";
397
- /**
398
- * Host-extensible service filter clause.
399
- * `path` should usually be "service.<prop>" or "service.meta.<prop>" etc.
400
- */
401
- type ServiceWhereClause = {
402
- path: string;
403
- op?: ServiceWhereOp;
404
- value?: unknown;
405
- };
406
- type DynamicRule = {
407
- id: string;
408
- label?: string;
409
- scope: "global" | "visible_group";
410
- subject: "services";
411
- /**
412
- * Package-level filter surface:
413
- * - role/tag/field are core to the builder schema
414
- * - where[] allows hosts to filter against extra service properties (handler_id/platform_id/type/key/category_id/etc.)
415
- */
416
- filter?: {
417
- role?: "base" | "utility" | "both";
418
- tag_id?: string | string[];
419
- field_id?: string | string[];
420
- where?: ServiceWhereClause[];
421
- };
422
- /**
423
- * Projection is intentionally open:
424
- * hosts may project custom service properties.
425
- */
426
- projection?: "service.id" | "service.name" | "service.rate" | "service.min" | "service.max" | "service.category" | string;
427
- op: "all_equal" | "unique" | "no_mix" | "all_true" | "any_true" | "max_count" | "min_count";
428
- value?: number | boolean;
429
- severity?: "error" | "warning";
430
- message?: string;
431
- };
432
- type ValidatorOptions = {
433
- serviceMap?: DgpServiceMap;
434
- nodeMap?: NodeMap;
435
- allowUnsafe?: boolean;
436
- selectedOptionKeys?: string[];
437
- globalUtilityGuard?: boolean;
438
- policies?: DynamicRule[];
439
- fallbackSettings?: FallbackSettings;
440
- };
441
- type RatePolicy = {
442
- kind: "lte_primary";
443
- } | {
444
- kind: "within_pct";
445
- pct: number;
446
- } | {
447
- kind: "at_least_pct_lower";
448
- pct: number;
449
- };
450
- type FallbackSettings = {
451
- /** Require fallbacks to satisfy tag constraints (dripfeed/refill/cancel) when a tag context is known. Default: true */
452
- requireConstraintFit?: boolean;
453
- /** Rate rule policy. Default: { kind: 'lte_primary' } i.e. candidate.rate <= primary.rate */
454
- ratePolicy?: RatePolicy;
455
- /** When multiple candidates remain, choose first (priority) or cheapest. Default: 'priority' */
456
- selectionStrategy?: "priority" | "cheapest";
457
- /** Validation mode: 'strict' → node-scoped violations reported as ValidationError; 'dev' → only collect diagnostics. Default: 'strict' */
458
- mode?: "strict" | "dev";
459
- };
460
-
461
462
  /** Options you can set on the builder (used for validation/visibility) */
462
463
  type BuilderOptions = Omit<ValidatorOptions, "serviceMap"> & {
463
464
  serviceMap?: DgpServiceMap;
@@ -630,10 +631,34 @@ declare class Selection {
630
631
  currentTag(): string | undefined;
631
632
  onChange(fn: Listener): () => void;
632
633
  visibleGroup(): VisibleGroupResult;
634
+ /**
635
+ * Build a fieldId -> triggerKeys[] map from the current selection set.
636
+ *
637
+ * What counts as a "button selection" (trigger key):
638
+ * - field key where the field has button === true (e.g. "f:dripfeed")
639
+ * - option key (e.g. "o:fast")
640
+ * - composite key "fieldId::optionId" (e.g. "f:speed::o:fast")
641
+ *
642
+ * Grouping:
643
+ * - button-field trigger groups under its own fieldId
644
+ * - option/composite groups under the option's owning fieldId (from nodeMap)
645
+ *
646
+ * Deterministic:
647
+ * - preserves selection insertion order
648
+ * - de-dupes per field
649
+ */
650
+ buttonSelectionsByFieldId(): Record<string, string[]>;
651
+ /**
652
+ * Returns only selection keys that are valid "trigger buttons":
653
+ * - field keys where field.button === true
654
+ * - option keys
655
+ * - composite keys "fieldId::optionId" (validated by optionId)
656
+ * Excludes tags and non-button fields.
657
+ */
658
+ selectedButtons(): string[];
633
659
  private emit;
634
660
  private updateCurrentTagFrom;
635
661
  private resolveTagContextId;
636
- private selectedButtonTriggerIds;
637
662
  private computeGroupForTag;
638
663
  private addServiceByRole;
639
664
  private findOptionById;
@@ -2,6 +2,146 @@ import * as React from 'react';
2
2
  import React__default, { ReactNode } from 'react';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
4
 
5
+ type TimeRangeEstimate = {
6
+ min_seconds?: number;
7
+ max_seconds?: number;
8
+ label?: string;
9
+ meta?: Record<string, unknown>;
10
+ };
11
+ type SpeedEstimate = {
12
+ amount?: number;
13
+ per?: "minute" | "hour" | "day" | "week" | "month";
14
+ unit?: string;
15
+ label?: string;
16
+ meta?: Record<string, unknown>;
17
+ };
18
+ type ServiceEstimates = {
19
+ start?: TimeRangeEstimate;
20
+ speed?: SpeedEstimate;
21
+ average?: TimeRangeEstimate;
22
+ meta?: Record<string, unknown>;
23
+ };
24
+ type ServiceFlag = {
25
+ enabled: boolean;
26
+ description: string;
27
+ meta?: Record<string, unknown>;
28
+ };
29
+ type IdType = string | number;
30
+ type ServiceFlags = Record<string, ServiceFlag>;
31
+ type DgpServiceCapability = {
32
+ id: IdType;
33
+ name?: string;
34
+ rate: number;
35
+ min?: number;
36
+ max?: number;
37
+ category?: string;
38
+ flags?: ServiceFlags;
39
+ estimates?: ServiceEstimates;
40
+ meta?: Record<string, unknown>;
41
+ [x: string]: any;
42
+ };
43
+ type DgpServiceMap = Record<string, DgpServiceCapability> & Record<number, DgpServiceCapability>;
44
+
45
+ type NodeRef$1 = {
46
+ kind: "tag";
47
+ id: string;
48
+ node: Tag;
49
+ } | {
50
+ kind: "field";
51
+ id: string;
52
+ node: Field;
53
+ } | {
54
+ kind: "option";
55
+ id: string;
56
+ node: FieldOption;
57
+ fieldId: string;
58
+ };
59
+ type NodeMap = Map<string, NodeRef$1>;
60
+
61
+ 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";
62
+ type ValidationError = {
63
+ code: ValidationCode;
64
+ message: string;
65
+ severity: "error" | "warning" | "info";
66
+ nodeId?: string;
67
+ details?: Record<string, unknown> & {
68
+ affectedIds?: string[];
69
+ };
70
+ };
71
+ type ServiceWhereOp = "eq" | "neq" | "in" | "nin" | "exists" | "truthy" | "falsy" | "sw";
72
+ /**
73
+ * Host-extensible service filter clause.
74
+ * `path` should usually be "service.<prop>" or "service.meta.<prop>" etc.
75
+ */
76
+ type ServiceWhereClause = {
77
+ path: string;
78
+ op?: ServiceWhereOp;
79
+ value?: unknown;
80
+ };
81
+ type DynamicRule = {
82
+ id: string;
83
+ label?: string;
84
+ scope: "global" | "visible_group";
85
+ subject: "services";
86
+ /**
87
+ * Package-level filter surface:
88
+ * - role/tag/field are core to the builder schema
89
+ * - where[] allows hosts to filter against extra service properties (handler_id/platform_id/type/key/category_id/etc.)
90
+ */
91
+ filter?: {
92
+ role?: "base" | "utility" | "both";
93
+ tag_id?: string | string[];
94
+ field_id?: string | string[];
95
+ where?: ServiceWhereClause[];
96
+ };
97
+ /**
98
+ * Projection is intentionally open:
99
+ * hosts may project custom service properties.
100
+ */
101
+ projection?: "service.id" | "service.name" | "service.rate" | "service.min" | "service.max" | "service.category" | string;
102
+ op: "all_equal" | "unique" | "no_mix" | "all_true" | "any_true" | "max_count" | "min_count";
103
+ value?: number | boolean;
104
+ severity?: "error" | "warning";
105
+ message?: string;
106
+ };
107
+ type ValidatorOptions = {
108
+ serviceMap?: DgpServiceMap;
109
+ nodeMap?: NodeMap;
110
+ allowUnsafe?: boolean;
111
+ selectedOptionKeys?: string[];
112
+ globalUtilityGuard?: boolean;
113
+ policies?: DynamicRule[];
114
+ fallbackSettings?: FallbackSettings;
115
+ };
116
+ type RatePolicy = {
117
+ kind: "lte_primary";
118
+ } | {
119
+ kind: "within_pct";
120
+ pct: number;
121
+ } | {
122
+ kind: "at_least_pct_lower";
123
+ pct: number;
124
+ };
125
+ type FallbackSettings = {
126
+ /** Require fallbacks to satisfy tag constraints (dripfeed/refill/cancel) when a tag context is known. Default: true */
127
+ requireConstraintFit?: boolean;
128
+ /** Rate rule policy. Default: { kind: 'lte_primary' } i.e. candidate.rate <= primary.rate */
129
+ ratePolicy?: RatePolicy;
130
+ /** When multiple candidates remain, choose first (priority) or cheapest. Default: 'priority' */
131
+ selectionStrategy?: "priority" | "cheapest";
132
+ /** Validation mode: 'strict' → node-scoped violations reported as ValidationError; 'dev' → only collect diagnostics. Default: 'strict' */
133
+ mode?: "strict" | "dev";
134
+ };
135
+
136
+ type ServiceIdRef = number | string;
137
+ type NodeIdRef = string;
138
+ type ServiceFallback = {
139
+ /** Node-scoped fallbacks: prefer these when that node’s primary service fails */
140
+ nodes?: Record<NodeIdRef, ServiceIdRef[]>;
141
+ /** Primary→fallback list used when no node-scoped entry is present */
142
+ global?: Record<ServiceIdRef, ServiceIdRef[]>;
143
+ };
144
+
5
145
  type PricingRole = "base" | "utility";
6
146
  type FieldType = "custom" | (string & {});
7
147
  /** ── Marker types (live inside meta; non-breaking) ───────────────────── */
@@ -100,14 +240,6 @@ type ServiceProps = {
100
240
  name?: string;
101
241
  notices?: ServicePropsNotice[];
102
242
  };
103
- type ServiceIdRef = number | string;
104
- type NodeIdRef = string;
105
- type ServiceFallback = {
106
- /** Node-scoped fallbacks: prefer these when that node’s primary service fails */
107
- nodes?: Record<NodeIdRef, ServiceIdRef[]>;
108
- /** Primary→fallback list used when no node-scoped entry is present */
109
- global?: Record<ServiceIdRef, ServiceIdRef[]>;
110
- };
111
243
  type NoticeType = "public" | "private";
112
244
  type NoticeSeverity = "info" | "warning" | "error";
113
245
  /**
@@ -327,137 +459,6 @@ type EditorSnapshot = {
327
459
  meta?: Record<string, unknown>;
328
460
  };
329
461
 
330
- type TimeRangeEstimate = {
331
- min_seconds?: number;
332
- max_seconds?: number;
333
- label?: string;
334
- meta?: Record<string, unknown>;
335
- };
336
- type SpeedEstimate = {
337
- amount?: number;
338
- per?: "minute" | "hour" | "day" | "week" | "month";
339
- unit?: string;
340
- label?: string;
341
- meta?: Record<string, unknown>;
342
- };
343
- type ServiceEstimates = {
344
- start?: TimeRangeEstimate;
345
- speed?: SpeedEstimate;
346
- average?: TimeRangeEstimate;
347
- meta?: Record<string, unknown>;
348
- };
349
- type ServiceFlag = {
350
- enabled: boolean;
351
- description: string;
352
- meta?: Record<string, unknown>;
353
- };
354
- type IdType = string | number;
355
- type ServiceFlags = Record<string, ServiceFlag>;
356
- type DgpServiceCapability = {
357
- id: IdType;
358
- name?: string;
359
- rate: number;
360
- min?: number;
361
- max?: number;
362
- category?: string;
363
- flags?: ServiceFlags;
364
- estimates?: ServiceEstimates;
365
- meta?: Record<string, unknown>;
366
- [x: string]: any;
367
- };
368
- type DgpServiceMap = Record<string, DgpServiceCapability> & Record<number, DgpServiceCapability>;
369
-
370
- type NodeRef$1 = {
371
- kind: "tag";
372
- id: string;
373
- node: Tag;
374
- } | {
375
- kind: "field";
376
- id: string;
377
- node: Field;
378
- } | {
379
- kind: "option";
380
- id: string;
381
- node: FieldOption;
382
- fieldId: string;
383
- };
384
- type NodeMap = Map<string, NodeRef$1>;
385
-
386
- 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";
387
- type ValidationError = {
388
- code: ValidationCode;
389
- message: string;
390
- severity: "error" | "warning" | "info";
391
- nodeId?: string;
392
- details?: Record<string, unknown> & {
393
- affectedIds?: string[];
394
- };
395
- };
396
- type ServiceWhereOp = "eq" | "neq" | "in" | "nin" | "exists" | "truthy" | "falsy" | "sw";
397
- /**
398
- * Host-extensible service filter clause.
399
- * `path` should usually be "service.<prop>" or "service.meta.<prop>" etc.
400
- */
401
- type ServiceWhereClause = {
402
- path: string;
403
- op?: ServiceWhereOp;
404
- value?: unknown;
405
- };
406
- type DynamicRule = {
407
- id: string;
408
- label?: string;
409
- scope: "global" | "visible_group";
410
- subject: "services";
411
- /**
412
- * Package-level filter surface:
413
- * - role/tag/field are core to the builder schema
414
- * - where[] allows hosts to filter against extra service properties (handler_id/platform_id/type/key/category_id/etc.)
415
- */
416
- filter?: {
417
- role?: "base" | "utility" | "both";
418
- tag_id?: string | string[];
419
- field_id?: string | string[];
420
- where?: ServiceWhereClause[];
421
- };
422
- /**
423
- * Projection is intentionally open:
424
- * hosts may project custom service properties.
425
- */
426
- projection?: "service.id" | "service.name" | "service.rate" | "service.min" | "service.max" | "service.category" | string;
427
- op: "all_equal" | "unique" | "no_mix" | "all_true" | "any_true" | "max_count" | "min_count";
428
- value?: number | boolean;
429
- severity?: "error" | "warning";
430
- message?: string;
431
- };
432
- type ValidatorOptions = {
433
- serviceMap?: DgpServiceMap;
434
- nodeMap?: NodeMap;
435
- allowUnsafe?: boolean;
436
- selectedOptionKeys?: string[];
437
- globalUtilityGuard?: boolean;
438
- policies?: DynamicRule[];
439
- fallbackSettings?: FallbackSettings;
440
- };
441
- type RatePolicy = {
442
- kind: "lte_primary";
443
- } | {
444
- kind: "within_pct";
445
- pct: number;
446
- } | {
447
- kind: "at_least_pct_lower";
448
- pct: number;
449
- };
450
- type FallbackSettings = {
451
- /** Require fallbacks to satisfy tag constraints (dripfeed/refill/cancel) when a tag context is known. Default: true */
452
- requireConstraintFit?: boolean;
453
- /** Rate rule policy. Default: { kind: 'lte_primary' } i.e. candidate.rate <= primary.rate */
454
- ratePolicy?: RatePolicy;
455
- /** When multiple candidates remain, choose first (priority) or cheapest. Default: 'priority' */
456
- selectionStrategy?: "priority" | "cheapest";
457
- /** Validation mode: 'strict' → node-scoped violations reported as ValidationError; 'dev' → only collect diagnostics. Default: 'strict' */
458
- mode?: "strict" | "dev";
459
- };
460
-
461
462
  /** Options you can set on the builder (used for validation/visibility) */
462
463
  type BuilderOptions = Omit<ValidatorOptions, "serviceMap"> & {
463
464
  serviceMap?: DgpServiceMap;
@@ -630,10 +631,34 @@ declare class Selection {
630
631
  currentTag(): string | undefined;
631
632
  onChange(fn: Listener): () => void;
632
633
  visibleGroup(): VisibleGroupResult;
634
+ /**
635
+ * Build a fieldId -> triggerKeys[] map from the current selection set.
636
+ *
637
+ * What counts as a "button selection" (trigger key):
638
+ * - field key where the field has button === true (e.g. "f:dripfeed")
639
+ * - option key (e.g. "o:fast")
640
+ * - composite key "fieldId::optionId" (e.g. "f:speed::o:fast")
641
+ *
642
+ * Grouping:
643
+ * - button-field trigger groups under its own fieldId
644
+ * - option/composite groups under the option's owning fieldId (from nodeMap)
645
+ *
646
+ * Deterministic:
647
+ * - preserves selection insertion order
648
+ * - de-dupes per field
649
+ */
650
+ buttonSelectionsByFieldId(): Record<string, string[]>;
651
+ /**
652
+ * Returns only selection keys that are valid "trigger buttons":
653
+ * - field keys where field.button === true
654
+ * - option keys
655
+ * - composite keys "fieldId::optionId" (validated by optionId)
656
+ * Excludes tags and non-button fields.
657
+ */
658
+ selectedButtons(): string[];
633
659
  private emit;
634
660
  private updateCurrentTagFrom;
635
661
  private resolveTagContextId;
636
- private selectedButtonTriggerIds;
637
662
  private computeGroupForTag;
638
663
  private addServiceByRole;
639
664
  private findOptionById;