canon 0.3.122 → 0.3.124

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: 5c8f274f503852b87469eb653b74b16cff4a24313a642ba665f9d00f4ddc5ca2
4
- data.tar.gz: 1ff259b65b606b03400e5c729ba2384643877a48fdca3e7ff12f6ba5adfae62d
3
+ metadata.gz: 8c444720f0582084ebebf230399b6a0e1e275d8543c4b4099c90fe46a42a7178
4
+ data.tar.gz: 76be8f3de7a5e02dbe6273c5e9ae03d9ecde088a727227ccf7af38f0f623143f
5
5
  SHA512:
6
- metadata.gz: 936d7dd4ca541dc44435294f26ac3f04886668ccfd5c919ef6cd50c033b75d633e33e4fcdbe9cdd809e9400f2c882af7baaf00d1316e6c832b46a87a8c0e6ec7
7
- data.tar.gz: ae0c2379e7aefd1445bd108aa6533c8570858418e02e35653339618d597bfedab7254af12955a5cd7c8717e29a93591bfd458f6b3920fc343cf8af0a8189b0f8
6
+ metadata.gz: 269545a7a9c1d496db323de5fb5a31a67739938e612c812a5ad332fdc28c3e119cd9d39cda0806d218c4f6ef34807bc63dbe879e1973c260c64353dc7efffdaf
7
+ data.tar.gz: 63ddcc8bb82d0d0548e26cd1ee2436e6d03a94d746c959fd3c36a2e21de7bc307f517113d86744942ed12d304592265f1ece8ffe4a87c7c96bf7748d4c996ec8
data/docs/HANDOFF.adoc CHANGED
@@ -5,18 +5,21 @@ published: false
5
5
 
6
6
  A snapshot of the open arcs across the org repos — what is known, where
7
7
  to dig, and what has already been proven — so any team can pick an issue
8
- up cold. Statuses as of 2026-11-01.
8
+ up cold. Statuses as of 2026-11-05.
9
9
 
10
10
  == State of the world
11
11
 
12
- Shipped since the last board (2026-10-28): leptris 1.9.398.0 (the
13
- #1866 adopt + parent-of-attribute fixes and the #1846/#1854/#1855 perf
14
- slices), yeptris 0.6.52.0/0.6.53.0 (marshal reader #322, multibyte
15
- breaks #782), moxml **0.6.0** (the Opal surface deleted by the owner's
16
- phase-4 call, lutaml/moxml#516 — the forward Opal path is the moxml-ts
17
- wasm binding, lutaml/moxml#489), lutaml-model 0.8.155 (admits both
18
- moxml lines: `>= 0.5.139, < 0.7`), canon 0.3.118 (floors `leptris ~>
19
- 1.9.398`, `yeptris ~> 0.6.52`, `moxml ~> 0.6.0`; Opal lane retired).
12
+ Shipped since the last board (2026-11-01): leptris 1.9.401→**1.9.408**
13
+ (the 1.9.407 plan ns_prefix deep-copy fix — the use-after-free behind
14
+ lutaml-model#1126's poisoning signatures; release PR leptris#1973),
15
+ yeptris **0.6.55**, moxml **0.6.2** (the engine XHTML display mode,
16
+ moxml#390 — canon's fixture-ready lane is now engine-served end to
17
+ end), lutaml-model **0.8.169** (the native emission lane fix series:
18
+ plan poisoning #1126, the render contract #1136, the valued
19
+ child_deferred gate #1130, xmlns retention #1131), canon 0.3.123
20
+ (floors `leptris ~> 1.9.401`, `moxml ~> 0.6.2`, `yeptris ~> 0.6.55`;
21
+ the subset-xpath engine grew parent/self/attribute steps, PR
22
+ canon#308).
20
23
 
21
24
  == lutaml/canon
22
25
 
@@ -24,11 +27,12 @@ moxml lines: `>= 0.5.139, < 0.7`), canon 0.3.118 (floors `leptris ~>
24
27
  |===
25
28
  |Item |Status |Known + first moves
26
29
 
27
- |Floors raised (0.3.118)
30
+ |Floors raised (0.3.123)
28
31
  |Shipped
29
- |`leptris ~> 1.9.398`, `yeptris ~> 0.6.52`, `moxml ~> 0.6.0` — all
30
- verified against exactly those engines (2744 examples, 0 failures, 25
31
- pending). Constraints live in the gemspec only; Gemfile/Gemfile.local
32
+ |`leptris ~> 1.9.401`, `yeptris ~> 0.6.55`, `moxml ~> 0.6.2` — all
33
+ verified against exactly those engines (2760 examples, 0 failures, 25
34
+ pending, verified 2026-11-05). Constraints live in the gemspec only;
35
+ Gemfile/Gemfile.local
32
36
  are untracked verification lanes.
33
37
 
34
38
  |Opal lane
@@ -39,11 +43,13 @@ The `RUBY_ENGINE == "opal"` branches in lib stay for the moxml-ts wasm
39
43
  migration (moxml#489).
40
44
 
41
45
  |Subset-xpath mini engine (`lib/canon/xml/xpath_engine.rb`)
42
- |Reported, owner decision pending
43
- |It silently returns `[]` for `..` and `@attr` step forms (XPath 1.0
44
- §2.2 names the owner element). No `lib/` caller uses those forms today
45
- (swept 2026-10-28); decide between implementing the steps and
46
- documenting/raising on unsupported forms.
46
+ |Shipped (canon#308, 0.3.123)
47
+ |The `..` and `@attr` step forms are implemented under a linear
48
+ context model: element steps filter one level down (block semantics),
49
+ parent/self/attribute steps operate on the matched nodes themselves,
50
+ and attribute nodes render per strict C14N law (only when the owner
51
+ element is in the node-set). The `//`-rooted and `./`-rooted relative
52
+ forms resolve from the document root, matching the subset context.
47
53
  |===
48
54
 
49
55
  == lutaml/moxml
@@ -76,6 +82,18 @@ plan-path-FFI perf slices
76
82
  |Open (umbrella)
77
83
  |Re-baseline against 1.9.398 with the interleaved A/B harness before
78
84
  opening new slices — the old ~3-4x numbers predate the closed slices.
85
+
86
+ |Plan descriptor memory safety + the 1.9.408 line
87
+ |Shipped (1.9.407/1.9.408); #1972 open
88
+ |1.9.407 fixes the `leptris_plan_build` use-after-free: attribute
89
+ rows' `ns_prefix` was the one caller-anchored string NOT deep-copied,
90
+ so the built descriptor read freed FFI anchor memory at serialize
91
+ time (the lutaml-model#1126 empty-output / garbage-prefix
92
+ signatures). There is no capability constant for a pure bugfix, so
93
+ consumers gate on the version floor. 1.9.408 is the next release line
94
+ (release PR #1973, merged 2026-11-05). Open on top: PR #1972
95
+ (xsd: namespace-aware particle typing — substitution members ride
96
+ attributeGroup refs).
79
97
  |===
80
98
 
81
99
  == leptris/yeptris
@@ -108,6 +126,16 @@ platforms)
108
126
  |Open, partially improved
109
127
  |Remaining: scalar-route derivation cost. `benchmarks/PERF-LEDGER.md`
110
128
  has the trusted-into entries.
129
+
130
+ |yeptris#951 — plain fold emits raw lead-stripped 0x85 at a
131
+ ¥-preceded NEL run + the PS-as-break law
132
+ |CLOSED (2026-11-05)
133
+ |Diff 1 (the raw continuation bytes in a k=2 NEL fold) fixed by the
134
+ engine team; Diff 2 settled as a comparator-claimed documented
135
+ divergence — libyaml's `READ_LINE` breaks AT LS/PS but spells them as
136
+ their raw 3 bytes (the fold law never applies since the join test
137
+ expects `\n`), while yeptris folds the run to one `\n`. The suite is
138
+ silent either way.
111
139
  |===
112
140
 
113
141
  == leptris/yeptris-ruby
@@ -130,17 +158,39 @@ If the CBOR face reopens, copy the registry law and its pin wholesale.
130
158
  |Item |Status |Known + first moves
131
159
 
132
160
  |moxml 0.6 admission
133
- |Shipped (0.8.155)
161
+ |Shipped (0.8.156; line now at 0.8.169)
134
162
  |`>= 0.5.139, < 0.7`; the Opal boot lane pins moxml 0.5.x locally in
135
163
  `opal.yml` (Gemfile-append + plain `bundle update moxml` — bundler 4
136
164
  removed `update --version`) until the moxml-ts wasm migration.
137
165
 
166
+ |The native emission lane fix series (the uniword corpus, 2026-11-05)
167
+ |Shipped (0.8.165-0.8.169)
168
+ |Four arcs, all closed: **#1126** (plans poison every later instance
169
+ of a class) root-caused to an ENGINE use-after-free —
170
+ `leptris_plan_build` kept a caller-anchored attribute `ns_prefix`
171
+ pointer into freed FFI anchor memory; fixed leptris 1.9.407.
172
+ **#1136** (render_default: false ignored — ghost `w:default="0"`
173
+ attributes) fixed by applying the DOM lane's `rule.render?` contract
174
+ at all three native emission sites (6fd1c0fc, 0.8.168). **#1130**
175
+ (nested same-element recursion drops data — the bibliography's
176
+ Author → author collapse to `<b:Author/>`) fixed by the compatibility
177
+ gate refusing valued `:child_deferred` rows, which ride the DOM lane
178
+ (PR #1135, 01ab5a3a, 0.8.169). **#1140** (dotx glossary TOC document
179
+ displaces body elements across sdtContent) is the #1130 shape —
180
+ bisect-proven fixed by the same gate; the OTHER five skip-listed dotx
181
+ glossary shapes fail identically with the native lane OFF (DOM lane,
182
+ engine without PLAN_OP_RAW), so they are uniword's pre-existing
183
+ TODO.refactor/14 drift, not this lane. Also shipped: #1131 (xmlns
184
+ declarations retained on parsed models, dbd7eb36).
185
+
138
186
  |update-baselines red on every main push
139
- |PR lutaml-model#1076
140
- |checkout's persisted Authorization extraheader collides with
187
+ |Shipped (0be7a7fc + a784f452)
188
+ |checkout's persisted Authorization extraheader collided with
141
189
  create-pull-request's token ("Duplicate header: Authorization", HTTP
142
- 400) on the action's `git remote prune`. Fix: `persist-credentials:
143
- false` on that job's checkout (the documented pairing).
190
+ 400) on the action's `git remote prune`. Fixed with
191
+ `persist-credentials: false` on that job's checkout, plus the
192
+ `contents: write` + `pull-requests: write` grants (the git push and
193
+ the API PR creation are separate permissions).
144
194
 
145
195
  |metanorma/docbook dependent leg (ubuntu/macos)
146
196
  |PR metanorma/docbook#15
@@ -194,10 +244,11 @@ the remaining step is the vendor commit into pubid's `data/parg/`
194
244
  (proven mechanical: baking origin/main at 1.3.121 yields `edition 2`
195
245
  headers with bindings intact — iso 84, bsi 92, ashrae 37). The
196
246
  owner's `worktrees/grammar-rebake` is on it. standoc's severity
197
- question (#1318/#1324) is re-pinned in metanorma-standoc#1333:
247
+ question (#1318/#1324/#1325) is RESOLVED — re-pinned on standoc main
248
+ directly by the owner (the dup PR #1333 was closed as superseded):
198
249
  relaton 3.0.2 degrades `ISO 0a` to RELATON_4 at severity 3, which a
199
- severity-2 filter legitimately drops; that PR's CI goes green the
200
- moment the vendor lands.
250
+ severity-2 filter legitimately drops, and the suppression semantics
251
+ are at-or-above N.
201
252
  |===
202
253
 
203
254
  == Mechanics every team hits
@@ -36,14 +36,10 @@ module Canon
36
36
  end
37
37
 
38
38
  def format(html_string)
39
- if @fixture_ready
40
- # The fixture-ready display mode is the one Nokogiri lane
41
- # left (libxml FORMAT|AS_XHTML save flags — moxml#390 filed
42
- # for an engine XHTML mode); everything else is engine-
43
- # served.
44
- Canon::NokogiriLoader.require!("HTML fixture-ready display")
45
- return format_fixture_ready(html_string)
46
- end
39
+ # The fixture-ready display lane is engine-served end to end
40
+ # (moxml 0.6.2's XHTML serialization mode — moxml#390): no
41
+ # Nokogiri requirement remains in this mode.
42
+ return format_fixture_ready(html_string) if @fixture_ready
47
43
 
48
44
  if xhtml?(html_string)
49
45
  format_as_xhtml(html_string)
@@ -91,14 +87,8 @@ module Canon
91
87
  end
92
88
  end
93
89
 
94
- # Fixture-ready serialisation: parse with Nokogiri::HTML5 (so we
95
- # get permissive recovery on real-world Word / XHTML5 / HTML5
96
- # input shapes), then write through libxml's XML writer with
97
- # +FORMAT+ + +AS_XHTML+ + +NO_DECLARATION+. +FORMAT+ inserts
98
- # indentation; +AS_XHTML+ produces well-shaped output (void
99
- # elements self-closed, non-void paired); +NO_DECLARATION+
100
- # suppresses the +<?xml ...?>+ prefix.
101
- # Engine parse + significance-aware strip + the engine's
90
+ # Fixture-ready serialisation, engine-served end to end:
91
+ # engine HTML parse + significance-aware strip + moxml 0.6.2's
102
92
  # XHTML serialization mode (moxml#390 delivered to_xhtml /
103
93
  # to_html(xhtml: true)): void elements self-closed, non-void
104
94
  # paired — the AS_XHTML contract, engine-served.
data/lib/canon/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Canon
4
- VERSION = "0.3.122"
4
+ VERSION = "0.3.124"
5
5
  end
@@ -14,9 +14,14 @@ module Canon
14
14
  # - Predicates: [1] (position), [@attr], [@attr='value']
15
15
  # - Wildcards: *
16
16
  # - Union: expr1 | expr2
17
+ # - Self and parent steps: ., ..
18
+ # - Attribute steps: @attr (selects the attribute node; per C14N
19
+ # law it renders only when its owner element is in the node-set,
20
+ # so @attr/.. is the productive form — XPath 1.0 section 2.2
21
+ # names the owner element)
17
22
  #
18
23
  # Not supported (not needed for C14N subset):
19
- # - Axes other than child and descendant-or-self
24
+ # - Axes other than child, parent, self, descendant-or-self
20
25
  # - Functions (last(), position(), etc.)
21
26
  # - Variables
22
27
  #
@@ -56,6 +61,10 @@ module Canon
56
61
  elsif xpath.start_with?("/")
57
62
  # Absolute path
58
63
  evaluate_absolute(xpath[1..])
64
+ elsif xpath.start_with?("./")
65
+ # Document-rooted self step: the subset context is the
66
+ # document, so ./rest resolves from the root like /rest
67
+ evaluate_absolute(xpath[2..])
59
68
  else
60
69
  # Relative path — treat as descendant
61
70
  evaluate_descendant(xpath)
@@ -68,35 +77,47 @@ module Canon
68
77
  steps = parse_steps(path)
69
78
  return [] if steps.empty?
70
79
 
71
- # Start from root's children
72
- current_nodes = @root.children
73
- apply_steps(current_nodes, steps)
80
+ # Start from root's children; the first element step filters
81
+ # this entry set directly
82
+ apply_steps(@root.children, steps, descend_first: false)
74
83
  end
75
84
 
76
85
  def evaluate_descendant(path)
77
86
  steps = parse_steps(path)
78
87
  return [] if steps.empty?
79
88
 
89
+ # ./ is transparent on the relative law: the subset context is
90
+ # the document, so ./rest searches descendants like rest
91
+ steps.shift while steps.first&.dig(:type) == :self
92
+ return [] if steps.empty?
93
+
80
94
  # Collect all descendant element nodes
81
95
  all_elements = []
82
96
  collect_elements(@root, all_elements)
83
97
 
84
- # For each element, try to match the full path starting there
85
- result = []
86
- all_elements.each do |element|
87
- first_step = steps.first
88
- next unless step_matches?(element, first_step)
98
+ first_step = steps.first
99
+ remaining = steps[1..]
89
100
 
90
- if steps.length == 1
91
- result << element
101
+ context =
102
+ case first_step[:type]
103
+ when :attribute
104
+ # //@attr — every matching attribute in the tree
105
+ all_elements.flat_map do |element|
106
+ attribute_matches(element, first_step)
107
+ end
108
+ when :parent
109
+ return [] # no meaningful parents above descendants
92
110
  else
93
- remaining = steps[1..]
94
- matched = apply_steps(element.children, remaining)
95
- result.concat(matched)
111
+ all_elements.select do |element|
112
+ step_matches?(element, first_step)
113
+ end
96
114
  end
97
- end
115
+ return context.uniq if remaining.empty?
98
116
 
99
- result.uniq
117
+ # The remaining steps run from the matches: element steps
118
+ # descend (block semantics), axis steps apply to the nodes
119
+ # themselves
120
+ apply_steps(context, remaining, descend_first: true).uniq
100
121
  end
101
122
 
102
123
  def collect_elements(node, result)
@@ -108,20 +129,54 @@ module Canon
108
129
  end
109
130
  end
110
131
 
111
- def apply_steps(nodes, steps)
112
- return nodes if steps.empty?
132
+ # Linear context model: each step transforms the current node
133
+ # set. Element steps filter one level down from the context
134
+ # (block semantics — the first step filters the entry set
135
+ # itself); parent/self/attribute steps are context steps and
136
+ # operate on the nodes themselves.
137
+ def apply_steps(nodes, steps, descend_first:)
138
+ steps.reduce(nodes) do |current, step|
139
+ break [] if current.empty?
140
+
141
+ if element_step?(step)
142
+ level = descend_first ? descend(current) : current
143
+ current = level.select { |n| step_matches?(n, step) }.uniq
144
+ else
145
+ current = axis_apply(current, step)
146
+ end
147
+ descend_first = true
148
+ current
149
+ end
150
+ end
113
151
 
114
- step = steps.first
115
- remaining = steps[1..]
152
+ def element_step?(step)
153
+ step[:type] == :element
154
+ end
116
155
 
117
- matched = nodes.select { |n| step_matches?(n, step) }
156
+ def descend(nodes)
157
+ nodes.flat_map do |n|
158
+ next [] if n.is_a?(Nodes::AttributeNode)
118
159
 
119
- if remaining.empty?
120
- matched
121
- else
122
- matched.flat_map do |node|
123
- apply_steps(node.children, remaining)
124
- end
160
+ n.children
161
+ end
162
+ end
163
+
164
+ # Context steps: navigate from the nodes themselves.
165
+ def axis_apply(nodes, step)
166
+ case step[:type]
167
+ when :parent
168
+ nodes.filter_map(&:parent).uniq
169
+ when :self
170
+ nodes
171
+ when :attribute
172
+ nodes.grep(Nodes::ElementNode)
173
+ .flat_map { |el| attribute_matches(el, step) }.uniq
174
+ end
175
+ end
176
+
177
+ def attribute_matches(element, step)
178
+ element.attribute_nodes.select do |a|
179
+ attr_name_matches?(a, step[:name])
125
180
  end
126
181
  end
127
182
 
@@ -132,6 +187,17 @@ module Canon
132
187
  predicates_match?(node, step[:predicates])
133
188
  end
134
189
 
190
+ def attr_name_matches?(attr, name)
191
+ return true if name == "*"
192
+
193
+ if name.include?(":")
194
+ prefix, local = name.split(":", 2)
195
+ attr.prefix == prefix && attr.name == local
196
+ else
197
+ attr.name == name
198
+ end
199
+ end
200
+
135
201
  def name_matches?(node, name)
136
202
  return true if name == "*"
137
203
 
@@ -179,7 +245,7 @@ module Canon
179
245
  # Parse a path string into an array of steps.
180
246
  #
181
247
  # @param path [String] XPath path (without leading /)
182
- # @return [Array<Hash>] Array of { name:, predicates: }
248
+ # @return [Array<Hash>] Array of { type:, name:, predicates: }
183
249
  def parse_steps(path)
184
250
  steps = []
185
251
  scanner = StringScanner.new(path)
@@ -189,14 +255,27 @@ module Canon
189
255
  break if scanner.eos?
190
256
 
191
257
  # Skip /
192
- scanner.scan(%r{/})
258
+ scanner.skip(%r{/})
259
+ scanner.skip(/\s+/)
260
+
261
+ if scanner.scan("..")
262
+ steps << { type: :parent, name: nil, predicates: [] }
263
+ next
264
+ elsif scanner.scan(".")
265
+ steps << { type: :self, name: nil, predicates: [] }
266
+ next
267
+ elsif scanner.scan(/@/)
268
+ name = scanner.scan(/[a-zA-Z_][\w.-]*/)
269
+ steps << { type: :attribute, name: name, predicates: [] }
270
+ next
271
+ end
193
272
 
194
273
  name = scan_name(scanner)
195
274
  break if name.nil?
196
275
 
197
276
  predicates = scan_predicates(scanner)
198
277
 
199
- steps << { name: name, predicates: predicates }
278
+ steps << { type: :element, name: name, predicates: predicates }
200
279
  end
201
280
 
202
281
  steps
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: canon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.122
4
+ version: 0.3.124
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-11-03 00:00:00.000000000 Z
11
+ date: 2026-11-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: diff-lcs
@@ -44,28 +44,28 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 1.9.401
47
+ version: 1.9.412
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 1.9.401
54
+ version: 1.9.412
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: moxml
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 0.6.0
61
+ version: 0.6.4
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 0.6.0
68
+ version: 0.6.4
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: paint
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -142,14 +142,14 @@ dependencies:
142
142
  requirements:
143
143
  - - "~>"
144
144
  - !ruby/object:Gem::Version
145
- version: 0.6.55
145
+ version: 0.6.60
146
146
  type: :runtime
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
150
  - - "~>"
151
151
  - !ruby/object:Gem::Version
152
- version: 0.6.55
152
+ version: 0.6.60
153
153
  description: |-
154
154
  Canon provides canonicalization and pretty-printing for various serialization
155
155
  formats (XML, HTML, JSON, YAML), producing standardized forms suitable for