asciichem 0.31.16 → 1.0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b985064f84599e157131e30ca2ae73555383b6e273c6ea3ae561f02d52df5da
4
- data.tar.gz: bb08895e055a18b16fe280b30da829666bf9f646d269f9a3b5312b61fbf86578
3
+ metadata.gz: 1bd4b9584ac1e0126a98efc020036d2532a9567f8f1cfb0107e66b9c15680135
4
+ data.tar.gz: 6c284fa95ed81aa18328046d02863f709113cb27aaaa2a9fe29a81fbe8cf6586
5
5
  SHA512:
6
- metadata.gz: 12604d14213ae4bc6beba2507b6fcd27e540b32d3be966cbdc56816c844f73309e5b6518ea632441e17b7610d7321fcaf25bc2be6ba46659d2165907039c5375
7
- data.tar.gz: 2676f58b2d881b8a9f321bebcaad712e165f2cc4520f419bdf7b41dab28e4330b72f278456265bd62e3c0101d953501d396435fec84c5281d1379de34637a938
6
+ metadata.gz: e047c4735a8ddf3a5f8c9afb36384a3b7b7f4e65be6a855bfa6b0a24bed4f5e19197f21ca30e37064809f4655d0fed5345bcf2166c087c643f10de48f5f552e3
7
+ data.tar.gz: db2212af6d815e7caa68facc95b636fb43f6ba7f50659d32fbc09050d7caefda4852b2664d66a364c646b8468d336a106b0d76d46357d9cc887d72d9044e66b8
@@ -9,7 +9,7 @@ env:
9
9
  # Hermetic CI: the corpus ref is pinned, never floating. Bump this
10
10
  # one line deliberately with each corpus release (the tag carries
11
11
  # what changed).
12
- CORPUS_REF: v0.8.0
12
+ CORPUS_REF: v0.11.0
13
13
 
14
14
  jobs:
15
15
  test:
data/CHANGELOG.md CHANGED
@@ -3,6 +3,33 @@
3
3
  All notable changes to AsciiChem are documented here.
4
4
  This project follows [Semantic Versioning](https://semver.org/).
5
5
 
6
+ ## [1.0.0.0] - 2026-11-13
7
+
8
+ ### Changed
9
+ - **The gem renumbers onto the 1.0.0 engine.** The version scheme is
10
+ `{engine-semver}.{runtime-increment}`: the asciichem-rs engine is
11
+ 1.0.0 and the gem's increment resets at the new engine major
12
+ (maintainer's call). The 0.31.x line was the pre-Rust gem line;
13
+ every future gem release derives from the engine semver. Was
14
+ 0.31.17.
15
+
16
+ ## [0.31.17] - 2026-11-13
17
+
18
+ ### Added
19
+ - The resolver clock seam: `resolve_all` and every adapter accept a
20
+ `now:` ISO-8601 UTC timestamp (nil = the wall clock) that pins
21
+ `retrievedAt` — recorded-fixture runs and the citation goldens that
22
+ consume them compare byte-stably. Mirrors the asciichem-py seam of
23
+ the same name.
24
+
25
+ ### Changed
26
+ - The conformance runner gates the corpus 0.11.0 parity families
27
+ (linter-expectations with engine posture + node matching, resolver
28
+ replay, citation byte-goldens, the 22 CLI contracts); the CI
29
+ corpus pin moves v0.8.0 → v0.11.0. Claims matrix: linter 27 run +
30
+ 3 posture skips, resolver 6/6, citation 4/4 byte-equal, CLI 22/22
31
+ (asciichem-ruby#159).
32
+
6
33
  ## [0.31.16] - 2026-11-12
7
34
 
8
35
  ### Changed
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # PARG gate + benchmark: AsciiChem::Engine.use(:parg) parses through
4
- # the compiled PARG artifact vendored from asciichem-parg, adapted to
4
+ # the compiled PARG artifact vendored from asciichem-grammar, adapted to
5
5
  # the shared TransformRules. (1) gates on the shared corpus, forked
6
6
  # per case so a native crash aborts the child — reported, not fatal;
7
7
  # (2) gates on capture completeness (a grammar edit leaving a
@@ -15,7 +15,7 @@ end
15
15
  module AsciiChem
16
16
  module Engine
17
17
  # The PARG engine: parses through the compiled AsciiChem PARG
18
- # artifact (grammar/asciichem.parg in asciichem-parg, vendored at
18
+ # artifact (grammar/asciichem.parg in asciichem-grammar, vendored at
19
19
  # lib/asciichem/engine/parg/asciichem.json) and adapts its parse
20
20
  # tree to the DSL shapes TransformRules consumes. The shared
21
21
  # conformance corpus passes model-identically (227/227) against
@@ -171,7 +171,7 @@ module AsciiChem
171
171
  raise NotAvailableError,
172
172
  "vendored PARG artifact version #{version.inspect} does not match " \
173
173
  "the engine's pinned grammar #{PARG_GRAMMAR_VERSION.inspect} — " \
174
- 're-vendor from asciichem-parg'
174
+ 're-vendor from asciichem-grammar'
175
175
  end
176
176
  end
177
177
  end
@@ -5,7 +5,7 @@ module AsciiChem
5
5
  # slice 9; the maintainer's PARG-only directive). The legacy
6
6
  # parslet and DSL-parsanol engines are deleted — the checksummed
7
7
  # AsciiChem PARG artifact (vendored at engine/parg/asciichem.json,
8
- # sourced from asciichem-parg) is the one grammar, and
8
+ # sourced from asciichem-grammar) is the one grammar, and
9
9
  # PargEngine adapts its tree to the DSL shapes TransformRules
10
10
  # consumes.
11
11
  module Engine
@@ -41,7 +41,11 @@ module AsciiChem
41
41
 
42
42
  # Cache-aware resolution. `fetch` must answer #get(url) -> body
43
43
  # String (NetFetch by default; specs inject recorded responses).
44
- def resolve(value:, convention:, fetch: nil, cache: nil, refresh: false)
44
+ # `now` pins the retrieval clock (an ISO-8601 UTC string) for
45
+ # recorded-fixture runs — the shared corpus pins `retrievedAt`
46
+ # so replayed records compare byte-stably; nil means the wall
47
+ # clock.
48
+ def resolve(value:, convention:, fetch: nil, cache: nil, refresh: false, now: nil)
45
49
  cache ||= Cache.default
46
50
  fetch ||= NetFetch.new
47
51
  key = cache.key_for(self.class.source_name, convention, value)
@@ -51,22 +55,22 @@ module AsciiChem
51
55
  return cached if cached
52
56
  end
53
57
 
54
- substance = fetch_record(value: value, convention: convention, fetch: fetch)
55
- .tap { |s| s&.identifiers&.each { |i| i.provenance ||= default_provenance } }
58
+ substance = fetch_record(value: value, convention: convention, fetch: fetch, now: now)
59
+ .tap { |s| s&.identifiers&.each { |i| i.provenance ||= default_provenance(now) } }
56
60
  cache.write(key, substance) if substance
57
61
  substance
58
62
  end
59
63
 
60
64
  # Not-found is nil, not an error — sources legitimately answer
61
65
  # "unknown substance" during multi-source resolution.
62
- def fetch_record(**)
66
+ def fetch_record(value:, convention:, fetch:, now: nil)
63
67
  raise NotImplementedError
64
68
  end
65
69
 
66
- def default_provenance
70
+ def default_provenance(now: nil)
67
71
  Provenance.new(
68
72
  source: self.class.source_name,
69
- retrieved_at: Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ"),
73
+ retrieved_at: now || Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ"),
70
74
  attribution: attribution
71
75
  )
72
76
  end
@@ -43,7 +43,7 @@ module AsciiChem
43
43
  @api_key = api_key
44
44
  end
45
45
 
46
- def fetch_record(value:, convention:, fetch:)
46
+ def fetch_record(value:, convention:, fetch:, now: nil)
47
47
  headers = {
48
48
  "Accept" => "application/json",
49
49
  "x-origin" => ORIGIN
@@ -55,7 +55,7 @@ module AsciiChem
55
55
  data = JSON.parse(body)
56
56
  return nil if data.empty?
57
57
 
58
- substance_from(data)
58
+ substance_from(data, now)
59
59
  rescue JSON::ParserError => e
60
60
  raise Error, "common_chemistry returned malformed JSON: #{e.message}"
61
61
  end
@@ -71,8 +71,8 @@ module AsciiChem
71
71
  (File.read(KEY_FILE).strip if File.readable?(KEY_FILE))
72
72
  end
73
73
 
74
- def substance_from(data)
75
- provenance = default_provenance
74
+ def substance_from(data, now)
75
+ provenance = default_provenance(now: now)
76
76
  identifiers = [Identifier.new(value: data["rn"], convention: "cas",
77
77
  provenance: provenance)]
78
78
  add_identifier(identifiers, data, "inchikey", "inchikey", provenance)
@@ -27,11 +27,11 @@ module AsciiChem
27
27
  BASE = 'https://opsin.ch.cam.ac.uk/opsin'
28
28
  private_constant :BASE
29
29
 
30
- def fetch_record(value:, convention: nil, fetch:)
30
+ def fetch_record(value:, convention: nil, fetch:, now: nil)
31
31
  body = fetch.get("#{BASE}/#{CGI.escape(value.to_s)}.smiles")
32
32
  return nil unless body
33
33
 
34
- substance_from(body.strip)
34
+ substance_from(body.strip, now)
35
35
  rescue StandardError => e
36
36
  raise Error, "opsin request failed: #{e.message}"
37
37
  end
@@ -42,10 +42,10 @@ module AsciiChem
42
42
 
43
43
  private
44
44
 
45
- def substance_from(smiles)
45
+ def substance_from(smiles, now)
46
46
  return nil if smiles.empty?
47
47
 
48
- provenance = default_provenance
48
+ provenance = default_provenance(now: now)
49
49
  Substance.new(
50
50
  preferred_name: nil,
51
51
  identifiers: [Identifier.new(value: smiles, convention: 'canonical-smiles',
@@ -26,14 +26,14 @@ module AsciiChem
26
26
  MolecularWeight IUPACName InChI InChIKey].freeze
27
27
  private_constant :BASE, :PROPERTIES
28
28
 
29
- def fetch_record(value:, convention:, fetch:)
29
+ def fetch_record(value:, convention:, fetch:, now: nil)
30
30
  body = fetch.get(url_for(value, convention))
31
31
  return nil unless body
32
32
 
33
33
  props = JSON.parse(body).dig("PropertyTable", "Properties", 0)
34
34
  return nil unless props
35
35
 
36
- substance_from(props)
36
+ substance_from(props, now)
37
37
  rescue JSON::ParserError => e
38
38
  raise Error, "pubchem returned malformed JSON: #{e.message}"
39
39
  end
@@ -53,8 +53,8 @@ module AsciiChem
53
53
  "#{BASE}/#{namespace}/#{CGI.escape(value.to_s)}/property/#{PROPERTIES.join(',')}/JSON"
54
54
  end
55
55
 
56
- def substance_from(props)
57
- provenance = default_provenance
56
+ def substance_from(props, now)
57
+ provenance = default_provenance(now: now)
58
58
  identifiers = []
59
59
  identifiers << Identifier.new(value: props["CID"].to_s, convention: "pubchem-cid", provenance: provenance) if props["CID"]
60
60
  add_identifier(identifiers, props, "InChIKey", "inchikey", provenance)
@@ -48,15 +48,16 @@ module AsciiChem
48
48
  # entries dropped). Sources answer in registration order. A
49
49
  # source-level failure (HTTP error, rejected request) skips that
50
50
  # source — one dead or key-gated endpoint must not poison the
51
- # others; callers see what answered.
52
- def resolve_all(value:, convention:, sources: nil, fetch: nil, cache: nil, refresh: false)
51
+ # others; callers see what answered. `now` pins the retrieval
52
+ # clock for recorded-fixture runs (the adapters' seam).
53
+ def resolve_all(value:, convention:, sources: nil, fetch: nil, cache: nil, refresh: false, now: nil)
53
54
  selected = sources ? Array(sources).map { |s| self[s] } : adapters.values
54
55
  selected.filter_map do |adapter_class|
55
56
  adapter = adapter_class.new
56
57
  next unless adapter.supports?(convention)
57
58
 
58
59
  begin
59
- adapter.resolve(value: value, convention: convention, fetch: fetch, cache: cache, refresh: refresh)
60
+ adapter.resolve(value: value, convention: convention, fetch: fetch, cache: cache, refresh: refresh, now: now)
60
61
  rescue AsciiChem::Error
61
62
  nil
62
63
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AsciiChem
4
- VERSION = "0.31.16"
4
+ VERSION = "1.0.0.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: asciichem
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.31.16
4
+ version: 1.0.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.