ecoportal-api-graphql 1.3.16 → 2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +384 -6
- data/lib/ecoportal/api/common/graphql/model/diffable/leaf_diff_service.rb +97 -0
- data/lib/ecoportal/api/common/graphql/model/diffable.rb +1 -0
- data/lib/ecoportal/api/graphql/base/action.rb +1 -1
- data/lib/ecoportal/api/graphql/base/action_event.rb +23 -0
- data/lib/ecoportal/api/graphql/base/action_event_page.rb +16 -0
- data/lib/ecoportal/api/graphql/base/activity.rb +38 -0
- data/lib/ecoportal/api/graphql/base/activity_log.rb +38 -0
- data/lib/ecoportal/api/graphql/base/ai_summary_version.rb +12 -7
- data/lib/ecoportal/api/graphql/base/auth_log.rb +24 -0
- data/lib/ecoportal/api/graphql/base/authorization_result.rb +20 -0
- data/lib/ecoportal/api/graphql/base/contractor_entity/member_changes.rb +17 -5
- data/lib/ecoportal/api/graphql/base/file_container.rb +12 -4
- data/lib/ecoportal/api/graphql/base/location_classification_type.rb +2 -0
- data/lib/ecoportal/api/graphql/base/location_structure.rb +3 -0
- data/lib/ecoportal/api/graphql/base/minimal_dashboard.rb +25 -0
- data/lib/ecoportal/api/graphql/base/organization.rb +2 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/contractor_entities.rb +26 -3
- data/lib/ecoportal/api/graphql/base/page/data_field/file_field.rb +47 -8
- data/lib/ecoportal/api/graphql/base/page/data_field/image_gallery.rb +5 -2
- data/lib/ecoportal/api/graphql/base/page/data_field/people.rb +50 -12
- data/lib/ecoportal/api/graphql/base/page/data_field.rb +12 -18
- data/lib/ecoportal/api/graphql/base/permissions.rb +15 -0
- data/lib/ecoportal/api/graphql/base/person_member_changelog.rb +23 -0
- data/lib/ecoportal/api/graphql/base/preset_view.rb +136 -1
- data/lib/ecoportal/api/graphql/base/register.rb +5 -0
- data/lib/ecoportal/api/graphql/base.rb +9 -0
- data/lib/ecoportal/api/graphql/builder/contractor_entity.rb +10 -0
- data/lib/ecoportal/api/graphql/builder/page.rb +0 -16
- data/lib/ecoportal/api/graphql/builder/template.rb +18 -0
- data/lib/ecoportal/api/graphql/compat/pages.rb +7 -2
- data/lib/ecoportal/api/graphql/compat/response.rb +14 -0
- data/lib/ecoportal/api/graphql/concerns/threadable.rb +58 -0
- data/lib/ecoportal/api/graphql/concerns.rb +1 -0
- data/lib/ecoportal/api/graphql/connection/action.rb +1 -1
- data/lib/ecoportal/api/graphql/connection/action_category.rb +1 -1
- data/lib/ecoportal/api/graphql/connection/activity.rb +11 -0
- data/lib/ecoportal/api/graphql/connection/activity_log.rb +11 -0
- data/lib/ecoportal/api/graphql/connection/auth_log.rb +11 -0
- data/lib/ecoportal/api/graphql/connection/contractor_entity.rb +1 -1
- data/lib/ecoportal/api/graphql/connection/minimal_dashboard.rb +11 -0
- data/lib/ecoportal/api/graphql/connection/page.rb +1 -1
- data/lib/ecoportal/api/graphql/connection/pages_workflow_command.rb +1 -1
- data/lib/ecoportal/api/graphql/connection/person_member.rb +1 -1
- data/lib/ecoportal/api/graphql/connection/person_member_changelog.rb +11 -0
- data/lib/ecoportal/api/graphql/connection/preview_page.rb +1 -1
- data/lib/ecoportal/api/graphql/connection.rb +5 -0
- data/lib/ecoportal/api/graphql/diff/pairing/signals.rb +21 -1
- data/lib/ecoportal/api/graphql/file_upload/client.rb +199 -140
- data/lib/ecoportal/api/graphql/fragment/activity.rb +84 -0
- data/lib/ecoportal/api/graphql/fragment/activity_log.rb +75 -0
- data/lib/ecoportal/api/graphql/fragment/auth_log.rb +29 -0
- data/lib/ecoportal/api/graphql/fragment/authorization_result.rb +14 -0
- data/lib/ecoportal/api/graphql/fragment/location_node.rb +1 -0
- data/lib/ecoportal/api/graphql/fragment/minimal_dashboard.rb +31 -0
- data/lib/ecoportal/api/graphql/fragment/pages/common_page_union.rb +21 -0
- data/lib/ecoportal/api/graphql/fragment/permissions.rb +98 -0
- data/lib/ecoportal/api/graphql/fragment/person_member_changelog.rb +33 -0
- data/lib/ecoportal/api/graphql/fragment.rb +7 -0
- data/lib/ecoportal/api/graphql/input/action/update.rb +12 -0
- data/lib/ecoportal/api/graphql/input/contractor_entity/manager_settings.rb +20 -0
- data/lib/ecoportal/api/graphql/input/contractor_entity/update.rb +6 -27
- data/lib/ecoportal/api/graphql/input/contractor_entity.rb +1 -0
- data/lib/ecoportal/api/graphql/input/date_range.rb +16 -0
- data/lib/ecoportal/api/graphql/input/location_structure/apply_commands.rb +2 -2
- data/lib/ecoportal/api/graphql/input/location_structure/draft/add_commands.rb +2 -2
- data/lib/ecoportal/api/graphql/input/location_structure/update_command.rb +6 -2
- data/lib/ecoportal/api/graphql/input/preset_view/update.rb +16 -1
- data/lib/ecoportal/api/graphql/input/workflow_command/add_checklist_field_item.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_smart_fill_config.rb +23 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_stage.rb +3 -2
- data/lib/ecoportal/api/graphql/input/workflow_command/add_viewable_field.rb +45 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/archive_public_config.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/create_public_config.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_checklist_field_item.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_field_configuration.rb +12 -2
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_public_config.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_smart_fill_config.rb +21 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_template_advanced_settings.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_template_basic_settings.rb +27 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_template_container_uid.rb +45 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/{edit_page_creator_permissions.rb → edit_template_creator_permissions.rb} +3 -2
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_template_descriptions.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/action_list.rb +31 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/cross_reference.rb +3 -1
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/date.rb +15 -2
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/file.rb +21 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/image_gallery.rb +3 -2
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/people.rb +27 -3
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/plain_text.rb +3 -2
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/rich_text.rb +3 -2
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/select.rb +3 -2
- data/lib/ecoportal/api/graphql/input/workflow_command/move_field.rb +3 -2
- data/lib/ecoportal/api/graphql/input/workflow_command/move_viewable_field.rb +33 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_checklist_field_item.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_smart_fill_config.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_viewable_field.rb +21 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/reorder_forces.rb +3 -2
- data/lib/ecoportal/api/graphql/input/workflow_command/reorder_section.rb +3 -2
- data/lib/ecoportal/api/graphql/input/workflow_command.rb +43 -5
- data/lib/ecoportal/api/graphql/input.rb +1 -0
- data/lib/ecoportal/api/graphql/interface/base_page.rb +36 -3
- data/lib/ecoportal/api/graphql/interface/location_structure/command.rb +2 -1
- data/lib/ecoportal/api/graphql/logic/connection.rb +17 -0
- data/lib/ecoportal/api/graphql/logic/input.rb +127 -0
- data/lib/ecoportal/api/graphql/model/action_event.rb +14 -0
- data/lib/ecoportal/api/graphql/model/action_event_page.rb +10 -0
- data/lib/ecoportal/api/graphql/model/activity.rb +14 -0
- data/lib/ecoportal/api/graphql/model/activity_log.rb +31 -0
- data/lib/ecoportal/api/graphql/model/auth_log.rb +11 -0
- data/lib/ecoportal/api/graphql/model/authorization_result.rb +12 -0
- data/lib/ecoportal/api/graphql/model/minimal_dashboard.rb +14 -0
- data/lib/ecoportal/api/graphql/model/organization.rb +27 -0
- data/lib/ecoportal/api/graphql/model/permissions.rb +179 -0
- data/lib/ecoportal/api/graphql/model/person_member.rb +7 -0
- data/lib/ecoportal/api/graphql/model/person_member_changelog.rb +15 -0
- data/lib/ecoportal/api/graphql/model.rb +9 -0
- data/lib/ecoportal/api/graphql/mutation/ai_summary/generate.rb +20 -10
- data/lib/ecoportal/api/graphql/mutation/ai_summary/submit_feedback.rb +25 -10
- data/lib/ecoportal/api/graphql/mutation/contractor_entity/update_manager_settings.rb +37 -0
- data/lib/ecoportal/api/graphql/mutation/contractor_entity.rb +1 -0
- data/lib/ecoportal/api/graphql/mutation/file_container/upload.rb +78 -0
- data/lib/ecoportal/api/graphql/mutation/file_container.rb +15 -0
- data/lib/ecoportal/api/graphql/mutation/template/update_related_page.rb +79 -0
- data/lib/ecoportal/api/graphql/mutation/template.rb +1 -0
- data/lib/ecoportal/api/graphql/mutation.rb +1 -1
- data/lib/ecoportal/api/graphql/payload/contractor_entity/manager_settings.rb +20 -0
- data/lib/ecoportal/api/graphql/payload/contractor_entity.rb +1 -0
- data/lib/ecoportal/api/graphql/payload/template/update_related_page.rb +15 -0
- data/lib/ecoportal/api/graphql/payload/template.rb +1 -0
- data/lib/ecoportal/api/graphql/payload/upload.rb +12 -0
- data/lib/ecoportal/api/graphql/payload.rb +1 -0
- data/lib/ecoportal/api/graphql/query/activity_logs.rb +78 -0
- data/lib/ecoportal/api/graphql/query/auth_logs.rb +51 -0
- data/lib/ecoportal/api/graphql/query/dashboard_search.rb +57 -0
- data/lib/ecoportal/api/graphql/query/location_structure.rb +2 -0
- data/lib/ecoportal/api/graphql/query/location_structures.rb +2 -0
- data/lib/ecoportal/api/graphql/query/page.rb +6 -1
- data/lib/ecoportal/api/graphql/query/page_activities.rb +56 -0
- data/lib/ecoportal/api/graphql/query/permissions.rb +35 -0
- data/lib/ecoportal/api/graphql/query/person_member_changelogs.rb +53 -0
- data/lib/ecoportal/api/graphql/query.rb +6 -0
- data/lib/ecoportal/api/graphql_version.rb +1 -1
- metadata +73 -12
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_page.rb +0 -28
- data/lib/ecoportal/api/graphql/mutation/smart_fill/generate.rb +0 -36
- data/lib/ecoportal/api/graphql/mutation/smart_fill/submit_feedback.rb +0 -40
- data/lib/ecoportal/api/graphql/mutation/smart_fill.rb +0 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e679d934d9305d1926abe7251617eea0e5333c04665672b25f5ad38afb67d9b3
|
|
4
|
+
data.tar.gz: 2b8047d56b9af371b71308bcabce23f8e4c4b67d014ce7a4095386fadb1f89f4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6ec6f2792de809534943497a98a74ec012b061778d379bb9f8ac205805cd9a629386c3293ee00e647549564fed5edbbc80e73cf856340922548309b8e9c68435
|
|
7
|
+
data.tar.gz: 948f0064aa886e310fc24f78a3919cbccc3b192495c724d86d003e1ff03bd39d2d12b312e82c04ca6626779abd822f9f9914de44b651c4903ba51d275085f7e8
|
data/CHANGELOG.md
CHANGED
|
@@ -2,15 +2,393 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## [
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [2.0.0] - 2026-08-13
|
|
8
|
+
|
|
9
|
+
Major release: everything accumulated on `main` since `v1.3.14` — the 2026-07-13/07-25/07-31
|
|
10
|
+
schema syncs, command-bus completion (105/105 commands), native file upload, and a long run of
|
|
11
|
+
correctness fixes. **Breaking, but narrowly:** every removed or reshaped surface either targeted
|
|
12
|
+
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
|
|
15
|
+
bump is deliberate: consumer repos declare `~> 1.3` (which allows 1.4.x), so a minor would have
|
|
16
|
+
been adopted silently on the next `bundle update`; 2.0.0 makes adoption a per-repo decision.
|
|
17
|
+
|
|
18
|
+
★ **What starts WORKING when you adopt this** (silently-broken → live, coming from ≤ 1.3.14):
|
|
19
|
+
the page-level `executeWorkflowCommands` bus, File-field attach writes, `updatePresetView`
|
|
20
|
+
column edits, AI-summary mutations, viewable-field management, Date-field renewals, and
|
|
21
|
+
eco-helpers' `OozeRedirect` force path (always errored before — re-verify on a sandbox org
|
|
22
|
+
before trusting it in a customer run). Review those paths before assuming a no-op upgrade.
|
|
23
|
+
|
|
24
|
+
Note: this release does not descend from `v1.3.15`, which shipped from the `release/1.3.15`
|
|
25
|
+
branch forked at 1.3.14 — do not expect a linear tag history.
|
|
26
|
+
|
|
27
|
+
★ **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/`
|
|
29
|
+
notes, `docs/` (including the rolling worklog), and `.claude/`. `spec.files` is now an allowlist
|
|
30
|
+
(`lib/`, `exe/`, README, LICENSE, CHANGELOG), which also drops the package from 959 KB to 195 KB.
|
|
31
|
+
All 568 `lib/` files are unchanged; nothing outside `lib/` was ever loaded at runtime.
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- `WorkflowCommandInput` commands: `editTemplateBasicSettings`, `editTemplateAdvancedSettings`,
|
|
36
|
+
`editTemplateCreatorPermissions`, `editTemplateDescriptions`, `createPublicConfig`,
|
|
37
|
+
`editPublicConfig`, `archivePublicConfig`, `addChecklistFieldItem`, `editChecklistFieldItem`,
|
|
38
|
+
`removeChecklistFieldItem`, `addSmartFillConfig`, `editSmartFillConfig`, `removeSmartFillConfig`.
|
|
39
|
+
- `addStage` gained `tags` (create a stage already tagged).
|
|
40
|
+
- `editFieldConfiguration` gained `addActionIds`/`removeActionIds`; `crossReference` gained
|
|
41
|
+
`addReferenceIds`/`removeReferenceIds`; `imageGallery` gained `addImageSourceIds`/`removeImageIds`;
|
|
42
|
+
new `file` by-type field config (`addFileContainerIds`/`removeItemIds`).
|
|
43
|
+
- **The WorkflowCommandInput command bus is now at full coverage: 105/105 commands, 14/14 field-config
|
|
44
|
+
builders, zero dead keys, zero unreachable keys** — verified against the live `20260731` schema.
|
|
45
|
+
Granular template management no longer has holes.
|
|
46
|
+
- **`tests/validate_commands.rb`** — new offline gate, the command-bus sibling of
|
|
47
|
+
`validate_queries.rb`. That gate can only see the documents we *send*; every command travels as a
|
|
48
|
+
member of a `[WorkflowCommandInput!]` **variable**, so unwired commands and stale key allow-lists
|
|
49
|
+
were completely invisible to it. The new script reports unwired commands, dead keys (gem sends,
|
|
50
|
+
schema rejects), orphaned `COMMAND_MAP` entries, missing keys, and byType builder coverage.
|
|
51
|
+
Fails the build on the first three. Wire it into CI next to `validate-queries`.
|
|
52
|
+
- **`addViewableField` / `moveViewableField` / `removeViewableField` commands** — these replaced
|
|
53
|
+
`WorkflowEditFieldConfigurationPeopleInput.addViewableFields`/`removeViewableFields`, which the
|
|
54
|
+
20260725 drift REMOVED. Between that drift and now, viewable-field management was impossible
|
|
55
|
+
through the gem: the old keys were dead and the new commands were unwired. The replacement also
|
|
56
|
+
adds ordering (`anchorId` + `anchorPosition`) the flat arrays could not express.
|
|
57
|
+
- **`Input::WorkflowCommand::EditFieldConfiguration::ActionList`** — the 14th and last unwired
|
|
58
|
+
`byType` member, so ActionsList field configuration is reachable at last.
|
|
59
|
+
- **`updateRelatedPage` mutation** + payload + `Builder::Template#update_related_page`. The worklog
|
|
60
|
+
carried this as an *ecoportal backend* gap ("the only genuine structural template omission") — that
|
|
61
|
+
was stale, the mutation is in the live schema, so the gap was ours. Related-pages entries could be
|
|
62
|
+
created and destroyed but never edited, forcing destroy+recreate for a title change, which
|
|
63
|
+
discards the entry id.
|
|
64
|
+
- **Cross-version template identity is now readable.** `CommonPageUnion` selects
|
|
65
|
+
`template { templateContainerUid active }`, and `Interface::BasePage` exposes `#template`
|
|
66
|
+
(a read-only `Model::Template::Information`) plus the `#templateContainerUid` shortcut.
|
|
67
|
+
`templateContainerUid` is the template **series** key: many versions may carry the same value,
|
|
68
|
+
but only one may be published (`active: true`) per organization at a time — publishing another
|
|
69
|
+
with the same uid unpublishes the incumbent. Previously the value was fetchable from the API but
|
|
70
|
+
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`.
|
|
72
|
+
- **`editTemplateContainerUid` workflow command** — `Input::WorkflowCommand::EditTemplateContainerUid`,
|
|
73
|
+
registered on the command bus. Unlike its `editTemplate*` siblings it raises on a nil value rather
|
|
74
|
+
than emitting an empty command (the schema argument is `String!`) and validates the backend's
|
|
75
|
+
`3-20 chars, [a-zA-Z0-9-_.]` format up front.
|
|
76
|
+
- **DataField `description` is now readable.** Added to the `dataFieldInterface` fragment and
|
|
77
|
+
declared `read_only: true` on `Base::Page::DataField`. It is the hidden per-field note that
|
|
78
|
+
integrations reuse as a pseudo external-id on fields. Read-only is deliberate: `description`
|
|
79
|
+
exists on `DataFieldsInterface` but on no member of `DataFieldInput`, so a writable accessor would
|
|
80
|
+
dirty the field and push a key `updatePage` cannot accept — the only writer is the
|
|
81
|
+
`editFieldConfiguration` workflow command, which is confirmed to work on **page instances** as
|
|
82
|
+
well as templates.
|
|
83
|
+
- `tests/page_field_description_update.rb` — scripted read-modify-verify for a single dataField
|
|
84
|
+
`description` on a page instance, with a `--dry-run` mode and a `canRestructure`/`canConfigure`
|
|
85
|
+
pre-flight.
|
|
86
|
+
- **`Diff::Pairing::Signals.genome` now recognises lineage ancestry** (scores `0.9`, just below
|
|
87
|
+
an exact match) when one signature is a strict prefix of the other. Verified live 2026-08-05
|
|
88
|
+
on a same-org restore of a full template backup: a restore re-mints every Mongo id and
|
|
89
|
+
APPENDS one segment to every membrane AND force genome signature (28/28 + 4/4 followed the
|
|
90
|
+
rule, e.g. `ktnrya` → `ktnrya877l`) — equality-only scoring handed the exact cross-object
|
|
91
|
+
case the pairing engine exists for (UAT backup → PROD restore) a `0.0` mismatch. Signatures
|
|
92
|
+
shorter than 4 chars are rejected as ancestry evidence (chance-collision guard).
|
|
93
|
+
- **New manual tool `tests/template_backup_diff.rb`**: loads the platform's template BACKUP
|
|
94
|
+
files (YAML = Mongoid/BSON object dump — loaded via shims, no bson dependency; JSON = same
|
|
95
|
+
document, ids as hex/times as ISO8601), converts YAML→JSON, and produces an id-keyed deep
|
|
96
|
+
diff (adds/removes/changes/reorders, bookkeeping noise hidden by default, nil≡empty-container
|
|
97
|
+
lenience for document-age drift). Detects a foreign id-space and points at
|
|
98
|
+
`Diff::CrossObjectDiff` instead of emitting a misleading add/remove-everything report.
|
|
99
|
+
- Scoping project `.ai-assistance/projects/data-toolset/` — coordination of the data
|
|
100
|
+
import/extract/migration tool-set (legacy imports, template and register metadata table
|
|
101
|
+
definitions, BI warehouse extracts, spreadsheet and back-end imports, register-to-register and
|
|
102
|
+
org-to-org migrations, register-revamp mapping and readiness reporting). Capability map C1-C8,
|
|
103
|
+
verified inventory of existing tooling, the two shared primitives the set is missing (field
|
|
104
|
+
identity across template versions, and a versioned table projection), staged plan through to
|
|
105
|
+
promotion into its own repo, and the open blockers.
|
|
106
|
+
|
|
107
|
+
### Added (read-model / type layer — additive, non-breaking)
|
|
108
|
+
|
|
109
|
+
- **System access logs:** `Base::AuthLog` / `Model::AuthLog` (+ `Connection::AuthLog`, `:AuthLog`
|
|
110
|
+
fragment) and `Model::Organization#authLogs` (`Query::AuthLogs`).
|
|
111
|
+
- **Changelog history:** `Base::PersonMemberChangelog` / `Model::PersonMemberChangelog`
|
|
112
|
+
(+ `Connection::PersonMemberChangelog`, `:PersonMemberChangelog` fragment),
|
|
113
|
+
`Model::Organization#personMemberChangelogs` (`Query::PersonMemberChangelogs`), and
|
|
114
|
+
`Model::PersonMember#changelogs`.
|
|
115
|
+
- **Dashboards:** `Base::MinimalDashboard` / `Model::MinimalDashboard`
|
|
116
|
+
(+ `Connection::MinimalDashboard`, `:MinimalDashboard` fragment) and
|
|
117
|
+
`Model::Organization#dashboardSearch` (`Query::DashboardSearch`).
|
|
118
|
+
- **Actions:** `Base::ActionEvent` / `Model::ActionEvent` and `Base::ActionEventPage` /
|
|
119
|
+
`Model::ActionEventPage`.
|
|
120
|
+
- **RS/location:** read-only `LocationStructure#generating`/`#aiPrompt`,
|
|
121
|
+
`LocationClassificationType#aiGenerated` (selected in the structure queries + `:LocationNode`
|
|
122
|
+
fragment).
|
|
123
|
+
- **Contractor:** `updateContractorManagerSettings` mutation
|
|
124
|
+
(`Mutation::ContractorEntity::UpdateManagerSettings` + `Input`/`Payload::…::ManagerSettings`,
|
|
125
|
+
exposed as `Builder::ContractorEntity#update_manager_settings`) and read-only
|
|
126
|
+
`Organization#allowContractorAdminsToAddAdmins`.
|
|
127
|
+
- **`Base::AuthorizationResult` / `Model::AuthorizationResult`** (+ `:AuthorizationResult` fragment) —
|
|
128
|
+
the reusable node type behind the schema's many `canXXX: AuthorizationResult` permission fields;
|
|
129
|
+
wired `Model::PersonMember#canManageContractorAdmins`. Unblocks the deferred `Permissions`-parented
|
|
130
|
+
`canXXX` fields (which still await a `Permissions` model).
|
|
131
|
+
- **`Base::Permissions` / `Model::Permissions`** (+ `:Permissions` fragment) — the org-level ability
|
|
132
|
+
set (`Organization.permissions`). Models all **83** `canXXX: AuthorizationResult!` fields as
|
|
133
|
+
`embeds_one` (e.g. `dashboardCanManage`, `locationStructuresCanAiGenerateStructure`,
|
|
134
|
+
`generatedPageFilterCanUseSmartQuery`). Wired `Model::Organization#permissions` (typed embed) plus a
|
|
135
|
+
native `Query::Permissions` (`currentOrganization.permissions`). The `:Permissions` fragment spreads
|
|
136
|
+
`:AuthorizationResult` per field (recursively assembled).
|
|
137
|
+
|
|
138
|
+
### Added (polymorphic activity feeds — the REAL access-logs & page-history fields)
|
|
139
|
+
|
|
140
|
+
The web "System Access Logs" page and page "history changelog" are driven by
|
|
141
|
+
`currentOrganization.activityLogs` (a UNION) and `page.activities` (an INTERFACE) — NOT the flat
|
|
142
|
+
`authLogs`/`personMemberChangelogs` types. These add the polymorphic read models for the real fields.
|
|
143
|
+
|
|
144
|
+
- **System Access Logs:** `Base::ActivityLog` / `Model::ActivityLog` (+ nested `Model::ActivityLog::Resource`,
|
|
145
|
+
`Connection::ActivityLog`, `:ActivityLog` fragment) over the `ActivityLogUnion` (9 members:
|
|
146
|
+
DashboardAccess/Create/Delete/Update, DeniedDashboardAccess, DeniedPageAccess, FileDownload, PageAccess,
|
|
147
|
+
ScimIntegrationAccess). Models the fields common to all members + `__typename` passthrough; the Dashboard*
|
|
148
|
+
members' `parentResource*` + `resource { isVisible: AuthorizationResult }` are nullable embeds. The
|
|
149
|
+
fragment uses inline fragments per member (spreading `:ActivityLogCommon` on `BaseActivityLogInterface`
|
|
150
|
+
and `:ActivityLogResource` on `BaseDashboardActivityLogInterface`). Exposed as
|
|
151
|
+
`Model::Organization#activityLogs` (`Query::ActivityLogs`) with the full filter set
|
|
152
|
+
(`userIds`, `resourceIds`, `filterSuperusers`, `activityType`, `resourceType: ActivityLogTypesEnum`,
|
|
153
|
+
`source: SourceEnum`, `dateFilter: DateRange`, cursor pagination). New `Input::DateRange`.
|
|
154
|
+
- **Page history / activities:** `Base::Activity` / `Model::Activity` (+ `Connection::Activity`,
|
|
155
|
+
`:Activity` fragment) capturing the `ActivityInterface` common fields (id, `__typename`, priority,
|
|
156
|
+
system, fromPublicTemplate, stageId, categories/subkey, source/user scalars, createdAt/updatedAt,
|
|
157
|
+
personMember, user) across its ~61 concrete types — an unmodelled type still resolves as a plain
|
|
158
|
+
`Model::Activity`. Exposed as `Model::Organization#pageActivities` (`Query::PageActivities`) resolving
|
|
159
|
+
`page(id:).activities(searchConf:, …)`; because `page` returns a `PageUnion`, the connection is selected
|
|
160
|
+
via inline fragments on BasicPage/PhasedPage (`:PageActivities` + `:ActivityConnection` fragments).
|
|
161
|
+
|
|
162
|
+
### Changed
|
|
163
|
+
|
|
164
|
+
- `ecoportal-api` dependency floor raised to `0.10.17`: inherits the `WithRetry` fix that
|
|
165
|
+
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).
|
|
167
|
+
- **`graphlient` dependency now points at the published `0.9.0` release** instead of the fork's
|
|
168
|
+
frozen git branch (`rellampec/graphlient@pre-v0.0.9`). `0.9.0` ships the same DSL/directive
|
|
169
|
+
work the gem already relied on (`#115` `to_query_string`/`spread`, `#116` directives DSL,
|
|
170
|
+
`spread(on:)` inline fragments, `fragment(:Name, on:)`, custom scalar registration) — non-breaking,
|
|
171
|
+
since the gem's own usage of these APIs is unchanged. `Gemfile` git override removed;
|
|
172
|
+
`ecoportal-api-graphql.gemspec` bound updated `>= 0.8.0, < 0.9` → `>= 0.9.0, < 0.10`.
|
|
173
|
+
|
|
174
|
+
### Changed (BREAKING)
|
|
175
|
+
|
|
176
|
+
- `reorderForces` and `reorderSection`: `after`/`before` replaced by `anchorId`/`anchorPosition`.
|
|
177
|
+
- `moveField`: gained `anchorId`/`anchorPosition`.
|
|
178
|
+
- SmartFill removed from `date`/`plainText`/`richText`/`select` field configs — moved to the
|
|
179
|
+
dedicated `*SmartFillConfig` commands above.
|
|
180
|
+
- **AI summary mutations rewired to the fields that actually exist.** Both targeted mutation names
|
|
181
|
+
that are absent from every introspection we hold (2026-07-13 and 2026-07-30), so neither ever
|
|
182
|
+
worked:
|
|
183
|
+
- `Mutation::AiSummary::Generate`: `generateAiSummary(pageId:, stageId:)` →
|
|
184
|
+
**`generatePageSummary(input: GenerateInput!)`**. `#query` now takes `page_id:` / `stage_id:`
|
|
185
|
+
(was `pageId:` / `stageId:`) and wraps them into `input`.
|
|
186
|
+
- `Mutation::AiSummary::SubmitFeedback`: `submitAiSummaryFeedback(aiSummaryVersionId:, sentiment:)`
|
|
187
|
+
→ **`submitPageAiSummaryFeedback(input: SubmitFeedbackInput!)`**. `#query` takes
|
|
188
|
+
`ai_summary_version_id:` / `sentiment:`. The enum is `SentimentEnum`, not
|
|
189
|
+
`AiSummarySentimentEnum`, and its values are **`positive` / `negative`** — the class previously
|
|
190
|
+
documented "THUMBS_UP"/"THUMBS_DOWN", which the schema has never accepted. `#query` validates
|
|
191
|
+
the value up front.
|
|
192
|
+
- `Base::AiSummaryVersion` dropped `prompt`, `inputTokens`, `outputTokens`, `totalTokens`,
|
|
193
|
+
`resourceType`, `ratings` — none exist on the type; they always read nil. Real fields:
|
|
194
|
+
`aiSummary`, `alreadyRated`, `createdAt`, `dataFieldLogs`, `generationErrors`, `id`,
|
|
195
|
+
`pagePatchVer`, `status` (the last three complex ones stay unmodelled until verified).
|
|
196
|
+
|
|
197
|
+
_(The `Payload::OkPayload` errors-modelling fix from the same commit is NOT breaking and
|
|
198
|
+
already shipped in 1.3.15 — see that release's section.)_
|
|
199
|
+
- **`FileUpload::Client` reimplemented natively from a captured web upload** (breaking, but the
|
|
200
|
+
old path could not work). The real flow — verified from a HAR of the React frontend on
|
|
201
|
+
2026-07-30, then cross-checked against the live schema — is three steps, all GraphQL except
|
|
202
|
+
the storage POST:
|
|
203
|
+
1. `FileSignature` (GraphQL) → presigned policy + credentials
|
|
204
|
+
2. multipart POST to S3 → `204`; fields in order: `key`, `AWSAccessKeyId`, `policy`,
|
|
205
|
+
`signature`, `content-type`, `x-amz-server-side-encryption`, `file`
|
|
206
|
+
3. **`uploadFile` (GraphQL)** → the `FileContainer`, whose `id` page mutations consume
|
|
207
|
+
|
|
208
|
+
The previous implementation was a port of `ecoportal-api-v2`'s REST S3 code and did
|
|
209
|
+
`POST /api/v2/<org>/s3/files` + polling. **The platform does not use those endpoints for this
|
|
210
|
+
flow** — there is no register-over-REST and no poll — so the 1.3.15 change that made those
|
|
211
|
+
calls authenticate (`X-ECOPORTAL-API-KEY`) fixed a step that does not exist. No API key is
|
|
212
|
+
needed for file upload at all; `api_key:` on `GraphQL.new` remains available but unused here.
|
|
213
|
+
- **New:** `Mutation::FileContainer::Upload` (`uploadFile`), `Payload::Upload`, and
|
|
214
|
+
`Concerns::Threadable` (bounded concurrency, ported from `ecoportal-api-v2` rather than
|
|
215
|
+
depended on, since that gem is being decommissioned).
|
|
216
|
+
- **New: `#upload_all(paths, threads: 4) { |result| }`** — concurrent multi-file upload with
|
|
217
|
+
per-file error isolation (a single failure never raises out of the batch; each `Result`
|
|
218
|
+
carries its own `error`/`container_id`), presigning **once** per batch, and a `Result` struct
|
|
219
|
+
shaped like v2's `S3::Files::BatchUpload::FileResult`. `threads: 1` runs inline.
|
|
220
|
+
- **New: stage hooks** — `client.on(:signature|:storage|:register) { … }`, chainable, for
|
|
221
|
+
logging/instrumentation/middleware around each step.
|
|
222
|
+
- **Fixed: storage keys no longer collide.** The key is
|
|
223
|
+
`uploads/<userId>/<epoch-ms>-<token>-/<basename>` as the web client builds it; the old
|
|
224
|
+
`upload_prefix + filename` concatenation meant two uploads of `report.pdf` overwrote each
|
|
225
|
+
other in the bucket.
|
|
226
|
+
- **Fixed: `x-amz-server-side-encryption` is read from the policy** (base64 JSON `conditions`)
|
|
227
|
+
instead of being hardcoded to `AES256` — the value now comes from the same source S3 validates
|
|
228
|
+
against, with `AES256` only as a fallback.
|
|
229
|
+
- **Fixed: `Base::FileContainer`** declared `activeVersionId`, which does not exist on the type
|
|
230
|
+
(it is `activeVersion`) and always read nil; `otherTags` was missing, which matters because
|
|
231
|
+
`UploadInput.otherTags` is the only way to tag a container — now exposed as
|
|
232
|
+
`upload(path, tags: […])`.
|
|
6
233
|
|
|
7
234
|
### Fixed
|
|
8
235
|
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
`
|
|
12
|
-
|
|
13
|
-
|
|
236
|
+
- **Cascaded leaf arrays are no longer dropped from `as_update`.** Leaf arrays declared with
|
|
237
|
+
`passarray`/embeds-many (id-lists, tag-lists, config-lists) were invisible to the diff: the
|
|
238
|
+
default `DiffService` strips cascaded keys from the flat classic diff, and `diff_reduce` only
|
|
239
|
+
re-adds children that are a `GraphQL::Model` — an `ArrayModel`/`CollectionModel` is not — so
|
|
240
|
+
the array change was silently discarded and no mutation was issued for it (SYNTHESIS.md 3.1).
|
|
241
|
+
The `DataField`-local `LeafDiffService` is promoted to the common layer
|
|
242
|
+
(`common/graphql/model/diffable/leaf_diff_service.rb`): it keeps cascaded doc keys in the flat
|
|
243
|
+
diff, preserves the flat-diff placeholder for non-model leaf children while still cascading
|
|
244
|
+
into real nested models and dropping `root!` children, and treats a leaf array as an opaque
|
|
245
|
+
full-set replace (no id-keyed nested patch-ops), matching how the write inputs consume these
|
|
246
|
+
arrays. Set as `DIFF_CLASS` on `Interface::BasePage` (covers Basic + Phased), `Base::Register`
|
|
247
|
+
and `Base::PresetView`; `Base::Page::DataField` now references the promoted class with
|
|
248
|
+
identical behaviour. Newly detected: page `otherTags` (full-set replace) and `locations`
|
|
249
|
+
(`[{id:}]` via `location_ids=`), `Register#filterTags`, and `PresetView#fieldConfigurations`.
|
|
250
|
+
- `Base::PresetView` now emits a schema-valid `updatePresetView` input for its column
|
|
251
|
+
configuration. The read model exposes the collection as `fieldConfigurations` (the key the
|
|
252
|
+
API returns), but `UpdatePresetViewInput` calls it `fieldConfigs` **and** takes it as a
|
|
253
|
+
`FieldConfigurationOneToManyInput` — `{ additions:, deletions:, updates: }` — not as the
|
|
254
|
+
read list; both facts are schema-confirmed in the live introspection dump (20260730), where
|
|
255
|
+
the field is also NON_NULL with no default, i.e. required on every call. Emitting the read
|
|
256
|
+
key would be an unknown-argument error and emitting the read list under the write key would
|
|
257
|
+
be a type error, either of which rejects the WHOLE mutation, a `name`-only edit included.
|
|
258
|
+
`#as_input` (and `Input::PresetView::Update.from_model`, which shares the same reshape, so
|
|
259
|
+
the implicit and explicit converter seams agree) now renames the key, computes the
|
|
260
|
+
additions/deletions/updates delta directly from `doc` vs `original_doc` — matching configs on
|
|
261
|
+
their `id` and whitelisting the fields `FieldConfigurationInput` accepts, so the read-only
|
|
262
|
+
`__typename` is dropped — and always sets `fieldConfigs`, using the no-op `{}` when the
|
|
263
|
+
columns did not change. A columns-only change is now sent at all: it was previously dropped
|
|
264
|
+
from the flat diff, so `as_input` returned `nil` and no mutation was issued.
|
|
265
|
+
`Input::PresetView::Update` declared `fieldConfigs` as a `passarray`; it is now a
|
|
266
|
+
`passthrough`, matching the object the schema expects. The create path is unaffected —
|
|
267
|
+
`CreatePresetViewInput.fieldConfigs` is a plain `[FieldConfigurationInput!]!` and
|
|
268
|
+
`Input::PresetView::Create` already used the write key with the right shape.
|
|
269
|
+
- `Base::Page::DataField::FileField` now reads and writes the shape the live API actually
|
|
270
|
+
uses. WRITE: `as_input` emits `file: { id:, items: [ItemInput] }` — live `FileInput` has
|
|
271
|
+
no `fileContainerIds` member, so every File-field write was rejected (live-confirmed
|
|
272
|
+
2026-08-01 via `pages/file_upload`; the 4-step upload passed, the attach failed). The
|
|
273
|
+
backend applies `items` as full-replace (any existing item id not echoed back is
|
|
274
|
+
destroyed), so kept containers are sent as `{ id: <item_id> }` and new ones as
|
|
275
|
+
`{ fileContainerId: }`. READ: `file_container_ids` now maps `items[].fileContainer.id`
|
|
276
|
+
(the fragment's actual selection, which now also selects the item `id`) instead of a
|
|
277
|
+
`fileContainers` doc key the API never returns — reads always came back empty before.
|
|
278
|
+
BREAKING (dead surface): the `FileField#fileContainers` accessor is gone, replaced by
|
|
279
|
+
`items`; no consumer existed in this gem or eco-helpers, and it could only ever return
|
|
280
|
+
empty against real API responses. `file_container_ids` / `file_container_ids=` keep
|
|
281
|
+
their contract. `ImageGallery` is untouched: its write shape is also invalid, but live
|
|
282
|
+
`ImageInput` attaches via `sourceId` (an `Enzyme::ImageReference::TempImage`, a
|
|
283
|
+
different upload pipeline than file containers) — needs its own design, tracked in the
|
|
284
|
+
DataField shape-asymmetry review.
|
|
285
|
+
- **`People#dirty?`** compares the people-id SET vs `original_doc` (falling back to the
|
|
286
|
+
`people{id}` nodes on both sides), not the raw `peopleIds` key — the backend can return
|
|
287
|
+
`peopleIds: null` on reads while still populating `people`/`cachedPeople`, so restating an
|
|
288
|
+
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`
|
|
290
|
+
fix for the identical disease; `as_input` now serialises the fallback-aware `people_ids`
|
|
291
|
+
reader instead of the raw doc key.
|
|
292
|
+
- `ContractorEntities` field: the reader read the staged write key (`contractors`), which read
|
|
293
|
+
responses never populate — so it always returned `[]` on a freshly-fetched page — and the
|
|
294
|
+
default leaf diff phantom-dirtied the field the moment any setter ran, even for a no-op
|
|
295
|
+
restatement or a clear on an already-empty field. Now reads `contractorEntities[].id` with
|
|
296
|
+
staged-write preference and compares effective id SETS against `original_doc` in `dirty?`
|
|
297
|
+
(the CrossReference fix template).
|
|
298
|
+
- `Query::Page` now accepts the `CommonPageUnion` document variables (`showHiddenData`,
|
|
299
|
+
`content`, `only_content`, `fields`) as call params, and `Compat::Pages#get` forwards
|
|
300
|
+
them (`pages.get(id, content: false)`). Previously `accepted_params :id` silently
|
|
301
|
+
dropped any override, so the document-level defaults could never be changed.
|
|
302
|
+
This matters for least-privilege accounts: `attachedFiles @include(if: $content)` is
|
|
303
|
+
preauthorized on the `files: download` ability, so an account without that grant could
|
|
304
|
+
not complete **any** page read — found live when the template-tests account
|
|
305
|
+
(`-space graphql_templates`) had every `pages.get` refused while a minimal hand-written
|
|
306
|
+
page query succeeded. `content: false` is the structure-only read (template tooling,
|
|
307
|
+
config audits) and needs no file grant. Defaults are unchanged — no caller impact.
|
|
308
|
+
- **Four `Input::LocationStructure` command bugs are fixed — `add(type, **kwargs)` was broken on
|
|
309
|
+
every path.** `UpdateCommand#name` / `#classificationIds` called a non-existent `super` (plus a
|
|
310
|
+
redundant `nil.empty?` guard), raising `NoMethodError`; both readers now fall back to the stored
|
|
311
|
+
`name` / `classificationIds` so they work with or without a `new*` override. `ApplyCommands` and
|
|
312
|
+
`Draft::AddCommands` mapped the `:reorder` command type through a typo'd `:reoder` key, so
|
|
313
|
+
`add(:reorder, ...)` raised `NameError: uninitialized constant ...::ReoderCommand` — reorder was
|
|
314
|
+
unreachable from either dispatcher. `Interface::LocationStructure::Command#set_values` called the
|
|
315
|
+
0-arg reader instead of the writer (`#{key}=`), raising `ArgumentError` and breaking the whole
|
|
316
|
+
`#add` path for every command type, not only reorder. Adds `commands_mapping_spec` (18 examples)
|
|
317
|
+
covering all four. Note: the typo symbol `:reoder` is no longer in the dispatch table, so
|
|
318
|
+
`add(:reoder)` now raises `RuntimeError: Unknown command type: 'reoder'` instead of `NameError` —
|
|
319
|
+
not a breaking change, since `:reoder` never resolved to a real class either way.
|
|
320
|
+
- **`FieldConfig::People` sent two keys the server rejects** — `addViewableFields` /
|
|
321
|
+
`removeViewableFields`, removed from the schema in the 20260725 drift. Now dropped, and the six
|
|
322
|
+
keys that drift *added* are reachable: `add`/`removeAllowedUserGroupIds`,
|
|
323
|
+
`attachedPeoplePermissionsEnabled`/`Editable`/`Flags`, `applyAttachedPeoplePermissionsTo`. The
|
|
324
|
+
nested flags hash is filtered to the seven declared flags so a stray key cannot sink the command.
|
|
325
|
+
- **`FieldConfig::Date` could not set renewals** — `createEvent`, `remindMeIn`, `renews`,
|
|
326
|
+
`renewsEvery`, `renewsUnit`, `renewsUntil` were all unreachable. Renewals are how expiry-driven
|
|
327
|
+
registers (certifications, inductions, permits) are configured, so this was a functional hole
|
|
328
|
+
rather than a cosmetic one.
|
|
329
|
+
- **`EditFieldConfiguration` silently dropped `stageId`** — required to disambiguate a field on a
|
|
330
|
+
phased page, which made the builder unusable there; callers had to hand-build the command hash.
|
|
331
|
+
`addPermittedPersonSchemaIds` / `removePermittedPersonSchemaIds` were dropped the same way.
|
|
332
|
+
- **Connection node-type poisoning.** `Logic::Connection` declared `embeds_many :nodes, klass: :node_class`
|
|
333
|
+
on the shared base, so the v2 collection layer memoized the first-resolved node class onto the single
|
|
334
|
+
shared collection — the first connection instantiated in a process poisoned every other connection's
|
|
335
|
+
instance `nodes` typing (e.g. a `MinimalDashboard` connection returning `AuthLog` nodes), corrupting
|
|
336
|
+
`QueryConnection#each` pagination when >1 connection type is used per process. Each subclass now binds
|
|
337
|
+
its own concrete-class collection via the new `Logic::Connection.nodes_of` macro. Regression spec added.
|
|
338
|
+
- `Compat::Response` now validates at construction that its payload honours the response contract
|
|
339
|
+
(`success?`/`error?`/`error_doc`/`item`), raising `ArgumentError` with the missing methods
|
|
340
|
+
otherwise. Closes the duck-typing drift class behind the eco-helpers 3.2.19 KPI-counter
|
|
341
|
+
regression (a non-response object slipping through). +spec `spec/ecoportal/api/graphql/compat/response_spec.rb`.
|
|
342
|
+
- **`main`'s version constant no longer advertises a published release it is not.** `2dad1e4`
|
|
343
|
+
(2026-08-08) set `GRAPQL_VERSION` to `1.3.15` while raising the `ecoportal-api` floor to
|
|
344
|
+
`0.10.17`. But `1.3.15` had already shipped nine days earlier from `release/1.3.15` (tag
|
|
345
|
+
`v1.3.15`, on RubyGems since 2026-07-30), and that tag is **not** an ancestor of `main` — the
|
|
346
|
+
two lines forked at `21516a3` (1.3.14) and `main` has carried ~200 commits of 1.4.0-track work
|
|
347
|
+
since. So `main` was building a materially different tree under a published version number,
|
|
348
|
+
against this repo's "never ship a fix under a published version" convention. The constant was
|
|
349
|
+
restored to `1.3.14` — the last release `main`'s tree actually descends from — until this
|
|
350
|
+
release set it to `2.0.0`. The `0.10.17` floor itself is unaffected and ships with this release.
|
|
351
|
+
|
|
352
|
+
### Removed (BREAKING)
|
|
353
|
+
|
|
354
|
+
- `WorkflowCommandInput` commands `editPage` and `editPageCreatorPermissions` — removed from the
|
|
355
|
+
live schema (2026-07-13) and now removed from the gem. `WorkflowCommand.build(:editPage, …)` /
|
|
356
|
+
`build(:editPageCreatorPermissions, …)` raise `ArgumentError` (unknown key). The
|
|
357
|
+
`DEPRECATED_KEYS` / `warn_deprecated` machinery on `Input::WorkflowCommand` was removed with them.
|
|
358
|
+
**Migrate to:** `editTemplateBasicSettings` (name/icon/settings/counter/tags/folders/creator
|
|
359
|
+
perms), `editTemplateDescriptions` (`description`), `editTemplateCreatorPermissions` (creator
|
|
360
|
+
perms). The old `editPage` `addOtherTags`/`removeOtherTags` keys have no `editTemplate*`
|
|
361
|
+
equivalent (dropped from schema). No downstream callers existed (eco-helpers / multi_org_api /
|
|
362
|
+
gem swept) so the removal is clean. See DECISIONS.md in the workflow-space project.
|
|
363
|
+
- **`Mutation::SmartFill::Generate` / `Mutation::SmartFill::SubmitFeedback` and
|
|
364
|
+
`Builder::Page#generate_smart_fill` / `#submit_smart_fill_feedback`** (breaking, but dead
|
|
365
|
+
code) — `smartFillGenerate` and `smartFillSubmitFeedback` **do not exist in the schema**, and
|
|
366
|
+
never did: the live mutation list has no `*fill*` mutation at all (checked against both the
|
|
367
|
+
2026-07-13 and 2026-07-30 introspections). SmartFill exists only as *configuration*
|
|
368
|
+
(`WorkflowCommandInput`'s `add/edit/removeSmartFillConfig`, the `SmartFill*Configuration`
|
|
369
|
+
types) and as a **read** data field — `Base::Page::DataField::SmartFill` is unaffected and
|
|
370
|
+
stays. Nothing could have depended on the removed mutations because they always failed; if
|
|
371
|
+
page-level SmartFill triggering is needed, it has to be added to the platform first.
|
|
372
|
+
|
|
373
|
+
### Documentation
|
|
374
|
+
|
|
375
|
+
- `ecoPortal_architecture/10_forces_workflow_builder.md`: operational rules for auditing force
|
|
376
|
+
bindings — a binding may target a field OR a section; `globalBindings` is a separate collection;
|
|
377
|
+
a field declaring `global_binding` is present at stage render regardless of its section/stage;
|
|
378
|
+
backup-YAML ObjectIds are binary so a text search for an id proves nothing.
|
|
379
|
+
- `ecoPortal_architecture/07_data_fields.md`: which subset of fields carries an identifier, and how
|
|
380
|
+
to measure coverage — only the QUESTION carries one; its satellites (comment, image, document,
|
|
381
|
+
recommended action) do not and are not expected to. The denominator is workbook questions
|
|
382
|
+
(`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
|
|
384
|
+
workbook-to-template contract recovered verbatim from the 2026-08-08 session transcript:
|
|
385
|
+
`QuestionSetReference` → template_container_id (UI "UNIQUE IDENTIFIER"), `QuestionReference` →
|
|
386
|
+
field `description` (CONFIRMED), `QuestionInformation` → field tooltip (strip HTML entities),
|
|
387
|
+
scores ignored, section-vs-stage left ambiguous, plus the template-pairing procedure and the
|
|
388
|
+
DEV-site targeting note.
|
|
389
|
+
- `.ai-assistance/conventions/recovering-prior-context.md` — past sessions are greppable JSONL
|
|
390
|
+
transcripts on disk (35 sessions, 85 MB); search them before asking the developer something he may
|
|
391
|
+
already have answered.
|
|
14
392
|
|
|
15
393
|
## [1.3.15] - 2026-07-30
|
|
16
394
|
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
module Ecoportal::API::Common::GraphQL::Model::Diffable
|
|
2
|
+
# Diff strategy for models whose *array* attributes are leaf DATA, not nested
|
|
3
|
+
# `GraphQL::Model` children.
|
|
4
|
+
#
|
|
5
|
+
# Array attributes declared with `passarray` (tag lists, id lists, config lists)
|
|
6
|
+
# — and `embeds_many` id-collections written as a whole set (e.g. page `locations`)
|
|
7
|
+
# — are registered as *cascaded* attributes. The default `DiffService`:
|
|
8
|
+
# 1. strips cascaded keys from the flat classic diff
|
|
9
|
+
# (`doc_with_non_cascaded_attributes`), on the assumption that a later
|
|
10
|
+
# `diff_reduce` pass re-adds them by cascading into nested `GraphQL::Model`
|
|
11
|
+
# children; and
|
|
12
|
+
# 2. in `diff_reduce`, deletes the flat placeholder for every cascaded key and
|
|
13
|
+
# only re-adds it when the child object `is_a?(GraphQL::Model)`.
|
|
14
|
+
# A `passarray` is wrapped as a v2 `ArrayModel` and a whole-set `embeds_many` as a
|
|
15
|
+
# `CollectionModel` — neither is a `GraphQL::Model`, so the re-add never happens and
|
|
16
|
+
# the array change becomes **invisible to `as_update`** (scalar `passthrough` fields
|
|
17
|
+
# are unaffected). This is the systemic "array/leaf fields don't diff" root cause
|
|
18
|
+
# (see `.ai-assistance/code/model_input_mapping/SYNTHESIS.md` §3.1).
|
|
19
|
+
#
|
|
20
|
+
# This service fixes it for leaf models by:
|
|
21
|
+
# 1. keeping cascaded keys in the flat classic diff (`doc_with_non_cascaded_attributes`
|
|
22
|
+
# is a no-op), and
|
|
23
|
+
# 2. overriding `diff_reduce` so the flat-diff placeholder for a NON-model leaf
|
|
24
|
+
# child is preserved (only real `GraphQL::Model` children are cascade-diffed;
|
|
25
|
+
# `root!` children are still dropped as independent entities), and
|
|
26
|
+
# 3. treating a leaf array as an OPAQUE value: a change replaces the whole array
|
|
27
|
+
# (a full-set replace), rather than the id-keyed nested update/delete/create
|
|
28
|
+
# patch-ops the default array diff would emit. This matches how the write inputs
|
|
29
|
+
# consume these arrays (`PageInput.otherTags`, `PageInput.locations = [{ id: }]`,
|
|
30
|
+
# register `filterTags`, PresetView `fieldConfigurations`).
|
|
31
|
+
#
|
|
32
|
+
# A model can either behave as a full leaf (call `dirty?`/`as_update` with
|
|
33
|
+
# `flat: true`, as `Base::Page::DataField` does — it has no nested `GraphQL::Model`
|
|
34
|
+
# children at all) or as a mixed model with both leaf arrays and real nested model
|
|
35
|
+
# children (page / register / preset view — the default cascaded `as_update` still
|
|
36
|
+
# recurses into the model children while the leaf arrays now diff inline).
|
|
37
|
+
class LeafDiffService < DiffService
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
# Keep all doc keys: leaf-field arrays are data, not nested models, so the flat
|
|
41
|
+
# classic diff must see them (the default strips every cascaded key).
|
|
42
|
+
def doc_with_non_cascaded_attributes(json)
|
|
43
|
+
json
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Treat array values as opaque leaves: emit the whole (current) array on any
|
|
47
|
+
# change, instead of the default id-keyed nested-array patch-ops. Non-array
|
|
48
|
+
# values fall through to the standard diff.
|
|
49
|
+
def change_diff(curr, prev, ignore: [])
|
|
50
|
+
return super unless curr.is_a?(Array) || prev.is_a?(Array)
|
|
51
|
+
return NO_CHANGES if same_array?(curr, prev)
|
|
52
|
+
|
|
53
|
+
keys_to_sym_deep(curr)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Full-set equality (order-insensitive), mirroring `patch_data_flat_array`.
|
|
57
|
+
def same_array?(curr, prev)
|
|
58
|
+
curr ||= []
|
|
59
|
+
prev ||= []
|
|
60
|
+
curr.length == prev.length && (curr & prev).length == prev.length
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Preserve the flat-diff placeholder for non-model leaf children; only
|
|
64
|
+
# cascade-diff real `GraphQL::Model` children, and still drop `root!` children.
|
|
65
|
+
def diff_reduce(init, ignore: [])
|
|
66
|
+
subject.cascaded_reduce(init, recurs: false) do |result, obj, _key, key_path, _trace|
|
|
67
|
+
next result if key_path.empty?
|
|
68
|
+
next result if obj == subject
|
|
69
|
+
|
|
70
|
+
sym_path = key_path.map(&:to_sym)
|
|
71
|
+
|
|
72
|
+
unless cascadeable_model?(obj)
|
|
73
|
+
# Leaf array (ArrayModel / CollectionModel) → keep the flat-diff value.
|
|
74
|
+
# A root! child is not mutation payload → drop its placeholder.
|
|
75
|
+
dig_delete!(result, sym_path) if root?(obj) && result && dig_path?(result, sym_path)
|
|
76
|
+
next result
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Remove the flat-diff placeholder before the child computes its own diff.
|
|
80
|
+
dig_delete!(result, sym_path) if result && dig_path?(result, sym_path)
|
|
81
|
+
|
|
82
|
+
value = obj.as_update(ignore: ignore)
|
|
83
|
+
next result if value.nil?
|
|
84
|
+
|
|
85
|
+
result ||= {}
|
|
86
|
+
result[:id] ||= get_id(keys_to_sym_deep(curr_doc(flat: true) || {}), exception: false)
|
|
87
|
+
dig_set!(result, sym_path, value)
|
|
88
|
+
result
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# A child worth cascading into: a non-root GraphQL::Model.
|
|
93
|
+
def cascadeable_model?(obj)
|
|
94
|
+
obj.is_a?(Ecoportal::API::Common::GraphQL::Model) && !root?(obj)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -29,7 +29,7 @@ module Ecoportal
|
|
|
29
29
|
embeds_one :createdAt, klass: Base::DateTime
|
|
30
30
|
embeds_one :updatedAt, klass: Base::DateTime
|
|
31
31
|
embeds_one :completedAt, klass: Base::DateTime
|
|
32
|
-
embeds_one :
|
|
32
|
+
embeds_one :openedAt, klass: Base::DateTime
|
|
33
33
|
embeds_one :closedAt, klass: Base::DateTime
|
|
34
34
|
embeds_one :dueOrClosedDate, klass: Base::DateTime
|
|
35
35
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Ecoportal
|
|
2
|
+
module API
|
|
3
|
+
class GraphQL
|
|
4
|
+
module Base
|
|
5
|
+
# A scheduled/recurring occurrence of an action (calendar-style event view).
|
|
6
|
+
# Read-only. `occursAt` is an ISO8601 timestamp scalar (a plain string),
|
|
7
|
+
# not the `{ dateTime:, timeZone: }` DateTime object used elsewhere.
|
|
8
|
+
#
|
|
9
|
+
# status is exposed as a plain string ('open' | 'closed').
|
|
10
|
+
class ActionEvent < Logic::BaseModel
|
|
11
|
+
read_only!
|
|
12
|
+
|
|
13
|
+
passkey :id
|
|
14
|
+
passthrough :uid
|
|
15
|
+
passthrough :title, :subtitle, :description
|
|
16
|
+
passthrough :status, :relativeStatus
|
|
17
|
+
passthrough :occursAt
|
|
18
|
+
passboolean :archived, :recurring, :standaloneAction
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Ecoportal
|
|
2
|
+
module API
|
|
3
|
+
class GraphQL
|
|
4
|
+
module Base
|
|
5
|
+
# The page (and stages) an ActionEvent is attached to. Read-only.
|
|
6
|
+
class ActionEventPage < Logic::BaseModel
|
|
7
|
+
read_only!
|
|
8
|
+
|
|
9
|
+
passkey :id
|
|
10
|
+
passthrough :name, :fieldId
|
|
11
|
+
passarray :stageIds
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Ecoportal
|
|
2
|
+
module API
|
|
3
|
+
class GraphQL
|
|
4
|
+
module Base
|
|
5
|
+
# A single entry of a page's activity/history feed, returned by
|
|
6
|
+
# `page.activities` (ActivityInterfaceConnection). Read-only.
|
|
7
|
+
#
|
|
8
|
+
# `ActivityInterface` is an INTERFACE with ~61 concrete implementations
|
|
9
|
+
# (ActivityPageChangelog, ActivityComment, ActivityStageAssigned,
|
|
10
|
+
# ActivityCreated, ActivityEmailOut, ActivityFallback, ...). Rather than
|
|
11
|
+
# hand-model all of them, this captures the fields COMMON to the interface
|
|
12
|
+
# and passes the concrete `__typename` through so callers can branch on it.
|
|
13
|
+
#
|
|
14
|
+
# Enum-valued fields are exposed as plain strings:
|
|
15
|
+
# - priority: PriorityEnum
|
|
16
|
+
class Activity < Logic::BaseModel
|
|
17
|
+
read_only!
|
|
18
|
+
|
|
19
|
+
passkey :id
|
|
20
|
+
# Concrete type name (e.g. 'ActivityPageChangelog', 'ActivityComment', 'ActivityFallback').
|
|
21
|
+
passthrough :__typename
|
|
22
|
+
passthrough :priority
|
|
23
|
+
passthrough :stageId
|
|
24
|
+
passthrough :timeZone
|
|
25
|
+
# Origin of the activity — sourceId/sourceName are scalars; the polymorphic
|
|
26
|
+
# `source: SourceUnion` (BasicPage|PhasedPage) subtree is intentionally omitted.
|
|
27
|
+
passthrough :sourceId, :sourceName
|
|
28
|
+
passthrough :userId, :userEmail, :userName
|
|
29
|
+
passarray :categories, :subkey
|
|
30
|
+
passboolean :system, :fromPublicTemplate
|
|
31
|
+
|
|
32
|
+
embeds_one :createdAt, klass: Base::DateTime
|
|
33
|
+
embeds_one :updatedAt, klass: Base::DateTime
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|