ecoportal-api-graphql 2.0.0 → 3.0.0

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.
Files changed (393) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +793 -14
  3. data/LICENSE +21 -21
  4. data/README.gem.md +53 -0
  5. data/lib/ecoportal/api/common/graphql/auth_service.rb +115 -115
  6. data/lib/ecoportal/api/common/graphql/class_helpers.rb +46 -46
  7. data/lib/ecoportal/api/common/graphql/client.rb +107 -69
  8. data/lib/ecoportal/api/common/graphql/hash_helpers.rb +105 -105
  9. data/lib/ecoportal/api/common/graphql/http_client.rb +124 -8
  10. data/lib/ecoportal/api/common/graphql/model/as_input.rb +54 -54
  11. data/lib/ecoportal/api/common/graphql/model/diffable/classic_diff_service.rb +49 -49
  12. data/lib/ecoportal/api/common/graphql/model/diffable/diff_service.rb +188 -188
  13. data/lib/ecoportal/api/common/graphql/model/diffable/hash_diff.rb +55 -55
  14. data/lib/ecoportal/api/common/graphql/model/diffable/hash_diff_nesting.rb +295 -295
  15. data/lib/ecoportal/api/common/graphql/model/diffable/leaf_diff_service.rb +1 -1
  16. data/lib/ecoportal/api/common/graphql/model.rb +17 -17
  17. data/lib/ecoportal/api/common/graphql/patches.rb +1 -1
  18. data/lib/ecoportal/api/common/graphql/query_integration.rb +45 -45
  19. data/lib/ecoportal/api/common/graphql.rb +16 -16
  20. data/lib/ecoportal/api/common.graphql.rb +8 -8
  21. data/lib/ecoportal/api/graphql/base/action_category.rb +15 -15
  22. data/lib/ecoportal/api/graphql/base/ai_summary_version.rb +1 -1
  23. data/lib/ecoportal/api/graphql/base/contractor_entity.rb +24 -24
  24. data/lib/ecoportal/api/graphql/base/date_time.rb +12 -12
  25. data/lib/ecoportal/api/graphql/base/delta_result.rb +16 -16
  26. data/lib/ecoportal/api/graphql/base/field.rb +12 -12
  27. data/lib/ecoportal/api/graphql/base/file_attachment.rb +15 -15
  28. data/lib/ecoportal/api/graphql/base/location_node.rb +14 -14
  29. data/lib/ecoportal/api/graphql/base/location_structure/draft/structure.rb +37 -37
  30. data/lib/ecoportal/api/graphql/base/location_structure/draft.rb +93 -93
  31. data/lib/ecoportal/api/graphql/base/page/data_field/actions_list.rb +9 -9
  32. data/lib/ecoportal/api/graphql/base/page/data_field/ai_summary.rb +18 -18
  33. data/lib/ecoportal/api/graphql/base/page/data_field/checklist.rb +29 -29
  34. data/lib/ecoportal/api/graphql/base/page/data_field/collection.rb +1 -1
  35. data/lib/ecoportal/api/graphql/base/page/data_field/cross_reference.rb +1 -1
  36. data/lib/ecoportal/api/graphql/base/page/data_field/date_field.rb +23 -23
  37. data/lib/ecoportal/api/graphql/base/page/data_field/geo.rb +24 -24
  38. data/lib/ecoportal/api/graphql/base/page/data_field/image_gallery.rb +160 -12
  39. data/lib/ecoportal/api/graphql/base/page/data_field/law.rb +8 -8
  40. data/lib/ecoportal/api/graphql/base/page/data_field/mailbox.rb +9 -9
  41. data/lib/ecoportal/api/graphql/base/page/data_field/plain_text.rb +17 -17
  42. data/lib/ecoportal/api/graphql/base/page/data_field/rich_text.rb +26 -26
  43. data/lib/ecoportal/api/graphql/base/page/data_field/signature.rb +9 -9
  44. data/lib/ecoportal/api/graphql/base/page/data_field/smart_fill.rb +17 -17
  45. data/lib/ecoportal/api/graphql/base/page/data_field/table.rb +9 -9
  46. data/lib/ecoportal/api/graphql/base/page/data_field/tag_field.rb +21 -21
  47. data/lib/ecoportal/api/graphql/base/page/phased/stage.rb +51 -0
  48. data/lib/ecoportal/api/graphql/base/page/phased.rb +15 -15
  49. data/lib/ecoportal/api/graphql/base/page/section.rb +48 -48
  50. data/lib/ecoportal/api/graphql/base/page_info.rb +12 -12
  51. data/lib/ecoportal/api/graphql/base/pages_workflow/operation_interface.rb +39 -39
  52. data/lib/ecoportal/api/graphql/base/pages_workflow/operations/assign_to.rb +27 -27
  53. data/lib/ecoportal/api/graphql/base/pages_workflow/operations/send_notification.rb +30 -30
  54. data/lib/ecoportal/api/graphql/base/pages_workflow/recipient_config.rb +34 -34
  55. data/lib/ecoportal/api/graphql/base/pages_workflow.rb +35 -35
  56. data/lib/ecoportal/api/graphql/base/person_member.rb +22 -22
  57. data/lib/ecoportal/api/graphql/base/preview_page.rb +23 -23
  58. data/lib/ecoportal/api/graphql/base/register.rb +1 -1
  59. data/lib/ecoportal/api/graphql/base/resource.rb +13 -13
  60. data/lib/ecoportal/api/graphql/base/temp_image.rb +28 -0
  61. data/lib/ecoportal/api/graphql/base/template/change_log_entry.rb +118 -0
  62. data/lib/ecoportal/api/graphql/base/template.rb +2 -0
  63. data/lib/ecoportal/api/graphql/base.rb +1 -0
  64. data/lib/ecoportal/api/graphql/builder/action.rb +41 -41
  65. data/lib/ecoportal/api/graphql/builder/location_structure/draft.rb +64 -64
  66. data/lib/ecoportal/api/graphql/builder/location_structure.rb +40 -40
  67. data/lib/ecoportal/api/graphql/builder/register/preset_view.rb +84 -84
  68. data/lib/ecoportal/api/graphql/builder/register.rb +49 -49
  69. data/lib/ecoportal/api/graphql/builder/template.rb +37 -4
  70. data/lib/ecoportal/api/graphql/builder/template_builder.rb +21 -2
  71. data/lib/ecoportal/api/graphql/compat/filter_translator.rb +1 -1
  72. data/lib/ecoportal/api/graphql/compat/page_reference.rb +23 -23
  73. data/lib/ecoportal/api/graphql/compat/registers.rb +84 -84
  74. data/lib/ecoportal/api/graphql/compat/search_results.rb +33 -33
  75. data/lib/ecoportal/api/graphql/compat.rb +17 -17
  76. data/lib/ecoportal/api/graphql/concerns/data_field_access.rb +71 -71
  77. data/lib/ecoportal/api/graphql/concerns/fragment_definitions.rb +68 -68
  78. data/lib/ecoportal/api/graphql/concerns/snake_camel_access.rb +60 -60
  79. data/lib/ecoportal/api/graphql/connection/preset_view.rb +11 -11
  80. data/lib/ecoportal/api/graphql/connection/template_change_log.rb +19 -0
  81. data/lib/ecoportal/api/graphql/connection.rb +1 -0
  82. data/lib/ecoportal/api/graphql/diff/change.rb +7 -2
  83. data/lib/ecoportal/api/graphql/diff/command_synthesizer.rb +19 -1
  84. data/lib/ecoportal/api/graphql/diff/version_diff.rb +13 -6
  85. data/lib/ecoportal/api/graphql/error/locations_error/fetch_nested.rb +51 -51
  86. data/lib/ecoportal/api/graphql/error/locations_error.rb +28 -28
  87. data/lib/ecoportal/api/graphql/error/locations_validation_error.rb +20 -20
  88. data/lib/ecoportal/api/graphql/error/validation_errors.rb +28 -28
  89. data/lib/ecoportal/api/graphql/error.rb +12 -12
  90. data/lib/ecoportal/api/graphql/file_upload/client.rb +140 -35
  91. data/lib/ecoportal/api/graphql/file_upload.rb +10 -10
  92. data/lib/ecoportal/api/graphql/fragment/action.rb +65 -65
  93. data/lib/ecoportal/api/graphql/fragment/action_category.rb +17 -17
  94. data/lib/ecoportal/api/graphql/fragment/contractor_entity.rb +20 -20
  95. data/lib/ecoportal/api/graphql/fragment/locations_error.rb +19 -19
  96. data/lib/ecoportal/api/graphql/fragment/page.rb +1 -0
  97. data/lib/ecoportal/api/graphql/fragment/pages/common_page_union.rb +10 -3
  98. data/lib/ecoportal/api/graphql/fragment/pages.rb +15 -15
  99. data/lib/ecoportal/api/graphql/fragment/pagination.rb +17 -17
  100. data/lib/ecoportal/api/graphql/fragment/permissions.rb +0 -2
  101. data/lib/ecoportal/api/graphql/fragment/template_change_log.rb +102 -0
  102. data/lib/ecoportal/api/graphql/fragment.rb +1 -0
  103. data/lib/ecoportal/api/graphql/helpers/locations_tree.rb +41 -41
  104. data/lib/ecoportal/api/graphql/helpers.rb +10 -10
  105. data/lib/ecoportal/api/graphql/input/action/archive.rb +14 -14
  106. data/lib/ecoportal/api/graphql/input/action/create.rb +13 -13
  107. data/lib/ecoportal/api/graphql/input/action.rb +14 -14
  108. data/lib/ecoportal/api/graphql/input/contractor_entity/create.rb +14 -14
  109. data/lib/ecoportal/api/graphql/input/contractor_entity/destroy.rb +13 -13
  110. data/lib/ecoportal/api/graphql/input/delta_input.rb +16 -16
  111. data/lib/ecoportal/api/graphql/input/id_diff.rb +11 -11
  112. data/lib/ecoportal/api/graphql/input/location_structure/archive_command.rb +13 -13
  113. data/lib/ecoportal/api/graphql/input/location_structure/delete_command.rb +13 -13
  114. data/lib/ecoportal/api/graphql/input/location_structure/draft/create.rb +15 -15
  115. data/lib/ecoportal/api/graphql/input/location_structure/draft/delete.rb +15 -15
  116. data/lib/ecoportal/api/graphql/input/location_structure/draft/drop_bad_commands.rb +16 -16
  117. data/lib/ecoportal/api/graphql/input/location_structure/draft/publish.rb +16 -16
  118. data/lib/ecoportal/api/graphql/input/location_structure/draft.rb +19 -19
  119. data/lib/ecoportal/api/graphql/input/location_structure/insert_command.rb +21 -13
  120. data/lib/ecoportal/api/graphql/input/location_structure/move_command.rb +14 -14
  121. data/lib/ecoportal/api/graphql/input/location_structure/reorder_command.rb +13 -13
  122. data/lib/ecoportal/api/graphql/input/location_structure/unarchive_command.rb +13 -13
  123. data/lib/ecoportal/api/graphql/input/location_structure.rb +20 -20
  124. data/lib/ecoportal/api/graphql/input/page/archive.rb +14 -14
  125. data/lib/ecoportal/api/graphql/input/page/build_from_template.rb +13 -13
  126. data/lib/ecoportal/api/graphql/input/page/create_draft.rb +13 -13
  127. data/lib/ecoportal/api/graphql/input/page/create_from_template.rb +18 -18
  128. data/lib/ecoportal/api/graphql/input/page/delete_draft.rb +13 -13
  129. data/lib/ecoportal/api/graphql/input/page/publish_draft.rb +13 -13
  130. data/lib/ecoportal/api/graphql/input/page/review_task.rb +27 -14
  131. data/lib/ecoportal/api/graphql/input/page/unarchive.rb +14 -14
  132. data/lib/ecoportal/api/graphql/input/page/update.rb +109 -6
  133. data/lib/ecoportal/api/graphql/input/page.rb +26 -26
  134. data/lib/ecoportal/api/graphql/input/preset_view/create.rb +18 -18
  135. data/lib/ecoportal/api/graphql/input/preset_view/permission.rb +16 -16
  136. data/lib/ecoportal/api/graphql/input/preset_view/update.rb +1 -1
  137. data/lib/ecoportal/api/graphql/input/preset_view.rb +14 -14
  138. data/lib/ecoportal/api/graphql/input/register/create.rb +18 -18
  139. data/lib/ecoportal/api/graphql/input/register/update.rb +15 -15
  140. data/lib/ecoportal/api/graphql/input/register.rb +13 -13
  141. data/lib/ecoportal/api/graphql/input/search.rb +13 -13
  142. data/lib/ecoportal/api/graphql/input/search_conf/ai_generator.rb +234 -234
  143. data/lib/ecoportal/api/graphql/input/search_conf.rb +1 -1
  144. data/lib/ecoportal/api/graphql/input/search_filter.rb +12 -12
  145. data/lib/ecoportal/api/graphql/input/search_sorter.rb +11 -11
  146. data/lib/ecoportal/api/graphql/input/workflow_command/add_action_tag.rb +18 -18
  147. data/lib/ecoportal/api/graphql/input/workflow_command/add_binding.rb +18 -18
  148. data/lib/ecoportal/api/graphql/input/workflow_command/add_comment_tagging_user_group.rb +18 -18
  149. data/lib/ecoportal/api/graphql/input/workflow_command/add_default_direct_strategy_user.rb +18 -18
  150. data/lib/ecoportal/api/graphql/input/workflow_command/add_default_strategy.rb +18 -18
  151. data/lib/ecoportal/api/graphql/input/workflow_command/add_direct_strategy_user.rb +18 -18
  152. data/lib/ecoportal/api/graphql/input/workflow_command/add_field.rb +10 -9
  153. data/lib/ecoportal/api/graphql/input/workflow_command/add_force.rb +18 -18
  154. data/lib/ecoportal/api/graphql/input/workflow_command/add_linked_field_config.rb +23 -23
  155. data/lib/ecoportal/api/graphql/input/workflow_command/add_linked_helper.rb +18 -18
  156. data/lib/ecoportal/api/graphql/input/workflow_command/add_operation.rb +18 -18
  157. data/lib/ecoportal/api/graphql/input/workflow_command/add_operation_direct_strategy_user.rb +18 -18
  158. data/lib/ecoportal/api/graphql/input/workflow_command/add_operation_strategy.rb +18 -18
  159. data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_action_type.rb +18 -18
  160. data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_filter.rb +18 -18
  161. data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_people_field.rb +18 -18
  162. data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_task_config.rb +18 -18
  163. data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_user.rb +18 -18
  164. data/lib/ecoportal/api/graphql/input/workflow_command/add_scheduled_callback.rb +23 -23
  165. data/lib/ecoportal/api/graphql/input/workflow_command/add_scheduled_callback_action.rb +18 -18
  166. data/lib/ecoportal/api/graphql/input/workflow_command/add_section.rb +18 -18
  167. data/lib/ecoportal/api/graphql/input/workflow_command/add_select_field_option.rb +25 -19
  168. data/lib/ecoportal/api/graphql/input/workflow_command/add_stage_section.rb +18 -18
  169. data/lib/ecoportal/api/graphql/input/workflow_command/add_stage_tag.rb +18 -18
  170. data/lib/ecoportal/api/graphql/input/workflow_command/add_strategy.rb +18 -18
  171. data/lib/ecoportal/api/graphql/input/workflow_command/add_task.rb +18 -18
  172. data/lib/ecoportal/api/graphql/input/workflow_command/add_task_assignment_user_group.rb +18 -18
  173. data/lib/ecoportal/api/graphql/input/workflow_command/add_workflow_callback.rb +18 -18
  174. data/lib/ecoportal/api/graphql/input/workflow_command/collapse_section.rb +18 -18
  175. data/lib/ecoportal/api/graphql/input/workflow_command/edit_binding.rb +18 -18
  176. data/lib/ecoportal/api/graphql/input/workflow_command/edit_creator_permissions.rb +18 -18
  177. data/lib/ecoportal/api/graphql/input/workflow_command/edit_default_strategy.rb +18 -18
  178. data/lib/ecoportal/api/graphql/input/workflow_command/edit_field_configuration.rb +7 -2
  179. data/lib/ecoportal/api/graphql/input/workflow_command/edit_force.rb +21 -18
  180. data/lib/ecoportal/api/graphql/input/workflow_command/edit_gauge_field_stop.rb +19 -19
  181. data/lib/ecoportal/api/graphql/input/workflow_command/edit_linked_field_config.rb +19 -19
  182. data/lib/ecoportal/api/graphql/input/workflow_command/edit_linked_helper.rb +18 -18
  183. data/lib/ecoportal/api/graphql/input/workflow_command/edit_operation.rb +18 -18
  184. data/lib/ecoportal/api/graphql/input/workflow_command/edit_operation_strategy.rb +18 -18
  185. data/lib/ecoportal/api/graphql/input/workflow_command/edit_reminder.rb +18 -18
  186. data/lib/ecoportal/api/graphql/input/workflow_command/edit_required_sign_offs.rb +18 -18
  187. data/lib/ecoportal/api/graphql/input/workflow_command/edit_restrict_comment_tagging.rb +18 -18
  188. data/lib/ecoportal/api/graphql/input/workflow_command/edit_restrict_task_assignment.rb +18 -18
  189. data/lib/ecoportal/api/graphql/input/workflow_command/edit_scheduled_callback.rb +22 -22
  190. data/lib/ecoportal/api/graphql/input/workflow_command/edit_section_header.rb +18 -18
  191. data/lib/ecoportal/api/graphql/input/workflow_command/edit_select_field_option.rb +23 -19
  192. data/lib/ecoportal/api/graphql/input/workflow_command/edit_stage.rb +18 -18
  193. data/lib/ecoportal/api/graphql/input/workflow_command/edit_strategy.rb +18 -18
  194. data/lib/ecoportal/api/graphql/input/workflow_command/edit_task_due.rb +18 -18
  195. data/lib/ecoportal/api/graphql/input/workflow_command/edit_template_basic_settings.rb +7 -2
  196. data/lib/ecoportal/api/graphql/input/workflow_command/edit_template_container_uid.rb +1 -1
  197. data/lib/ecoportal/api/graphql/input/workflow_command/edit_trigger.rb +18 -18
  198. data/lib/ecoportal/api/graphql/input/workflow_command/expand_section.rb +18 -18
  199. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/contractor_entities.rb +28 -24
  200. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/cross_reference.rb +8 -2
  201. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/gauge.rb +20 -20
  202. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/location_field.rb +24 -24
  203. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/plain_text.rb +8 -2
  204. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/signature.rb +20 -20
  205. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/table.rb +25 -25
  206. data/lib/ecoportal/api/graphql/input/workflow_command/manage_copy_page_configuration.rb +47 -0
  207. data/lib/ecoportal/api/graphql/input/workflow_command/move_section_to_stage.rb +33 -0
  208. data/lib/ecoportal/api/graphql/input/workflow_command/move_stage.rb +18 -18
  209. data/lib/ecoportal/api/graphql/input/workflow_command/remove_action_tag.rb +18 -18
  210. data/lib/ecoportal/api/graphql/input/workflow_command/remove_binding.rb +18 -18
  211. data/lib/ecoportal/api/graphql/input/workflow_command/remove_callback.rb +18 -18
  212. data/lib/ecoportal/api/graphql/input/workflow_command/remove_comment_tagging_user_group.rb +18 -18
  213. data/lib/ecoportal/api/graphql/input/workflow_command/remove_default_direct_strategy_user.rb +18 -18
  214. data/lib/ecoportal/api/graphql/input/workflow_command/remove_default_strategy.rb +18 -18
  215. data/lib/ecoportal/api/graphql/input/workflow_command/remove_direct_strategy_user.rb +18 -18
  216. data/lib/ecoportal/api/graphql/input/workflow_command/remove_field.rb +18 -18
  217. data/lib/ecoportal/api/graphql/input/workflow_command/remove_force.rb +18 -18
  218. data/lib/ecoportal/api/graphql/input/workflow_command/remove_linked_helper.rb +18 -18
  219. data/lib/ecoportal/api/graphql/input/workflow_command/remove_operation.rb +18 -18
  220. data/lib/ecoportal/api/graphql/input/workflow_command/remove_operation_direct_strategy_user.rb +18 -18
  221. data/lib/ecoportal/api/graphql/input/workflow_command/remove_operation_strategy.rb +18 -18
  222. data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_action_type.rb +18 -18
  223. data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_filter.rb +18 -18
  224. data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_people_field.rb +18 -18
  225. data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_task_config.rb +18 -18
  226. data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_user.rb +18 -18
  227. data/lib/ecoportal/api/graphql/input/workflow_command/remove_scheduled_callback.rb +18 -18
  228. data/lib/ecoportal/api/graphql/input/workflow_command/remove_section.rb +18 -18
  229. data/lib/ecoportal/api/graphql/input/workflow_command/remove_stage.rb +18 -18
  230. data/lib/ecoportal/api/graphql/input/workflow_command/remove_stage_section.rb +18 -18
  231. data/lib/ecoportal/api/graphql/input/workflow_command/remove_stage_tag.rb +18 -18
  232. data/lib/ecoportal/api/graphql/input/workflow_command/remove_strategy.rb +18 -18
  233. data/lib/ecoportal/api/graphql/input/workflow_command/remove_task.rb +18 -18
  234. data/lib/ecoportal/api/graphql/input/workflow_command/remove_task_assignment_user_group.rb +18 -18
  235. data/lib/ecoportal/api/graphql/input/workflow_command/remove_task_due.rb +18 -18
  236. data/lib/ecoportal/api/graphql/input/workflow_command/remove_task_priority_level.rb +18 -18
  237. data/lib/ecoportal/api/graphql/input/workflow_command.rb +5 -0
  238. data/lib/ecoportal/api/graphql/interface/base_page.rb +14 -0
  239. data/lib/ecoportal/api/graphql/interface/location_structure/node.rb +43 -43
  240. data/lib/ecoportal/api/graphql/interface/location_structure.rb +14 -14
  241. data/lib/ecoportal/api/graphql/interface.rb +11 -11
  242. data/lib/ecoportal/api/graphql/logic/base_model.rb +15 -15
  243. data/lib/ecoportal/api/graphql/logic/base_query.rb +260 -193
  244. data/lib/ecoportal/api/graphql/logic/mutation.rb +59 -59
  245. data/lib/ecoportal/api/graphql/logic/payload.rb +42 -42
  246. data/lib/ecoportal/api/graphql/logic/query.rb +23 -23
  247. data/lib/ecoportal/api/graphql/logic/query_array.rb +30 -30
  248. data/lib/ecoportal/api/graphql/logic/query_connection.rb +48 -48
  249. data/lib/ecoportal/api/graphql/logic.rb +18 -18
  250. data/lib/ecoportal/api/graphql/model/account.rb +15 -15
  251. data/lib/ecoportal/api/graphql/model/action.rb +22 -22
  252. data/lib/ecoportal/api/graphql/model/ai_summary_version.rb +10 -10
  253. data/lib/ecoportal/api/graphql/model/contractor_entity.rb +12 -12
  254. data/lib/ecoportal/api/graphql/model/field.rb +10 -10
  255. data/lib/ecoportal/api/graphql/model/file_attachment.rb +10 -10
  256. data/lib/ecoportal/api/graphql/model/file_container.rb +11 -11
  257. data/lib/ecoportal/api/graphql/model/location_node.rb +14 -14
  258. data/lib/ecoportal/api/graphql/model/location_structure/draft/structure.rb +15 -15
  259. data/lib/ecoportal/api/graphql/model/location_structure/draft.rb +15 -15
  260. data/lib/ecoportal/api/graphql/model/location_structure.rb +16 -16
  261. data/lib/ecoportal/api/graphql/model/organization.rb +9 -0
  262. data/lib/ecoportal/api/graphql/model/page/basic.rb +14 -14
  263. data/lib/ecoportal/api/graphql/model/page/data_field.rb +12 -12
  264. data/lib/ecoportal/api/graphql/model/page/phased/stage.rb +14 -14
  265. data/lib/ecoportal/api/graphql/model/page/phased.rb +17 -0
  266. data/lib/ecoportal/api/graphql/model/page/section.rb +12 -12
  267. data/lib/ecoportal/api/graphql/model/page_union.rb +21 -21
  268. data/lib/ecoportal/api/graphql/model/pages_workflow.rb +20 -20
  269. data/lib/ecoportal/api/graphql/model/permissions.rb +6 -0
  270. data/lib/ecoportal/api/graphql/model/preset_view.rb +10 -10
  271. data/lib/ecoportal/api/graphql/model/preview_page.rb +10 -10
  272. data/lib/ecoportal/api/graphql/model/register.rb +10 -10
  273. data/lib/ecoportal/api/graphql/model/resource.rb +13 -13
  274. data/lib/ecoportal/api/graphql/model/temp_image.rb +10 -0
  275. data/lib/ecoportal/api/graphql/model/template/binding.rb +60 -0
  276. data/lib/ecoportal/api/graphql/model/template/change_log_entry.rb +19 -0
  277. data/lib/ecoportal/api/graphql/model/template/command_grouper.rb +107 -0
  278. data/lib/ecoportal/api/graphql/model/template/command_normalizer.rb +116 -0
  279. data/lib/ecoportal/api/graphql/model/template/command_synthesis.rb +262 -0
  280. data/lib/ecoportal/api/graphql/model/template/field.rb +68 -0
  281. data/lib/ecoportal/api/graphql/model/template/force.rb +65 -0
  282. data/lib/ecoportal/api/graphql/model/template/helper.rb +32 -0
  283. data/lib/ecoportal/api/graphql/model/template/instance.rb +202 -0
  284. data/lib/ecoportal/api/graphql/model/template/node.rb +78 -0
  285. data/lib/ecoportal/api/graphql/model/template/option.rb +49 -0
  286. data/lib/ecoportal/api/graphql/model/template/read.rb +164 -0
  287. data/lib/ecoportal/api/graphql/model/template/section.rb +82 -0
  288. data/lib/ecoportal/api/graphql/model/template/stage.rb +49 -0
  289. data/lib/ecoportal/api/graphql/model/template/staged_executor.rb +236 -0
  290. data/lib/ecoportal/api/graphql/model/template.rb +37 -1
  291. data/lib/ecoportal/api/graphql/model/user.rb +14 -14
  292. data/lib/ecoportal/api/graphql/model.rb +1 -0
  293. data/lib/ecoportal/api/graphql/mutation/action/archive.rb +41 -41
  294. data/lib/ecoportal/api/graphql/mutation/action/create.rb +40 -40
  295. data/lib/ecoportal/api/graphql/mutation/action/update.rb +40 -40
  296. data/lib/ecoportal/api/graphql/mutation/action.rb +14 -14
  297. data/lib/ecoportal/api/graphql/mutation/ai_summary/generate.rb +1 -1
  298. data/lib/ecoportal/api/graphql/mutation/ai_summary/submit_feedback.rb +1 -1
  299. data/lib/ecoportal/api/graphql/mutation/ai_summary.rb +13 -13
  300. data/lib/ecoportal/api/graphql/mutation/contractor_entity/create.rb +40 -40
  301. data/lib/ecoportal/api/graphql/mutation/contractor_entity/destroy.rb +40 -40
  302. data/lib/ecoportal/api/graphql/mutation/contractor_entity/update.rb +40 -40
  303. data/lib/ecoportal/api/graphql/mutation/file_container/upload.rb +11 -4
  304. data/lib/ecoportal/api/graphql/mutation/image/upload.rb +88 -0
  305. data/lib/ecoportal/api/graphql/mutation/image.rb +14 -0
  306. data/lib/ecoportal/api/graphql/mutation/location_structure/apply_commands.rb +54 -54
  307. data/lib/ecoportal/api/graphql/mutation/location_structure/draft/add_commands.rb +55 -55
  308. data/lib/ecoportal/api/graphql/mutation/location_structure/draft/create.rb +54 -54
  309. data/lib/ecoportal/api/graphql/mutation/location_structure/draft/delete.rb +43 -43
  310. data/lib/ecoportal/api/graphql/mutation/location_structure/draft/drop_bad_commands.rb +56 -56
  311. data/lib/ecoportal/api/graphql/mutation/location_structure/draft/publish.rb +66 -66
  312. data/lib/ecoportal/api/graphql/mutation/location_structure/draft.rb +18 -18
  313. data/lib/ecoportal/api/graphql/mutation/location_structure.rb +13 -13
  314. data/lib/ecoportal/api/graphql/mutation/page/approve_review_task.rb +1 -1
  315. data/lib/ecoportal/api/graphql/mutation/page/archive.rb +40 -40
  316. data/lib/ecoportal/api/graphql/mutation/page/batch_update_review_task.rb +1 -1
  317. data/lib/ecoportal/api/graphql/mutation/page/build_from_template.rb +50 -50
  318. data/lib/ecoportal/api/graphql/mutation/page/create_draft.rb +1 -1
  319. data/lib/ecoportal/api/graphql/mutation/page/create_from_template.rb +43 -43
  320. data/lib/ecoportal/api/graphql/mutation/page/delete_draft.rb +1 -1
  321. data/lib/ecoportal/api/graphql/mutation/page/publish_draft.rb +1 -1
  322. data/lib/ecoportal/api/graphql/mutation/page/reject_review_task.rb +1 -1
  323. data/lib/ecoportal/api/graphql/mutation/page/restart_review_task.rb +1 -1
  324. data/lib/ecoportal/api/graphql/mutation/page/unarchive.rb +40 -40
  325. data/lib/ecoportal/api/graphql/mutation/page/undo_review_task.rb +1 -1
  326. data/lib/ecoportal/api/graphql/mutation/page/update.rb +40 -40
  327. data/lib/ecoportal/api/graphql/mutation/page.rb +28 -28
  328. data/lib/ecoportal/api/graphql/mutation/preset_view/create.rb +35 -35
  329. data/lib/ecoportal/api/graphql/mutation/preset_view/destroy.rb +1 -1
  330. data/lib/ecoportal/api/graphql/mutation/preset_view/permission.rb +37 -37
  331. data/lib/ecoportal/api/graphql/mutation/preset_view/update.rb +35 -35
  332. data/lib/ecoportal/api/graphql/mutation/preset_view.rb +15 -15
  333. data/lib/ecoportal/api/graphql/mutation/register/create.rb +35 -35
  334. data/lib/ecoportal/api/graphql/mutation/register/destroy.rb +1 -1
  335. data/lib/ecoportal/api/graphql/mutation/register/update.rb +35 -35
  336. data/lib/ecoportal/api/graphql/mutation/register.rb +14 -14
  337. data/lib/ecoportal/api/graphql/mutation/template/create.rb +35 -3
  338. data/lib/ecoportal/api/graphql/mutation/template/update.rb +4 -2
  339. data/lib/ecoportal/api/graphql/mutation.rb +1 -0
  340. data/lib/ecoportal/api/graphql/payload/action/archive.rb +13 -13
  341. data/lib/ecoportal/api/graphql/payload/action/create.rb +13 -13
  342. data/lib/ecoportal/api/graphql/payload/action/update.rb +13 -13
  343. data/lib/ecoportal/api/graphql/payload/action.rb +14 -14
  344. data/lib/ecoportal/api/graphql/payload/ai_summary_generate.rb +12 -12
  345. data/lib/ecoportal/api/graphql/payload/contractor_entity/create.rb +13 -13
  346. data/lib/ecoportal/api/graphql/payload/contractor_entity/destroy.rb +12 -12
  347. data/lib/ecoportal/api/graphql/payload/contractor_entity/update.rb +12 -12
  348. data/lib/ecoportal/api/graphql/payload/images_upload.rb +14 -0
  349. data/lib/ecoportal/api/graphql/payload/kickstand/bulk_update_jobs.rb +2 -2
  350. data/lib/ecoportal/api/graphql/payload/kickstand/bulk_update_workflows.rb +2 -2
  351. data/lib/ecoportal/api/graphql/payload/location_structure/apply_commands.rb +51 -51
  352. data/lib/ecoportal/api/graphql/payload/location_structure/command_execution_result.rb +50 -50
  353. data/lib/ecoportal/api/graphql/payload/location_structure/draft/add_commands.rb +30 -30
  354. data/lib/ecoportal/api/graphql/payload/location_structure/draft/delete.rb +30 -30
  355. data/lib/ecoportal/api/graphql/payload/location_structure/draft/drop_bad_commands.rb +43 -43
  356. data/lib/ecoportal/api/graphql/payload/location_structure/draft/publish.rb +63 -63
  357. data/lib/ecoportal/api/graphql/payload/location_structure/draft.rb +18 -18
  358. data/lib/ecoportal/api/graphql/payload/location_structure.rb +14 -14
  359. data/lib/ecoportal/api/graphql/payload/ok_payload.rb +1 -1
  360. data/lib/ecoportal/api/graphql/payload/page/archive.rb +13 -13
  361. data/lib/ecoportal/api/graphql/payload/page/build_from_template.rb +13 -13
  362. data/lib/ecoportal/api/graphql/payload/page/create_from_template.rb +13 -13
  363. data/lib/ecoportal/api/graphql/payload/page/draft.rb +1 -1
  364. data/lib/ecoportal/api/graphql/payload/page/review_task.rb +1 -1
  365. data/lib/ecoportal/api/graphql/payload/page/unarchive.rb +13 -13
  366. data/lib/ecoportal/api/graphql/payload/page/update.rb +13 -13
  367. data/lib/ecoportal/api/graphql/payload/preset_view.rb +1 -1
  368. data/lib/ecoportal/api/graphql/payload/register.rb +1 -1
  369. data/lib/ecoportal/api/graphql/payload.rb +1 -0
  370. data/lib/ecoportal/api/graphql/query/action.rb +33 -33
  371. data/lib/ecoportal/api/graphql/query/action_categories.rb +53 -53
  372. data/lib/ecoportal/api/graphql/query/actions.rb +43 -43
  373. data/lib/ecoportal/api/graphql/query/contractor_entities.rb +53 -53
  374. data/lib/ecoportal/api/graphql/query/file_upload_signature.rb +76 -76
  375. data/lib/ecoportal/api/graphql/query/location_classifications.rb +39 -39
  376. data/lib/ecoportal/api/graphql/query/page_delta.rb +47 -47
  377. data/lib/ecoportal/api/graphql/query/pages.rb +59 -59
  378. data/lib/ecoportal/api/graphql/query/permissions.rb +1 -1
  379. data/lib/ecoportal/api/graphql/query/person_members.rb +76 -0
  380. data/lib/ecoportal/api/graphql/query/register_preset_views.rb +1 -1
  381. data/lib/ecoportal/api/graphql/query/register_preview_pages.rb +83 -83
  382. data/lib/ecoportal/api/graphql/query/template_change_log.rb +118 -0
  383. data/lib/ecoportal/api/graphql/query/templates.rb +53 -53
  384. data/lib/ecoportal/api/graphql/query.rb +2 -0
  385. data/lib/ecoportal/api/graphql_version.rb +1 -1
  386. data/lib/ecoportal/api-graphql.rb +10 -10
  387. metadata +45 -9
  388. data/README.md +0 -24
  389. data/lib/ecoportal/api/common/graphql/CLAUDE.md +0 -36
  390. data/lib/ecoportal/api/common/graphql/model/CLAUDE.md +0 -28
  391. data/lib/ecoportal/api/graphql/CLAUDE.md +0 -37
  392. data/lib/ecoportal/api/graphql/base/CLAUDE.md +0 -50
  393. data/lib/ecoportal/api/graphql/builder/CLAUDE.md +0 -65
data/CHANGELOG.md CHANGED
@@ -2,7 +2,787 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- ## [Unreleased]
5
+ ## [3.0.0] - 2026-09-29
6
+
7
+ **Major, not minor:** `Base::Page::DataField::ImageGallery#file_container_ids`/
8
+ `#file_container_ids=` are REMOVED (see the first entry under `Changed`, below) -- a real
9
+ public-interface removal, not merely an addition, so this is a major version bump under
10
+ the versioning convention (minor = additive/breaking-but-additive, major = removal).
11
+ `lib/ecoportal/api/graphql/forces/` also left this gem for `oscar/eco-forces` (see the
12
+ `Changed` entry below) -- NOT breaking for any rubygems.org consumer (never packaged in a
13
+ released version). `lib/ecoportal/api/graphql/dsl/` left earlier, for `oscar/eco-dsl-dialects`
14
+ (FAAP-106 L2, `0bb6cd2`) -- also never released, and (owner ruling) not itself re-announced
15
+ here since there is nothing to announce; only this gem's own release guard and the fragments
16
+ whose described code STAYED are recorded below. **`lib/ecoportal/api/graphql/versioning/`
17
+ is likewise never packaged** (owner ARCHITECTURE ruling, PERKS, same day) -- unlike `dsl/`/
18
+ `forces/`, the CODE stays in this repo for now (extraction to its own private gem is a
19
+ follow-up card); every `Versioning::*`/`TemplateRegistry`/`DiffProfile`/`ProfileRunner`
20
+ entry this release would otherwise have listed is dropped from this changelog entirely
21
+ (none of it has ever shipped in a released version either) -- see the one `Removed` line
22
+ below.
23
+
24
+ ### Added
25
+
26
+ - **Added:** `WorkflowCommand` builder for `moveSectionToStage` (new in the 2026-09-29 live schema):
27
+ `build(:moveSectionToStage, source_stage_id:, target_stage_id:, section_id:, anchorId:, anchorPosition:)`,
28
+ moving an existing section to another stage. `anchorPosition` is validated like the other anchor commands.
29
+
30
+ - Test/tooling: DSL-08 emitter equivalence acceptance test
31
+ (`spec/ecoportal/api/graphql/emitters_equivalence_spec.rb`) and a new pure
32
+ `Model::Template::CommandNormalizer` helper (`lib/.../model/template/command_normalizer.rb`)
33
+ that repositions placeholder/id tokens to position and deep-sorts command keys so batches
34
+ from different emitters can be compared for structural equivalence. Measures (does not yet
35
+ fix) 8 real divergences between `Builder::TemplateBuilder`, `Diff::CommandSynthesizer`,
36
+ `Model::Template::CommandSynthesis`, the `template_field_config` DSL dialect, and the
37
+ a downstream script repo's writers against a shared one-stage/one-section/one-Select-field/
38
+ one-force/one-binding fixture -- see `TEMPLATE-MODEL-DESIGN.md` section 8 for the full
39
+ divergence table and fix plan. No production behaviour changed by this fragment.
40
+
41
+ - **`FileUpload::Client#upload_image`/`#upload_all_images`** — uploads a local file into the
42
+ Image Gallery upload pipeline (`uploadImage` → a `TempImage`, NOT `uploadFile` →
43
+ `FileContainer`) and returns the `TempImage` id (the `sourceId` an `ImageInput` write
44
+ needs). Shares its presign + S3-POST step with `#upload`/`#upload_all` (same collision-proof
45
+ key convention, same field order) — only the registration mutation differs. Same
46
+ `Error` hierarchy, same `:signature`/`:storage`/`:register` hooks, same bounded-concurrency
47
+ batch semantics as the existing File-container upload. New: `Mutation::Image::Upload`
48
+ (wraps `uploadImage(input: ImagesUploadInput!)`), `Payload::ImagesUpload`,
49
+ `Base::TempImage`/`Model::TempImage` (`id`, `s3Key`, `error`, `complete`).
50
+ - **`Base::Page::DataField::ImageGallery#add_source_images`** — merge-safe writer for Image
51
+ Gallery fields (mirrors `FileField#file_container_ids=`'s kept-vs-new split): keeps every
52
+ currently-loaded image (echoed back as `{id:}` — `ImageGalleryInput.images` is a FULL
53
+ REPLACE server-side, same trap as `FileField#items`) and adds new ones (`{sourceId:,
54
+ weight:, fileName:}`, `sourceId` being a `TempImage` id from `#upload_image`), at
55
+ sequential weights starting at `#next_weight` (one past the current maximum — there is no
56
+ server-side auto-increment for `weight`). Also adds `#images`/`#image_ids`/`#next_weight`
57
+ readers.
58
+
59
+ - `Model::Template.load(api, id:)` / `Model::Template::Instance` — the mutable, "three
60
+ lines" editable template model (the repo's internal docs): `template = Model::Template.load(api, id:); template.fields.each
61
+ { |f| f.label = ... }; template.save!(api)`. Reconciles the gem's existing one-shot
62
+ `Builder::TemplateBuilder`/`Diff::CommandSynthesizer` command-emission layer with the
63
+ a downstream script repo's toolkit's proven staged/chunked write pattern
64
+ (`CommandBatchExecutor`, `TemplateFieldsRead`, `SectionWriter`/`FieldWriter`/
65
+ `SelectOptionWriter`/`ForceAndBindingWriter`), promoted here as a GENERIC gem primitive
66
+ rather than duplicated per org.
67
+ - `#as_commands` — diffs the in-memory tree against its load-time snapshot, ordered stage
68
+ -> section -> field -> option/config -> force -> helper -> binding, with a per-stage
69
+ `reorderSection` trailer.
70
+ - `#save!(api, simulate: true)` (default) returns the staged PLAN with no server call;
71
+ `simulate: false` executes it through `Builder::Template#update` (`updatePageTemplate` —
72
+ never `executeWorkflowCommands`, per `18_template_editor_save_path.md` section D.4's own
73
+ recommendation), chunked (`Model::Template::CommandGrouper`, a generic union-find grouping
74
+ that never splits a placeholder-minting command from a same-batch dependent), staged in
75
+ four phases (stage / section / field+option / force / helper+binding) with a live re-read
76
+ + positional/count-guard/cross-check id resolution between phases — any number of
77
+ stages/sections/fields/forces, not one template's shape.
78
+ - `#verify(api)` re-reads and reports per changed property `verified`/`mismatch`/`missing`
79
+ against the model's OWN current (post-save) state.
80
+ - `Builder::Template#update` / `Mutation::Template::Update#query` gained an optional
81
+ `client_mutation_id:` (omitted when not given — the pre-existing call shape is byte-
82
+ identical for every other caller) for `StagedExecutor`'s own per-chunk tracing id.
83
+
84
+ - `rewrite_field_labels`/`rewrite_field_tooltips` results now carry a `selection_digest`
85
+ (a sha256 hex digest over the sorted touched field ids + their before-values -- never the
86
+ values themselves) -- `verify_field_labels`/`verify_field_tooltips` gained an OPTIONAL
87
+ `expected_selection_digest` argument: when given and it differs from a fresh digest of
88
+ the current selection, the verify call refuses with `selection_drift: true, verified:
89
+ false` and both digests (hex only) instead of the usual buckets. Closes "apply requires
90
+ a paired verify over the SAME selection" pinning a PREDICATE, not a fixed set -- the live
91
+ field set (or a field's own value) can change between a rewrite call and a later verify
92
+ call (a field added, a label hand-edited, a force re-installed) with nothing to detect
93
+ it (eco-forces consumer review finding F-04). Fully backwards compatible: omitting
94
+ `expected_selection_digest` verifies against the current selection exactly as before.
95
+ See the repo's internal docs, "Selection digest".
96
+
97
+ ### Changed
98
+
99
+ - **BREAKING:** `Base::Page::DataField::ImageGallery#file_container_ids`/
100
+ `#file_container_ids=` are REMOVED — an Image Gallery image was never a `FileContainer`;
101
+ `ImageGalleryInput` has no `fileContainerIds` on the live schema (confirmed against the live
102
+ ecoPortal server source — `ImageGalleryInput` takes `images: [ImageInput]`, referencing a
103
+ NEW image by `sourceId`, a `TempImage` id from the separate `uploadImage` mutation, never a
104
+ `fileContainerId`). The old accessors read/wrote a doc key (`fileContainers`) the live API
105
+ never returns or accepts — any caller depending on them was already silently broken (see the
106
+ three characterization bugs closed below). Replaced by `#images`/`#image_ids`/
107
+ `#add_source_images` (see `Added`, above) — a caller previously doing
108
+ `field.file_container_ids = [...]` should now upload via
109
+ `FileUpload::Client#upload_image` and call `field.add_source_images([{source_id:,
110
+ file_name:}, ...])` instead.
111
+
112
+ - `imageGalleryField` (`Fragment::Pages::CommonPageUnion`) now selects `id`/`weight` on each
113
+ `images` item — both were previously omitted entirely, which is why no correct reader was
114
+ possible before this fix (see the `Fixed` entry below).
115
+ - `fileName`/`fileSize` on an Image Gallery image are confirmed NULLABLE server-side even on
116
+ a successful upload (a documented upstream platform bug, per the live schema's own field
117
+ comment) — any comparison built on either must treat a nil on either side as "not a
118
+ confident match", never a positive one. Documented on
119
+ `Base::Page::DataField::ImageGallery`'s own header.
120
+
121
+ - **Changed (behaviour):** `Base::Page::DataField::ImageGallery#as_input` now echoes a KEPT
122
+ (currently-existing, untouched) image with its FULL `ImageInput` field set — `id`,
123
+ `sourceId` (read back from `uploadId`), `weight`, `caption`, `fileName`,
124
+ `sensitiveContent`, `inaccurateDescription`, `inaccurateExtractedText` — instead of a bare
125
+ `{id:}`. `ImageGalleryInput.images` is a full replace server-side
126
+ (`assign_attributes_service.rb#set_image_gallery_attrs` → `format_removed_ids`): a bare
127
+ `{id:}` is read by the server as "clear every other attribute on this image", not "leave
128
+ it as it is" — the previous shape silently reset caption/moderation flags to their
129
+ defaults on every write that merely appended a new photo alongside existing ones.
130
+ `#add_source_images` now keeps each existing image's FULL doc entry (not collapsed to a
131
+ bare `{id:}`) so this echo has the data to build from. `#add_source_images`'s own
132
+ kept-vs-new merge semantics (weight = current max + 1, sequential) are unchanged.
133
+
134
+ - `lib/ecoportal/api/graphql/forces/` (dry-run docking, the L1 force-representation
135
+ reader/bridge/history slice, the conformance harness -- 14 files) is REMOVED from
136
+ this gem and moves to `oscar/eco-forces` (owner ruling R-2026-09-28-64 D-19:
137
+ eco-forces is the most sensitive IP in this stack, so the GraphQL client adapter
138
+ code that docks directly into it gets its own repo, same reasoning as the earlier
139
+ `dsl/` -> `eco-dsl-dialects` move). NOT BREAKING for rubygems.org consumers: this
140
+ tree was never published in any released version of this gem -- confirmed 0 files
141
+ under this path in every release through 2.2.0 (measured via `gem fetch` +
142
+ `Gem::Package#contents`, per the release-guard's own header). The only consumer this
143
+ could ever have reached is a repo-checkout-level `require
144
+ "ecoportal/api/graphql/forces"` (opt-in, never part of the main require chain, only
145
+ resolvable under the now-removed `Gemfile.forces`). `lib/ecoportal/api/graphql/
146
+ forces/CLAUDE.md` stays as a short pointer (same pattern as `dsl/CLAUDE.md`). The
147
+ release guard (`scripts/audit/dsl_release_guard.rb`) keeps its own
148
+ `lib/ecoportal/api/graphql/forces/` protected-prefix entry, now lifted by this MR's
149
+ own `docs/audits/rulings/forces-licence-ruling.md` -- independent of `dsl/`'s own
150
+ entry, which stays blocked until a separate ruling exists.
151
+ - `Gemfile.forces` removed entirely -- nothing else in this gem required eco-forces
152
+ (confirmed: a full-repo grep for `eco/forces`/`eco-forces`/`Eco::Forces` outside the
153
+ removed tree found only doc/tooling mentions unrelated to any require path). Its
154
+ `.rubocop.yml` exclude entry removed with it.
155
+ - `tools/forces-conformance/` and `tools/forces-l1/` removed -- both existed only to
156
+ drive the now-removed lib tree (their own `require_relative`s pointed directly into
157
+ it); both moved to `oscar/eco-forces` verbatim instead (same commit/MR as the tree
158
+ itself).
159
+ - The six `changelog.d/` fragments describing this tree's own prior history
160
+ (`added-forces-conformance-harness.md`, `added-forces-docking-dry-run.md`,
161
+ `added-forces-l1-dump-adapter.md`, `added-forces-l1-history.md`,
162
+ `added-forces-l1-reader-bridge.md`, `fixed-forces-parity-rerun-protocol-run.md`) are
163
+ DELETED from this gem's changelog.d and migrated, reworded only where a path/name
164
+ changed, into `oscar/eco-forces`'s own `CHANGELOG.md` `[Unreleased]` section -- this
165
+ gem never shipped a tagged release carrying any of them, so nothing is lost from
166
+ this gem's own history by moving them. `security-faap-906b-guard-forces.md` (the
167
+ guard itself, not the moved code) stays here, unchanged.
168
+
169
+ **Migration:** if you were requiring `ecoportal/api/graphql/forces` at the
170
+ repo-checkout level (never possible via the published gem, see above), add
171
+ `eco-forces` to your own Gemfile instead -- see that gem's README, "GraphQL client
172
+ adapters (moved from ecoportal-api-graphql 3.0.0)".
173
+
174
+ - **Changed (behaviour):** `Input::Page::Update.from_model` now defaults `client_mutation_id:`
175
+ to a fresh `SecureRandom.uuid` per call instead of an omitted empty string — every
176
+ `updatePage` mutation this input builds is now traceable by default (correlation only; the
177
+ server does NOT dedupe mutations on `clientMutationId`, so this buys no idempotency — a
178
+ retried call with the same id still re-applies the write). Pass an explicit
179
+ `client_mutation_id:` to correlate several calls under one value, or `''`/`nil` to omit the
180
+ key entirely (unchanged).
181
+
182
+ - **Changed** `scripts/release_smoke_check.rb` (release tooling, not packaged in the gem): the
183
+ packaging gate now honours per-gem exceptions declared in a `.release-smoke-allow` file at the
184
+ gem root — one EXACT packaged path per line, no globs — plus a `RELEASE_SMOKE_EXTRA_ALLOWED`
185
+ env override for one-off runs. Needed because the shared gate hardcoded `lib/**/*.rb` and
186
+ refused eco-helpers 3.3.2, whose three JSON data files are loaded at require time and are
187
+ allowlisted by its own gemspec and packaging spec. Exceptions are themselves gated: a declared
188
+ path missing from the package FAILS (stale declarations cannot rot silently), and a declared
189
+ `.json` file must parse. Proven red-then-green against the real eco-helpers-3.3.2.gem artifact.
190
+
191
+ ### Fixed
192
+
193
+ - **Fixed:** `api.permissions` (`Query::Permissions`) failed against live after the 2026-09-29
194
+ platform release, which removed `Permissions.pageSmartAssistantCanChat` and
195
+ `Permissions.registerAssistantCanChat` (smart-assistant consolidation). `Fragment::Permissions` no
196
+ longer selects them. The model accessors are kept, deprecated, and now always return `nil`, so
197
+ callers do not break. Affects every earlier version, including 2.2.0.
198
+
199
+ - `AuthService::InstanceMethods#session_token_renewed` read the wrong hash key when no
200
+ explicit `refresh_token:` was given -- `body['resfresh_token']` (typo) instead of
201
+ `body['refresh_token']` -- so a session refresh attempted without an explicit token
202
+ always silently returned `nil` instead of renewing, even though the server's own
203
+ response body carried a real `refresh_token`. Fixed at
204
+ `lib/ecoportal/api/common/graphql/auth_service.rb`.
205
+ Zero specs existed for `AuthService` before this fix; `spec/ecoportal/api/common/
206
+ graphql/auth_service_spec.rb` now covers `#session_token` (happy path, the
207
+ auto-renew window, `auto_renew: false`, and a failing `session_token_data` call),
208
+ `#session_token_renewed` (explicit `refresh_token:`, reading it from the body, a
209
+ missing key, and a failing refresh call), and `#token_renew?`'s own boundary
210
+ (`TOKEN_AUTORENEW * 60` seconds, exactly at / one second inside / one second
211
+ outside) -- with a plain-Ruby fake HTTP client, no network, no new gem dependency.
212
+
213
+ - `Model::Template::CommandSynthesis` now emits `stageId` on `editFieldConfiguration` when
214
+ the owning template is phased (a new `Instance#phased?`, derived from the read's own
215
+ `page['__typename']`) -- `Field` always carries a `#stage` reference, but the command never
216
+ threaded it through, so editing a field via `Model::Template#save!` on a phased page would
217
+ omit a schema-required disambiguator and 500 server-side (found measuring DSL-08's emitter
218
+ equivalence acceptance test against the DSL dialect's own tested binding and the downstream
219
+ `FieldWriter`, both of which already got this right). `stageId` is still correctly OMITTED
220
+ (never sent as an explicit `null`) when the template is non-phased.
221
+
222
+ - `Ecoportal::API::Common::GraphQL::HttpClient#base_request` (the only place in this gem's
223
+ dependency chain that talks to the server via `http.rb`; every live GraphQL query goes
224
+ through it via `Logic::BaseQuery#graphql_query` -> `client.http_client.execute`, and the
225
+ OAuth token POST in `AuthService#auth_http_client` reuses the same method) called
226
+ `.timeout(read: READ_TIMEOUT, write: WRITE_TIMEOUT)` without a `connect:` value. http.rb's
227
+ `HTTP::Timeout::PerOperation` (`lib/http/timeout/per_operation.rb`) defaults any timeout key
228
+ NOT given to 0.25 seconds, so `@connect_timeout` stayed at that 0.25s library default even
229
+ though read/write were correctly set to 90s. `connect_ssl` (the TLS handshake) uses
230
+ `@connect_timeout` via `rescue_readable`/`rescue_writable`, whose error text is
231
+ unconditionally "Read timed out after #{@read_timeout} seconds" -- so a slow TLS handshake
232
+ against the EU instance surfaced as `HTTP::TimeoutError: Read timed out after 0.25 seconds`
233
+ even though the actual `@read_timeout` was 90 the whole time. Live-confirmed 2026-09-08 via
234
+ backtrace: `connect_ssl` -> `start_tls` (`http/connection.rb`) -> `Common::Client#post` ->
235
+ `AuthService#session_token_data` -> `Common::GraphQL::Client#initialize`. Added a
236
+ `CONNECT_TIMEOUT = 30` constant alongside `READ_TIMEOUT`/`WRITE_TIMEOUT` and pass
237
+ `connect: CONNECT_TIMEOUT` in the same `.timeout(...)` call. Read and write were never the
238
+ problem and are unchanged.
239
+
240
+ - `Ecoportal::API::Common::GraphQL::HttpClient#base_request` (the only place in this gem's
241
+ dependency chain that talks to the server via `http.rb`; every live GraphQL query goes
242
+ through it via `Logic::BaseQuery#graphql_query` -> `client.http_client.execute`, and the
243
+ OAuth token POST in `AuthService#auth_http_client` reuses the same method) built a plain,
244
+ non-persistent `HTTP` client. http.rb sends `Connection: close` on every request from a
245
+ non-persistent client (`http/client.rb`: `default_options.persistent? ? KEEP_ALIVE : CLOSE`).
246
+ Live-confirmed 2026-09-08: `eu.live.ecoportal.com` closes any HTTP/1.1 request carrying that
247
+ header without ever sending a response -- immediately, or after ~61s -- which surfaced in
248
+ Ruby as `OpenSSL::SSL::SSLError: SSL_read: unexpected eof while reading`. The same Ruby stack
249
+ with a persistent (keep-alive) client works (`HTTP.persistent("https://eu.live.ecoportal.com")
250
+ .post(...)` -> 200 in 1.9s); Sydney (`live.ecoportal.com`) is fine either way. `base_request`
251
+ now returns a client made persistent to the configured host (`HTTP.persistent(base_url,
252
+ timeout: KEEP_ALIVE_TIMEOUT)`, new `KEEP_ALIVE_TIMEOUT = 5` constant) before chaining the
253
+ existing `.accept(:json).timeout(...)`. Confirmed against the installed http.rb 5.3.1 source
254
+ that this is safe: one `HttpClient` is bound to one host, so `HTTP::StateError` (raised only
255
+ on a cross-origin request against a persistent client, `client.rb#verify_connection!`) cannot
256
+ occur here; and an expired or dead persistent connection is transparently closed and
257
+ reconnected on the next request (`verify_connection!` checks `@connection.expired?` /
258
+ `keep_alive?` and calls `close`, then `perform` lazily re-opens via
259
+ `@connection ||= HTTP::Connection.new(...)`), with any resulting `HTTP::ConnectionError`
260
+ already covered by the inherited `Common::Client` retry pipeline. `execute` and the rest of
261
+ the retry/throttle pipeline are unchanged.
262
+
263
+ - **Fixed:** `imageGalleryField` (`Fragment::Pages::CommonPageUnion`) now also selects
264
+ `sensitiveContent`, `inaccurateDescription`, `inaccurateExtractedText` on each `images`
265
+ item, alongside the pre-existing `id`/`weight`/`downloadUrl`/`caption`/`fileName`/
266
+ `fileSize`/`uploadId` — every `ImageInput`-echoable attribute is now read in one query, so
267
+ `#kept_image_input`'s full-field echo (see the `Changed` entry above) never needs a
268
+ follow-up read. `fileName`/`fileSize` remain documented NULLABLE even on a successful
269
+ upload (upstream platform bug) — never treat a nil on either as a confident match.
270
+
271
+ - `Base::Page::DataField::ImageGallery`'s reader closes all three defects
272
+ `image_gallery_characterization_spec.rb` documented: BUG-3a (reader keyed on the wrong doc
273
+ key, `fileContainers` instead of `images` — see `Changed`, above, for the fragment fix this
274
+ needed first), BUG-3b (the fragment omitted `images[].id`, so no correct reader was
275
+ possible), and BUG-3c (a pure read injected a phantom `fileContainers` key into `doc`) — the
276
+ new reader is a plain `doc['images']` accessor (no `passarray`), so a pure read no longer
277
+ mutates `doc` at all, a stronger fix than the prior array-diff no-op (MR !106) gave the old
278
+ reader.
279
+
280
+ - `Common::GraphQL::Client#schema` no longer lets graphql-ruby's "Input Object types must
281
+ have arguments" warning leak out for the argument-less INPUT_OBJECT types the live
282
+ ecoPortal schema actually ships (confirmed today: `SupervisedPeopleFieldValuesGeneratorInput`,
283
+ `DiscreteMetadataFilterWidgetInput`, `DateFilterWidgetInput`, `PeopleFilterWidgetInput`,
284
+ `ByUserAnalysisInput`, `ByTypeAnalysisInput`, `ByRelativeStatusAnalysisInput`,
285
+ `TemplatesAnalysisInput`, `RestorePageOperationInput`, `UnarchiveOperationInput`,
286
+ `ArchiveOperationInput`, `DeleteFilesOperationInput` -- 12 types, 0 after the fix).
287
+ `GraphQL::Schema::Loader` (used by `GraphQL::Schema.from_introspection`, which
288
+ `Graphlient::Schema` / `GraphQL::Client.load_schema` call to build our client-side schema
289
+ from the server's raw introspection JSON) defines one `GraphQL::Schema::InputObject`
290
+ subclass per INPUT_OBJECT type but never calls `has_no_arguments(true)` on the ones whose
291
+ `inputFields` came back empty -- so graphql-ruby warns (and, in a future version, will
292
+ raise) the first time anything asks such a type for its arguments, which happens whenever
293
+ the loaded schema is re-serialized (`schema.to_json` / `to_definition`), as eco-helpers'
294
+ `-graphql-schema` use case does. The live server never warns because its own Ruby classes
295
+ are defined with real argument lists (or `has_no_arguments(true)` already set) -- only the
296
+ client-side schema rebuilt purely from introspection data loses that annotation, so the fix
297
+ belongs here rather than in graphlient/graphql-client/graphql-ruby or in eco-helpers.
298
+ `#schema` now walks the freshly loaded schema's types once and calls the library's own
299
+ `has_no_arguments(true)` on every INPUT_OBJECT with no arguments, leaving types that do have
300
+ arguments untouched. No pre-processing of the introspection JSON, no schema shape change.
301
+
302
+ - **Fixed:** `updatePage` on a PHASED page has been observed to 500 when `input.stageId` is
303
+ missing on an Image Gallery / File field write (live-diagnosed, page
304
+ a customer's phased page) — `Input::Page::Update.from_model` now DERIVES a default
305
+ `stageId` from the owning stage of the data fields being changed in the same call (walks
306
+ `model.stages`/`Stage#stage_field_ids`), for both `dataFields.updates`/`.additions` and
307
+ `.deletions`. A `BasicPage` (no `#stages` at all) is unaffected — `stageId` is omitted
308
+ entirely, never sent as an explicit `null`. Explicit `stage_id:` still overrides the
309
+ derivation. If the fields being updated in ONE call span more than one stage,
310
+ `from_model` now raises a clear `ArgumentError` instead of guessing which stage's id to
311
+ send — split such a change into one call per stage, in stage order, re-fetching the page
312
+ (fresh `patchVer`) between calls. Corrects a stale doc comment on `#require_stage!` that
313
+ claimed "a plain field update ... needs no stage" — true only for a `BasicPage`.
314
+
315
+ - `Ecoportal::API::GraphQL::DSL::TemplateFieldConfig::Generator.declare_rewrite!`'s
316
+ `rewrite_field_<x>s`/`verify_field_<x>s` set-shaped verb pair now declares `acts_on` as
317
+ the UNION of every element noun type it can reach (`template` AND `data_field` -- was
318
+ `template` only). A set-shaped verb reaches EVERY `DataField` matching its pattern, not
319
+ just the template it is invoked against; under-declaring `acts_on` would under-approximate
320
+ a future eco-dsl-core strict-mode required-class set (found by the eco-forces consumer
321
+ review of this convention, finding F-07 -- `eco-dsl-core` `docs/reviews/2026-09-22-set-
322
+ shaped-verb-forces-consumer-review.md`).
323
+
324
+ - `spec/ecoportal/api/graphql/diff/{version_diff_spec,command_synthesizer_spec,deploy_spec,
325
+ version_diff_typed_config_spec,version_diff_modalities_spec}.rb` each duplicated the same
326
+ `fixture(name)` helper calling `File.read(path)` with no `encoding:` -- under a non-UTF-8
327
+ ambient locale (`LANG=C LC_ALL=C`, no `RUBYOPT` encoding flags) this raised
328
+ `Encoding::InvalidByteSequenceError` inside `JSON.parse` on the first non-ASCII byte in a
329
+ fixture, before a single document was even parsed (35 spec failures reproduced this way).
330
+ JSON is always UTF-8 per its own spec (RFC 8259 SS8.1), never a guess. Extracted the
331
+ duplicated helper into a single shared `Support.template_fixture` (`spec/support/
332
+ template_fixtures.rb`, `encoding: 'UTF-8'`); the five spec files now delegate to it. Spec
333
+ code only -- no `lib/` change. Verified: `LANG=C LC_ALL=C bundle exec rspec` and the normal
334
+ `bundle exec rspec` both green (2069 examples, 0 failures, 45 pending either way).
335
+
336
+ - `Mutation::Template::Create` / `Builder::Template#create` now accept an optional
337
+ `register_id:`, sent as `CreatePageTemplateInput.registerId` (verified against the live SDL,
338
+ 2026-09-04 — `registerId: ID` is an INPUT FIELD, because
339
+ `Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation` folds the backend's
340
+ `argument :register_id` into the generated input object). Without it a template create is
341
+ **unauthorizable**, not merely unbound: the backend resolves a page's registers by tag
342
+ (`NewEp::Pages::AssociatedRegisters` — org registers whose `filter_tags` are a subset of the
343
+ page's `combined_tags`) and `RegisterTemplatePermissionChecker` returns `false` outright when
344
+ that set is empty. A template created with no `registerId` carries no `base_tags`, so it
345
+ matches zero registers, so the check fails no matter what the account is granted — surfacing
346
+ as the misleading `You are not authorized to perform: edit_template_basic_settings`.
347
+ `Pages::Templates::CreateForm#bind_register!` is what closes the hole, setting
348
+ `page.base_tags = register.filter_tags` before the command batch runs.
349
+ A nil `register_id:` is OMITTED, never sent as an explicit null, so every existing call site
350
+ produces the byte-identical input it produced before.
351
+
352
+ - `tools/template_yaml/cli.rb --delta` now wires `reviewer: Review.method(:call)` into
353
+ `Delta.call`, so the printed JSON always carries a `review_findings` key (`{'before' => [...],
354
+ 'after' => [...]}` of Review finding triples), matching the fixture ground truth the
355
+ ep-rovo-qa delta-verification agent reads. It also merges a top-level **`warnings`** key
356
+ (`Delta::Result#warnings`) into the printed JSON -- pairing-key collisions, duplicate force
357
+ names, rebuilt-field counts -- so the `colliding_keys` false-pairing trap reaches the consumer
358
+ instead of being silently dropped. `Delta::Result#payload` itself is unchanged (it stays
359
+ byte-for-byte comparable to the Python projector's JSON); `warnings` is added by the CLI only,
360
+ on top of the payload, for `--delta` output specifically.
361
+ - Documented, in `cli.rb`'s header and `id_guard.rb`'s header, that `IdGuard`'s `HEX24` check is
362
+ a plain regex (`/\A[0-9a-f]{24}\z/`) with no ObjectId timestamp/counter decoding and no
363
+ uniqueness check beyond it -- confirming `md5(readable-name)[:24]` as an accepted convention for
364
+ synthetic fixture ids (a hex-24 string of either provenance passes identically).
365
+
366
+ ### Removed
367
+
368
+ - `versioning/` (git-core diff/versioning) is a perk -- not packaged in the public gem;
369
+ available from a private gem in a follow-up.
370
+
371
+ ### Security
372
+
373
+ - **Security (release gate):** `scripts/release_smoke_check.rb` now also gates packaged CONTENT,
374
+ not only which files ship. Every packaged text file is scanned for object ids, the private
375
+ source-host name, internal documentation paths, internal repository names, and a local
376
+ (never-shipped) denylist of customer names; hits fail the release unless excused by an exact
377
+ line in `.release-content-allow`. The gem now ships a short public `README.gem.md` instead of
378
+ the repository's developer README, and code comments and this changelog were reworded to
379
+ remove internal identifiers.
380
+
381
+ - New `bundler-audit` CI job (MIDW-909): runs `bundle exec bundle-audit check
382
+ --update` on every merge-request and branch pipeline, `allow_failure: false`.
383
+ `bundler-audit` (`~> 0.9`) added to the `Gemfile` (repo-local CI tool, not a
384
+ gemspec dependency).
385
+ - Fixed the "Insecure Source URI" finding the job first reported: the optional
386
+ `anthropic` dependency (only needed for `SearchConf::AIGenerator` live tests)
387
+ no longer has its own `source 'http://rubygems.org' do ... end` block -- it is
388
+ folded into the file's top-level `https://rubygems.org` source (owner ruling
389
+ 2026-09-29, option a). The block existed to bypass local SSL inspection on
390
+ some developer machines (e.g. Norton Antivirus); that workaround now belongs
391
+ on the machine (point `SSL_CERT_FILE` at the OS certificate store's CA
392
+ bundle), not in the dependency source URI. `bundle exec bundle-audit check
393
+ --update` now reports no vulnerabilities.
394
+
395
+ - Added a release guard, `scripts/audit/dsl_release_guard.rb`: no gem build that
396
+ packages a path under `lib/ecoportal/api/graphql/dsl/` may be published (CI job
397
+ `dsl-release-guard` on every pipeline; `rake release:build`, before the smoke check)
398
+ until `docs/audits/rulings/dsl-licence-ruling.md` carries a `RULED:` line. Stopgap
399
+ while the DSL licence question (FAAP-906) is open; does not change `LICENSE`, the
400
+ gemspec licence field, or `spec.files`.
401
+
402
+ - Extended the release guard, `scripts/audit/dsl_release_guard.rb` (FAAP-906): the
403
+ protected prefixes are now a list, each lifted independently by its own ruling
404
+ file. No gem build that packages a path under `lib/ecoportal/api/graphql/dsl/` OR
405
+ `lib/ecoportal/api/graphql/forces/` may be published (CI job `dsl-release-guard`;
406
+ `rake release:build`) until THAT prefix's own ruling file
407
+ (`docs/audits/rulings/dsl-licence-ruling.md` or
408
+ `docs/audits/rulings/forces-licence-ruling.md`) carries a `RULED:` line --
409
+ R-2026-09-28-64 (D-19): eco-forces is the most sensitive IP. Measured: the
410
+ published 2.2.0 gem ships zero files under either prefix. Does not change
411
+ `LICENSE`, the gemspec licence field, or `spec.files`.
412
+
413
+ - Extended the release guard, `scripts/audit/dsl_release_guard.rb`, with a THIRD
414
+ protected prefix, `lib/ecoportal/api/graphql/versioning/` (owner ARCHITECTURE
415
+ ruling 2026-09-29: dsl dialects, forces, and git-core diff/versioning are perks,
416
+ not core). No gem build that packages a path under this prefix may be published
417
+ until `docs/audits/rulings/versioning-licence-ruling.md` carries a `RULED:` line.
418
+ Unlike the dsl/forces rulings, this ruling file records that the prefix stays
419
+ blocked by design -- extraction to a private gem is a follow-up card, not
420
+ something this ruling completes. Measured: 0 packaged paths under all three
421
+ prefixes. Does not change `LICENSE`, the gemspec licence field, or `spec.files`.
422
+
423
+ ## [2.2.0] - 2026-09-02
424
+
425
+ ★ **This release supersedes 2.1.0, which was version-bumped and changelogged on 2026-08-21 but never
426
+ tagged or published** — RubyGems' highest was still 2.0.0. Do not go looking for 2.1.0; it does not
427
+ exist as a gem. Its contents are included below by inheritance (they were already on `main`), and
428
+ 2.2.0 adds what landed after that bump. Publishing the 2.1.0 artifact would have shipped a state
429
+ already superseded by merged `lib/` fixes and required a second release immediately.
430
+
431
+ **Minor, not patch:** `Input::WorkflowCommand::AddSelectFieldOption.build` now REQUIRES a `value:`
432
+ keyword. Under the versioning convention (minor = breaking, patch = compatible) that is breaking,
433
+ even though the omitted argument meant the old call shape was already rejected by the server — a
434
+ caller adapting to this is changing working-looking code.
435
+
436
+ ### Added
437
+
438
+ - `tools/template_yaml/yaml_to_raw_json.py` -- a small, self-contained YAML-to-raw-JSON bridge
439
+ (inlines the BSON-aware `EpLoader`/`load_dump` the retired Python projector used) so a template
440
+ backup YAML attached to a ticket can still be fed to the Ruby CLI, which only ever reads JSON.
441
+ Drops `update_histories` by default (churn neither projector read, and a recurring source of
442
+ corrupted ObjectIds), with `--keep-churn` to keep it.
443
+
444
+
445
+ - **`Page#all_tags` / `Stage#all_tags` read side** (`Model::Page::Phased`, `Base::Page::Phased::Stage`,
446
+ `Interface::BasePage`): the compiled tag set for a page or a single stage, folded client-side from
447
+ the page's `baseTags` and the stage's own tags, with both fetched by the page fragments
448
+ (`Fragment::Page`, `Fragment::Pages::CommonPageUnion`). `Stage#page` now resolves through the
449
+ native `DoubleModel` root reference rather than a hand-rolled back-pointer, and `location_ids`
450
+ is redirected page -> stage accordingly.
451
+
452
+ ★ This code was written on 2026-07-06 and then sat on three unmerged local branches for seven
453
+ weeks. `main` never received it in any form, and no other trace of it exists in history --
454
+ deleting those branches during a sweep would have lost a spec-covered feature permanently. It is
455
+ cherry-picked here onto current `main` rather than merged from the branch: `main` has since
456
+ changed four of the five files involved, so taking the branch's copies wholesale would have
457
+ reverted shipped fixes. That is the stranded-work failure mode this repo has a root-cause note
458
+ about, caught this time only because a branch sweep asked what each unmerged branch actually
459
+ contained.
460
+
461
+ - `tools/template_yaml/forces_walker.rb`: Ruby port of the template projector's `project_forces` /
462
+ `script_identifiers` (P4), classifying each binding as a FIELD or a SECTION target rather than
463
+ conflating the two, and reporting the unread force-level `globalBindings` gap as unverified
464
+ instead of as a finding. Gated by `tools/template_yaml/compare_forces.rb` against the Python IR.
465
+
466
+ - `tools/template_yaml/review.rb` / `update_gate.rb`: Ruby port of the template projector's
467
+ `review` / `update_gate` (P5), refusing a template's bulk update PER TEMPLATE (not per field)
468
+ when no field carries a usable identifier or identifiers are non-unique. Adds a Ruby-only
469
+ `ximport` tooltip guard: fields marked exclude-from-import are counted and protected, never
470
+ treated as overwritable. `Delta.call`'s `reviewer:` seam is now wired to `Review.method(:call)`,
471
+ so `review_findings` appears in the delta payload. Gated by `tools/template_yaml/compare_gate.rb`
472
+ against a customer's 18 templates -- refusal set matches the Python exactly.
473
+
474
+ - `tools/template_yaml/model.rb`: Ruby port of the template projector's `project(doc, source)`
475
+ (P3's IR assembly), composing field and force walks into a compact IR dict with key order
476
+ matching the Python prototype for P8 differential acceptance.
477
+ - `tools/template_yaml/render_markdown.rb`: Ruby port of `render_markdown(model, findings)` (P7),
478
+ rendering a template and its review findings as Markdown with all Python quirks reproduced
479
+ verbatim: the dead `stage` column, escape-then-truncate on `label`, redundant `type` re-split,
480
+ CRLF-to-LF normalization on force scripts, and preserved literals like `'(unnamed)'`,
481
+ `'(no custom script)'`, `'(none)'`.
482
+ - `tools/template_yaml/cli.rb`: Ruby CLI entrypoint matching Python's `main()` (P7), supporting
483
+ single-file and directory modes, `--out`, `--json`, and `--delta` flags. **Input is JSON
484
+ (`*.json`), not YAML**, per Loader's design (see loader.rb header). File writes are LF-only
485
+ on all platforms via binary mode. Directory mode processes `*.json` files alphabetically,
486
+ accumulates an `_index.md` at the end, and leaves earlier files on disk if an error occurs
487
+ mid-run (no transactional cleanup).
488
+
489
+ - `tools/template_yaml/differential_gate.rb`: P8, the template-yaml Ruby port's acceptance gate.
490
+ Regenerates BOTH IRs fresh in one run -- Ruby in-process (`Loader` -> `Model.build` -> `Review`
491
+ -> `UpdateGate`) over the `*.json` corpus, Python via a single batched subprocess (direct
492
+ import of `ep_template_yaml.py`, not its CLI) over the `*.yaml` corpus -- and diffs the ENTIRE
493
+ IR: every template attribute, every field, every force (incl. bindings), every review finding,
494
+ and the `update_gate` refusal set. Live run over a customer's 18 templates: **`P8 GATE: PASS`**,
495
+ 0 unexpected divergences, 12 expected (the six known-corrected ObjectIds, each seen twice --
496
+ once as a field id, once as a binding `reference_id`).
497
+ - `tools/template_yaml/known_divergences.rb`: the six-id whitelist and its `corrupted_id?` rule,
498
+ extracted out of `compare_fields.rb` so P3's gate and the new P8 gate share one definition
499
+ instead of two copies drifting apart.
500
+ - Fixed a Ruby port defect in `review.rb`, found live by the P8 differential run: finding
501
+ `detail` text quoted force/binding/field names with Ruby's `String#inspect` (always
502
+ double-quoted) where the Python source uses `%r` (CPython `repr()`, single-quoted by
503
+ default). Added `Review.py_repr` to reproduce Python's exact quoting.
504
+ - Excluded `patch_ver` and `updated_at` from P8's template-attribute comparison, alongside the
505
+ pre-existing `source_file` exclusion: the `templates_yaml`/`templates_json` fixture corpora
506
+ under `tmp/mns_01_tooltip_update/` are two separate exports pulled two days apart (confirmed
507
+ by file mtime), so these two pass-through fields legitimately disagree -- a corpus-provenance
508
+ artifact, not a port defect. `compare_fields.rb` / `compare_forces.rb`'s existing 0-unexpected
509
+ results already proved field/force content is identical across the two pulls.
510
+
511
+ ### Changed
512
+
513
+ - **Changed** what a failing query prints. `Logic::BaseQuery`'s rescue used to `pp kargs` /
514
+ `pp query_params` / `pp block` to **stdout** on every error. That existed for a real reason —
515
+ graphlient masks the origin, so it was the only way to see which query failed — but it put GraphQL
516
+ variables (page ids, emails, field values) on stdout and into CI logs. Ruby populates `.cause` when
517
+ raising inside a rescue, so the original error keeps the source location `graphql-client`
518
+ deliberately sets: the report now names `err.cause`'s class and **the failing query's own source
519
+ location** on **stderr**, which is more of what the dump was for. Variables are emitted only when
520
+ the client's existing `deep_logging` flag is on; `pp block` is dropped. A blank message (which
521
+ graphlient's `GraphQLError#to_s` can render) is now labelled instead of printing nothing.
522
+ Tracked as Q-43.
523
+
524
+ ### Fixed
525
+
526
+ - **Fixed** `currentOrganization.pageActivities` returning nothing. The wiring in
527
+ `Model::Organization` injected `base_path: ['currentOrganization']`, which overrides the
528
+ `base_path 'currentOrganization.page'` declared by `Query::PageActivities` — so the client
529
+ dug `currentOrganization.activities`, a path the emitted document never contains (the
530
+ inline-fragment `activities` merges into `page`). Added `subpath: 'page'`, plus a spec that
531
+ compares every wired query's declared base_path against the one the wiring injects, so the
532
+ whole class of bug is guarded rather than the one instance. Tracked as Q-13.
533
+
534
+ - **Fixed** `Common::GraphQL::ResponseError` discarding `errors[].extensions`. It now carries the
535
+ raw error entries and exposes `#errors`, `#extensions`, `#codes`, `#details`, `#full_messages`
536
+ and `#unauthorized?`. This is what makes a permission problem distinguishable from a request
537
+ problem in Ruby — the diagnosis a customer integration pack tells customers to perform, which until now no tool
538
+ built on this gem could actually follow. The message still LEADS with the joined server messages
539
+ (existing matchers keep working) and appends `code:` / `details:` after them. `tools/<customer>/cli.rb`
540
+ now reports the server's own classification instead of sniffing the message for `/unauthoriz/i` —
541
+ a sniff that could not tell a policy refusal from a FIELD-level gate. Tracked as Q-27.
542
+
543
+ - **Fixed** `Payload::Kickstand::BulkUpdateJobs` and `BulkUpdateWorkflows` raising `NoMethodError`
544
+ on `#items` and `#errors`. Both called `from_doc`, which is a per-class convention in this gem
545
+ (only 7 classes define one) and does not exist on `Base::Kickstand::Job`/`Workflow` or on
546
+ `Error::ValidationErrors`. Any caller reading the result of a Kickstand bulk update crashed.
547
+ Replaced with the real constructor, `new(doc)`. These two classes had **no spec of any kind** —
548
+ that is why it shipped — so this adds the missing coverage (12 examples over both, shared) as
549
+ well as the guard. Found while mapping the client's failure surface for Q-31.
550
+
551
+ - **Packaging allowlist is now keyed on the full path, not the root directory.** `spec.files`
552
+ previously allowed anything under `lib/` or `exe/`, which packaged five `CLAUDE.md`
553
+ agent-instruction files living inside `lib/` (573 files now ship, down from 578). The
554
+ publish-time gate in `scripts/release_smoke_check.rb` had the same hole -- it exempted `lib/`
555
+ wholesale, so it could not see them by construction. Both now require every packaged path to
556
+ match `lib/**/*.rb`, `exe/*`, or one of the three named root docs, and the rule is pinned by
557
+ `spec/packaging_spec.rb` (15 examples, including one that proves the excluded files remain
558
+ tracked in the repo). No credentials or customer data were ever affected.
559
+
560
+ - **Seven workflow-command input builders were silently discarding arguments the backend accepts.**
561
+ Each builder filters its keyword arguments through an allowlist with `kwargs.slice(*KEYS)`, so a
562
+ key missing from the allowlist is dropped before the request is built: no error, no warning, no
563
+ effect. The mutation succeeds and does nothing.
564
+
565
+ | command | argument restored |
566
+ |---|---|
567
+ | `addSelectFieldOption` | `value` — ★ `required: true` on the backend, so **every call this builder produced was rejected by the server** |
568
+ | `editSelectFieldOption` | `value` |
569
+ | `editFieldConfiguration` | `required`, `hiddenOnReports` |
570
+ | `editTemplateBasicSettings` | `enableCopyPage` |
571
+ | `EditFieldConfiguration::PlainText` | `multiline` |
572
+ | `EditFieldConfiguration::ContractorEntities` | `applyPermissionsToParent` (distinct from `applyAttachedContractorsPermissionsTo`, which was already present) |
573
+ | `editForce` | `lastSyncedAt` |
574
+
575
+ Found by comparing all 123 command-bus inputs against the backend Rails source rather than
576
+ against the gem's stored introspection snapshot — that snapshot is dated 2026-07-30, does not
577
+ show these arguments, and would have confirmed the wrong answer. 104 commands were verified
578
+ clean. Every restored key is now pinned by a spec asserting on the OUTPUT of `build`, and three
579
+ of the affected classes had no spec file at all before this, which is a large part of how the
580
+ drift survived.
581
+
582
+ - **`Diff::Change` and the template-builder options DSL now carry an option's `value`.** Making
583
+ `value` required on `AddSelectFieldOption.build` exposed that two production paths never had one:
584
+ `Builder::TemplateBuilder#emit_options` and `Diff::CommandSynthesizer#option_command`. Neither had
585
+ ever been able to emit a valid `addSelectFieldOption`; the specs that passed were pinning a
586
+ command shape the server rejects. `VersionDiff#index_options` now captures `o['value']` alongside
587
+ label and weight — the field was already being fetched by the page-content fragment and thrown
588
+ away by the diff layer. Where no value is available, both paths **raise** naming the option and
589
+ field: `value` is documented as cast to a number for numeric select fields, so defaulting it to
590
+ the label would silently store wrong data on precisely the scored fields that matter.
591
+
592
+ - Corrected a false comment in `add_field.rb` asserting that a field's `required` flag "cannot be
593
+ persisted through the workflow command bus at all". It can, via `editFieldConfiguration`. A
594
+ comment stating a platform limitation that does not exist is worse than no comment: it stops
595
+ people trying.
596
+
597
+ ### Removed
598
+
599
+ - `tools/template_yaml/ep_template_yaml.py` and `tools/template_yaml/mns_review.py` -- the
600
+ original Python template-backup projector and its cross-template audit. P9 of the template-yaml
601
+ Ruby port: the Ruby port (`model.rb`, `review.rb`, `update_gate.rb`, `render_markdown.rb`,
602
+ `cli.rb`) was proven byte/field-parity-identical to both by the P8 differential gate
603
+ (`differential_gate.rb`, 0 unexpected divergences over 18 live templates) before removal, and
604
+ the last remaining live use case -- a Jira ticket's YAML-only attachment (EPT-1872) -- was
605
+ re-verified against the promoted bridge (`yaml_to_raw_json.py`) with byte-identical
606
+ Python/Ruby deltas. Recoverable in full: `git show
607
+ 0caa6f3d80c6495a9f92132178b9a6a42deb40f5:tools/template_yaml/ep_template_yaml.py` (and the same
608
+ path for `mns_review.py`).
609
+
610
+ ## [2.1.0] - 2026-08-21
611
+
612
+ Everything accumulated on `main` since `v2.0.0` (2026-08-13): 150 commits touching 73 files under
613
+ `lib/`. **Minor, not patch:** the bulk is additive -- new data-field models, the last command-bus
614
+ command, a native people-directory query, template-change-log reading -- and 73 library files is not
615
+ a patch by any honest reading. **Minor, not major:** no surface was removed or reshaped, so `~> 2.0`
616
+ consumers adopt it without a floor change.
617
+
618
+ ★ Why this release exists at all, beyond the contents: a remote clone installs PUBLISHED gems, so
619
+ until this went out none of that work could reach the api runner -- and `main` could not be released
620
+ either, because its version constant still read `2.0.0`, which `rake release:check` correctly refuses
621
+ as already published. Three weeks of library work with no route to a remote is the failure mode this
622
+ closes.
623
+
624
+
625
+ ### Added
626
+
627
+ - **Wired the `manageCopyPageConfiguration` workflow command** (`Input::WorkflowCommand::ManageCopyPageConfiguration`,
628
+ first observed in the live schema 20260819): controls `overridePageCreatorPermissions`,
629
+ `autoIncludeNewContent`, and per-data-field `inclusionSettingsChanges` (`dataFieldId` /
630
+ `include` / `enforce`) for copy-page behaviour. Closes the last gap in the command bus --
631
+ `validate-queries` now reports 106/106 commands wired.
632
+
633
+ - `tools/<customer>/collection.rb` -- a pure-Ruby, stdlib-only read interface over a customer's Postman "ALL
634
+ CASES" master collection (the sibling `ep-api-collections` repo masters it; this gem never
635
+ hand-copies its knowledge). Walks the folder tree, exposes each GraphQL request's full path,
636
+ name, query, parsed variables, and `{{placeholder}}` inventory, with a name/path lookup that
637
+ tolerates a folder-path prefix and raises a named error listing near-matches when nothing
638
+ resolves. `spec/tools/<customer>/collection_sync_spec.rb` pins the webhook-admin and build/update-page
639
+ request paths plus the placeholders the tooling depends on, and fails -- naming exactly what
640
+ disappeared -- if the collection renames or removes one of them; it skips cleanly (not fails)
641
+ when no sibling checkout is present. See `tools/<customer>/README.md`.
642
+
643
+ - `tools/template_yaml/mns_review.py` -- cross-template audit over a directory of IR projections:
644
+ identifier coverage, `templateContainerUid` uniqueness and name plausibility, findings by code,
645
+ authoring-maturity signal, and classification of orphaned force bindings into projector gaps
646
+ versus real defects (checked against the source dumps). First run recorded in
647
+ `tools/template_yaml/reviews/2026-08-09-<customer>-18-templates.md`.
648
+
649
+ - **`tools/<customer>/cli.rb`** -- a Ruby CLI that creates/lists/destroys a customer's webhook
650
+ subscription and verifies the setup read-only, runnable from a support server with nothing
651
+ but `USER_EMAIL`/`USER_PASS` supplied (every other value defaults to a customer UAT org, EU
652
+ instance). `webhook create` is idempotent in practice: it lists first and reports the
653
+ existing subscription instead of creating a duplicate when one already matches the URL and
654
+ template(s). `--dry-run` prints the GraphQL document and variables with no network call.
655
+ Document builders live in `tools/<customer>/webhooks.rb` (`<Customer>Webhooks::Documents`) as pure
656
+ functions, spec'd in `spec/tools/<customer>/webhooks_spec.rb` with no network involved.
657
+
658
+ - **`Query::PersonMembers`** — the GraphQL-native people-directory path over
659
+ `organization.personMembers`, filling the gap flagged in `model/organization.rb`. Salvaged
660
+ from a stranded July branch (`feat/scim-apiv0-adapter-design`) whose SCIM design and
661
+ scaffold were superseded by the `ecoportal-scim` gem's `apiv0-people-adapter` project; this
662
+ query was the only part of it with no equivalent anywhere. ★ It is NOT the SCIM-sourced
663
+ People replacement for the APIv0 retirement — that port lives in `ecoportal-scim`.
664
+ Reuses the existing `Base`/`Model`/`Connection::PersonMember` classes; no new models.
665
+
666
+ - `scripts/release_smoke_check.rb` — pre-push gate for BUILT gem artifacts (any gem in the
667
+ stack): packaging allowlist, byte corruption (`\r\r`, BOM, mixed EOL), and corruption-class
668
+ parse warnings via `ruby -wc`. Proven against real defects: red on eco-helpers 3.2.21
669
+ (shipped `\r\r\n`) and on the first 3.2.22 build (CHANGELOG `\r\r` via autocrlf smudge),
670
+ green on the corrected artifacts. Run it on every `.gem` before `gem push`.
671
+
672
+ - **Template change log now reports WHAT changed**, not only who/when/which-resource. New
673
+ registered fragment `:TemplateChangeLogEntryDetail`, opt-in via
674
+ `api.template.change_log(id:, first:, detail: true)` or
675
+ `&Query::TemplateChangeLog.detail_block`. The default selection is unchanged and still light —
676
+ the common "who touched this template and when" read does not pay for per-type detail.
677
+ `Base::Template::ChangeLogEntry` gains `#changes`, `#changeMessages`, `#detail_kind`
678
+ (`:changes` / `:change_messages` / `:es_changes` / nil) and `#detail?`.
679
+ ★ The three concrete command types report changes in three different shapes, so `#changes`
680
+ returns raw hashes rather than a typed embed that would have to lie about one of them:
681
+ `PagesWorkflowCommandChange.changes` is `{field, from, to}` with STRING values;
682
+ `PagesWorkflowCommandEsChange.changes` has `from`/`to` as `StoredEsFilterType` OBJECTS (only
683
+ `field` is selected, so an ES change names which filter moved, not its before/after);
684
+ `PagesWorkflowCommandChangeMessage` carries pre-rendered `changeMessages` and no field/from/to.
685
+ Branch on `#detail_kind`. ★ Empty readers mean NOT SELECTED, never "nothing changed".
686
+
687
+ - **Template change log (GraphQL-native)** — `Query::TemplateChangeLog` reads the platform's
688
+ per-change audit log for a template: "for template X, who changed what and when", newest first,
689
+ paginated. Reachable as `api.template.change_log(id: template_page_id, first: 50)`, or bare
690
+ (`api.template.change_log.each(id: …)`) for an auto-paginating enumerator.
691
+ Read path: `currentOrganization.page(id:) → template → commands` — `template` is declared on
692
+ `BasePageInterface`, so it is selected through a type condition (selecting on the returned
693
+ `PageUnion` directly is invalid GraphQL). New supporting types:
694
+ `Base::Template::ChangeLogEntry`, `Model::Template::ChangeLogEntry` (adds the `owner` embed —
695
+ WHO changed it), `Connection::TemplateChangeLog`, and the registered fragment
696
+ `:TemplateChangeLogEntry` on the interface `PagesWorkflowCommandInterface`.
697
+ ★ Two access caveats, both silent and both documented on `Base::Template::ChangeLogEntry`:
698
+ (1) the connection is **nil, not empty**, unless the caller satisfies
699
+ `Pages::TemplatePolicy#update_information?` — a read-only account sees no log and no error, so
700
+ never read nil as "nothing changed"; (2) the server scope is `from_template_builder`, so only
701
+ `TEMPLATE_BUILDER`-sourced commands appear — `WORKFLOW_BUILDER` changes to the same page are
702
+ excluded (those are on `Query::PagesWorkflowCommands`).
703
+ ★ Why this beats polling a watermark: a forces run writes with `skip_patch_incr` + `timeless`,
704
+ bumping **neither `patchVer` nor `updatedAt`** — any watermark-polling change tracker is blind to
705
+ force-driven change, whereas the command log is per-change. (Open: whether forces run against
706
+ templates specifically is not confirmed.)
707
+ Also documented in the repo's internal docs.
708
+
709
+ - **New tool `tools/template_yaml/delta.rb`** (P6 of the template-YAML-tooling Ruby port): the
710
+ deterministic before/after difference between two `project()` snapshots, shaped for the Rovo
711
+ delta-verification agent. Field pairing on id-then-identifier (so a REBUILT field -- new id,
712
+ same `description` -- is one `changed` entry rather than an add/remove pair), `TRACKED`-only
713
+ attribute diffing, force diffing by name with binding set-difference, the five-key template
714
+ diff, and the four `not_evaluable` strings reproduced VERBATIM, because the Rovo agent is
715
+ instructed to read that list literally and any wording drift changes what it is told it cannot
716
+ verify. **Verified against the canonical Python on the known pre/post pair (all 7 comparable
717
+ sections identical) and on 9 synthetic scenarios covering every branch -- rebuild, removal,
718
+ untracked-only change, template attributes, force add/remove/script/binding-set-diff, and
719
+ reference-id-only binding moves -- 0 mismatches.**
720
+ - **`Delta::Result` reports what the pairing could not say cleanly** -- colliding pairing keys,
721
+ duplicate force names (only the last is diffed), and rebuild pairings -- rather than returning
722
+ a bare hash the way the Python does. `review_findings` is **omitted** from the payload until a
723
+ reviewer is injected (it re-runs `review()`, which is P5): emitting an empty list there would
724
+ read as "no findings" when the truth is "not checked".
725
+ - **Corrected `P1-CONTRACT.md` section 8.** It claimed `_key` can return `None` and that fields
726
+ with neither id nor description are "filtered out of both snapshots entirely ... invisible to
727
+ `delta` by construction". Verified by running the Python: `'ref:' + ''` is truthy, so the
728
+ `or None` branch is unreachable and `_key` never returns `None`. Those fields are not filtered
729
+ out -- they all collapse onto the single key `'ref:'`, so an unidentified field in `before`
730
+ pairs with an **unrelated** unidentified field in `after` and their differences are reported as
731
+ a change. Not a live defect (every membrane in the JSON export carries a validated hex `_id`),
732
+ but a latent trap; the port reproduces it for parity and warns on it.
733
+
734
+ - **New tools `tools/template_yaml/field_walker.rb` + `tools/template_yaml/compare_fields.rb`**
735
+ (P3 of the template-YAML-tooling Ruby port): enumerates a template's fields in on-screen
736
+ LAYOUT order -- `flow_nodes` by `weight`, then `membrane_ids` / `left_membrane_ids` /
737
+ `right_membrane_ids`, then each id list in stored order -- porting the Python projector's
738
+ `walk_fields` against the contract frozen in `P1-CONTRACT.md`.
739
+ **Verified against the real 18-template customer dump set: 2,714 fields, ids and ordering match the
740
+ Python projector exactly, with 0 unexpected divergences.** The only 6 differences are the
741
+ `ActionsList` ids the Python's YAML path corrupts (a contiguous run of `ActionsList` ids), where
742
+ the Ruby is correct and the Python is not; `compare_fields.rb` whitelists those six BY VALUE,
743
+ so a seventh divergence -- or any ordering defect on that same template -- still fails.
744
+ - **`FieldWalker` distinguishes "no fields" from "could not see the fields"**, which the Python
745
+ cannot: it returns a `Result` carrying the faithful field list plus every membrane it dropped,
746
+ every layout reference it could not resolve, duplicate placements, non-hash nodes and
747
+ defaulted weights, with `#blind?` true exactly when an empty list is untrustworthy. A
748
+ command-bus-built template with no layout, a `SplitSection` with no `dataFields`, and a
749
+ genuinely empty template all return an empty or short list from the Python with no error; they
750
+ are now told apart. Three port-critical quirks are preserved deliberately and covered by
751
+ specs: Python truthiness in the `or` chains (an empty `heading` must fall through to
752
+ `left_heading`, an empty `_type` must stay `''` -- `''` is truthy in Ruby), the STABLE node
753
+ sort (`Array#sort_by` is not stable), and `bool`-is-`int` weights.
754
+
755
+ - **New tools `tools/template_yaml/loader.rb` + `tools/template_yaml/id_guard.rb`** (P2 of the
756
+ template-YAML-tooling Ruby port): loads an ecoPortal template backup dump from its **JSON**
757
+ export -- not the YAML -- and validates every id-shaped value against `/\A[0-9a-f]{24}\z/`,
758
+ raising loudly (template, field path, offending value) rather than letting a bad id through.
759
+ Reading JSON instead of YAML is deliberate: the Python projector's YAML path decodes
760
+ `!ruby/object:BSON::ObjectId` via a step with a silent-failure mode (confirmed unchanged on
761
+ the canonical post-!86 surface in `P1-CONTRACT.md`) that corrupts 6 of 2,432 fields on one
762
+ customer template without affecting counts. The JSON export carries identical data with plain hex
763
+ ids already, so the whole decode-bug class is structurally impossible on this path rather
764
+ than reimplemented. Verified against the real 18-template customer JSON dump set, including the 6
765
+ previously-corrupted `ActionsList` ids on "Yard 5S Site Standard"
766
+ (a contiguous run of `ActionsList` ids), which load and validate correctly.
767
+
768
+ ### Changed
769
+
770
+ - **`ecoportal-api-v2` floor raised to `>= 3.3.5`** (was `>= 3.3.3`). 3.3.5 carries the fix for
771
+ `ClassHelpers#to_time`, whose rescue clause named a constant that does not exist
772
+ (`ArgumentArgument`), so its documented `exception: false` mode raised
773
+ `NameError: uninitialized constant ...ClassHelpers::ArgumentArgument` instead of returning nil.
774
+ This gem inherits that helper through DoubleModel, so any null or object-shaped date reaching a
775
+ `passdate` field hit it. ★ Side benefit: the old floor pointed at **3.3.3, which was yanked**
776
+ for shipping internal repository content to rubygems — a floor should not name a yanked version.
777
+
778
+ ### Fixed
779
+
780
+ - `FieldConfig::CrossReference` could not set `enableCrossRegisterReporting` (Boolean) — the schema
781
+ accepts the key but no builder path could reach it. Closes the last MISSING-key advisory reported
782
+ by `tests/validate_commands.rb` for the `crossReference` byType key.
783
+
784
+ - Stripped UTF-8 BOMs from 58 `lib/` source files (present since early development; harmless
785
+ to Ruby but flagged by the release smoke gate, and byte-noise for every downstream diff).
6
786
 
7
787
  ## [2.0.0] - 2026-08-13
8
788
 
@@ -10,8 +790,7 @@ Major release: everything accumulated on `main` since `v1.3.14` — the 2026-07-
10
790
  schema syncs, command-bus completion (105/105 commands), native file upload, and a long run of
11
791
  correctness fixes. **Breaking, but narrowly:** every removed or reshaped surface either targeted
12
792
  a mutation the live schema never had (so it could never have worked) or was a dead accessor with
13
- no consumer — a 2026-07-30 sweep of `eco-helpers`, `integration-training`, `multi_org_api` and
14
- `multi-org-idle` found no consumer of any breaking surface (re-confirmed 2026-08-12). The major
793
+ no consumer — a 2026-07-30 sweep of `eco-helpers` and three internal script repos found no consumer of any breaking surface (re-confirmed 2026-08-12). The major
15
794
  bump is deliberate: consumer repos declare `~> 1.3` (which allows 1.4.x), so a minor would have
16
795
  been adopted silently on the next `bundle update`; 2.0.0 makes adoption a per-repo decision.
17
796
 
@@ -25,7 +804,7 @@ Note: this release does not descend from `v1.3.15`, which shipped from the `rele
25
804
  branch forked at 1.3.14 — do not expect a linear tag history.
26
805
 
27
806
  ★ **The published gem is now `lib/` only.** `spec.files` excluded just `test|spec|features`, so
28
- every top-level directory added since shipped by default — 244 files of internal `.ai-assistance/`
807
+ every top-level directory added since shipped by default — 244 files of internal AI-tooling
29
808
  notes, `docs/` (including the rolling worklog), and `.claude/`. `spec.files` is now an allowlist
30
809
  (`lib/`, `exe/`, README, LICENSE, CHANGELOG), which also drops the package from 959 KB to 195 KB.
31
810
  All 568 `lib/` files are unchanged; nothing outside `lib/` was ever loaded at runtime.
@@ -68,7 +847,7 @@ All 568 `lib/` files are unchanged; nothing outside `lib/` was ever loaded at ru
68
847
  but only one may be published (`active: true`) per organization at a time — publishing another
69
848
  with the same uid unpublishes the incumbent. Previously the value was fetchable from the API but
70
849
  unreachable through the gem (the embed was commented out and no fragment selected it).
71
- Semantics documented in `.ai-assistance/code/ecoPortal_architecture/02_data_model.md`.
850
+ Semantics documented in the repo's internal docs.
72
851
  - **`editTemplateContainerUid` workflow command** — `Input::WorkflowCommand::EditTemplateContainerUid`,
73
852
  registered on the command bus. Unlike its `editTemplate*` siblings it raises on a nil value rather
74
853
  than emitting an empty command (the schema argument is `String!`) and validates the backend's
@@ -96,7 +875,7 @@ All 568 `lib/` files are unchanged; nothing outside `lib/` was ever loaded at ru
96
875
  diff (adds/removes/changes/reorders, bookkeeping noise hidden by default, nil≡empty-container
97
876
  lenience for document-age drift). Detects a foreign id-space and points at
98
877
  `Diff::CrossObjectDiff` instead of emitting a misleading add/remove-everything report.
99
- - Scoping project `.ai-assistance/projects/data-toolset/` — coordination of the data
878
+ - Scoping project the repo's internal docs — coordination of the data
100
879
  import/extract/migration tool-set (legacy imports, template and register metadata table
101
880
  definitions, BI warehouse extracts, spreadsheet and back-end imports, register-to-register and
102
881
  org-to-org migrations, register-revamp mapping and readiness reporting). Capability map C1-C8,
@@ -163,7 +942,7 @@ The web "System Access Logs" page and page "history changelog" are driven by
163
942
 
164
943
  - `ecoportal-api` dependency floor raised to `0.10.17`: inherits the `WithRetry` fix that
165
944
  makes `HTTP::TimeoutError` (connect/read/write timeouts) retryable — previously a single
166
- transient timeout killed whole batch loops (live-confirmed on Farmers, 2026-07-30).
945
+ transient timeout killed whole batch loops (live-confirmed on a customer org, 2026-07-30).
167
946
  - **`graphlient` dependency now points at the published `0.9.0` release** instead of the fork's
168
947
  frozen git branch (`rellampec/graphlient@pre-v0.0.9`). `0.9.0` ships the same DSL/directive
169
948
  work the gem already relied on (`#115` `to_query_string`/`spread`, `#116` directives DSL,
@@ -286,7 +1065,7 @@ The web "System Access Logs" page and page "history changelog" are driven by
286
1065
  `people{id}` nodes on both sides), not the raw `peopleIds` key — the backend can return
287
1066
  `peopleIds: null` on reads while still populating `people`/`cachedPeople`, so restating an
288
1067
  already-attached person id no longer registers as a change. Live-confirmed 2026-07-16
289
- (act-gov TOOCS dry-run reported "Updated 6/6" for a no-op). Mirrors the `CrossReference`
1068
+ (a customer dry-run reported "Updated 6/6" for a no-op). Mirrors the `CrossReference`
290
1069
  fix for the identical disease; `as_input` now serialises the fallback-aware `people_ids`
291
1070
  reader instead of the raw doc key.
292
1071
  - `ContractorEntities` field: the reader read the staged write key (`contractors`), which read
@@ -358,7 +1137,7 @@ The web "System Access Logs" page and page "history changelog" are driven by
358
1137
  **Migrate to:** `editTemplateBasicSettings` (name/icon/settings/counter/tags/folders/creator
359
1138
  perms), `editTemplateDescriptions` (`description`), `editTemplateCreatorPermissions` (creator
360
1139
  perms). The old `editPage` `addOtherTags`/`removeOtherTags` keys have no `editTemplate*`
361
- equivalent (dropped from schema). No downstream callers existed (eco-helpers / multi_org_api /
1140
+ equivalent (dropped from schema). No downstream callers existed (eco-helpers / internal script repos /
362
1141
  gem swept) so the removal is clean. See DECISIONS.md in the workflow-space project.
363
1142
  - **`Mutation::SmartFill::Generate` / `Mutation::SmartFill::SubmitFeedback` and
364
1143
  `Builder::Page#generate_smart_fill` / `#submit_smart_fill_feedback`** (breaking, but dead
@@ -380,13 +1159,13 @@ The web "System Access Logs" page and page "history changelog" are driven by
380
1159
  to measure coverage — only the QUESTION carries one; its satellites (comment, image, document,
381
1160
  recommended action) do not and are not expected to. The denominator is workbook questions
382
1161
  (`question_ref`), never the field count; measuring against all fields yields a meaningless ~16%.
383
- - `.ai-assistance/projects/mns-template-lifecycle/WORKBOOK-FIELD-MAPPING.md` — the authoritative
1162
+ - the repo's internal docs — the authoritative
384
1163
  workbook-to-template contract recovered verbatim from the 2026-08-08 session transcript:
385
1164
  `QuestionSetReference` → template_container_id (UI "UNIQUE IDENTIFIER"), `QuestionReference` →
386
1165
  field `description` (CONFIRMED), `QuestionInformation` → field tooltip (strip HTML entities),
387
1166
  scores ignored, section-vs-stage left ambiguous, plus the template-pairing procedure and the
388
1167
  DEV-site targeting note.
389
- - `.ai-assistance/conventions/recovering-prior-context.md` — past sessions are greppable JSONL
1168
+ - the repo's internal docs — past sessions are greppable JSONL
390
1169
  transcripts on disk (35 sessions, 85 MB); search them before asking the developer something he may
391
1170
  already have answered.
392
1171
 
@@ -514,7 +1293,7 @@ signed-off future decision.)
514
1293
  (`external_id`/`id`/`state`) — a spurious path produced term `membranes.external_id.external_id`
515
1294
  → 0 results (silent miss → dup-on-upsert). Register FIELD keys still get the nested path.
516
1295
  - Verified against the backend filter definitions; catalogued in
517
- `.ai-assistance/code/filter_contract_matrix.md`.
1296
+ the repo's internal docs.
518
1297
 
519
1298
  ### Added
520
1299
 
@@ -586,7 +1365,7 @@ shapes checked against the live schema dump); the live UAT→PROD round trip is
586
1365
  longer exists (fragments are a registry now) → `uninitialized constant … Fragment::… (NameError)` at
587
1366
  render time. Now `spread :Action` / `spread :ContractorEntity`, matching the already-migrated queries.
588
1367
  (The sibling breakage in eco-helpers' location-command optimizations is fixed in eco-helpers 3.2.17;
589
- it crashed the live act-gov locations sync.)
1368
+ it crashed a customer's live locations sync.)
590
1369
  - **Kickstand workflow mutations — bare `DateTime` selection.** `Kickstand::{Start,Stop,Fail}Workflow`
591
1370
  selected `startedAt`/`stoppedAt`/`failedAt` bare → "Field must have selections". Now
592
1371
  `{ dateTime timeZone }` (same class as the 1.3.10 LocationStructure fix).
@@ -614,7 +1393,7 @@ shapes checked against the live schema dump); the live UAT→PROD round trip is
614
1393
 
615
1394
  ## [1.3.10] - 2026-07-03
616
1395
 
617
- Hotfix (backwards-compatible). Resolves a live failure on the Turners & Growers users sync.
1396
+ Hotfix (backwards-compatible). Resolves a live failure on a customer's users sync.
618
1397
 
619
1398
  ### Fixed
620
1399