@epilot/cli 0.1.26 → 0.1.27

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,7 +2,7 @@
2
2
  "openapi": "3.0.2",
3
3
  "info": {
4
4
  "title": "Entity API",
5
- "version": "2.8.0",
5
+ "version": "2.9.0",
6
6
  "description": "Flexible data layer for epilot Entities.\n\nUse this API configure and access your business objects like Contacts, Opportunities and Products.\n\n[Feature Documentation](https://docs.epilot.io/docs/entities/flexible-entities)\n"
7
7
  },
8
8
  "tags": [
@@ -1428,6 +1428,9 @@
1428
1428
  "schema": {
1429
1429
  "$ref": "#/components/schemas/FieldsParam"
1430
1430
  }
1431
+ },
1432
+ {
1433
+ "$ref": "#/components/parameters/ApplyChangesetsQueryParam"
1431
1434
  }
1432
1435
  ],
1433
1436
  "responses": {
@@ -1619,6 +1622,9 @@
1619
1622
  },
1620
1623
  {
1621
1624
  "$ref": "#/components/parameters/ValidateEntityQueryParam"
1625
+ },
1626
+ {
1627
+ "$ref": "#/components/parameters/DirectQueryParam"
1622
1628
  }
1623
1629
  ],
1624
1630
  "requestBody": {
@@ -1682,6 +1688,9 @@
1682
1688
  },
1683
1689
  {
1684
1690
  "$ref": "#/components/parameters/ValidateEntityQueryParam"
1691
+ },
1692
+ {
1693
+ "$ref": "#/components/parameters/DirectQueryParam"
1685
1694
  }
1686
1695
  ],
1687
1696
  "requestBody": {
@@ -2023,6 +2032,138 @@
2023
2032
  }
2024
2033
  }
2025
2034
  },
2035
+ "/v1/entity/{slug}/{id}/changesets/{attribute}:apply": {
2036
+ "post": {
2037
+ "operationId": "applyChangeset",
2038
+ "summary": "applyChangeset",
2039
+ "description": "Applies the proposed value from a pending changeset to the entity attribute\nand removes the changeset. Used for human approval of pending changes.\n",
2040
+ "tags": [
2041
+ "Entities"
2042
+ ],
2043
+ "parameters": [
2044
+ {
2045
+ "$ref": "#/components/parameters/EntitySlugPathParam"
2046
+ },
2047
+ {
2048
+ "$ref": "#/components/parameters/EntityIdPathParam"
2049
+ },
2050
+ {
2051
+ "in": "path",
2052
+ "name": "attribute",
2053
+ "required": true,
2054
+ "description": "Attribute name of the changeset to apply",
2055
+ "schema": {
2056
+ "type": "string"
2057
+ }
2058
+ }
2059
+ ],
2060
+ "responses": {
2061
+ "200": {
2062
+ "description": "Changeset applied successfully — returns updated entity",
2063
+ "content": {
2064
+ "application/json": {
2065
+ "schema": {
2066
+ "$ref": "#/components/schemas/EntityItem"
2067
+ }
2068
+ }
2069
+ }
2070
+ },
2071
+ "404": {
2072
+ "$ref": "#/components/responses/NotFoundError"
2073
+ }
2074
+ }
2075
+ }
2076
+ },
2077
+ "/v1/entity/{slug}/{id}/changesets/{attribute}:dismiss": {
2078
+ "post": {
2079
+ "operationId": "dismissChangeset",
2080
+ "summary": "dismissChangeset",
2081
+ "description": "Removes a pending changeset without applying it. The attribute value remains unchanged.\n",
2082
+ "tags": [
2083
+ "Entities"
2084
+ ],
2085
+ "parameters": [
2086
+ {
2087
+ "$ref": "#/components/parameters/EntitySlugPathParam"
2088
+ },
2089
+ {
2090
+ "$ref": "#/components/parameters/EntityIdPathParam"
2091
+ },
2092
+ {
2093
+ "in": "path",
2094
+ "name": "attribute",
2095
+ "required": true,
2096
+ "description": "Attribute name of the changeset to dismiss",
2097
+ "schema": {
2098
+ "type": "string"
2099
+ }
2100
+ }
2101
+ ],
2102
+ "requestBody": {
2103
+ "content": {
2104
+ "application/json": {
2105
+ "schema": {
2106
+ "type": "object",
2107
+ "properties": {
2108
+ "reason": {
2109
+ "type": "string",
2110
+ "description": "Optional reason for dismissing the changeset"
2111
+ }
2112
+ }
2113
+ }
2114
+ }
2115
+ }
2116
+ },
2117
+ "responses": {
2118
+ "200": {
2119
+ "description": "Changeset dismissed successfully — returns updated entity",
2120
+ "content": {
2121
+ "application/json": {
2122
+ "schema": {
2123
+ "$ref": "#/components/schemas/EntityItem"
2124
+ }
2125
+ }
2126
+ }
2127
+ },
2128
+ "404": {
2129
+ "$ref": "#/components/responses/NotFoundError"
2130
+ }
2131
+ }
2132
+ }
2133
+ },
2134
+ "/v1/entity/{slug}/{id}/changesets": {
2135
+ "get": {
2136
+ "operationId": "listChangesets",
2137
+ "summary": "listChangesets",
2138
+ "description": "Returns all pending changesets for an entity.",
2139
+ "tags": [
2140
+ "Entities"
2141
+ ],
2142
+ "parameters": [
2143
+ {
2144
+ "$ref": "#/components/parameters/EntitySlugPathParam"
2145
+ },
2146
+ {
2147
+ "$ref": "#/components/parameters/EntityIdPathParam"
2148
+ }
2149
+ ],
2150
+ "responses": {
2151
+ "200": {
2152
+ "description": "Pending changesets for the entity",
2153
+ "content": {
2154
+ "application/json": {
2155
+ "schema": {
2156
+ "$ref": "#/components/schemas/ChangesetMap"
2157
+ }
2158
+ }
2159
+ }
2160
+ },
2161
+ "404": {
2162
+ "$ref": "#/components/responses/NotFoundError"
2163
+ }
2164
+ }
2165
+ }
2166
+ },
2026
2167
  "/v1/entity/{slug}/{id}/activity": {
2027
2168
  "get": {
2028
2169
  "operationId": "getEntityActivityFeed",
@@ -3704,7 +3845,7 @@
3704
3845
  "description": "ISO 8601 timestamp to filter jobs created after this time (e.g., 2023-01-01T00:00:00Z).",
3705
3846
  "type": "string",
3706
3847
  "format": "date-time",
3707
- "example": "2023-01-01T00:00:00.000Z"
3848
+ "example": "2023-01-01T00:00:00Z"
3708
3849
  }
3709
3850
  },
3710
3851
  {
@@ -5497,6 +5638,22 @@
5497
5638
  },
5498
5639
  "has_primary": {
5499
5640
  "type": "boolean"
5641
+ },
5642
+ "edit_mode": {
5643
+ "allOf": [
5644
+ {
5645
+ "$ref": "#/components/schemas/EditMode"
5646
+ }
5647
+ ],
5648
+ "description": "Controls how updates to this attribute are handled. See the `EditMode`\nschema for the per-mode semantics. Defaults to `direct`.\n"
5649
+ },
5650
+ "edit_mode_config": {
5651
+ "description": "Configuration for auto-clear matching on `edit_mode: external` attributes.\n`match_strategy` and `fuzzy_config` are only consulted for `external` mode —\nthey are ignored for `approval` mode, which resolves via explicit\n`:apply` / `:dismiss` endpoints and never auto-clears.\n",
5652
+ "allOf": [
5653
+ {
5654
+ "$ref": "#/components/schemas/EditModeConfig"
5655
+ }
5656
+ ]
5500
5657
  }
5501
5658
  },
5502
5659
  "required": [
@@ -7417,6 +7574,15 @@
7417
7574
  "example": "123e4567-e89b-12d3-a456-426614174000"
7418
7575
  },
7419
7576
  "nullable": true
7577
+ },
7578
+ "_changesets": {
7579
+ "readOnly": true,
7580
+ "type": "object",
7581
+ "nullable": true,
7582
+ "description": "Pending attribute changesets for attributes configured with external or approval edit mode.\n\nThe value shape is `Changeset` (`proposed_value`, `created_at`, `edit_mode`, ...)\nand is what `:apply` / `:dismiss` operate on.\n\nRead-only via normal entity PATCH/PUT operations — those handlers strip `_changesets`\nfrom request bodies. Use the changeset management endpoints to mutate this field.\n",
7583
+ "additionalProperties": {
7584
+ "$ref": "#/components/schemas/Changeset"
7585
+ }
7420
7586
  }
7421
7587
  },
7422
7588
  "example": {
@@ -8350,6 +8516,11 @@
8350
8516
  "type": "boolean",
8351
8517
  "description": "If true, return full entity objects in entityNodes instead of just entity IDs in nodes",
8352
8518
  "default": false
8519
+ },
8520
+ "apply_changesets": {
8521
+ "type": "boolean",
8522
+ "description": "When true and hydrate is also true, entity objects in entityNodes have pending changeset proposed values applied in-place. The _changesets field is still included in the response.",
8523
+ "default": false
8353
8524
  }
8354
8525
  }
8355
8526
  },
@@ -8646,11 +8817,16 @@
8646
8817
  "float",
8647
8818
  "date",
8648
8819
  "flattened",
8649
- "nested"
8820
+ "nested",
8821
+ "object"
8650
8822
  ]
8651
8823
  },
8652
8824
  "fields": {
8653
8825
  "additionalProperties": true
8826
+ },
8827
+ "dynamic": {
8828
+ "type": "boolean",
8829
+ "description": "When false, prevents ES from inferring types for nested fields. Used for _changesets where values can be any type."
8654
8830
  }
8655
8831
  }
8656
8832
  }
@@ -8717,7 +8893,7 @@
8717
8893
  },
8718
8894
  "ActivityType": {
8719
8895
  "type": "string",
8720
- "description": "A type for the activity. Used to categorize activities in the activity feed and for event subscriptions.\n\nBuilt-in entity activity types (custom activities can be defined as well):\n- EntityCreated\n- EntityUpdated\n- EntityDeleted\n- EntitySoftDeleted\n- EntityRestored\n- RelationsAdded\n- RelationsRemoved\n- RelationsSoftDeleted\n- RelationsRestored\n- RelationsDeleted\n"
8896
+ "description": "A type for the activity. Used to categorize activities in the activity feed and for event subscriptions.\n\nBuilt-in entity activity types (custom activities can be defined as well):\n- EntityCreated\n- EntityUpdated\n- EntityDeleted\n- EntitySoftDeleted\n- EntityRestored\n- RelationsAdded\n- RelationsRemoved\n- RelationsSoftDeleted\n- RelationsRestored\n- RelationsDeleted\n- ChangesetCreated\n- ChangesetAutoCleared\n- ChangesetApplied\n- ChangesetDismissed\n"
8721
8897
  },
8722
8898
  "Activity": {
8723
8899
  "type": "object",
@@ -9505,6 +9681,374 @@
9505
9681
  "example": "Bad Request"
9506
9682
  }
9507
9683
  }
9684
+ },
9685
+ "ChangesetCreator": {
9686
+ "type": "object",
9687
+ "description": "Identifies the actor that created the changeset.",
9688
+ "properties": {
9689
+ "type": {
9690
+ "type": "string",
9691
+ "description": "Type of actor that created the changeset",
9692
+ "enum": [
9693
+ "user",
9694
+ "portal_user",
9695
+ "api_client",
9696
+ "automation"
9697
+ ]
9698
+ },
9699
+ "id": {
9700
+ "type": "string",
9701
+ "description": "ID of the actor (user ID, portal user ID, API client ID, etc.)"
9702
+ }
9703
+ }
9704
+ },
9705
+ "Changeset": {
9706
+ "type": "object",
9707
+ "description": "A pending proposed change for a single entity attribute, awaiting external confirmation or human approval.",
9708
+ "required": [
9709
+ "proposed_value",
9710
+ "created_at",
9711
+ "edit_mode"
9712
+ ],
9713
+ "properties": {
9714
+ "proposed_value": {
9715
+ "description": "The proposed new value for the attribute. Type matches the attribute type."
9716
+ },
9717
+ "previous_value": {
9718
+ "description": "The attribute value at the time the changeset was created. Stored for reference."
9719
+ },
9720
+ "created_at": {
9721
+ "type": "string",
9722
+ "format": "date-time",
9723
+ "description": "Timestamp when the changeset was created"
9724
+ },
9725
+ "created_by": {
9726
+ "$ref": "#/components/schemas/ChangesetCreator"
9727
+ },
9728
+ "edit_mode": {
9729
+ "type": "string",
9730
+ "description": "The edit mode that triggered this changeset.\n- `external`: auto-cleared by a matching `?direct=true` write (see `match_strategy`).\n- `approval`: resolved only via explicit `:apply` / `:dismiss` endpoints; never auto-clears.\n",
9731
+ "enum": [
9732
+ "external",
9733
+ "approval"
9734
+ ]
9735
+ },
9736
+ "match_strategy": {
9737
+ "description": "Match strategy copied from the attribute's `edit_mode_config` at changeset\ncreation time. Only consulted when `edit_mode` is `external`; ignored for\n`approval` (which never auto-clears).\n",
9738
+ "allOf": [
9739
+ {
9740
+ "$ref": "#/components/schemas/MatchStrategy"
9741
+ }
9742
+ ]
9743
+ },
9744
+ "source": {
9745
+ "type": "string",
9746
+ "description": "Optional label indicating where the change originated (e.g. end_customer_portal, installer_portal, journey, automation)"
9747
+ },
9748
+ "related_values": {
9749
+ "type": "object",
9750
+ "description": "Proposed and previous values for related fields in a multi-field attribute group (e.g. currency _decimal/_currency suffixes). Keyed by full field name.",
9751
+ "additionalProperties": {
9752
+ "type": "object",
9753
+ "properties": {
9754
+ "proposed_value": {
9755
+ "description": "The proposed new value for the related field."
9756
+ },
9757
+ "previous_value": {
9758
+ "description": "The value of the related field when the changeset was created."
9759
+ }
9760
+ }
9761
+ }
9762
+ }
9763
+ }
9764
+ },
9765
+ "ChangesetMap": {
9766
+ "type": "object",
9767
+ "description": "Map of attribute name to pending changeset. At most one changeset per attribute.",
9768
+ "additionalProperties": {
9769
+ "$ref": "#/components/schemas/Changeset"
9770
+ }
9771
+ },
9772
+ "MeterReadingChangesetEntry": {
9773
+ "type": "object",
9774
+ "description": "A meter readings changeset entry — service-managed by metering-api.\nStored on `Meter._meter_readings_changeset` array attribute. Each entry mirrors a\nClickHouse meter reading row plus a changeset metadata overlay.\n\nNote: `org_id` is NOT stored on the entry. The entries live on the Meter entity,\nwhich is already org-scoped (`_org`). Round-tripping it would only invite drift;\ndownstream consumers receive the org from the meter / handler context.\n",
9775
+ "required": [
9776
+ "changeset_id",
9777
+ "meter_id",
9778
+ "counter_id",
9779
+ "value",
9780
+ "edit_mode",
9781
+ "created_at"
9782
+ ],
9783
+ "properties": {
9784
+ "value": {
9785
+ "type": "number"
9786
+ },
9787
+ "direction": {
9788
+ "type": "string",
9789
+ "enum": [
9790
+ "feed-in",
9791
+ "feed-out"
9792
+ ]
9793
+ },
9794
+ "timestamp": {
9795
+ "type": "string",
9796
+ "format": "date-time"
9797
+ },
9798
+ "meter_id": {
9799
+ "type": "string",
9800
+ "format": "uuid"
9801
+ },
9802
+ "counter_id": {
9803
+ "type": "string",
9804
+ "format": "uuid"
9805
+ },
9806
+ "source": {
9807
+ "type": "string"
9808
+ },
9809
+ "reason": {
9810
+ "type": "string"
9811
+ },
9812
+ "read_by": {
9813
+ "type": "string"
9814
+ },
9815
+ "status": {
9816
+ "type": "string",
9817
+ "enum": [
9818
+ "valid",
9819
+ "in-validation",
9820
+ "implausible"
9821
+ ]
9822
+ },
9823
+ "external_id": {
9824
+ "type": "string"
9825
+ },
9826
+ "remark": {
9827
+ "type": "string"
9828
+ },
9829
+ "metadata": {
9830
+ "type": "object",
9831
+ "additionalProperties": {
9832
+ "type": "string"
9833
+ }
9834
+ },
9835
+ "changeset_id": {
9836
+ "type": "string",
9837
+ "description": "Unique changeset identifier (UUID v4)."
9838
+ },
9839
+ "edit_mode": {
9840
+ "type": "string",
9841
+ "enum": [
9842
+ "external",
9843
+ "approval"
9844
+ ]
9845
+ },
9846
+ "match_strategy": {
9847
+ "type": "string",
9848
+ "enum": [
9849
+ "exact",
9850
+ "fuzzy"
9851
+ ]
9852
+ },
9853
+ "timestamp_tolerance": {
9854
+ "description": "Slack on `reading.timestamp` when metering-api auto-clear matches an\nincoming reading against this pending changeset.\n\nVariants:\n- `'exact'`: strict millisecond equality.\n- `{ type: 'same-day', timezone? }`: strip the time component and\n compare year-month-day. Optional `timezone` is an IANA name\n (e.g. `'Europe/Berlin'`); the day is bucketed in that zone.\n Defaults to UTC when omitted.\n- `{ type: 'within-seconds', seconds }`: symmetric ±N-second window.\n",
9855
+ "oneOf": [
9856
+ {
9857
+ "type": "string",
9858
+ "enum": [
9859
+ "exact"
9860
+ ]
9861
+ },
9862
+ {
9863
+ "type": "object",
9864
+ "required": [
9865
+ "type"
9866
+ ],
9867
+ "properties": {
9868
+ "type": {
9869
+ "type": "string",
9870
+ "enum": [
9871
+ "same-day"
9872
+ ]
9873
+ },
9874
+ "timezone": {
9875
+ "type": "string",
9876
+ "description": "IANA timezone identifier (e.g. `'Europe/Berlin'`, `'UTC'`).\nWhen omitted, the day is bucketed in UTC.\n"
9877
+ }
9878
+ }
9879
+ },
9880
+ {
9881
+ "type": "object",
9882
+ "required": [
9883
+ "type",
9884
+ "seconds"
9885
+ ],
9886
+ "properties": {
9887
+ "type": {
9888
+ "type": "string",
9889
+ "enum": [
9890
+ "within-seconds"
9891
+ ]
9892
+ },
9893
+ "seconds": {
9894
+ "type": "integer",
9895
+ "minimum": 0,
9896
+ "description": "Tolerance in seconds. e.g. 60 = ±1 minute, 3600 = ±1 hour."
9897
+ }
9898
+ }
9899
+ }
9900
+ ]
9901
+ },
9902
+ "fuzzy_config": {
9903
+ "type": "object",
9904
+ "properties": {
9905
+ "percentage_threshold": {
9906
+ "type": "number"
9907
+ },
9908
+ "absolute_threshold": {
9909
+ "type": "number"
9910
+ }
9911
+ }
9912
+ },
9913
+ "created_at": {
9914
+ "type": "string",
9915
+ "format": "date-time"
9916
+ },
9917
+ "created_by": {
9918
+ "$ref": "#/components/schemas/ChangesetCreator"
9919
+ },
9920
+ "previous": {
9921
+ "type": "object",
9922
+ "properties": {
9923
+ "value": {
9924
+ "type": "number"
9925
+ },
9926
+ "direction": {
9927
+ "type": "string",
9928
+ "enum": [
9929
+ "feed-in",
9930
+ "feed-out"
9931
+ ]
9932
+ },
9933
+ "timestamp": {
9934
+ "type": "string",
9935
+ "format": "date-time"
9936
+ }
9937
+ }
9938
+ }
9939
+ }
9940
+ },
9941
+ "EditMode": {
9942
+ "type": "string",
9943
+ "description": "Controls whether a write goes through immediately or is held as a pending entry\non the parent entity's `_changesets` map.\n\n- `direct`: write applied immediately. No changeset created.\n- `external`: write held as a pending changeset; auto-cleared on a matching direct/ERP write.\n- `approval`: write held as a pending changeset; requires explicit human apply/dismiss.\n\nUsed at two levels: per-attribute (entity-attribute changesets) and per-capability\n(currently the `meter_readings` capability on the Meter schema, gating reading submissions).\n",
9944
+ "enum": [
9945
+ "direct",
9946
+ "external",
9947
+ "approval"
9948
+ ],
9949
+ "default": "direct"
9950
+ },
9951
+ "EditModeConfig": {
9952
+ "type": "object",
9953
+ "description": "Configuration for `edit_mode: external` auto-clear matching.\nFields here (`match_strategy`, `fuzzy_config`) only take effect when\n`edit_mode` is `external`. They are ignored for `edit_mode: approval`,\nwhich never auto-clears and is resolved exclusively via the\n`:apply` / `:dismiss` changeset endpoints.\n",
9954
+ "properties": {
9955
+ "match_strategy": {
9956
+ "$ref": "#/components/schemas/MatchStrategy"
9957
+ },
9958
+ "fuzzy_config": {
9959
+ "$ref": "#/components/schemas/FuzzyConfig"
9960
+ }
9961
+ }
9962
+ },
9963
+ "FuzzyConfig": {
9964
+ "type": "object",
9965
+ "description": "Configuration for fuzzy auto-clear matching on `edit_mode: external` attributes.\nNot used for `edit_mode: approval`.\n\nType compatibility with attribute shape is enforced at schema save time:\n- scalar string attributes: `suffix`, `digits_only`, `regex`\n- repeatable attributes: `set_equivalent`, `entry_match`, `ignore_fields`, `normalize_phone`\n- relation attributes: `relation_set`\n",
9966
+ "required": [
9967
+ "type"
9968
+ ],
9969
+ "properties": {
9970
+ "type": {
9971
+ "type": "string",
9972
+ "description": "Which fuzzy algorithm to apply.",
9973
+ "enum": [
9974
+ "suffix",
9975
+ "digits_only",
9976
+ "normalize_phone",
9977
+ "ignore_fields",
9978
+ "set_equivalent",
9979
+ "entry_match",
9980
+ "relation_set",
9981
+ "regex"
9982
+ ]
9983
+ },
9984
+ "suffix_length": {
9985
+ "type": "integer",
9986
+ "description": "For type=suffix: number of characters to compare from end of string."
9987
+ },
9988
+ "fields_to_ignore": {
9989
+ "type": "array",
9990
+ "items": {
9991
+ "type": "string"
9992
+ },
9993
+ "description": "For type=ignore_fields and type=set_equivalent: field names to exclude when comparing array entries. `_id` is always stripped by the platform regardless of this config."
9994
+ },
9995
+ "regex_flags": {
9996
+ "type": "string",
9997
+ "description": "For type=regex: flags to apply to the regex (e.g. 'i' for case-insensitive)."
9998
+ },
9999
+ "country_code": {
10000
+ "type": "string",
10001
+ "description": "For type=normalize_phone: country dialing code digits to strip (e.g. '49' for Germany). No '+' prefix."
10002
+ },
10003
+ "match_on": {
10004
+ "type": "string",
10005
+ "description": "For type=normalize_phone: attribute key within array entries to compare on (e.g. 'phone_number')."
10006
+ },
10007
+ "key": {
10008
+ "description": "For type=entry_match: business key(s) within each entry used to match proposed entries against incoming entries.",
10009
+ "oneOf": [
10010
+ {
10011
+ "type": "string"
10012
+ },
10013
+ {
10014
+ "type": "array",
10015
+ "items": {
10016
+ "type": "string"
10017
+ }
10018
+ }
10019
+ ]
10020
+ },
10021
+ "mode": {
10022
+ "type": "string",
10023
+ "description": "For type=entry_match and type=set_equivalent: how strict the comparison is.\n- `subset` (default for entry_match): every proposed entry must exist in incoming; extra incoming entries are allowed.\n- `exact_set` (default for set_equivalent): the two sides must contain the same entries (order-insensitive, multiset semantics).\n",
10024
+ "enum": [
10025
+ "subset",
10026
+ "exact_set"
10027
+ ]
10028
+ },
10029
+ "ordered": {
10030
+ "type": "boolean",
10031
+ "description": "For type=relation_set: when true, relation order is significant. Defaults to false (order-insensitive)."
10032
+ },
10033
+ "require_tags_match": {
10034
+ "type": "boolean",
10035
+ "description": "For type=relation_set: when true, each relation item's `_tags` must match on both sides (order-insensitive). Defaults to false — `_tags` are stripped before compare."
10036
+ },
10037
+ "pattern": {
10038
+ "type": "string",
10039
+ "description": "For type=regex: regular expression pattern to test the incoming value against."
10040
+ }
10041
+ }
10042
+ },
10043
+ "MatchStrategy": {
10044
+ "type": "string",
10045
+ "description": "Strategy for auto-clearing a changeset on an `edit_mode: external` attribute\nwhen a direct write (`?direct=true`) arrives — typically an ERP inbound sync.\nIgnored for `edit_mode: approval`, which does not auto-clear and is resolved\nexclusively via the `:apply` / `:dismiss` changeset endpoints.\n- `exact`: The inbound value must exactly match the proposed value (deep equality).\n- `fuzzy`: The inbound value is compared using the configured `fuzzy_config` algorithm.\n- `any`: Any update to the attribute clears the changeset, regardless of value.\n",
10046
+ "enum": [
10047
+ "exact",
10048
+ "fuzzy",
10049
+ "any"
10050
+ ],
10051
+ "default": "exact"
9508
10052
  }
9509
10053
  },
9510
10054
  "parameters": {
@@ -9741,6 +10285,26 @@
9741
10285
  "default": false,
9742
10286
  "type": "boolean"
9743
10287
  }
10288
+ },
10289
+ "DirectQueryParam": {
10290
+ "name": "direct",
10291
+ "description": "When true, bypasses changeset interception: attribute values in the payload\nare written directly to the entity regardless of each attribute's `edit_mode`.\nThe write always lands, independently of any auto-clear outcome below.\n\nAfter the direct write, for each attribute in the payload that also has a\npending changeset:\n- `edit_mode: external` — the incoming value is checked against the\n changeset's `match_strategy` (and `fuzzy_config` when `fuzzy`). On match,\n `_changesets[attr]` is cleared. On no-match, the write still stands and\n the changeset stays pending (signalling the ERP/trusted source applied a\n different correction than originally proposed; resolve via a later\n matching direct write, or via the `:apply` / `:dismiss` endpoints).\n- `edit_mode: approval` — never auto-cleared. The write lands but the\n pending changeset remains until explicitly resolved via `:apply` or\n `:dismiss`.\n\nIntended for trusted integrations (e.g. ERP inbound sync). ERP middleware\nmust always use `?direct=true` — without it, an inbound sync on an\n`external` attribute would create a new changeset instead of confirming\nthe pending one.\n\nDefaults to false — no breaking change for existing callers.\n",
10292
+ "in": "query",
10293
+ "required": false,
10294
+ "schema": {
10295
+ "type": "boolean",
10296
+ "default": false
10297
+ }
10298
+ },
10299
+ "ApplyChangesetsQueryParam": {
10300
+ "name": "apply_changesets",
10301
+ "description": "When true, applies pending changeset proposed values in-place on the response entity.\nThe response includes both the hydrated values and the raw _changesets field.\nDoes not mutate stored data — pure read-time transform.\n",
10302
+ "in": "query",
10303
+ "required": false,
10304
+ "schema": {
10305
+ "type": "boolean",
10306
+ "default": false
10307
+ }
9744
10308
  }
9745
10309
  },
9746
10310
  "examples": {