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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.ai-assistance/code/diff_pairing_engine.md +243 -0
  3. data/.ai-assistance/code/graphql_domain_knowledge.md +20 -10
  4. data/.ai-assistance/code/template_diff_pairing_domain.md +175 -0
  5. data/.ai-assistance/code/workflow-command-guide.md +28 -0
  6. data/.ai-assistance/projects/TODO.md +21 -0
  7. data/.ai-assistance/projects/ooze-graphql-native-migration/INVENTORY.md +136 -0
  8. data/.ai-assistance/projects/ooze-graphql-native-migration/TODO.md +6 -1
  9. data/.ai-assistance/projects/qa-services-delivery/DECISIONS.md +93 -0
  10. data/.ai-assistance/projects/qa-services-delivery/INTENT.md +76 -0
  11. data/.ai-assistance/projects/qa-services-delivery/PHASE3-SCOPE.md +115 -0
  12. data/.ai-assistance/projects/qa-services-delivery/ROADMAP.md +99 -0
  13. data/.ai-assistance/projects/qa-services-delivery/TODO.md +81 -0
  14. data/.ai-assistance/projects/template-automatic-build-maintenance/INTENT.md +77 -0
  15. data/.ai-assistance/projects/template-automatic-build-maintenance/TODO.md +97 -0
  16. data/.ai-assistance/projects/template-diff-deploy/INTENT.md +12 -0
  17. data/.ai-assistance/projects/template-diff-deploy/TODO.md +9 -0
  18. data/.ai-assistance/projects/template-maintenance/PHASE0-FINDINGS.md +93 -0
  19. data/.ai-assistance/projects/template-maintenance/README.md +14 -0
  20. data/CHANGELOG.md +116 -0
  21. data/docs/worklog.md +408 -0
  22. data/ecoportal-api-graphql.gemspec +1 -1
  23. data/lib/ecoportal/api/graphql/base/page/data_field.rb +1 -1
  24. data/lib/ecoportal/api/graphql/builder/template_builder.rb +174 -0
  25. data/lib/ecoportal/api/graphql/builder.rb +17 -16
  26. data/lib/ecoportal/api/graphql/diff/change.rb +59 -0
  27. data/lib/ecoportal/api/graphql/diff/command_synthesizer.rb +329 -0
  28. data/lib/ecoportal/api/graphql/diff/cross_object_diff.rb +165 -0
  29. data/lib/ecoportal/api/graphql/diff/deploy.rb +121 -0
  30. data/lib/ecoportal/api/graphql/diff/id_resolver.rb +64 -0
  31. data/lib/ecoportal/api/graphql/diff/pairing/candidate.rb +32 -0
  32. data/lib/ecoportal/api/graphql/diff/pairing/engine.rb +173 -0
  33. data/lib/ecoportal/api/graphql/diff/pairing/ledger.rb +119 -0
  34. data/lib/ecoportal/api/graphql/diff/pairing/signals.rb +104 -0
  35. data/lib/ecoportal/api/graphql/diff/strategy.rb +113 -0
  36. data/lib/ecoportal/api/graphql/diff/version_diff.rb +332 -0
  37. data/lib/ecoportal/api/graphql/diff.rb +34 -0
  38. data/lib/ecoportal/api/graphql/fragment/location_draft.rb +53 -53
  39. data/lib/ecoportal/api/graphql/fragment/pages/common_page_union.rb +1 -0
  40. data/lib/ecoportal/api/graphql/input/workflow_command/add_field.rb +27 -18
  41. data/lib/ecoportal/api/graphql/mutation/action/archive.rb +1 -1
  42. data/lib/ecoportal/api/graphql/mutation/action/create.rb +1 -1
  43. data/lib/ecoportal/api/graphql/mutation/action/update.rb +1 -1
  44. data/lib/ecoportal/api/graphql/mutation/contractor_entity/create.rb +1 -1
  45. data/lib/ecoportal/api/graphql/mutation/contractor_entity/destroy.rb +1 -1
  46. data/lib/ecoportal/api/graphql/mutation/contractor_entity/update.rb +1 -1
  47. data/lib/ecoportal/api/graphql/mutation/kickstand/fail_workflow.rb +1 -1
  48. data/lib/ecoportal/api/graphql/mutation/kickstand/start_workflow.rb +1 -1
  49. data/lib/ecoportal/api/graphql/mutation/kickstand/stop_workflow.rb +1 -1
  50. data/lib/ecoportal/api/graphql/query/location_structure/draft.rb +62 -62
  51. data/lib/ecoportal/api/graphql/query/location_structure.rb +61 -61
  52. data/lib/ecoportal/api/graphql.rb +139 -138
  53. data/lib/ecoportal/api/graphql_version.rb +1 -1
  54. data/tests/dump_template_model.rb +90 -0
  55. data/tests/validate_queries.rb +127 -0
  56. metadata +32 -3
@@ -0,0 +1,77 @@
1
+ # Project: Template Automatic Build & Maintenance ★ #1 PRIORITY
2
+
3
+ **Status:** ACTIVE — the #1-priority project. **Created:** 2026-07-04 (merges the former
4
+ `template-diff-deploy` + `template-maintenance` projects into one plan).
5
+ **Domain reference (read first):** `.ai-assistance/code/template_diff_pairing_domain.md`
6
+ **Code spec:** `.ai-assistance/code/diff_pairing_engine.md`
7
+ **Superseded folders (pointers only now):** `template-diff-deploy/`, `template-maintenance/`.
8
+ **Sibling projects:** `qa-services-delivery` (verify), `ooze-graphql-native-migration`,
9
+ `template-csv-pipeline` (memory).
10
+
11
+ ---
12
+
13
+ ## Why this is one project
14
+
15
+ Both former projects are two halves of the SAME lifecycle and share ONE emission layer (an ordered
16
+ `WorkflowCommand` batch with `placeholderId` threading applied to `executeWorkflowCommands`):
17
+
18
+ - **BUILD** (was `template-maintenance`) — build/maintain 300+ templates programmatically
19
+ (CSV→templates pipeline, deadline ~Sept 2026): declare a desired template → emit an ordered command
20
+ sequence → apply → read back to verify.
21
+ - **MAINTAIN / DIFF-DEPLOY** (was `template-diff-deploy`) — make UAT→PROD change replication smooth:
22
+ detect (diff) → review → deploy (delta commands) → verify → monitor, eliminating the two manual
23
+ regressions (lost PROD-only changes; premature release of unapproved UAT changes).
24
+
25
+ BUILD is "add everything"; DIFF-DEPLOY is "add/remove/edit only the delta". Same command vocabulary,
26
+ same id-threading, same apply path. Keeping them as one project keeps the emission layer unified.
27
+
28
+ ## The core hard problem (unchanged)
29
+
30
+ No stable identity across independent Mongo objects (template vs child page, UAT vs PROD). Self-version
31
+ diff (same ids) is trivial and done; cross-object diff is an **equivalence-matching** problem — pair
32
+ fields on multiple weak signals (genome + type + label + options), human-assisted for the ambiguous
33
+ ones, with a persisted **learning ledger**. See the domain reference for the full failure-mode map.
34
+
35
+ ## Approach (confirmed)
36
+
37
+ - **Self-version diff first** (done) — the portable "commit".
38
+ - **Pairing = equivalence matching**, not identity — multi-signal, confidence-scored, human-in-loop,
39
+ ledger-backed (`Diff::Pairing::Engine` + `Ledger`; also feeds Product's Field-ID / template-entity-id).
40
+ - **Diff MODALITIES** — a composable family, NOT one diff: `Diff::Strategy` over four axes
41
+ (pairing `:id|:genome|:type_label|:assisted` × scope `:structural|:config_only|:data_migration` ×
42
+ move-sensitivity × intent `:changelog|:deploy|:sync_readiness`). `VersionDiff` is the `:id`
43
+ front-end; `CrossObjectDiff` is the cross-object front-end (pairs fields, emits the SAME `Change`
44
+ output against the pairing map, consumed by the existing synthesizer/deploy unchanged).
45
+ - **Unified BUILD emitter in the gem** — `Builder::TemplateBuilder` turns a declarative spec into the
46
+ ordered `WorkflowCommand` batch (stages→sections→fields→options→config→gauge-stops), threading
47
+ `placeholderId`s exactly as `Diff::CommandSynthesizer` does. BUILD and DIFF-DEPLOY share one layer.
48
+ - **Layering** — mechanics (read model, command vocab, build emitter, diff, apply, pairing engine +
49
+ ledger) live in the gem so admin/troubleshooting/integration/QA all reuse them; orchestration
50
+ (interactive assisted-resolution UX, session flows, `TypedFieldsPairing` as an extra signal) is in
51
+ eco-helpers.
52
+
53
+ ## The pipeline
54
+
55
+ ```
56
+ BUILD: desired spec ─► TemplateBuilder ─► ordered WorkflowCommands ─► Builder::Template#create/update ─► verify
57
+ MAINTAIN: pair (id|genome|type_label|assisted+ledger) ─► diff (modality) ─► delta WorkflowCommands
58
+ ─► Deploy#execute! (gated on `unsupported`) ─► verify (ecoportal-qa) ─► monitor (sync-ready)
59
+ ```
60
+
61
+ ## Scope
62
+
63
+ - In: self-version diff (done); genomeSignature exposure (done); pairing engine + ledger (done); diff
64
+ modalities/Strategy (done); cross-object diff path (done); Change→WorkflowCommands synthesis (done);
65
+ gem-level BUILD emitter (done); UAT→PROD replay; sync-readiness monitoring; CSV→templates pipeline
66
+ (later); tighten `ecoportal-qa` to consume the gem's page-model.
67
+ - Out: waiting on Product's template-entity-id (we bridge + feed it); auto-applying without human
68
+ review of unresolved pairings; workflow-config-per-stage diff until `PagesWorkflow.stages` read model
69
+ is completed (see `template-maintenance/PHASE0-FINDINGS.md` §2).
70
+
71
+ ## Success criteria
72
+
73
+ 1. A ticket's UAT changes are captured exactly and rendered as a review checklist (self-version). ✅
74
+ 2. Two templates diff correctly with pairing; unresolved pairings are escalated to a human + remembered. ✅ engine + cross-object path ready; interactive UX pending (eco-helpers).
75
+ 3. A reviewed delta deploys to PROD as WorkflowCommands, then is verified (ecoportal-qa) — no regressions.
76
+ 4. A template can be BUILT from a declarative spec via one ordered command batch. ✅ emitter ready; live characterization pending (sandbox creds).
77
+ 5. Monitoring shows which registers/subsets are sync-ready to their active template.
@@ -0,0 +1,97 @@
1
+ # TODOs — Template Automatic Build & Maintenance ★ #1 PRIORITY
2
+
3
+ Legend: `[ ]` todo · `[~]` in progress · `[x]` done · `[!]` blocked
4
+ Domain reference: `.ai-assistance/code/template_diff_pairing_domain.md`
5
+ Code spec: `.ai-assistance/code/diff_pairing_engine.md`
6
+
7
+ This merges the former `template-diff-deploy/TODO.md` (MAINTAIN track) and
8
+ `template-maintenance/DESIGN.md`+`PHASE0-FINDINGS.md` (BUILD track). BUILD and DIFF-DEPLOY share ONE
9
+ emission layer: an ordered `WorkflowCommand` batch with `placeholderId` threading.
10
+
11
+ ---
12
+
13
+ ## MAINTAIN track (diff → deploy → verify → monitor)
14
+
15
+ ### Phase 1 — Self-version diff (same object, exact) ✅ DONE
16
+ - [x] `Diff::VersionDiff` — two same-id snapshots → structured changelog (stages/sections/fields/options).
17
+ - [x] `Diff::Change` — command-ready (op/kind/id/before/after) + human `#description`.
18
+
19
+ ### Phase 2 — Expose genome + Change→commands ✅ DONE
20
+ - [x] `genomeSignature` on the data-field fragment + `Base::Page::DataField` (`passthrough`).
21
+ - [x] `Diff::CommandSynthesizer` — `[Change]` → ordered built `WorkflowCommand` batch; edit-mode moves
22
+ gated on `Diff::IdResolver`; type change / resolver-less move → UNSUPPORTED (never guessed).
23
+ - [x] `Diff::Deploy` — `from_versions(before, after, target_doc:)`; `#execute!` gates on `unsupported`.
24
+
25
+ ### Phase 3 — Pairing engine (equivalence) ✅ DONE
26
+ - [x] `Diff::Pairing::Engine` + `Signals` (genome 0.5 / type 0.2 / label 0.2 / options 0.1), greedy 1:1,
27
+ auto-accept ≥0.85 unless near-tie, ambiguous 0.5–0.85, unmatched <0.5; genome fallible (0 not veto).
28
+ - [x] `Diff::Pairing::Ledger` — persisted confirmed pairs (consulted first; supersede-on-correct).
29
+ - [x] `Engine#confirm!(candidate, matched_by: :human)` assisted-resolution hook.
30
+ - [ ] TODO (eco-helpers): interactive assisted-resolution UX; `TypedFieldsPairing` as an extra signal;
31
+ live A/B parity harness.
32
+
33
+ ### Phase 3b — Gauge-stop + typed byType config emission + placeholderId threading ✅ DONE
34
+ - [x] `VersionDiff` emits `:gauge_stop` + `:field_config` (byType, data-driven `BYTYPE_CONFIG`,
35
+ conservative — only confirmed read↔byType-key matches; unmappable props NOT fabricated).
36
+ - [x] `CommandSynthesizer` maps them; `thread_placeholders:` id-threading primitive (Deploy → on).
37
+
38
+ ### Phase 4 — Diff modalities (composable strategies) ✅ DONE 2026-07-04 (branch `feature/template-phase4-modalities-builder-docs`)
39
+ - [x] `Diff::Strategy` value object over the four axes: pairing `:id|:genome|:type_label|:assisted` ×
40
+ scope `:structural|:config_only|:data_migration` × move-sensitivity × intent
41
+ `:changelog|:deploy|:sync_readiness`. Validates axes; `#filter(changes)` applies scope +
42
+ move-sensitivity; `.default` reproduces the pre-Phase-4 self-version behaviour EXACTLY (BC).
43
+ - [x] `VersionDiff.new(before, after, strategy:)` — the `:id` front-end now filters its computed
44
+ change-set through the strategy (default = unchanged; specs prove BC + config_only + move-insensitive).
45
+ - [x] `Diff::CrossObjectDiff` — the CROSS-OBJECT front-end: pairs FIELDS via `Pairing::Engine` (+ optional
46
+ `Ledger`), builds the id-correspondence map from the ACCEPTED pairs, then emits the SAME `Change`
47
+ output (relabel/retype on paired fields; target-only → :added; confidently-unpaired source → :removed).
48
+ Ambiguous/unmatched (incl. same-genome relabels + near-ties) are held in `#unresolved` for a human —
49
+ NEVER auto-paired, auto-added, or auto-removed. Feeds `CommandSynthesizer`/`Deploy` unchanged.
50
+ - [x] `Deploy.from_cross_object(source, target, engine:, strategy:)` + `Deploy#pairing` (exposes
51
+ accepted/ambiguous/unmatched for adjudication before apply).
52
+ - [x] Specs: `strategy_spec` (16), `cross_object_diff_spec`, `version_diff_modalities_spec`. Full diff
53
+ suite 120 examples (was 93; +27). Rubocop clean.
54
+
55
+ ### Phase 5 — Deploy + verify + monitor (NEXT)
56
+ - [ ] UAT→PROD replay end-to-end: pair → diff → delta commands → apply → verify via `ecoportal-qa`.
57
+ - [ ] Pre/post self-version diff on PROD to confirm the recreation matches the intended delta.
58
+ - [ ] Sync-readiness monitoring: which registers/subsets can sync to their active template.
59
+
60
+ ---
61
+
62
+ ## BUILD track (build-from-scratch → verify)
63
+
64
+ ### Phase B0 — Study & capture ✅ DONE (see `template-maintenance/PHASE0-FINDINGS.md`)
65
+ - [x] `Builder::Template#create/update(commands:)` + `Input::WorkflowCommand` are the apply facade.
66
+ - [x] `placeholderId` is the id-threading primitive (client-chosen, resolved intra-batch).
67
+ - [x] `tests/dump_template_model.rb` captures a template's structural page doc; reference target
68
+ `6a3fa5b8f89e07c758df622b` (needs sandbox creds to run live).
69
+
70
+ ### Phase B1 — Gem-level BUILD emitter (unified with diff) ✅ DONE 2026-07-04 (branch `feature/template-phase4-modalities-builder-docs`)
71
+ - [x] `Builder::TemplateBuilder` — declarative spec (stages→sections→fields→options→config→gauge-stops)
72
+ → ordered `WorkflowCommand` batch, `placeholderId`-threaded. Order: addStage → addSection →
73
+ addStageSection → editSectionHeader → addField → addSelectFieldOption / addGaugeFieldStop /
74
+ editFieldConfiguration(byType:). Verified against `Builder::Template#create/update(commands:)` + the
75
+ real input classes' VALID_KEYS (only valid keys emitted; nothing fabricated — config passed through
76
+ under its byType sub-hash exactly as supplied). Shares the SAME `placeholderId` threading concept as
77
+ `Diff::CommandSynthesizer`. 13 specs. Rubocop clean.
78
+ - [ ] UNSUPPORTED/deferred here (honest): forces/strategies/callbacks/tasks/recipients in a build spec
79
+ (the command inputs exist but the build spec shape for them was not confirmed — add per confirmed
80
+ shape); `field_type` enum values not validated against the schema (passed through as given).
81
+
82
+ ### Phase B2 — Live characterization (NEXT, needs sandbox)
83
+ - [ ] Replay `TemplateBuilder` output to reconstruct sample `6a3fa5b8…622b`; assert structure-equivalence
84
+ vs the pinned `dump_template_model.rb` fixture. `executeWorkflowCommands` mutates → sandbox, not CI.
85
+
86
+ ### Phase B3 — CSV→templates pipeline (later, deadline ~Sept 2026)
87
+ - [ ] CSV extract → `TemplateBuilder` spec → build batch, for the 300+ template delivery. Section/field
88
+ identity via hidden-field + description (see memory: project-template-csv-pipeline).
89
+
90
+ ---
91
+
92
+ ## Cross-cutting
93
+ - [ ] Tighten `ecoportal-qa` to consume the gem's page-model instead of its own `TemplateModel`.
94
+ - [ ] Emit `stageId`/`sectionId` back-refs on diff-side `addField`/`addSection` (BUILD already does;
95
+ the diff synthesizer's structural adds still emit only label/placeholder — then threading covers them).
96
+ - [ ] Extend `BYTYPE_CONFIG` per field type as read↔byType-key shapes are confirmed; gauge-stop reorder.
97
+ - [ ] Complete the `PagesWorkflow.stages` read model to unblock workflow-config-per-stage diff.
@@ -0,0 +1,12 @@
1
+ # MOVED → `template-automatic-build-maintenance` ★ #1 PRIORITY
2
+
3
+ This project has been **merged** into the unified **Template Automatic Build & Maintenance** project.
4
+
5
+ - Single INTENT: `../template-automatic-build-maintenance/INTENT.md`
6
+ - Single TODO: `../template-automatic-build-maintenance/TODO.md`
7
+
8
+ The MAINTAIN track (diff → deploy → verify → monitor) that lived here is now the "MAINTAIN track"
9
+ section of the merged TODO. Nothing here is authoritative any more — do not update this folder.
10
+
11
+ Domain reference (unchanged): `.ai-assistance/code/template_diff_pairing_domain.md`.
12
+ Code spec (unchanged): `.ai-assistance/code/diff_pairing_engine.md`.
@@ -0,0 +1,9 @@
1
+ # MOVED → `template-automatic-build-maintenance/TODO.md`
2
+
3
+ The diff/deploy TODOs (Phases 1–5) are now the **MAINTAIN track** of the merged
4
+ **Template Automatic Build & Maintenance** project (★ #1 priority):
5
+
6
+ → `../template-automatic-build-maintenance/TODO.md`
7
+
8
+ Phases 1–3b + Phase 4 (diff modalities: `Diff::Strategy` + `CrossObjectDiff`) are DONE there.
9
+ Do not add TODOs here.
@@ -0,0 +1,93 @@
1
+ # Template-Maintenance — Phase 0 Findings (2026-07-02)
2
+
3
+ Ground-truth survey of the gem + eco-helpers surface, verified against actual code (not the
4
+ DESIGN.md second-hand survey). Corrections to DESIGN.md assumptions are flagged **⚠**.
5
+
6
+ ---
7
+
8
+ ## 1. The gem mutation surface is MORE complete than DESIGN.md implied
9
+
10
+ - **⚠ `Builder::Template` already exists and is wired** (`lib/ecoportal/api/graphql/builder/template.rb`,
11
+ exposed as `GraphQL#template`, `graphql.rb:98`). It offers:
12
+ `create(commands:)`, `update(model, commands:, patch_ver:)`, `publish(id:)`, `unpublish(id:)`,
13
+ `update_information(id:, ...)`, `create_related_page(...)`, `destroy_related_page(...)`.
14
+ → The template create/update **apply** step is done. We do **not** need to build a new facade
15
+ for `executeWorkflowCommands` on the template side — `Builder::Template#create/update` already
16
+ wrap `Mutation::Template::{Create,Update}` with a `commands:` array.
17
+ - `Builder::Page#execute_workflow_commands` also exists (per `builder/CLAUDE.md`) for the
18
+ page-level workflow command path.
19
+ - `Input::WorkflowCommand` (`input/workflow_command.rb`) is the command registry:
20
+ **108 commands** in `COMMAND_MAP`, `SCHEMA_VERSION = '20260605'`, with:
21
+ - `.build(command_key, **kwargs)` → `{ key => sliced_kwargs }` (validates key, slices to VALID_KEYS, compacts nils, keeps false)
22
+ - `.build_commands(specs)` → maps an ordered array of single-key specs into the batch
23
+ - `.valid_key?(key)`
24
+
25
+ ## 2. A template IS a page (read path)
26
+
27
+ - **⚠ DESIGN.md Open Question #2 ("is PagesWorkflow read complete enough to diff a template?")
28
+ is partly moot for STRUCTURE.** `Query::Templates` returns `PageUnion` nodes with `PageFields`
29
+ — a template is read as an ordinary page. So the structural skeleton
30
+ (**stages → sections → components(fields)**) comes from the page model, exactly like a live page.
31
+ `dump_template_model.rb` therefore reuses `api.pages.get(id)` for structure.
32
+ - The `PagesWorkflow` read model (`base/pages_workflow.rb`) is the **workflow-command config**
33
+ layer (callbacks, operations, triggers, strategies, recipients). **⚠ It is partial:** `stages`
34
+ is a bare `passarray` (no per-stage strategy/section tree). So:
35
+ - **Build-from-scratch (Phase 2): UNBLOCKED.** Emit commands, execute, read the page back to verify.
36
+ - **Structural diff (Phase 4): mostly UNBLOCKED** via the page model (stages/sections/fields).
37
+ - **Workflow-config diff (callbacks/strategies per stage): BLOCKED** on completing the
38
+ `PagesWorkflow.stages` read model. Track as a Phase-4 dependency, not a Phase-2 blocker.
39
+ - Forces read is on the page model but `Query::PageWithForces` is WIP (see forces notes) — a
40
+ forces-aware diff is a later dependency.
41
+
42
+ ## 3. `placeholderId` is the id-threading primitive (the key design fact)
43
+
44
+ The location-structure engine threads server-assigned ids **post-hoc** (zip input↔result, extract
45
+ `newId`, remap table). **The workflow command bus does NOT need that** — it uses **client-chosen
46
+ `placeholderId`s resolved intra-batch**:
47
+
48
+ | Command | Placeholder it defines | References (by placeholder or real id) |
49
+ |---|---|---|
50
+ | `addStage` | `placeholderId` | — |
51
+ | `addSection` | `placeholderId` | — |
52
+ | `addStageSection` | — | `stageId`, `sectionId` |
53
+ | `addField` | `placeholderId` | `stageId`, `sectionId`, `column` |
54
+ | `addSelectFieldOption` | `placeholderId` | `dataFieldId` |
55
+ | `addForce` | `placeholderId` | — |
56
+ | `addBinding` | `placeholderId` | `forceId` |
57
+
58
+ → The emitter assigns a stable `placeholderId` to every new node and references it downstream in
59
+ the **same batch**. This is simpler than the location `track_changed_ids` remap. **⚠ Correction to
60
+ DESIGN.md:** we mirror the location engine's *shape* (DSL → ordered commands → apply → read-back),
61
+ but the id-threading is placeholder-based, not `newId`-remap-based.
62
+
63
+ **Open question to confirm live:** does `executeWorkflowCommands` resolve placeholderIds across the
64
+ *whole* batch, or only within ordering constraints (e.g. must addStage precede addStageSection)?
65
+ The emitter already emits in dependency order (stage → section → stageSection → field → option),
66
+ so this is safe either way; confirm during the Phase-3 sandbox replay.
67
+
68
+ ## 4. What Phase 0 delivered (this commit)
69
+
70
+ - `tests/dump_template_model.rb` — captures a template's structural page doc
71
+ (`spec/fixtures/templates/<id>.json`) + best-effort workflow command log
72
+ (`<id>.commands.json`). Mirrors `dump_page_model.rb`; run `--label before/after` around a batch
73
+ to diff. **Reference target:** sample `6a3fa5b8f89e07c758df622b` (needs a live/sandbox org to run).
74
+ - `spec/.../input/workflow_command_serialization_spec.rb` — offline serialization contract for the
75
+ structural command families + the `build_commands` ordered-emitter shape + placeholderId threading.
76
+ 10 examples, green. This is the net the eco-helpers emitter is written against.
77
+
78
+ ## 5. Revised phase view
79
+
80
+ - **Phase 1 (gem):** DONE-ENOUGH. `Builder::Template` + `Input::WorkflowCommand` are the facade;
81
+ offline command specs exist and grow as new command types are exercised.
82
+ - **Phase 2 (eco-helpers):** `samples/pages/template/{base,dsl}.rb` + a command-emitter service
83
+ (DSL → ordered commands w/ placeholderId threading → `Builder::Template#create/update` → read-back).
84
+ Offline emitter specs are fully achievable without live calls.
85
+ - **Phase 3 (sandbox):** replay to reconstruct `6a3fa5b8…622b`; assert structure-equivalence vs the
86
+ `dump_template_model.rb` fixture. Needs a sandbox org (Open Question #1 — creds TBD).
87
+ - **Phase 4 (diff):** structural diff via page model now; workflow-config diff waits on the
88
+ `PagesWorkflow.stages` read model.
89
+
90
+ ## Open questions still owned by Oscar
91
+ 1. Sandbox org + creds for the Phase-3 live characterization harness.
92
+ 2. Priority of completing the `PagesWorkflow.stages` read model (gates workflow-config diff).
93
+ 3. Does `6a3fa5b8…622b` exercise forces? (gates the forces read/write split for its replay)
@@ -0,0 +1,14 @@
1
+ # MOVED → `template-automatic-build-maintenance` ★ #1 PRIORITY
2
+
3
+ This project has been **merged** into the unified **Template Automatic Build & Maintenance** project.
4
+
5
+ - Single INTENT: `../template-automatic-build-maintenance/INTENT.md`
6
+ - Single TODO: `../template-automatic-build-maintenance/TODO.md`
7
+
8
+ The BUILD track (build-from-scratch → verify → CSV pipeline) that lived here is now the "BUILD track"
9
+ section of the merged TODO. The gem-level BUILD emitter is DONE
10
+ (`Builder::TemplateBuilder`, shares the diff's `placeholderId` emission layer).
11
+
12
+ `DESIGN.md` and `PHASE0-FINDINGS.md` are RETAINED here as background — the merged INTENT/TODO reference
13
+ them for the survey + the `placeholderId` id-threading facts. They are historical context, not the
14
+ active task list. Do not add new TODOs here — use the merged TODO.
data/CHANGELOG.md CHANGED
@@ -2,6 +2,122 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.3.11] - 2026-07-04
6
+
7
+ Template automatic **build & maintenance** engine (backwards-compatible — a self-contained new
8
+ `Diff` module + a build emitter; nothing existing changed). Validated **offline** (specs + fixtures +
9
+ shapes checked against the live schema dump); the live UAT→PROD round trip is pending a sandbox org.
10
+
11
+ ### Added
12
+
13
+ - **`genomeSignature`** fetched on every data-field type (added once to the `dataFieldInterface`
14
+ fragment, `@skip(if: $only_content)`) + `passthrough :genomeSignature` on `Base::Page::DataField` —
15
+ a strong-but-fallible field-pairing signal.
16
+ - **`Diff::VersionDiff`** — two same-object (retained-id) template snapshots → an exact structural
17
+ changelog: added/removed/changed/moved for stages, sections, fields (label/type/section-move),
18
+ select options (label/weight), **gauge stops** (add/remove/threshold/color, matched by stop id), and
19
+ typed **`byType` field config** (conservative, only confirmed read↔input-key matches: Gauge `max`;
20
+ Select `dataType/multiple/flat/other/otherDesc`; Date `showTime/pastOnly/todayButton`).
21
+ - **`Diff::Change`** — command-ready change (`op`/`kind`/`id`/`label`/`path`/`attribute`/`before`/
22
+ `after`/`parent_id`/`by_type`) with a human `#description`; `#to_h` is the stable interchange shape.
23
+ - **`Diff::CommandSynthesizer`** — a `Change` set → an ordered `WorkflowCommand` batch (the replayable
24
+ "commit"): moves/renames/config, options, gauge stops, `editFieldConfiguration(byType:)`, with
25
+ **`placeholderId` threading** so dependent intra-batch creates reference not-yet-created nodes.
26
+ Genuinely ambiguous cases (field type change, moves without a target id) are left **UNSUPPORTED** —
27
+ never guessed.
28
+ - **`Diff::Deploy`** — `from_versions(before, after, target_doc:)` / `from_cross_object(...)` produce
29
+ the ordered batch ready for `executeWorkflowCommands`; `#execute!` refuses to run while the honest
30
+ `unsupported` list is non-empty unless `allow_partial: true`; inert until an executor is passed.
31
+ - **`Diff::IdResolver`** — indexes a target doc's stage names + section headings → ids for move
32
+ resolution; duplicate keys resolve to `nil` (never guesses).
33
+ - **`Diff::Pairing::{Engine,Signals,Candidate,Ledger}`** — multi-signal equivalence matching for
34
+ cross-object templates (genome 0.5 + type 0.2 + label 0.2 + options-by-value 0.1, averaged over
35
+ applicable signals; greedy 1:1; auto-accept ≥0.85, ambiguous 0.5–0.85, unmatched <0.5; genome
36
+ mismatch scores 0 but does **not** veto). `Ledger` persists confirmed pairs (consulted first;
37
+ human corrections supersede).
38
+ - **`Diff::Strategy`** — composable diff modalities: pairing (`:id`/`:genome`/`:type_label`/
39
+ `:assisted`) × scope (`:structural`/`:config_only`/`:data_migration`) × move-sensitivity × intent
40
+ (`:changelog`/`:deploy`/`:sync_readiness`). `.default` reproduces exact pre-existing behaviour.
41
+ - **`Diff::CrossObjectDiff`** — diffs two templates with **no shared ids** by building the
42
+ id-correspondence map via `Pairing::Engine` (+ optional `Ledger`), then emitting the same `Change`
43
+ output that feeds `CommandSynthesizer`/`Deploy`. Ambiguous/unmatched held in `#unresolved` for a
44
+ human — never auto-paired.
45
+ - **`Builder::TemplateBuilder`** — a declarative template spec (stages→sections→fields→options→
46
+ config→gauge-stops) → an ordered `WorkflowCommand` batch for `Builder::Template#create/update`,
47
+ using the **same `placeholderId` threading** as the diff synthesizer so build-from-scratch and
48
+ diff-deploy emit through one layer. Emits only verified input `VALID_KEYS`. A field's `description`
49
+ (e.g. a CSV/hidden-field identity token) is emitted as a follow-up `editFieldConfiguration(
50
+ dataFieldId:, description:)` — the correct schema seam, since `WorkflowAddFieldInput` has no
51
+ `description` key.
52
+ - **Stage/section back-refs on added nodes** (`Diff::CommandSynthesizer`) — an added field's `addField`
53
+ now carries `sectionId` (`Change#parent_id`) + `stageId` (new `Change#owner_id`); an added section
54
+ emits a follow-up `addStageSection(stageId:, sectionId:)`. All threaded via `ref()` (parent
55
+ placeholder when created same-batch, else real id; omitted when unknown → historical behaviour
56
+ unchanged). `VersionDiff` records `parent_id`/`owner_id` on added field/section changes.
57
+
58
+ ### Fixed
59
+
60
+ - **Fragment references migrated to the `spread :Name` registry convention.** The Action and
61
+ ContractorEntity **mutations** (`create`/`update`/`destroy`, `create`/`update`/`archive`) still used
62
+ the dead `___Const__Fragment` convention, which resolves a Ruby constant `Fragment::<Name>` that no
63
+ longer exists (fragments are a registry now) → `uninitialized constant … Fragment::… (NameError)` at
64
+ render time. Now `spread :Action` / `spread :ContractorEntity`, matching the already-migrated queries.
65
+ (The sibling breakage in eco-helpers' location-command optimizations is fixed in eco-helpers 3.2.17;
66
+ it crashed the live act-gov locations sync.)
67
+ - **Kickstand workflow mutations — bare `DateTime` selection.** `Kickstand::{Start,Stop,Fail}Workflow`
68
+ selected `startedAt`/`stoppedAt`/`failedAt` bare → "Field must have selections". Now
69
+ `{ dateTime timeZone }` (same class as the 1.3.10 LocationStructure fix).
70
+ - **`tests/validate_queries.rb` now renders MUTATIONS too** (was queries only) and **hard-fails on a
71
+ render error** (dead fragment / unresolved constant), not just a soft skip — the offline guard for
72
+ both classes above. It caught the 3 Kickstand bugs immediately.
73
+
74
+ ### Schema realities (verified against the live introspection dump)
75
+
76
+ - **`required` cannot be set via the workflow command bus** — there is NO `required` input key anywhere
77
+ in the schema (not on `WorkflowAddFieldInput`, not on `WorkflowEditFieldConfigurationInput` or any of
78
+ its 12 byType sub-inputs). Callers must treat field-`required` as **read-only / out-of-band**
79
+ (ecoportal-qa `:skip`s `required`-readiness by design).
80
+
81
+ ### Dependencies
82
+
83
+ - **Requires `ecoportal-api-v2 >= 3.3.3`** (was `>= 3.3.2`). 3.3.2 has a Ruby-3.x `TypeError` in the
84
+ `DoubleModel` cascade (`_cascaded_attributes_trace`) that crashes `as_update`/`DiffService` on any
85
+ model with nested attributes — fixed in v2 3.3.3. Raising the floor stops the broken v2 from
86
+ resolving on consumers.
87
+
88
+ ### Known gaps (tracked, not in this release)
89
+
90
+ - Live characterization (UAT→PROD replay + verify) is pending a sandbox org + credentials.
91
+
92
+ ## [1.3.10] - 2026-07-03
93
+
94
+ Hotfix (backwards-compatible). Resolves a live failure on the Turners & Growers users sync.
95
+
96
+ ### Fixed
97
+
98
+ - **LocationStructure query — `updatedAt` bare selection** → `Field must have selections (field
99
+ 'updatedAt' returns DateTime but has no selections)`. `LocationStructureType.updatedAt` returns the
100
+ `DateTime` object (`{ dateTime, timeZone }`), so it must be selected with sub-fields. Fixed in
101
+ `Query::LocationStructure` and `Query::LocationStructure::Draft` (now `updatedAt { dateTime timeZone }`).
102
+ The plural `Query::LocationStructures` does not select it and was unaffected.
103
+ - **Root cause:** the bare selection has existed since 2023 (v0.3.x) but was **never exercised** —
104
+ no spec covered these queries, and the tagtree/users-sync path only began fetching the location
105
+ structure via GraphQL (rather than APIv2) as of the cutover, so it first ran in production.
106
+ - **Regression guard added:** `spec/.../query/location_structure_spec.rb` renders the query blocks
107
+ offline and asserts `updatedAt` carries `{ dateTime timeZone }` — this would have failed in CI.
108
+ - **LocationDraft fragment — `createdAt` over-selection** (found by the full audit below): selected
109
+ `createdAt { dateTime }`, but `Draft.createdAt` is an `ISO8601DateTime` **scalar** → would throw
110
+ `Selections can't be made on scalars` whenever the location-structure **draft** query runs. Now bare
111
+ `createdAt`. Guarded by the same spec.
112
+
113
+ ### Added
114
+
115
+ - **`tests/validate_queries.rb`** — offline audit: renders every gem query (auth stubbed, no network),
116
+ assembles its fragments, and validates the full document against a schema introspection JSON. Flags
117
+ the whole bug class — object fields selected bare ("must have selections") and scalars over-selected
118
+ ("selections can't be made on scalars"). A full run confirmed these two location fixes are the ONLY
119
+ such issues across all 17 query classes (contractors, actions, pages, register, templates: clean).
120
+
5
121
  ## [1.3.9] - 2026-07-02
6
122
 
7
123
  APIv2→GraphQL cutover hardening (backwards-compatible). Live-validated on the maintained ooze