ecoportal-api-graphql 1.3.9 → 1.3.11
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/.ai-assistance/code/diff_pairing_engine.md +243 -0
- data/.ai-assistance/code/graphql_domain_knowledge.md +20 -10
- data/.ai-assistance/code/template_diff_pairing_domain.md +175 -0
- data/.ai-assistance/code/workflow-command-guide.md +28 -0
- data/.ai-assistance/projects/TODO.md +21 -0
- data/.ai-assistance/projects/ooze-graphql-native-migration/INVENTORY.md +136 -0
- data/.ai-assistance/projects/ooze-graphql-native-migration/TODO.md +6 -1
- data/.ai-assistance/projects/qa-services-delivery/DECISIONS.md +93 -0
- data/.ai-assistance/projects/qa-services-delivery/INTENT.md +76 -0
- data/.ai-assistance/projects/qa-services-delivery/PHASE3-SCOPE.md +115 -0
- data/.ai-assistance/projects/qa-services-delivery/ROADMAP.md +99 -0
- data/.ai-assistance/projects/qa-services-delivery/TODO.md +81 -0
- data/.ai-assistance/projects/template-automatic-build-maintenance/INTENT.md +77 -0
- data/.ai-assistance/projects/template-automatic-build-maintenance/TODO.md +97 -0
- data/.ai-assistance/projects/template-diff-deploy/INTENT.md +12 -0
- data/.ai-assistance/projects/template-diff-deploy/TODO.md +9 -0
- data/.ai-assistance/projects/template-maintenance/PHASE0-FINDINGS.md +93 -0
- data/.ai-assistance/projects/template-maintenance/README.md +14 -0
- data/CHANGELOG.md +116 -0
- data/docs/worklog.md +408 -0
- data/ecoportal-api-graphql.gemspec +1 -1
- data/lib/ecoportal/api/graphql/base/page/data_field.rb +1 -1
- data/lib/ecoportal/api/graphql/builder/template_builder.rb +174 -0
- data/lib/ecoportal/api/graphql/builder.rb +17 -16
- data/lib/ecoportal/api/graphql/diff/change.rb +59 -0
- data/lib/ecoportal/api/graphql/diff/command_synthesizer.rb +329 -0
- data/lib/ecoportal/api/graphql/diff/cross_object_diff.rb +165 -0
- data/lib/ecoportal/api/graphql/diff/deploy.rb +121 -0
- data/lib/ecoportal/api/graphql/diff/id_resolver.rb +64 -0
- data/lib/ecoportal/api/graphql/diff/pairing/candidate.rb +32 -0
- data/lib/ecoportal/api/graphql/diff/pairing/engine.rb +173 -0
- data/lib/ecoportal/api/graphql/diff/pairing/ledger.rb +119 -0
- data/lib/ecoportal/api/graphql/diff/pairing/signals.rb +104 -0
- data/lib/ecoportal/api/graphql/diff/strategy.rb +113 -0
- data/lib/ecoportal/api/graphql/diff/version_diff.rb +332 -0
- data/lib/ecoportal/api/graphql/diff.rb +34 -0
- data/lib/ecoportal/api/graphql/fragment/location_draft.rb +53 -53
- data/lib/ecoportal/api/graphql/fragment/pages/common_page_union.rb +1 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_field.rb +27 -18
- data/lib/ecoportal/api/graphql/mutation/action/archive.rb +1 -1
- data/lib/ecoportal/api/graphql/mutation/action/create.rb +1 -1
- data/lib/ecoportal/api/graphql/mutation/action/update.rb +1 -1
- data/lib/ecoportal/api/graphql/mutation/contractor_entity/create.rb +1 -1
- data/lib/ecoportal/api/graphql/mutation/contractor_entity/destroy.rb +1 -1
- data/lib/ecoportal/api/graphql/mutation/contractor_entity/update.rb +1 -1
- data/lib/ecoportal/api/graphql/mutation/kickstand/fail_workflow.rb +1 -1
- data/lib/ecoportal/api/graphql/mutation/kickstand/start_workflow.rb +1 -1
- data/lib/ecoportal/api/graphql/mutation/kickstand/stop_workflow.rb +1 -1
- data/lib/ecoportal/api/graphql/query/location_structure/draft.rb +62 -62
- data/lib/ecoportal/api/graphql/query/location_structure.rb +61 -61
- data/lib/ecoportal/api/graphql.rb +139 -138
- data/lib/ecoportal/api/graphql_version.rb +1 -1
- data/tests/dump_template_model.rb +90 -0
- data/tests/validate_queries.rb +127 -0
- metadata +32 -3
data/docs/worklog.md
CHANGED
|
@@ -8,6 +8,414 @@ This file tracks cross-project session state and the overall repo status.
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
+
## ▶ SESSION 2026-07-06 — RELEASED the fixed stack; act-gov tagtree + toocs live-green
|
|
12
|
+
|
|
13
|
+
The 2026-07-05 crash-fixing arc shipped. **All three live-verified green** on act-gov (direct
|
|
14
|
+
`-no-email` runs): **tagtree-update** (created draft → 8 insert / 3 archive → published, no customer
|
|
15
|
+
email) and **toocs-coding** (Oscar confirmed). Released, in dependency order:
|
|
16
|
+
- **ecoportal-api-v2 `v3.3.3`** — Ruby-3.x cascade block-param fix + regression spec.
|
|
17
|
+
- **ecoportal-api-graphql `v1.3.11`** — Diff build+maintenance engine; fragment-convention + Kickstand
|
|
18
|
+
DateTime fixes; requires v2 >= 3.3.3; validate_queries now covers mutations + hard-fails on render.
|
|
19
|
+
- **eco-helpers `v3.2.17`** — native template build+maintenance + ooze-native; location fragment
|
|
20
|
+
(`spread :LocationDraft`) + `worfklow` no-email typo fixes; requires gem >= 1.3.11 + v2 >= 3.3.3.
|
|
21
|
+
- **multi_org_api** (`master` + `api-deprecation`) — `-no-email` now forwarded through the rake
|
|
22
|
+
wrapper + `worfklow` typo fixed (stops customer emails during rake/scheduled tests).
|
|
23
|
+
|
|
24
|
+
Oscar git-pushed all repos + gem-published; **deploying via server `bundle update ecoportal-api-v2
|
|
25
|
+
ecoportal-api-graphql eco-helpers`** (production Gemfile floors gem>=1.3.9/eco-helpers>=3.2.16, but the
|
|
26
|
+
gemspec floors force v2>=3.3.3 + gem>=1.3.11 transitively). Systemic guard against the recurring
|
|
27
|
+
stale-release drift: raised gemspec dependency FLOORS everywhere + extended `validate_queries.rb`.
|
|
28
|
+
|
|
29
|
+
**STILL OPEN (non-blocking):** rake `notify_exception` emails the customer on task error regardless of
|
|
30
|
+
`-no-email` — decision pending (suppress / route-to-dev / keep). See [[no-email-leak-paths]].
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## ▶ SESSION 2026-07-05 — 3 live act-gov crashes = ONE root cause (stale released gems); fixed-stack floors
|
|
35
|
+
|
|
36
|
+
Three live crashes on act-gov (`-toocs-coding` + org-structure sync), all the **same class**: the
|
|
37
|
+
server runs **stale RELEASED gems** while the fixes already exist in local/tagged code.
|
|
38
|
+
1. **v2 cascade `TypeError`** (`_cascaded_attributes_trace … is not a symbol nor a string`) via
|
|
39
|
+
`as_update`/`DiffService` on a `Phased` page — server `ecoportal-api-v2 3.3.2`; fix (Ruby-3.x
|
|
40
|
+
block-param swap) is commit `6a2b1b5`, released now as **v3.3.3**. See [[v2-cascade-blockparam-332-bug]].
|
|
41
|
+
2. **`updatedAt` "must have selections"** + 3. **`createdAt` "selections can't be made on scalars"** —
|
|
42
|
+
LocationStructure queries; server gem **1.3.9**; fixed in gem **1.3.10** (already tagged).
|
|
43
|
+
|
|
44
|
+
**Resolution (release-prepped; publish + server `bundle update` are Oscar's):**
|
|
45
|
+
- v2 **`v3.3.3`** tagged (`03561ed`): CHANGELOG + **cascade regression spec** (double_model_spec, 6 ex green).
|
|
46
|
+
- gem **`1.3.11`** re-tagged (`ad4f788`): gemspec now requires **`ecoportal-api-v2 >= 3.3.3`**.
|
|
47
|
+
- eco-helpers (`ecd5f03f`): gemspec requires **`ecoportal-api-graphql >= 1.3.11` + `ecoportal-api-v2 >= 3.3.3`**.
|
|
48
|
+
- **Systemic guard = raised dependency FLOORS** so `bundle` cannot resolve a broken version again;
|
|
49
|
+
plus the existing `tests/validate_queries.rb` (query class) + the new cascade regression spec.
|
|
50
|
+
- **LOCAL runs already bind the fixed clones** — `multi_org_api` (branch `api-deprecation`) Gemfile
|
|
51
|
+
`:local` group path-mounts gem `main` (1.3.11) + v2 `master` (3.3.3).
|
|
52
|
+
|
|
53
|
+
**Then Oscar ran `-tagtree-update` locally → the version-drift crashes were GONE** (loaded 14126 nodes,
|
|
54
|
+
created the draft), exposing the NEXT layer: **a dead-fragment-convention class.**
|
|
55
|
+
- `uninitialized constant …Fragment::LocationDraft (NameError)` at render — eco-helpers
|
|
56
|
+
`Helpers::Location::Command::EndPoints::Optimizations` still used the legacy `___Const__Fragment`
|
|
57
|
+
convention, which evals a `Fragment::<Name>` CONSTANT that no longer exists (fragments are a registry
|
|
58
|
+
now; `spread :Name`). Fixed → `spread :LocationDraft`/`:LocationsError` (eco-helpers `352a9657`).
|
|
59
|
+
- **Swept the whole class:** the gem's **Action + ContractorEntity MUTATIONS** had the same dead refs
|
|
60
|
+
(queries were migrated, mutations missed) → `spread :Action`/`:ContractorEntity` (gem `44a4288`).
|
|
61
|
+
- **Systemic guard:** `tests/validate_queries.rb` now renders **mutations** (was queries only) and
|
|
62
|
+
**hard-fails on render error** (dead fragment / unresolved constant). It immediately caught **3
|
|
63
|
+
`Kickstand::{Start,Stop,Fail}Workflow` bare-`DateTime`** bugs → fixed `{ dateTime timeZone }`.
|
|
64
|
+
Validator now **EXIT 0** (fragment + structural buckets clean); suite **635/0/2**, rubocop clean.
|
|
65
|
+
See [[fragment-spread-convention]]. Remaining validator "OTHER" findings (union/schema-dump-staleness)
|
|
66
|
+
are pre-existing → separate follow-up with a fresh schema dump.
|
|
67
|
+
- Gem `v1.3.11` re-tagged (`44a4288`); eco-helpers `3.2.17` (`352a9657`); both unpublished.
|
|
68
|
+
**NEXT: Oscar re-runs `-tagtree-update` + `-toocs-coding` locally to confirm, then publishes + deploys.**
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## ▶ SESSION 2026-07-04 (cont.) — v1.3.11 tag, addField schema-verified, learnings captured, live-script drafted
|
|
73
|
+
|
|
74
|
+
Post-round-4 follow-through on the #1 template project. **Gem `v1.3.11` tagged locally** (`fc45db7`,
|
|
75
|
+
unpublished — Oscar pushes/`gem push`); **eco-helpers bumped 3.2.17** (`243822b9`, unpublished).
|
|
76
|
+
- **addField (`5754d7f`, folded into v1.3.11):** schema-verified — `WorkflowAddFieldInput` has NO
|
|
77
|
+
`description`/`required`; `required` exists NOWHERE in the schema (read-only). `description` now via
|
|
78
|
+
follow-up `editFieldConfiguration`; added-field/section `stageId`/`sectionId` back-refs emitted. See
|
|
79
|
+
[[workflow-command-schema-realities]].
|
|
80
|
+
- **Learnings recorded (answering Oscar's "are these learnings?"):** cross-session memory
|
|
81
|
+
([[workflow-command-schema-realities]], [[register-membership-via-tags]]) + AI-readable code-spec
|
|
82
|
+
(`workflow-command-guide.md` "Schema realities" section, `2098415`) + CHANGELOG + this worklog. Prime
|
|
83
|
+
material for the [[project-graphql-agent-aws]] corpus (GAP_ANALYSIS flagged this exact gap class).
|
|
84
|
+
- **qa tag-superset SyncReadiness (merged, qa `c058782`):** `base_tags ⊆ compiled_tags` register-
|
|
85
|
+
membership check. **Surfaced a gem read-path gap:** gem has NO `compiledTags`; `baseTags` has an
|
|
86
|
+
accessor but the `corePageData` fragment fetches only `otherTags`. So live tag-membership `:skip`s
|
|
87
|
+
until (a) `baseTags` is added to the read fragment (genomeSignature-style one-liner) AND (b) Oscar
|
|
88
|
+
confirms "compiled tags" semantics (baseTags? base+other? backend-computed incl. inherited/forces?).
|
|
89
|
+
- **Live characterization script drafted (training `feature/template-live-characterization` `ab147f0`,
|
|
90
|
+
UNMERGED):** `-template-maintenance` case, dry-run default, `-commit` gates the only live write.
|
|
91
|
+
Offline smoke PASSED against local clones (6 well-formed commands, 0 unsupported, Applier dry-run,
|
|
92
|
+
DriftReport match?==true). Commands for Oscar in its README. 4 assumptions to confirm (template read
|
|
93
|
+
path; register-search signature; tag-superset membership; `graphql.page` executor accepts a template
|
|
94
|
+
workflow batch). Runs only in `ENVIRO_CONTEXT=local` (path-mounted clones) until v1.3.11 is published.
|
|
95
|
+
|
|
96
|
+
**OPEN for Oscar:** (1) publish gem v1.3.11 + repoint eco-helpers; (2) confirm "compiled tags" semantics
|
|
97
|
+
→ then the `baseTags` fragment fix; (3) set up the Mini test register + before/after template, then run
|
|
98
|
+
the live characterization (dry-run → commit); (4) decide whether the training case sits on `master` or
|
|
99
|
+
`api-deprecation`.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## ▶ SESSION 2026-07-04 (cont.) — Template build+maintenance made #1; increments #4 merged; gem v1.3.11 prepped
|
|
104
|
+
|
|
105
|
+
Oscar set **template automatic build & maintenance as #1 priority**. Ran the usual 3 parallel threads
|
|
106
|
+
(one per repo), all merged to mains, developer-only authorship. Suites **gem 627 (2 pending live-AI) /
|
|
107
|
+
eco-helpers 274 / qa 104 — 0 failures.** Mains: gem `a07f708`→release `70fd252`, eco-helpers `8dc89c45`,
|
|
108
|
+
qa `ac8077c`.
|
|
109
|
+
|
|
110
|
+
- **Gem (thread A):** `Diff::Strategy` (composable modalities: pairing × scope × move-sensitivity ×
|
|
111
|
+
intent; `.default` = prior behaviour) + `Diff::CrossObjectDiff` (two templates with NO shared ids →
|
|
112
|
+
`Pairing::Engine` builds the id map → same `Change` output → `CommandSynthesizer`/`Deploy`;
|
|
113
|
+
ambiguous held in `#unresolved`) + `Builder::TemplateBuilder` (declarative spec → build command
|
|
114
|
+
batch, shares the diff's placeholderId threading). Reconciled the two project-doc folders into
|
|
115
|
+
`.ai-assistance/projects/template-automatic-build-maintenance/` (★#1). +27 diff / +13 builder / +16
|
|
116
|
+
strategy specs.
|
|
117
|
+
- **eco-helpers (thread B):** native deploy→verify→monitor loop under `samples/pages/template/deploy/`
|
|
118
|
+
(`Applier` dry-run default / `commit:` + injected executor / refuses on `unsupported` unless
|
|
119
|
+
`allow_partial:`; `DriftReport` pre/post self-version diff vs intended on an id-free signature;
|
|
120
|
+
pluggable `Verifier`, `QaVerifier` soft-wires ecoportal-qa only if on load path, else `NullVerifier`;
|
|
121
|
+
`SyncReadiness` per-entry) + CSV→template build (`FormatMap`→`Parser`→`Builder` →
|
|
122
|
+
`Builder::Template#create`; format isolated in `FormatMap`). +31 specs.
|
|
123
|
+
- **qa (thread C):** deploy-verification + sync-readiness as new check-sets in the existing framework
|
|
124
|
+
(two `Runner` entrypoints → canonical `Result`); consumes the gem page-model. Intended-change
|
|
125
|
+
contract = `{meta, changes:[Change#to_h]}` (accepts wrapper or bare list, sym/str keys); needs a
|
|
126
|
+
`baseline_doc:` for no-unintended-change; richer gem `Diff::VersionDiff` injectable via
|
|
127
|
+
`Compiler.new(diff_class:)`. +30 specs.
|
|
128
|
+
|
|
129
|
+
**★ TWO INDEPENDENTLY-CONFIRMED SEAMS (B and C reached the same conclusions):**
|
|
130
|
+
1. The gem `Diff` module was **unreleased** (1.3.10 was the location hotfix). B/C bind to it via
|
|
131
|
+
injected/lazy seams so they run offline against released 1.3.9/1.3.10. **→ Prepped gem `v1.3.11`**
|
|
132
|
+
(`chore(release)` `70fd252`: version bump + full CHANGELOG for the Diff module + `TemplateBuilder`).
|
|
133
|
+
NOT yet tagged/published — Oscar to tag `v1.3.11` + publish, then repoint eco-helpers to it so the
|
|
134
|
+
deploy loop binds to the real `Diff` classes.
|
|
135
|
+
2. Gem **`addField` input drops `description`/`required`** → CSV/hidden-field identity token + `required`
|
|
136
|
+
readiness can't persist through `addField` (C `:skip`s `required`; B keeps only the hidden anchor).
|
|
137
|
+
Also diff-side `addField`/`addSection` lack `stageId`/`sectionId` back-refs.
|
|
138
|
+
**✅ RESOLVED (schema-verified, folded into v1.3.11 — merge `d42c528`, retag at `fc45db7`):**
|
|
139
|
+
introspection shows `WorkflowAddFieldInput` = `{placeholderId,fieldType,label,stageId,sectionId,
|
|
140
|
+
column}` — **no `description`**, and **`required` exists NOWHERE in the schema** (not on AddField,
|
|
141
|
+
not on any of the 12 byType edit sub-inputs). So: `description` now persists via a follow-up
|
|
142
|
+
`editFieldConfiguration(dataFieldId:, description:)` (TemplateBuilder + synthesizer); added-field
|
|
143
|
+
`addField` carries `sectionId`(parent_id)+`stageId`(new `Change#owner_id`), added-section emits
|
|
144
|
+
`addStageSection`, all `ref()`-threaded. **`required` is permanently read-only/out-of-band** (qa
|
|
145
|
+
`:skip` is correct-by-design, not a gap). Suite 635/0/2, rubocop clean.
|
|
146
|
+
|
|
147
|
+
**STATE OF #1 PROJECT:** full build+maintain engine exists **end-to-end offline** — build (CSV or
|
|
148
|
+
declarative → commands), diff (self-version + cross-object via pairing), deploy (apply + drift-check),
|
|
149
|
+
verify (qa), monitor (sync-readiness). Gap to "trusted" = **(a) publish v1.3.11** (tagged locally at
|
|
150
|
+
`fc45db7`, incl. the addField resolution; push + `gem push` are Oscar's) **+ repoint eco-helpers**
|
|
151
|
+
(bumped to 3.2.17, unpublished), and **(b) the sandbox live pass** (Mini site; a dedicated test
|
|
152
|
+
register + a template whose compiled tags ⊇ the register `base_tags` + a before/after version — see
|
|
153
|
+
[[register-membership-via-tags]]; script to be prepped; sandbox can't reach live.ecoportal.com so
|
|
154
|
+
Oscar runs it). Sync-readiness should also gain the **tag-superset** dimension (register membership =
|
|
155
|
+
`base_tags` ⊆ compiled tags), not just field presence.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## ▶ SESSION 2026-07-04 (cont.) — 3 parallel-thread increments #3 merged + strategic briefing captured
|
|
160
|
+
|
|
161
|
+
**Increments #3 (all merged to mains, green, developer-only authorship):** gem `main` `c607be9`,
|
|
162
|
+
eco-helpers `master` `5adb4a21`, qa `master` `41cb2e6`. Suites **gem 587 (2 pending live-AI) /
|
|
163
|
+
eco-helpers 243 / qa 74 — 0 failures.**
|
|
164
|
+
- **Templates (gem):** `VersionDiff` now **emits** gauge-stop (`add/remove/editGaugeFieldStop`, matched
|
|
165
|
+
by retained stop id) + typed `byType` field-config changes (`editFieldConfiguration(byType:)`,
|
|
166
|
+
conservative — only confirmed read-prop↔input-key 1:1: Gauge `max`; Select `dataType/multiple/flat/
|
|
167
|
+
other/otherDesc`; Date `showTime/pastOnly/todayButton`; shapes verified vs the live schema dump).
|
|
168
|
+
`CommandSynthesizer` maps them + **placeholderId threading** (Deploy default on): deterministic
|
|
169
|
+
client placeholders on added nodes, later same-batch refs rewritten via `ref()`. +26 specs.
|
|
170
|
+
UNSUPPORTED (honest): unconfirmed byType props (PlainText `multiline`/`required`, RichText/People/
|
|
171
|
+
Table/CrossReference bodies), gauge-stop reorder (no ordering field), structural back-refs in adds.
|
|
172
|
+
- **samples/pages (eco-helpers):** native `TypedFieldsPairing` (groups by GraphQL type string, 2-pass
|
|
173
|
+
label match, `EXCLUDED_TYPES` TagField/Chart/FrequencyRateChart) + `Copying#copy_field_content` +
|
|
174
|
+
`Register::MigrationCase < Register::Base`. Ports live FLAT at `graphql/helpers/pages/*` (not the
|
|
175
|
+
TODO's `.../migration/*`) for round-2 consistency. Deferred: v2 mapped-field hooks
|
|
176
|
+
(`copy_hooked_fields`/`field_maps`), live A/B parity run (creds). +27 specs.
|
|
177
|
+
- **QA (ecoportal-qa):** sub-phase **3c CLI wiring** (`--jira KEY` → DryRunClient default, offline
|
|
178
|
+
CI-preview; `--audit-log PATH` JSONL; report-only default) + **3b `RestClient` skeleton**
|
|
179
|
+
(config-parameterized transition-names/verdict-field, injected HTTP transport, stub-driven specs,
|
|
180
|
+
no real HTTP). No McpClient. +22 specs. Note: `PHASE3-SCOPE.md`/`ROADMAP.md` live on the gem branch
|
|
181
|
+
`feature/qa-services-delivery-project` under `.ai-assistance/projects/qa-services-delivery/`, NOT in
|
|
182
|
+
the qa repo — worklog wording earlier was ambiguous.
|
|
183
|
+
|
|
184
|
+
**★ STRATEGIC BRIEFING from Oscar (captured to memory — see the index).** The next arc is
|
|
185
|
+
**customer-facing agents/apps**, all governed by a management **no-support-dependency mandate**
|
|
186
|
+
([[principle-customer-facing-automation]]): self-documenting config, auto gap-detection, guide the
|
|
187
|
+
technician, CI/CD that blocks breaking changes + auto-generates self-tested migrations, pricing
|
|
188
|
+
transparency, extensive Confluence docs the AI can pull from. Four threads:
|
|
189
|
+
1. **GraphQL / "Integrations" agent → Claude-on-AWS (Client Services workspace)** — corpus already
|
|
190
|
+
staged externally (`C:\claude\Projects\Claude AWS Platform\tmp\corpus\staging\success\graphql\`,
|
|
191
|
+
18 docs), push deliberately held until repo learnings could enrich it. **NOW the moment.** Ships a
|
|
192
|
+
generic Postman/Insomnia collection + a new Confluence self-serve subsection ("Do it yourself"/
|
|
193
|
+
Training/Learning/Examples). `.ai-assistance/projects/graphql-agent/GAP_ANALYSIS.md` is the (stale,
|
|
194
|
+
2026-06-07) gap map — many gaps now filled by code-specs written since. See [[project-graphql-agent-aws]].
|
|
195
|
+
2. **SCIM agent = Entra ID gallery app** — driver: **APIv0 retires END-JUL 2026, FIRM.** Kill the
|
|
196
|
+
high-touch Entra SCIM enrollment (free Travis). Design gated on a **rich structured Travis
|
|
197
|
+
interview** + an **Oscar walkthrough of the Entra caveats** (both Oscar-requested, pending). Open:
|
|
198
|
+
Microsoft Partner vs unlisted-app, gallery upgrade/migration mechanism. See
|
|
199
|
+
[[project-azure-gallery-app]] + [[project-scim-users-apiv0-adapter]].
|
|
200
|
+
3. **Webhooks** — about to officially release; page payload sends only internal `page_id`. Oscar's
|
|
201
|
+
private (un-raised) view: should also send `externalId`. See [[project-webhooks-subscriptions]].
|
|
202
|
+
4. **Platform-agnostic integrations (Zapier as referent, not dependency)** — reusable, auto-detects
|
|
203
|
+
template-drift breakage; UAT-vs-PROD ideal; generic requirements interview with Oscar pending. See
|
|
204
|
+
[[project-integrations-platform-agnostic]].
|
|
205
|
+
|
|
206
|
+
**NEXT:** decide with Oscar whether to start the GraphQL-agent corpus enrichment now (in-repo work I
|
|
207
|
+
can do; the actual AWS push needs his creds/workspace). The 3 gem/helpers/qa threads can also continue
|
|
208
|
+
(#4 increments) in parallel as before.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## ▶ SESSION 2026-07-04 — 3 parallel-thread increments #2 (all merged to mains, green)
|
|
213
|
+
|
|
214
|
+
Same shape as the prior session: one background agent per repo, separate working trees, each on a
|
|
215
|
+
feature branch, then **all consolidated to their mains + branches deleted** (Oscar's call),
|
|
216
|
+
developer-only authorship. Suites: **gem 561 (2 pending live-AI, pre-existing) / eco-helpers 216 /
|
|
217
|
+
qa 52 — 0 failures.** Mains: gem `b16b5e5`, eco-helpers `c37847d5`, qa `ec4a507`.
|
|
218
|
+
|
|
219
|
+
- **Templates (gem) — `template-diff-deploy`:** edit-mode synthesis + deploy orchestration + pairing.
|
|
220
|
+
`Diff::Deploy.from_versions(before, after, target_doc:)` turns a diff into an ordered replayable
|
|
221
|
+
`WorkflowCommand` batch for `executeWorkflowCommands`; `execute!` refuses to run while `unsupported`
|
|
222
|
+
is non-empty unless `allow_partial:`; inert until an explicit executor is passed. Edit-mode moves
|
|
223
|
+
(`moveField`, section stage-reassign via `removeStageSection`+`addStageSection`) are gated on a new
|
|
224
|
+
`Diff::IdResolver` (`from_doc` indexes stage names + section headings → ids; duplicate keys → nil,
|
|
225
|
+
**never guesses**). `Diff::Pairing::{Engine,Ledger,Signals,Candidate}`: weighted fallible signals
|
|
226
|
+
(genome 0.5 + type 0.2 + label 0.2 + select-options 0.1, averaged over applicable), greedy 1:1,
|
|
227
|
+
auto-accept ≥0.85 / ambiguous 0.5–0.85 / unmatched <0.5; **genome mismatch scores 0 but does not
|
|
228
|
+
veto** → contradicting-genome/same-label fields are escalated, never auto-paired. `Ledger` is a
|
|
229
|
+
first-class JSON artifact of confirmed equivalences, consulted first. Code-spec:
|
|
230
|
+
`.ai-assistance/code/diff_pairing_engine.md`. +44 diff specs.
|
|
231
|
+
- **samples/pages (eco-helpers):** native `Helpers::Pages::OozeHandlers#merge_values` (dispatches on
|
|
232
|
+
GraphQL DataField **type string** via `TYPE_MAP`, not V2 Component classes), included into
|
|
233
|
+
`Register::Base` with `#merge_field_values`. `Compat::Parity::{RunResult,Comparison,Harness}` A/B
|
|
234
|
+
harness (canonical-KPI + normalised payload snapshot, `equivalent?` verdict) — **pure/offline;
|
|
235
|
+
running legacy-vs-native live needs a test org + creds.** Native `Register::TargetOozesUpdateCase`
|
|
236
|
+
(CSV target ids, id-batching, dup-detection preview, fetch-per-id, dedup, KPIs; org-scan `each_page`
|
|
237
|
+
disabled). Native-only: no case flipped, no shim change, no public-API rename. +38 specs.
|
|
238
|
+
- **QA repo — sub-phase 3a:** `Jira::{Client,DryRunClient,Publisher}`. Jira is *just another Result
|
|
239
|
+
consumer* (no runner/DSL/compiler/Result changes). DryRunClient writes nothing + records intended
|
|
240
|
+
posts; Publisher redacts-before-post (ON by default, reuses `Redactor`), per-ticket idempotency
|
|
241
|
+
marker, **report-only default (never transitions unless a human names one)**, audit trail per
|
|
242
|
+
action. No RestClient/McpClient (that's blocked 3b/3c). +21 specs.
|
|
243
|
+
|
|
244
|
+
**NEXT increments (parallel threads continue):**
|
|
245
|
+
- **Templates:** extend `VersionDiff` to *emit* gauge-stop + typed `byType` field-config changes — the
|
|
246
|
+
one part of edit-mode genuinely blocked upstream in the diff (synthesizer is ready to map them once
|
|
247
|
+
emitted). Then `placeholderId` threading for dependent intra-batch creates; interactive
|
|
248
|
+
assisted-resolution UX + `TypedFieldsPairing` as an extra pairing signal (eco-helpers layer).
|
|
249
|
+
- **samples/pages:** live A/B parity run (needs test org + creds); Phase-5 `TypedFieldsPairing` /
|
|
250
|
+
migration-case porting; later phases flip `OozeSamples::*` to native + shim removal (parity-gated).
|
|
251
|
+
- **QA:** 3b/3c **decision-blocked** on 4 open Qs for Oscar — (1) Jira project + exact transition
|
|
252
|
+
names, (2) REST service-account token vs MCP-only, (3) verdict field id vs comment-only, (4) where
|
|
253
|
+
check-sets live long-term. 3a is done and unblocks the offline/CI-preview path today.
|
|
254
|
+
- Standing: 1.3.11 union fixes + CI query-validation TODO (due **2026-07-17**); live capture of
|
|
255
|
+
template `6a3fa5b8…622b` (blocked on sandbox creds); FARMERS/Travis F1/F2 (`api-deprecation`).
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## ▶ SESSION 2026-07-03 (cont.) — Consolidation + 3 parallel-thread increments (all merged, green)
|
|
260
|
+
|
|
261
|
+
**Consolidated** all green foundation branches to their mains (Oscar's call), then built the next
|
|
262
|
+
increment of each of the 3 threads in parallel (one background agent per repo, separate working
|
|
263
|
+
trees). All merged to mains, branches deleted, developer-only authorship. Suites: **gem 515 (~live) /
|
|
264
|
+
eco-helpers 178 / qa 31 — 0 failures.**
|
|
265
|
+
|
|
266
|
+
- **Templates (gem):** `genomeSignature` now fetched on ALL data-field types (added once to the
|
|
267
|
+
`dataFieldInterface` fragment, `@skip(if: $only_content)`) + `passthrough :genomeSignature` on
|
|
268
|
+
`Base::Page::DataField` — unblocks field pairing for cross-template diffs. New
|
|
269
|
+
`Diff::CommandSynthesizer` turns a `VersionDiff` change-set into an ordered `WorkflowCommand` batch
|
|
270
|
+
(the replayable "commit"); correctly leaves UNSUPPORTED (never guesses) field type-change and
|
|
271
|
+
field/section moves (need a pairing map / target id). `Change` gained `parent_id` (stamped on option
|
|
272
|
+
changes).
|
|
273
|
+
- **samples/pages (eco-helpers):** native `Samples::Pages::Register::Base < Page::Base` reproducing
|
|
274
|
+
`RegisterUpdateCase` semantics (register-scoped batched cursor search, dedup-by-id, KPIs, dry-run,
|
|
275
|
+
process_page/process_ooze override) + native `Helpers::Pages::Creatable`. **Shim + OozeSamples names
|
|
276
|
+
untouched** (native-only). Deferred (TODO): `merge_values` typed-field pairing; v2 batch_queue dropped
|
|
277
|
+
(GraphQL is per-page). +17 specs.
|
|
278
|
+
- **QA repo:** dropped the hand-rolled `TemplateModel` (−62 net lines); `Source::{Fixture,Graphql}`
|
|
279
|
+
now yield the gem's page model (`Model::PageUnion.new(doc)`, parses offline); Compiler navigates gem
|
|
280
|
+
accessors with small adapters preserving skip-honest options/required/forces. Same toocs verdict (7/2/3).
|
|
281
|
+
|
|
282
|
+
**NEXT increments (parallel threads continue):** Templates — extend the emitter to *edit* mode +
|
|
283
|
+
wire diff→commands into a deploy flow; pairing engine (uses genomeSignature + type+label) + ledger.
|
|
284
|
+
samples/pages — native `merge_values`/typed-field pairing + A/B parity harness; native
|
|
285
|
+
`TargetOozesUpdateCase`. QA — Phase 3 Jira adapter (decision-blocked). Plus the standing 1.3.11 union
|
|
286
|
+
fixes + CI query-validation TODO (due 2026-07-17).
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## ▶ SESSION 2026-07-03 (cont.) — Template self-version diff + genome finding + domain capture
|
|
291
|
+
|
|
292
|
+
**Built `Ecoportal::API::GraphQL::Diff::VersionDiff`** (gem branch `feature/template-version-diff`,
|
|
293
|
+
commit `19c1dea`, 8 specs, rubocop clean): two SAME-object snapshots (ids retained) → exact structural
|
|
294
|
+
changelog (added/removed/changed/moved for stages/sections/fields[label,type,section-move]/options
|
|
295
|
+
[label,weight]). `Diff::Change` is command-ready. This is a **shared gem capability** (admin/
|
|
296
|
+
troubleshooting/integration/QA), not QA-only — per Oscar's clarification that these tools are the
|
|
297
|
+
epicenter of admin+troubleshooting automation.
|
|
298
|
+
|
|
299
|
+
**KEY FINDING — corrected a wrong doc claim:** `genomeSignature: String` **IS exposed** in the live
|
|
300
|
+
GraphQL schema on `DataFieldsInterface` + every data-field type (verified in the schema dump). The old
|
|
301
|
+
`graphql_domain_knowledge.md` claim "NOT exploitable" was wrong — corrected. Real gap: our gem doesn't
|
|
302
|
+
REQUEST it (add to the data-field fragment). Genome stays a strong-but-fallible pairing signal.
|
|
303
|
+
|
|
304
|
+
**Comprehensive harvestable knowledge doc:** `.ai-assistance/code/template_diff_pairing_domain.md` —
|
|
305
|
+
captures Oscar's full brain-dump (UAT→PROD regression modes, no-stable-identity Mongo obstacle,
|
|
306
|
+
self-version vs cross-object diff, genome verified facts + 6 failure modes, data-in-fields principle,
|
|
307
|
+
equivalence-matching reframe + learning ledger, diff MODALITIES, layering, pipeline, Product context)
|
|
308
|
+
so it never needs re-explaining and ep-ai-standards can harvest it. New project `template-diff-deploy`
|
|
309
|
+
(INTENT+TODO). Confirmed with Oscar: equivalence reframe, self-version-first, ledger as first-class.
|
|
310
|
+
|
|
311
|
+
**NEXT (template-diff-deploy):** add `genomeSignature` to fragment; `Change`→WorkflowCommands; pairing
|
|
312
|
+
engine + ledger (eco-helpers, reuse `TypedFieldsPairing`); diff modalities; deploy+verify+monitor. Also
|
|
313
|
+
tighten ecoportal-qa to use the gem's page-model (drop its own TemplateModel).
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## ▶ SESSION 2026-07-03 — NEW tool: ecoportal-qa (QA-as-CI/CD, Phases 1–2 built, Phase 3 scoped)
|
|
318
|
+
|
|
319
|
+
**Phase 3 SCOPED (docs only, branch `feature/qa-services-delivery-project`):** Jira adapter design in
|
|
320
|
+
`qa-services-delivery/PHASE3-SCOPE.md` — the Jira channel is *just another consumer of the canonical
|
|
321
|
+
Result* (no runner/DSL changes); `Jira::{Client,RestClient,McpClient,DryRunClient,Publisher}`;
|
|
322
|
+
human-gated (report-only default, explicit `--transition`, never auto); redact-before-post + idempotent
|
|
323
|
+
comments + audit. Decision (pending Oscar): interface + **REST-first**, MCP later (CI endgame likely
|
|
324
|
+
lacks MCP). Sub-phase **3a (DryRunClient + Publisher) is unblocked** and can start before creds; 3b/3c
|
|
325
|
+
need Jira project + token. Also added **`ROADMAP.md`** — north-star narrative (one Result → many
|
|
326
|
+
channels; P1–P5 capability ladder; how each rung changes the human's day) written to make the
|
|
327
|
+
vision→execution path legible for Oscar. Build blocked on 4 open Qs (Jira project/statuses, REST-vs-MCP,
|
|
328
|
+
verdict field, where check-sets live).
|
|
329
|
+
|
|
330
|
+
**Phase 2 done (branch `feature/phase-2`, commit `22cc865`, 31 specs green, rubocop clean):**
|
|
331
|
+
CSV + HTML reporters (JSON/Text refactored to render from the canonical hash); `Redactor` PII scrub
|
|
332
|
+
(preserves verdict/counts); `exe/ecoportal-qa` CLI (`run --source fixture:PATH|graphql:ID
|
|
333
|
+
--format text|json|csv|html [--redact]`, exit code = verdict for CI); richer checks — negative
|
|
334
|
+
assertions (stage/section/field absent), `label_pattern` regex, `field_count` (exact/`{min,max}`),
|
|
335
|
+
`fields_in_order`/`sections_in_order`, select `option_weights`, top-level force presence/binding
|
|
336
|
+
(skip when not captured). Synthetic `sample_template_page.json` fixture exercises them offline.
|
|
337
|
+
**Only remaining Phase-2 item: live capture of `6a3fa5b8…622b` — BLOCKED on sandbox creds.**
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
New standalone repo **`ecoportal-qa`** (`C:\ruby_scripts\git\ecoportal-qa`, git-init'd, commit
|
|
342
|
+
`1a62031`, v0.0.1) for the **Services Delivery QA team** who review **org config/template changes**
|
|
343
|
+
against Jira tickets. **QA-as-CI/CD:** QA authors declarative check-sets (YAML DSL) → Ruby compiler →
|
|
344
|
+
runnable checks → read-only runner → canonical Result JSON → reporting/Jira/monitoring.
|
|
345
|
+
|
|
346
|
+
- Phase 1 built + green: `DSL::CheckSet`, `Compiler`, `TemplateModel`, `Source::{Fixture,Graphql}`
|
|
347
|
+
(GraphqlSource lazy/read-only), `Runner`, `Result` (verdict + honest `:skip`), `Reporters::{Json,Text}`.
|
|
348
|
+
Proven vs the real `toocs_coding_page.json` snapshot (7 pass / 2 fail / 3 skip). 11 specs, rubocop clean.
|
|
349
|
+
Core has NO runtime gem deps (doc-based) → offline-runnable.
|
|
350
|
+
- **ep-ai-standards question resolved:** standards live in ep-ai-standards; implementations live with
|
|
351
|
+
their deps. `ecoportal-qa` is its own repo but ships a `.ai-assistance/` conformance stub
|
|
352
|
+
(`conformance.json`) so ep-ai-standards discovers/governs it without owning the code.
|
|
353
|
+
- Project docs on gem branch **`feature/qa-services-delivery-project`** (UNMERGED):
|
|
354
|
+
`.ai-assistance/projects/qa-services-delivery/{INTENT,DECISIONS,TODO}.md`.
|
|
355
|
+
- Drivers: safety/privacy/simplicity/delegation-to-QA/monitoring/reporting/governance, Ruby, human-in-loop.
|
|
356
|
+
- Roadmap: P2 CSV/HTML reporters + more checks + capture `6a3fa5b8…622b`; P3 Jira adapter (human-gated,
|
|
357
|
+
MCP vs REST TBD); P4 CI-as-code "ticket as pipeline"; P5 opt-in governed auto-approve.
|
|
358
|
+
|
|
359
|
+
**Merge note:** `ecoportal-qa` is a fresh standalone repo (no default-branch merge needed — it IS the
|
|
360
|
+
main line there). Only the gem's `feature/qa-services-delivery-project` docs branch awaits merge to main.
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## ▶ SESSION 2026-07-02 — Template pipeline + ooze-native foundations (4 branches, unmerged)
|
|
365
|
+
|
|
366
|
+
Post-cutover, picked up **two** parallel threads at Oscar's direction: (3) ooze → native GraphQL
|
|
367
|
+
migration — **native classes ONLY, no delegation flip yet** — and (4) the template-editing (CSV)
|
|
368
|
+
pipeline. Grounded both against live code via two parallel Explore surveys before writing. All work
|
|
369
|
+
is on feature branches, **green + rubocop-clean, unmerged**, pending Oscar's review + the merge plan
|
|
370
|
+
below. Nothing touches `main`/`master` yet; all additions are pure/non-breaking (no case flipped, no
|
|
371
|
+
shim removed, no existing API changed).
|
|
372
|
+
|
|
373
|
+
**Key findings (corrected the design docs):**
|
|
374
|
+
- A **template IS a page** — `Query::Templates` returns PageUnion; structure reads via the page
|
|
375
|
+
model. `Builder::Template` (create/update with `commands:`) + `executeWorkflowCommands` already
|
|
376
|
+
exist. So build-from-scratch is unblocked; only workflow-config *diff* waits on the partial
|
|
377
|
+
`PagesWorkflow.stages` read model.
|
|
378
|
+
- **`placeholderId` is the id-threading primitive** — commands reference not-yet-created nodes
|
|
379
|
+
intra-batch by client-chosen placeholder; simpler than the location engine's `newId` remap.
|
|
380
|
+
- **`build_commands` is non-idempotent** for key-renaming commands (addSelectFieldOption:
|
|
381
|
+
data_field_id→dataFieldId). Emitter output is FINAL built form, straight to the mutation.
|
|
382
|
+
- Ooze: first-wave cutover cases (RegisterUpdateCase/TargetOozesUpdateCase) are **force-free**
|
|
383
|
+
(migratable now); forces live only in downstream training scripts. Native `Pages::Page::Base`
|
|
384
|
+
already gives the loop skeleton to build on.
|
|
385
|
+
|
|
386
|
+
**Branches (all green, rubocop clean, UNMERGED):**
|
|
387
|
+
| Repo | Branch | Contents | Base |
|
|
388
|
+
|---|---|---|---|
|
|
389
|
+
| gem | `feature/template-editing-pipeline` | `tests/dump_template_model.rb`, WorkflowCommand serialization spec (10 ex), `template-maintenance/PHASE0-FINDINGS.md` | main @03799f5 |
|
|
390
|
+
| gem | `feature/ooze-native-phase0` | `ooze-graphql-native-migration/INVENTORY.md` + TODO scope note | main @03799f5 |
|
|
391
|
+
| eco-helpers | `feature/ooze-graphql-native-migration` | `graphql/helpers/pages/{shortcuts,filters,rescuable}` + specs (37 ex) | master @5b9d7c07 |
|
|
392
|
+
| eco-helpers | `feature/template-editing-pipeline` | `graphql/samples/pages/template/{command_emitter,base}` + spec (9 ex) | master @5b9d7c07 |
|
|
393
|
+
|
|
394
|
+
**STRATEGIC MERGE PLAN (for Oscar to action):**
|
|
395
|
+
1. **Gem first** (no cross-repo dep): merge `feature/ooze-native-phase0` (docs-only) then
|
|
396
|
+
`feature/template-editing-pipeline` into `main`. Independent; either order. No release bump
|
|
397
|
+
needed (tool + specs + docs; patch-level if tagging).
|
|
398
|
+
2. **eco-helpers next** (depends only on the *released* gem v1.3.9, which ships the WorkflowCommand
|
|
399
|
+
contract — no gem changes are required by either eco-helpers branch): merge
|
|
400
|
+
`feature/ooze-graphql-native-migration` (pure helper additions) then
|
|
401
|
+
`feature/template-editing-pipeline` (samples additions) into `master`. Independent of each other.
|
|
402
|
+
3. **Order rationale:** gem branches carry no runtime coupling to eco-helpers; eco-helpers branches
|
|
403
|
+
only reference the *already-released* gem constant `Input::WorkflowCommand` at runtime. So there
|
|
404
|
+
is **no hard merge-order dependency** — the split is by repo, and everything is additive.
|
|
405
|
+
4. **Do NOT** merge as a case flip / shim removal — those stay `[ ]` in the ooze TODO until parity
|
|
406
|
+
is proven (unchanged by this session).
|
|
407
|
+
|
|
408
|
+
**★ NEXT SESSION:**
|
|
409
|
+
- Template Phase 2/3: extend the emitter (editStage/editFieldConfiguration/gauge stops), then the
|
|
410
|
+
**sandbox live characterization** — run `dump_template_model.rb` on `6a3fa5b8…622b`, replay via
|
|
411
|
+
Template::Base, assert structure-equivalence. Needs a sandbox org + creds (Open Q #1).
|
|
412
|
+
- Ooze Phase 1/2: re-express `OozeHandlers#merge_values` + native `Creatable` against GraphQL
|
|
413
|
+
data-field types; then native `Register::Base` on top of `Pages::Page::Base`; A/B parity harness.
|
|
414
|
+
- Still open from prior: FARMERS/Travis support (F1 simulate-noop, F2 stale-patchVer) — locate
|
|
415
|
+
`FARMERS_GRAPHQL_HANDOVER.md` (api-deprecation branch, likely training repo).
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
11
419
|
## ▶ DEEP REVIEW (2026-06-30 late) — pre-live audit of TOOCS + CANS; 3 more fixes
|
|
12
420
|
|
|
13
421
|
Exhaustive audit (4 parallel sub-investigations) of both prod dry-runs before going live.
|
|
@@ -33,7 +33,7 @@ Gem::Specification.new do |spec|
|
|
|
33
33
|
spec.add_development_dependency 'yard', '>= 0.9.34', '< 1'
|
|
34
34
|
|
|
35
35
|
spec.add_dependency 'ecoportal-api', '~> 0.10', '>= 0.10.16'
|
|
36
|
-
spec.add_dependency 'ecoportal-api-v2', '~> 3.3', '>= 3.3.
|
|
36
|
+
spec.add_dependency 'ecoportal-api-v2', '~> 3.3', '>= 3.3.3'
|
|
37
37
|
spec.add_dependency 'graphlient', '>= 0.8.0', '< 0.9'
|
|
38
38
|
end
|
|
39
39
|
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
module Ecoportal
|
|
2
|
+
module API
|
|
3
|
+
class GraphQL
|
|
4
|
+
module Builder
|
|
5
|
+
# Build-from-scratch emitter for a template/workflow: turns a DECLARATIVE spec
|
|
6
|
+
# (stages -> sections -> fields -> options / config / gauge-stops) into an ordered batch of
|
|
7
|
+
# built `WorkflowCommand` hashes, ready for `Builder::Template#create(commands:)` /
|
|
8
|
+
# `#update(model, commands:)` (which wrap `executeWorkflowCommands`).
|
|
9
|
+
#
|
|
10
|
+
# This is the gem-level, reusable BUILD emitter (previously a one-off in eco-helpers samples).
|
|
11
|
+
# It shares ONE emission layer with the DIFF-DEPLOY pipeline: both produce an ordered
|
|
12
|
+
# `WorkflowCommand` batch, and both use the SAME `placeholderId` id-threading primitive that
|
|
13
|
+
# `Diff::CommandSynthesizer` uses — a newly-created node is addressed by a client-chosen
|
|
14
|
+
# `placeholderId`, and every later command in the same batch that references it substitutes
|
|
15
|
+
# that placeholder (the server has not assigned real ids yet). Here every node is brand new,
|
|
16
|
+
# so the whole batch is threaded through placeholders.
|
|
17
|
+
#
|
|
18
|
+
# SPEC SHAPE (all keys optional unless noted; only VALID command inputs are emitted):
|
|
19
|
+
#
|
|
20
|
+
# {
|
|
21
|
+
# stages: [
|
|
22
|
+
# { name: 'Report', ordering: 0, sections: [ # addStage(name, ordering)
|
|
23
|
+
# { layout: 'ONE_COLUMN', fields: [ # addSection(layout) + addStageSection
|
|
24
|
+
# { field_type: 'PlainText', label: 'Description', column: 0,
|
|
25
|
+
# description: 'ID:token' }, # addField (+ editFieldConfiguration for description)
|
|
26
|
+
# { field_type: 'Select', label: 'Severity',
|
|
27
|
+
# options: [ { label: 'Low', weight: 0 }, ... ], # addSelectFieldOption
|
|
28
|
+
# config: { select: { multiple: false } } }, # editFieldConfiguration(byType:)
|
|
29
|
+
# { field_type: 'Gauge', label: 'Risk',
|
|
30
|
+
# stops: [ { threshold: 0, color: '#0f0' }, ... ], # addGaugeFieldStop
|
|
31
|
+
# config: { gauge: { max: 100.0 } } }
|
|
32
|
+
# ] }
|
|
33
|
+
# ] }
|
|
34
|
+
# ]
|
|
35
|
+
# }
|
|
36
|
+
#
|
|
37
|
+
# ORDER — addStage -> addSection -> addStageSection -> addField -> (addSelectFieldOption |
|
|
38
|
+
# addGaugeFieldStop | editFieldConfiguration). This is the dependency-safe order the workflow
|
|
39
|
+
# command bus expects; placeholderId resolution makes the intra-batch references valid.
|
|
40
|
+
#
|
|
41
|
+
# SAFETY — only keys present in each command input's VALID_KEYS are emitted (the input classes
|
|
42
|
+
# slice + compact). A spec key with no corresponding command input is dropped, not fabricated.
|
|
43
|
+
# `field_type` shape and per-type config (`byType`) are NOT invented: config is passed through
|
|
44
|
+
# under its byType sub-hash exactly as given (the caller supplies confirmed byType keys), and
|
|
45
|
+
# is emitted only for a field that carries one.
|
|
46
|
+
class TemplateBuilder
|
|
47
|
+
# @param spec [Hash] the declarative template spec (see class doc). String or symbol keys.
|
|
48
|
+
def initialize(spec)
|
|
49
|
+
@spec = symbolize(spec || {})
|
|
50
|
+
@commands = []
|
|
51
|
+
@counter = 0
|
|
52
|
+
@built = false
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Ordered Array of built command hashes ({ commandKey => input }), placeholder-threaded.
|
|
56
|
+
def commands
|
|
57
|
+
return @commands if @built
|
|
58
|
+
|
|
59
|
+
@built = true
|
|
60
|
+
build_all
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Ready to hand to `Builder::Template#create(commands: builder.to_commands)`.
|
|
64
|
+
alias_method :to_commands, :commands
|
|
65
|
+
|
|
66
|
+
def to_h
|
|
67
|
+
{ commands: commands }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def build_all
|
|
73
|
+
Array(@spec[:stages]).each_with_index { |stage, idx| emit_stage(stage, idx) }
|
|
74
|
+
@commands
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def emit_stage(stage, index)
|
|
78
|
+
stage_ph = placeholder(:stg)
|
|
79
|
+
add(:addStage, name: stage[:name], ordering: stage.fetch(:ordering, index), placeholderId: stage_ph)
|
|
80
|
+
Array(stage[:sections]).each { |section| emit_section(section, stage_ph) }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def emit_section(section, stage_ph)
|
|
84
|
+
section_ph = placeholder(:sec)
|
|
85
|
+
add(:addSection, layout: section[:layout], placeholderId: section_ph)
|
|
86
|
+
# Attach the freshly-created section to its (freshly-created) stage.
|
|
87
|
+
add(:addStageSection, stageId: stage_ph, sectionId: section_ph)
|
|
88
|
+
emit_section_heading(section, section_ph)
|
|
89
|
+
Array(section[:fields]).each { |field| emit_field(field, stage_ph, section_ph) }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# A section's heading is not an addSection input; set it via editSectionHeader after create.
|
|
93
|
+
def emit_section_heading(section, section_ph)
|
|
94
|
+
heading = section[:heading]
|
|
95
|
+
return if heading.nil?
|
|
96
|
+
|
|
97
|
+
add(:editSectionHeader, sectionId: section_ph, header: heading)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def emit_field(field, stage_ph, section_ph)
|
|
101
|
+
field_ph = placeholder(:fld)
|
|
102
|
+
add(:addField,
|
|
103
|
+
fieldType: field[:field_type] || field[:fieldType],
|
|
104
|
+
label: field[:label],
|
|
105
|
+
stageId: stage_ph,
|
|
106
|
+
sectionId: section_ph,
|
|
107
|
+
column: field[:column],
|
|
108
|
+
placeholderId: field_ph)
|
|
109
|
+
emit_description(field, field_ph)
|
|
110
|
+
emit_options(field, field_ph)
|
|
111
|
+
emit_stops(field, field_ph)
|
|
112
|
+
emit_config(field, field_ph)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# A field's `description` is NOT an addField input (the schema's WorkflowAddFieldInput has
|
|
116
|
+
# no `description` key — VERIFIED against the 20260605 dump). It is a top-level key of
|
|
117
|
+
# editFieldConfiguration, so we persist it with a follow-up editFieldConfiguration
|
|
118
|
+
# addressed to the freshly-created field placeholder. Emitted only when supplied. This is
|
|
119
|
+
# the seam the CSV/hidden-field identity token rides on. NOTE: `required` has no schema
|
|
120
|
+
# input key at all, so it is intentionally NOT emitted (a spec `required:` is ignored).
|
|
121
|
+
def emit_description(field, field_ph)
|
|
122
|
+
description = field[:description]
|
|
123
|
+
return if description.nil?
|
|
124
|
+
|
|
125
|
+
add(:editFieldConfiguration, data_field_id: field_ph, description: description)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def emit_options(field, field_ph)
|
|
129
|
+
Array(field[:options]).each do |opt|
|
|
130
|
+
add(:addSelectFieldOption, data_field_id: field_ph, label: opt[:label], weight: opt[:weight])
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def emit_stops(field, field_ph)
|
|
135
|
+
Array(field[:stops]).each do |stop|
|
|
136
|
+
add(:addGaugeFieldStop, data_field_id: field_ph, threshold: stop[:threshold], color: stop[:color])
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Per-type field configuration passed through under its byType sub-hash exactly as supplied
|
|
141
|
+
# (e.g. { select: { multiple: true } }, { gauge: { max: 100.0 } }). Emitted only when
|
|
142
|
+
# present; keys are validated/sliced by the EditFieldConfiguration input, never fabricated.
|
|
143
|
+
def emit_config(field, field_ph)
|
|
144
|
+
config = field[:config]
|
|
145
|
+
return if config.nil? || config.empty?
|
|
146
|
+
|
|
147
|
+
add(:editFieldConfiguration, data_field_id: field_ph, byType: symbolize(config))
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Build one command and append it. Reuses the SAME builder the diff synthesizer uses, so
|
|
151
|
+
# BUILD and DIFF-DEPLOY emit through one layer.
|
|
152
|
+
def add(command_key, **kwargs)
|
|
153
|
+
@commands << Ecoportal::API::GraphQL::Input::WorkflowCommand.build(command_key, **kwargs)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# A deterministic client-chosen placeholderId — the id-threading primitive shared with the
|
|
157
|
+
# diff synthesizer. Prefix marks the node kind for readability of the emitted batch.
|
|
158
|
+
def placeholder(prefix)
|
|
159
|
+
@counter += 1
|
|
160
|
+
"ph_#{prefix}_#{@counter}"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def symbolize(value)
|
|
164
|
+
case value
|
|
165
|
+
when Hash then value.each_with_object({}) { |(k, v), h| h[k.to_sym] = symbolize(v) }
|
|
166
|
+
when Array then value.map { |e| symbolize(e) }
|
|
167
|
+
else value
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
module Ecoportal
|
|
2
|
-
module API
|
|
3
|
-
class GraphQL
|
|
4
|
-
module Builder
|
|
5
|
-
end
|
|
6
|
-
end
|
|
7
|
-
end
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
require_relative 'builder/location_structure'
|
|
11
|
-
require_relative 'builder/action'
|
|
12
|
-
require_relative 'builder/contractor_entity'
|
|
13
|
-
require_relative 'builder/register'
|
|
14
|
-
require_relative 'builder/page'
|
|
15
|
-
require_relative 'builder/kickstand'
|
|
16
|
-
require_relative 'builder/template'
|
|
1
|
+
module Ecoportal
|
|
2
|
+
module API
|
|
3
|
+
class GraphQL
|
|
4
|
+
module Builder
|
|
5
|
+
end
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
require_relative 'builder/location_structure'
|
|
11
|
+
require_relative 'builder/action'
|
|
12
|
+
require_relative 'builder/contractor_entity'
|
|
13
|
+
require_relative 'builder/register'
|
|
14
|
+
require_relative 'builder/page'
|
|
15
|
+
require_relative 'builder/kickstand'
|
|
16
|
+
require_relative 'builder/template'
|
|
17
|
+
require_relative 'builder/template_builder'
|