openehr-rails 0.7.0 → 0.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 065a9ba44f893adaea9e6c0ccf6f3c5f8a0af8c8c3897379cd008e5642e99a7a
4
- data.tar.gz: 98f81ad0baf2b086e2193102c25804b3affcd2fa1ffb4c0730952d6a916e3a58
3
+ metadata.gz: a30bd4a561f8193c0603c1e43e1873e1c8835d2e7c418b95c429ac6e2b397e83
4
+ data.tar.gz: 180145cf2e294cc68ae84ddc35b147e07e6fb52f99062d14f8df3cb0e306108e
5
5
  SHA512:
6
- metadata.gz: 42353942db8a02b901e3727a85446fe1995f3c953fd33f3da2dfea68d7018b533de95b97494db856593df77ccbacd8d57f0f4bb4cdcedee086da6c4e44035a04
7
- data.tar.gz: 4fd7e793d2d77230e585dcf06204147926d3b36a86a576525614d19395930f80b4fe38bc0b534de5a0f6b26e98e0859d5c8275ce865a1efedf3aa942dc1d272e
6
+ metadata.gz: 46b84468c263139208e522562831d103cd43d8d3c865aa274fe7cf6db7ebe486fa67329b4e175564a9fc0f94d0a0a4454d679f3d42735ac4c7214b739fa7ba3a
7
+ data.tar.gz: dc189063ef16d43253287e9fed472bdc7d69b0b0c72fb5f393f772cf9adbe596ebd24b6dbea57514435c3a6afe8cdba048fd4b1c3c2e73e39b4a17315ad03b6d
@@ -26,6 +26,8 @@ jobs:
26
26
  run: bundle exec rake release:check
27
27
  - name: Build gem
28
28
  run: bundle exec rake build
29
+ - name: Record sha256
30
+ run: sha256sum pkg/*.gem | tee pkg/SHA256SUMS
29
31
  - name: Upload gem artifact
30
32
  uses: actions/upload-artifact@v7
31
33
  with:
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 4.0.6
1
+ 4.0.7
data/CHANGELOG.md CHANGED
@@ -7,6 +7,63 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.8.0] - 2026-10-12
11
+
12
+ > **Upgrade note.** A stored Composition holding a `SECTION` or an `INSTRUCTION`
13
+ > (with its `ACTIVITY`s) now reads back through `to_rm` and AQL; the ones 0.7.1
14
+ > skipped with `openehr-rails AQL: skipping composition uid=...` are exactly the
15
+ > ones that become visible, so query row counts can grow. Two injected defaults
16
+ > are approximations until a later release persists them as columns: an
17
+ > INSTRUCTION without a stored narrative reads back with its node name as
18
+ > `narrative`, and every ACTIVITY with `action_archetype_id` `'/.*/'`.
19
+
20
+ ### Added
21
+ - `RmObjectBuilder` rebuilds `SECTION`, `INSTRUCTION` and `ACTIVITY` nodes (and
22
+ every CARE_ENTRY's `protocol`), so compositions holding them -- a referral's
23
+ `service_request` with its requester/receiver under `protocol[...]`, entries
24
+ nested in a `SECTION` -- become readable through `to_rm` and queryable by AQL
25
+ instead of being skipped with the 0.7.1 warning (#45). Two values are
26
+ **approximations** because the graph does not carry them yet: an INSTRUCTION
27
+ without a stored narrative gets its node name as `narrative`, and every
28
+ ACTIVITY gets `action_archetype_id` `'/.*/'` (any ACTION archetype). Both will
29
+ be persisted as columns in a later release. `ACTION`, `ITEM_SINGLE` and
30
+ `ITEM_TABLE` are still unsupported (skipped with the warning).
31
+ - `FieldExtractor` fields carry `rm_type_alternatives`: every value type the OPT
32
+ allows for the ELEMENT, in OPT order (e.g. `["DV_TEXT", "DV_CODED_TEXT"]` for
33
+ `problem_diagnosis` at0002). Additive; the chosen `rm_type` is unchanged. Lets a
34
+ host app tell an OR-constrained coded text (free text allowed) from a coded-only
35
+ one instead of rendering an empty select (#52, anlage #33).
36
+
37
+ ### Fixed
38
+ - An entry with no extracted leaves (today every `INSTRUCTION`, see #47) no
39
+ longer gets a profile consisting of the archetype `code` rules and an empty
40
+ `component` slicing header; it is skipped and reported through `#skipped`
41
+ with a message saying the entry has no leaves, whatever its resource (#49).
42
+
43
+ ## [0.7.1] - 2026-09-25
44
+
45
+ > **Upgrade note.** AQL now skips a stored composition that holds an RM type
46
+ > `RmObjectBuilder` cannot rebuild, with a warning
47
+ > (`openehr-rails AQL: skipping composition uid=...`), instead of failing every
48
+ > query in the store. No host-app change is needed; if that line appears in
49
+ > your log, the composition is a #45 case (SECTION / INSTRUCTION / ACTIVITY
50
+ > read-back, next minor release).
51
+
52
+ ### Fixed
53
+ - A single-leaf entry mapped to a resource other than `Observation` and without a
54
+ `TypeMap::ENTRY_ELEMENT_MAPS` row no longer produces `<Resource>.value[x]`, which
55
+ `Condition`/`ServiceRequest`/`Procedure`/`Encounter` do not have; it is skipped and
56
+ reported through `#skipped` like the multi-leaf case since 0.7.0 (#38). The
57
+ `UnsupportedProfileError` message now names both missing elements.
58
+ - AQL no longer fails for the whole store when one stored composition cannot be
59
+ rebuilt as RM objects (a node type outside `RmObjectBuilder::TYPE_CLASSES`,
60
+ such as `SECTION`/`INSTRUCTION`, or an RM constructor rejecting stored data).
61
+ `DatasetAdapter` now warns (`Rails.logger` when present, else stderr), naming
62
+ the composition uid and the cause, and skips that composition; queries on the
63
+ rest of the store return their rows. `RmObjectBuilder` raises
64
+ `OpenehrRails::Rm::UnsupportedRmTypeError` (composition uid, node path,
65
+ rm_type) instead of an accidental `NoMethodError` (#44).
66
+
10
67
  ## [0.7.0] - 2026-09-10
11
68
 
12
69
  > **Upgrade note.** Regenerate `app/fhir/profiles/*.json`. `Condition.component`
data/CLAUDE.md CHANGED
@@ -61,6 +61,13 @@ documentation files and `lib/` was byte-identical - but nothing in the release
61
61
  path caught it. Measurements in `docs/reports/fsh-generator-log.md` R8; the
62
62
  matching `release:check` guard is `skoba/openehr-rails#34`.)
63
63
 
64
+ **Local dev Ruby tracks the patch release CI builds with.** `.ruby-version`
65
+ moved 4.0.6 -> 4.0.7 (2026-10-12) so a local rebuild at the tag stamps the same
66
+ `rubygems_version` (4.0.20) as `ruby/setup-ruby`'s ruby 4.0 image did for 0.7.1,
67
+ whose `metadata.gz` differed from the local rebuild by that one line
68
+ (`docs/reports/referral-upstream-log.md` R2). The cross-check still judges by
69
+ `data.tar.gz`, not the whole-gem sha256: the two toolchains can drift apart again.
70
+
64
71
  **Confirm publication by checksum, not by version number.** RubyGems publishes
65
72
  the sha256 it recorded at push time (`/api/v1/gems/openehr-rails.json` `sha`, and
66
73
  the `checksum:` field in `https://index.rubygems.org/info/openehr-rails`). The
@@ -225,7 +232,7 @@ This is `openehr-rails`, a Rails engine gem that turns an openEHR Operational Te
225
232
 
226
233
  ## Development Notes
227
234
 
228
- - Ruby 3.3+ required (raised from 3.0 in 2026-08 to track the `openehr` gem dependency dropping Ruby 3.2 support at EOL); CI matrix covers 3.3/3.4/4.0, local dev pinned via `.ruby-version` (currently 4.0.6)
235
+ - Ruby 3.3+ required (raised from 3.0 in 2026-08 to track the `openehr` gem dependency dropping Ruby 3.2 support at EOL); CI matrix covers 3.3/3.4/4.0, local dev pinned via `.ruby-version` (currently 4.0.7)
229
236
  - Rails 7.0+ and Rails 8.x supported (CI matrix: `gemfiles/rails_{7_2,8_0,8_1}.gemfile`; demo app runs on Rails 8.1)
230
237
  - URI compatibility workaround implemented for Ruby 3.4
231
238
  - `ostruct` is an explicit development dependency — it left Ruby's default gems as of 4.0, and a couple of specs use `OpenStruct` as a lightweight test double
data/docs/backlog.md CHANGED
@@ -74,6 +74,26 @@ that verification is deferred to the next real release (>= 0.5.0), not simulated
74
74
 
75
75
  ## Release automation
76
76
 
77
+ - **`release.yml` records no sha256 of the built `.gem`** (found at the 0.7.0
78
+ release, 2026-09-10, `docs/reports/fsh-generator-log.md` R14). The only digest in
79
+ the run log is `actions/upload-artifact`'s "SHA256 digest of uploaded artifact",
80
+ which hashes the uploaded archive, not the gem file, so `CLAUDE.md`'s "compare
81
+ against the value recorded for that run" cannot be followed literally; the
82
+ tag-rebuild cross-check is what pins the bytes today. Add a "Record sha256" step
83
+ after `rake build` (`sha256sum pkg/*.gem`), as openehr-ruby's `release.yml` has.
84
+ CI-only change; needs a PR, no Issue. **In PR #51 as `1410274` (2026-10-12,
85
+ `docs/reports/referral-upstream-log.md` R5)**; first run that prints it will be
86
+ the `v0.8.0` tag's.
87
+ - **The tag-rebuild cross-check compares `data.tar.gz`, not the whole `.gem`**
88
+ (0.7.1, 2026-09-25, `docs/reports/referral-upstream-log.md` R2). A `.gem` embeds
89
+ `rubygems_version` in `metadata.gz`, so a rebuild on a different ruby/rubygems
90
+ (local 4.0.6/4.0.16 vs CI 4.0.7/4.0.20) yields a different whole-gem sha256 with
91
+ byte-identical shipped files. Procedure: `tar xf` both gems, compare
92
+ `sha256sum data.tar.gz` (must match) and `diff` the gunzipped `metadata.gz`
93
+ (only `rubygems_version` may differ). 0.7.0's whole-gem match was a coincidence
94
+ of equal toolchains. Fold this into `CLAUDE.md`'s Release convention together
95
+ with the "Record sha256" step above.
96
+
77
97
  **Done (2026-08-23, PR #28)** — kept below for the original rationale; see the CI
78
98
  status entry above for verification details.
79
99
 
@@ -122,6 +142,14 @@ status entry above for verification details.
122
142
 
123
143
  ## Fixture conventions
124
144
 
145
+ - **`spec/templates/sample_blood_pressure.opt` does not parse** -- `OpenehrRails::Opt.parse`
146
+ raises `ArgumentError: invalid archetype id form` from
147
+ `OpenEHR::RM::Support::Identification::ArchetypeID` under openehr 2.4.2 *and* 2.4.3
148
+ (measured 2026-09-10 while checking fixtures for #38), and no spec, script or
149
+ generator references the file. Orphan fixture: either find which archetype id in
150
+ it is malformed and decide whether that is an openehr-ruby parser gap worth an
151
+ upstream Issue, or delete the file. Not touched in 0.7.0.
152
+
125
153
  - **Licensed terminology-code literals in spec expectations: keep minimal, cite the
126
154
  source fixture's file:line.** Adopted from Anlage's C2 firewall precedent (SNOMED
127
155
  CT is a licensed terminology; reproducing its codes verbatim in more places than
@@ -365,3 +365,34 @@ Still **minor**, on top of §8.5: `#skipped` and `UnsupportedProfileError` are
365
365
  new public API; the `category` anchor and the `comment` change the JSON facade's
366
366
  shape again for `EVALUATION` entries. `CHANGELOG.md` `[Unreleased]` updated in
367
367
  the same change.
368
+
369
+ ## 9.7 #38 (2026-09-10 ruling): the decision point widened to any leaf count
370
+
371
+ The ruling on 9.5 allowed #38 to ride in 0.7.0 if it stayed on the same
372
+ decision point, small, and green. It does: `TypeMap.assert_supported!` drops
373
+ its `fields.size <= 1` early return, so *any* entry whose base resource is not
374
+ `Observation` and which has no `ENTRY_ELEMENT_MAPS` row is skipped and
375
+ reported -- a single leaf would have produced `<Resource>.value[x]`, which
376
+ `Condition` / `ServiceRequest` / `Procedure` / `Encounter` lack just as they
377
+ lack `component` (measured: 1 sushi Error on the 9.5 probe). The error message
378
+ now names both missing elements and the leaf count in the singular where it is
379
+ one. Resolution shape (a) bug.
380
+
381
+ - **Fixture check before widening**: every OPT under `spec/templates/`,
382
+ `spec/generators/templates/` and `demo_assets/templates/` was listed with
383
+ `FieldExtractor#entries` -- all single-leaf entries are `OBSERVATION`
384
+ (`height.v2`, `body_weight.v2`, `heart_rate-pulse.v1`); the only
385
+ non-`Observation` entry is `problem_diagnosis.v1` (5 leaves, mapped). So no
386
+ fixture's output changes. (`spec/templates/sample_blood_pressure.opt` does not
387
+ parse at all -- `ArgumentError: invalid archetype id form` -- under openehr
388
+ 2.4.2 *and* 2.4.3, and no spec references it; pre-existing, noted in
389
+ `docs/backlog.md`, not touched here.)
390
+ - **Red**: 4 new examples (two per generator, synthetic single-leaf
391
+ `EVALUATION` entry `openEHR-EHR-EVALUATION.synthetic_single_leaf_test.v1`):
392
+ **40 examples, 4 failures** across the two spec files.
393
+ - **Green**: the same files 40/0; `spec/openehr_rails/fhir/` 52/0; full suite
394
+ 308/0. Regenerated FSH for `problem_list.opt` + `bmi_calculation.opt`
395
+ byte-identical to before and **0 Errors** under `sushi` 3.16.0 -- the pin.
396
+ - **Semver**: 0.7.1 (0.7.0 shipped before PR #43 was merged; see log R13); a bug fix (patch) that changes no
397
+ fixture output, only what an unmapped single-leaf non-`Observation` entry
398
+ yields (invalid FHIR before, a `#skipped` entry now).
@@ -0,0 +1,134 @@
1
+ # Plan: `RmObjectBuilder` reads back SECTION / INSTRUCTION / ACTIVITY
2
+
3
+ - Status: **explore + plan, awaiting approval** (2026-09-25). No code written.
4
+ - Issue: [#45](https://github.com/skoba/openehr-rails/issues/45) (anlage upstream candidate 15)
5
+ - Log: `docs/reports/referral-upstream-log.md` R1
6
+ - Prerequisite landed as its own fix: #44 / PR #50 (a failing `to_rm` no longer
7
+ fails the whole store; unknown types raise `UnsupportedRmTypeError`).
8
+
9
+ ## 1. Current behaviour (measured, `file:line` on `master` after PR #50)
10
+
11
+ **Write side accepts the types.** `Rm::TypeMap::NODE_TYPES`
12
+ (`lib/openehr_rails/rm/type_map.rb:9-26`) lists SECTION, INSTRUCTION, ACTION,
13
+ ACTIVITY (and ITEM_SINGLE, ITEM_TABLE); the STI classes exist
14
+ (`lib/openehr_rails/rm/nodes.rb:10,14,15,18`). `GraphBuilder#infer_type`
15
+ (`lib/openehr_rails/rm/graph_builder.rb:111-122`) maps `activities` -> ACTIVITY and
16
+ `protocol` / `description` -> ITEM_TREE for `_type`-less hashes. `build_children`
17
+ (`:64-79`) persists Hash-valued attributes as child nodes or data-value rows and
18
+ Array-valued ones as ordered children; **String-valued attributes are dropped** --
19
+ so `ACTIVITY.action_archetype_id` never reaches the graph. `INSTRUCTION.narrative`
20
+ (DV_TEXT), `ACTIVITY.timing` (DV_PARSABLE: `text_value` + `formalism`, `:180-181`)
21
+ and `INSTRUCTION.expiry_time` (DV_DATE_TIME) are stored as data-value rows keyed by
22
+ `rm_attribute_name`.
23
+
24
+ **Read side does not.** `RmObjectBuilder::TYPE_CLASSES`
25
+ (`lib/openehr_rails/rm/rm_object_builder.rb`, 10 entries) has none of them;
26
+ `build_node` sets entry defaults only for Observation / Evaluation / AdminEntry,
27
+ `data` only for `EntryNode`, and never `protocol`, `narrative`, `activities`,
28
+ `description`. After PR #50 such a node raises `UnsupportedRmTypeError` and the
29
+ composition is skipped from AQL with a warning; before it, the whole store failed
30
+ (anlage referral-intake-log R6).
31
+
32
+ **What the openehr gem (2.4.3) demands of the objects** (`openehr-ruby`
33
+ `lib/openehr/rm/composition/content/`):
34
+
35
+ | class | mandatory | optional / notes |
36
+ |---|---|---|
37
+ | `Navigation::Section` (`navigation.rb:12-27`) | -- | `items` must be nil or non-empty; `path_attribute :items` |
38
+ | `Entry::Instruction` (`entry.rb:112-138`) | Entry: `language`, `encoding`, `subject`; `narrative` | `activities` nil or non-empty; `expiry_time`, `wf_definition`; CareEntry `protocol`, `guideline_id` (`entry.rb:69-78`); `path_attribute :activities, :protocol` |
39
+ | `Entry::Activity` (`entry.rb:140-171`) | `description`, `action_archetype_id` (non-empty String) | `timing` optional since RM 1.1.0; `path_attribute :description` |
40
+ | `Entry::Action` (`entry.rb:173-204`) | Entry attrs; `time`, `description`, `ism_transition` (with `current_state` validated against the openEHR terminology, `:236-275`) | out of scope here, see section 5 |
41
+
42
+ `OpenEHR::RM::Composition::Composition` has `path_attribute :content, :context`,
43
+ so objects built with the attributes above are navigable by the AQL engine's
44
+ path evaluator without engine changes.
45
+
46
+ ## 2. Design -- phase A, no schema change
47
+
48
+ 1. `TYPE_CLASSES` gains `SECTION`, `INSTRUCTION`, `ACTIVITY`.
49
+ 2. `build_node` gains, per type (each in its own small private helper, because
50
+ `build_node` already sits at rubocop's AbcSize/complexity limits -- PR #50 had to
51
+ extract `node_class` for a one-line change):
52
+ - **Section**: `items` = children built in position order; `nil` when there
53
+ are none (the constructor rejects `[]`).
54
+ - **Instruction**: the same entry defaults as the other entries
55
+ (`language` / `encoding` / `subject`; extend the `is_a?` list);
56
+ `narrative` = the `narrative` data-value row as DvText, **defaulting to the
57
+ node's name** when the row is absent (an injected default, documented like
58
+ `language`); `activities` = children with `rm_attribute_name == 'activities'`,
59
+ `nil` when none; `expiry_time` when its row exists.
60
+ - **Activity**: `description` = the `description` child (ITEM_TREE) -- no
61
+ default: a missing description is an RM-conformance failure and surfaces
62
+ through #44's warning; `timing` = the `timing` row as
63
+ `OpenEHR::RM::DataTypes::Encapsulated::DvParsable` when present;
64
+ `action_archetype_id` = the injected default `'/.*/'` (the RM's "any ACTION
65
+ archetype" pattern) because the graph cannot carry it yet (section 5).
66
+ - **`protocol` for every CareEntry** (Observation, Evaluation, Instruction):
67
+ `attrs[:protocol] = build_node(protocol child)` when present. Same
68
+ mechanism, and it is where `jp_referral` keeps 紹介先/紹介元
69
+ (`service_request` `protocol[at0008]`, anlage memory of 2026-09-25) -- without
70
+ it the INSTRUCTION would be readable but its most-queried data not.
71
+ 3. `UnsupportedRmTypeError` keeps covering ACTION, ITEM_SINGLE, ITEM_TABLE.
72
+
73
+ Injected defaults are listed in the class comment next to the existing ones.
74
+
75
+ ## 3. Spec plan (t-wada: red before green), resolution shape (b) enhancement
76
+
77
+ - **Fixture**: a spec-level synthetic canonical hash (invented ids, stated in the
78
+ comment with this section as design authority; no real canonical JSON with
79
+ SECTION/INSTRUCTION exists in this repo -- anlage's is hand-mapped and outside):
80
+ COMPOSITION whose `content` is `[SECTION { items: [EVALUATION { data: ITEM_TREE {
81
+ items: [ELEMENT DV_TEXT] } }] }, INSTRUCTION { narrative, protocol: ITEM_TREE {
82
+ items: [ELEMENT DV_TEXT] }, activities: [ACTIVITY { description: ITEM_TREE {
83
+ items: [ELEMENT DV_TEXT] }, timing: DV_PARSABLE }] }]`, committed with
84
+ `CompositionCommitter`.
85
+ - `rm_object_builder_spec.rb`: **red** today = `UnsupportedRmTypeError`; green =
86
+ `Section` with one `Evaluation` item; `Instruction` with the stored narrative,
87
+ one `Activity` whose `description.items.first.value` is the DvText,
88
+ `action_archetype_id == '/.*/'`, `protocol.items` present; entry defaults on the
89
+ Instruction.
90
+ - `executor_spec.rb`: AQL through the new objects --
91
+ `SELECT i/activities[at0001]/description[at0009]/items[at0121]/value/value ...
92
+ CONTAINS INSTRUCTION i[<synthetic id>]` and
93
+ `SELECT i/protocol[at0008]/items[at0010]/value/value`, plus
94
+ `... CONTAINS SECTION s[<id>] CONTAINS EVALUATION ev[<id>]`. Whether the
95
+ engine's CONTAINS chain accepts SECTION/ACTIVITY is **unknown until measured**;
96
+ if it does not, that becomes an openehr-ruby item and the spec pins the `i/...`
97
+ path form only.
98
+ - `dataset_adapter_spec.rb` / `executor_spec.rb` (#44): switch the "to_rm fails"
99
+ fixture from SECTION to ITEM_TABLE, which stays unsupported.
100
+ - Regression pins: `bmi_calculation` / `problem_list` specs unchanged; full suite
101
+ green; `CanonicalSerializer` round-trip of the new fixture is checked and, if it
102
+ does not survive (`narrative` / `timing` rows), recorded as its own issue rather
103
+ than fixed here.
104
+
105
+ ## 4. Size and schedule
106
+
107
+ Estimate: +3 map entries; ~50 lines of new helpers plus splitting `build_node`'s
108
+ existing per-type `if` blocks into helpers (~80 lines moved, behaviour-neutral,
109
+ needed to stay inside the rubocop limits); ~130 lines of specs across three files;
110
+ no migration, no generator template change, no dependency change. **Small** by the
111
+ ruling's criterion (same builder, one file of runtime code, existing test
112
+ infrastructure). Recommendation: **implement before the freeze**, in one PR,
113
+ `Fixes #45`, after this plan is approved.
114
+
115
+ ## 5. Out of scope (phase B, December unless pulled forward)
116
+
117
+ - **Persisting `action_archetype_id`**, `narrative`, `timing` as columns instead
118
+ of injected defaults / data-value rows: needs a migration in
119
+ `lib/generators/**/templates/` (shipped product, host apps migrate) -- its own
120
+ issue and plan.
121
+ - **ACTION**: `ism_transition` is persisted today as a generic CLUSTER node
122
+ (`infer_type` falls through), and rebuilding it needs codes valid against the
123
+ terminology; separate design.
124
+ - **ITEM_SINGLE / ITEM_TABLE**: no consumer yet.
125
+
126
+ ## 6. Semver
127
+
128
+ New read-side capability, observable through AQL and `to_rm`: **minor**. If it
129
+ lands with #38 (PR #43) and #44 (PR #50), the next release is **0.8.0**, not 0.7.1
130
+ -- to be decided at the inventory.
131
+
132
+ ## 7. Stop point
133
+
134
+ Explore + plan only. Implementation starts on approval.
@@ -923,3 +923,79 @@ retitled `[0.7.0] - 2026-09-10` with the Upgrade note, fresh empty
923
923
  `[Unreleased]`) -> tag `v0.7.0` -> `release.yml` -> artifact sha256 (R14) ->
924
924
  human `gem push` of that artifact -> confirmation by published checksum, the
925
925
  first application of the rule PR #42 added.
926
+
927
+ ## R14 -- v0.7.0 tagged, release.yml green, artifact sha256 recorded; gem push is the human's
928
+
929
+ - Release prep on `master` after `acb1e3f`: `b1bfaa3` (lint), `2212955` (R13),
930
+ `aafbd2c` (Release: bump version to 0.7.0). Before the release commit: full
931
+ suite **304 examples, 0 failures**, `rubocop` **109 files, no offenses**,
932
+ `release:check OK` (no tag yet, so the tag check was silent by design).
933
+ - Tag `v0.7.0` -> `aafbd2c`, pushed 2026-09-10. **Deviation noted**: this is a
934
+ lightweight tag; `v0.6.0` is an annotated tag object. Harmless for
935
+ `release.yml` (tag push) and `release:check` (`v0.7.0^{commit}`), but the
936
+ next release should use `git tag -a` to keep the series uniform.
937
+ - `release.yml` run **34438238454**: all 12 jobs success (9 spec matrix, demo
938
+ smoke, application template smoke, Build gem artifact). `release:check OK`
939
+ inside the run, i.e. HEAD was the tag commit.
940
+ - **Artifact**: downloaded with `gh run download 34438238454 -n gem -D pkg`:
941
+ `pkg/openehr-rails-0.7.0.gem`, 242176 bytes,
942
+ sha256 **`922ec940f15c8793ef6ee08568e4d7ea16f8a158da0d45936ff898e73cd1171a`**.
943
+ - **Cross-check**: a fresh `gem build` in a scratch worktree at `v0.7.0`
944
+ (local ruby 4.0.6, not `pkg/`) produced the **same sha256** -- CI artifact and
945
+ tag rebuild are byte-identical, as for 0.5.0 and 0.6.0.
946
+ - **Gap found in the comparison rule**: `CLAUDE.md` says to compare the
947
+ downloaded artifact "against the value recorded for that run", but
948
+ `release.yml` records no gem sha256 -- the only digest in the run log is
949
+ `actions/upload-artifact`'s `SHA256 digest of uploaded artifact`
950
+ (`671c8de1…238b`), which is the digest of the uploaded archive, **not of the
951
+ .gem**. The two are not comparable. The tag-rebuild cross-check above is what
952
+ actually pins the bytes today. Filed in `docs/backlog.md` "Release
953
+ automation": add a "Record sha256" step (as openehr-ruby's `release.yml`
954
+ has) so the rule can be followed literally.
955
+ - **Handed to the human**: `gem push pkg/openehr-rails-0.7.0.gem` (the
956
+ downloaded CI artifact, never a local build), then confirm by checksum per
957
+ the rule PR #42 added: `https://rubygems.org/api/v1/gems/openehr-rails.json`
958
+ `sha` (or the compact index `checksum:`) must equal `922ec940…171a`; use
959
+ `versions/openehr-rails/latest.json` rather than `versions/openehr-rails.json`
960
+ while the latter lags. Then delete `pkg/openehr-rails-0.7.0.gem`. Result goes
961
+ in R15.
962
+ - `master` is now past the tag by the docs commits that record this entry, so
963
+ `release:check` on `master` fails again by design until 0.7.1.
964
+
965
+ ## R15 -- 0.7.0 published; confirmed by checksum (first application of the PR #42 rule)
966
+
967
+ Human `gem push` of the CI artifact `pkg/openehr-rails-0.7.0.gem` (R14). Confirmation
968
+ 2026-09-10, every source compared against the CI artifact's sha256
969
+ `922ec940f15c8793ef6ee08568e4d7ea16f8a158da0d45936ff898e73cd1171a`:
970
+
971
+ | source | value |
972
+ |---|---|
973
+ | `/api/v1/gems/openehr-rails.json` `sha` | `922ec940…171a`, version 0.7.0 |
974
+ | compact index `https://index.rubygems.org/info/openehr-rails` | `checksum:922ec940…171a`, `created_at:2026-09-10T11:47:16Z` |
975
+ | `/api/v1/versions/openehr-rails/latest.json` | `{"version":"0.7.0"}` |
976
+ | `/api/v1/versions/openehr-rails.json` | 0.7.0, `sha` `922ec940…171a` (no lag observed this time) |
977
+ | `gem list -r -e openehr-rails` | 0.7.0 |
978
+
979
+ CI artifact, tag rebuild and published gem are one and the same bytes. The
980
+ `pkg/` copy was deleted after the confirmation, leaving `pkg/` empty.
981
+
982
+ ### Tooling note (why two commits on the #43 branch fix single characters)
983
+
984
+ The PowerShell -> `wsl` wrapper used to run this session's scripts carried a
985
+ `sed -i "s/\r$//"`; the `\r` reached WSL as a plain `r`, so every script line
986
+ ending in "r" lost that letter. Effects found and handled: `git checkout master`
987
+ silently became `maste` (harmless, caught by the error), a backlog line on the
988
+ #43 branch was committed as "script o" (restored in `566cddb`), and the
989
+ `5fcc0ec` commit message reads "unde" for "under" (left as is). `master`'s
990
+ tracked files were grepped for the same pattern: clean. The wrapper no longer
991
+ runs sed.
992
+
993
+ ### State after 0.7.0
994
+
995
+ - `master` = tag `v0.7.0` + docs (R14, R15). `release:check` fails on `master`
996
+ by design until the next release.
997
+ - PR #43 (`fix/38-single-leaf-non-observation`, Fixes #38): rebased for 0.7.1,
998
+ CHANGELOG line under the new `[Unreleased]`, awaiting the human's merge.
999
+ - Open follow-ups: `release.yml` "Record sha256" step (backlog, CI-only PR);
1000
+ next tag annotated (`git tag -a`); `json < 3` pins until Rails ships the
1001
+ `JSON.decode` fix (#40, backlog); reserves #35, #36, #37.