canon 0.3.116 → 0.3.117
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/docs/_references/canonical-digests.adoc +50 -0
- data/docs/_references/jcs-rfc8785.adoc +52 -0
- data/lib/canon/version.rb +1 -1
- data/lib/canon/xml/exclusive_c14n.rb +24 -6
- data/lib/tasks/performance_comparator.rb +19 -2
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 223b5ab0201bbebfe8bfee1de2f23229260f574bd6d42fed426256b199f03d6c
|
|
4
|
+
data.tar.gz: 803eaba7a9fd850a38669c9ea2bbd567503fbc936fd39147a473d2526b3539ce
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 24ae7e292b598242df4c98bad7d98d98815d7d28ddebd6d1ed79df4451e40017cd3840f2a3556bf4b78681ee7b6653ee91a67623d806d2aec1d8e170093a19d8
|
|
7
|
+
data.tar.gz: 35a5642e0e6b5b0268935d6888470614058888a3e4cc33d587ea079737a3817b9543aa63fbae986da95240a2c4c72174419eae1fba9336e871859ae6aae6ee3d
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
= The canonical digests (W5.3)
|
|
2
|
+
|
|
3
|
+
This document specifies canon's canonical digest API as shipped by
|
|
4
|
+
`Canon::Digest`. Reviewed against the implementation by
|
|
5
|
+
`spec/canon/docs/canonical_digests_spec.rb` (the W6.3 review-gate
|
|
6
|
+
pattern), with the cross-release byte pins carried by
|
|
7
|
+
`spec/canon/digest_stability_spec.rb`.
|
|
8
|
+
|
|
9
|
+
== 1. The law
|
|
10
|
+
|
|
11
|
+
The digest of a document is the OpenSSL hash of its CANONICAL
|
|
12
|
+
FORM — the form under which the format's comparison pipeline deems
|
|
13
|
+
documents equivalent. Two documents that compare equivalent produce
|
|
14
|
+
identical digests; two that compare distinct produce distinct
|
|
15
|
+
digests. The digest is an API over the comparison semantics, not a
|
|
16
|
+
new canonicalization.
|
|
17
|
+
|
|
18
|
+
== 2. Per-format canonical forms
|
|
19
|
+
|
|
20
|
+
* *XML* rides Canonical XML 1.1
|
|
21
|
+
(`Canon::Xml::C14n.canonicalize`, reached as
|
|
22
|
+
`Formatters::XmlFormatter.format(pretty: false)`). The
|
|
23
|
+
pretty-printed XML form preserves attribute order and is NOT
|
|
24
|
+
canonical; the digest lane never touches it.
|
|
25
|
+
* *JSON and YAML* ride the key-sorted formatter forms (the JCS
|
|
26
|
+
profile for JSON's canonical lane; the `[class, to_s]` TOTAL
|
|
27
|
+
key order for YAML).
|
|
28
|
+
* *HTML* rides the whitespace-law form with sorted attributes
|
|
29
|
+
(the W1.3 specification).
|
|
30
|
+
|
|
31
|
+
Equivalence consequences the digest inherits: attribute order is
|
|
32
|
+
insignificant in XML and HTML; mapping key order is insignificant
|
|
33
|
+
in JSON and YAML; element/sequence order is significant everywhere.
|
|
34
|
+
|
|
35
|
+
== 3. Entry points
|
|
36
|
+
|
|
37
|
+
`Canon::Digest.digest(content, format, algorithm: "sha256")`
|
|
38
|
+
accepts any OpenSSL digest name; `canonical_form(content, format)`
|
|
39
|
+
exposes the hashed bytes. Per-format shorthands
|
|
40
|
+
(`Canon.digest_xml`, `digest_yaml`, `digest_json`, ...) mirror the
|
|
41
|
+
format lane's shorthands.
|
|
42
|
+
|
|
43
|
+
== 4. Stability contract
|
|
44
|
+
|
|
45
|
+
The digests are stable across releases for as long as the
|
|
46
|
+
underlying canonical form is. The stability suite pins the digest
|
|
47
|
+
hex of a representative document per format; a canonical-form
|
|
48
|
+
change moves a digest and MUST fail that suite, forcing a
|
|
49
|
+
conscious pin update (and a release note) rather than silent drift
|
|
50
|
+
for downstream dedup stores.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
= The JCS canonical profile (W6.1, RFC 8785)
|
|
2
|
+
|
|
3
|
+
This document specifies canon's JSON JCS canonical form as shipped
|
|
4
|
+
by `Canon::Json::JCS`. Reviewed against the implementation by
|
|
5
|
+
`spec/canon/docs/jcs_spec_review.rb` (the W6.3 review-gate
|
|
6
|
+
pattern).
|
|
7
|
+
|
|
8
|
+
== 1. Scope
|
|
9
|
+
|
|
10
|
+
RFC 8785 — the I-JSON Canonicalization Scheme. Input must be I-JSON
|
|
11
|
+
(RFC 7493): valid UTF-8 without lone surrogates, no duplicate
|
|
12
|
+
object keys, numbers expressible as IEEE 754 doubles. Violations
|
|
13
|
+
raise `Canon::Json::JCS::Error`.
|
|
14
|
+
|
|
15
|
+
== 2. The canonical laws
|
|
16
|
+
|
|
17
|
+
* *Strings* (`escape_string`): RFC 8785 §3.2.2.2 — the
|
|
18
|
+
JSON.stringify escaping set: short forms for `\b \t \n \f \r`
|
|
19
|
+
(the SHORT_ESCAPES table), two-hex lowercase `\u00xx` for the
|
|
20
|
+
remaining C0 controls, `\"` and `\\`; every other code point
|
|
21
|
+
emits verbatim as UTF-8 (DEL U+007F is NOT escaped).
|
|
22
|
+
|
|
23
|
+
* *Numbers* (`es6_number`): ECMAScript `Number::toString`
|
|
24
|
+
serialization — the shortest round-tripping decimal, fixed
|
|
25
|
+
notation inside [1e-6, 1e21), exponential outside; integers
|
|
26
|
+
via `ijson_integer` (the I-JSON 2^53 range).
|
|
27
|
+
|
|
28
|
+
* *Object keys* (`write_object`): sorted by their
|
|
29
|
+
UTF-16 code units (`utf16_code_units` — the RFC's sort is over
|
|
30
|
+
UTF-16 code units, not code points, so surrogate pairs and BMP
|
|
31
|
+
characters interleave per the RFC's examples; e.g. the key
|
|
32
|
+
U+1D160 (a surrogate pair, code units 0xD834 0xDD60) sorts
|
|
33
|
+
BEFORE U+20AC (a single BMP code unit 0x20AC), which sorts
|
|
34
|
+
BEFORE "a" (0x0061)).
|
|
35
|
+
|
|
36
|
+
* *Containers*: arrays comma-separated in order; objects
|
|
37
|
+
comma-separated in sorted key order; no whitespace anywhere.
|
|
38
|
+
|
|
39
|
+
== 3. Entry points
|
|
40
|
+
|
|
41
|
+
`canonicalize(json)` accepts a JSON string (parsed through
|
|
42
|
+
`JSON.parse`) or an already-parsed Ruby graph; `serialize(value)`
|
|
43
|
+
skips the parse. The canonical form is byte-stable across
|
|
44
|
+
platforms and suitable for hashing and signing.
|
|
45
|
+
|
|
46
|
+
== 4. Fixed point and parity
|
|
47
|
+
|
|
48
|
+
JCS output re-parses to the same graph, and the form is a fixed
|
|
49
|
+
point. The phase 02 property suite (JSON/YAML equivalence legs)
|
|
50
|
+
and the diff-display corpus pin the behavior; the JCS spec
|
|
51
|
+
vectors (RFC 8785 §3.2.3 examples) pin the number and sorting
|
|
52
|
+
laws.
|
data/lib/canon/version.rb
CHANGED
|
@@ -55,12 +55,30 @@ module Canon
|
|
|
55
55
|
# — every node in the subtrees rooted at pfx:name. Returns
|
|
56
56
|
# [prefix, local_name], or nil for anything else.
|
|
57
57
|
def self.c14n_selection_idiom(xpath)
|
|
58
|
-
#
|
|
59
|
-
# expression (with prefix declarations and inline comments)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
# The suite's .xpath files are XML documents wrapping the
|
|
59
|
+
# expression (with prefix declarations and inline comments).
|
|
60
|
+
# Extraction is index-based string surgery — the CodeQL
|
|
61
|
+
# polynomial-redos query flags the gsub-chain form.
|
|
62
|
+
close_tag = xpath.rindex("</XPath>")
|
|
63
|
+
text = close_tag ? xpath[0...close_tag] : xpath.dup
|
|
64
|
+
# drop the XML declaration and the <XPath> open tag: each
|
|
65
|
+
# ends at the first '>' at/after its '<'
|
|
66
|
+
if (decl = text.index("<"))
|
|
67
|
+
gt = text.index(">", decl)
|
|
68
|
+
if (xopen = text.index("<XPath", decl)) && (xopen < gt)
|
|
69
|
+
gt = text.index(">", xopen)
|
|
70
|
+
end
|
|
71
|
+
text = text[(gt + 1)..] if gt
|
|
72
|
+
end
|
|
73
|
+
# strip <!-- --> comments by index (no regex backtracking)
|
|
74
|
+
loop do
|
|
75
|
+
open_c = text.index("<!--")
|
|
76
|
+
break unless open_c
|
|
77
|
+
|
|
78
|
+
close_c = text.index("-->", open_c + 4)
|
|
79
|
+
text = close_c ? text[0...open_c] + text[(close_c + 3)..] : text[0...open_c]
|
|
80
|
+
end
|
|
81
|
+
text = text.squeeze(" ")
|
|
64
82
|
m = text.match(%r{\(//\.\s*\|\s*//@\*\s*\|\s*//namespace::\*\)\s*\[\s*ancestor-or-self::([A-Za-z_][\w.-]*)(?::([\w.-]+))?\s*\]\s*\z})
|
|
65
83
|
return nil unless m
|
|
66
84
|
|
|
@@ -30,16 +30,33 @@ class PerformanceComparator
|
|
|
30
30
|
DIM = "\e[2m"
|
|
31
31
|
CLEAR = "\e[0m"
|
|
32
32
|
|
|
33
|
+
BEST_OF = Integer(ENV.fetch("CANON_PERF_BEST_OF", "3"))
|
|
34
|
+
|
|
33
35
|
def run
|
|
34
36
|
clone_base
|
|
35
|
-
current =
|
|
36
|
-
base =
|
|
37
|
+
current = best_of(REPO_ROOT, "current")
|
|
38
|
+
base = best_of(base_clone_dir, "base (#{DEFAULT_BASE})")
|
|
37
39
|
print_report(current, base)
|
|
38
40
|
exit(1) if regressions?(current, base)
|
|
39
41
|
ensure
|
|
40
42
|
cleanup
|
|
41
43
|
end
|
|
42
44
|
|
|
45
|
+
# The html_parsing lanes drift up to ~27% BETWEEN SAME-CONTENT runs
|
|
46
|
+
# on shared runners (main failed against itself: run 40047205814) —
|
|
47
|
+
# single-run comparisons measure the runner, not the code. Each side
|
|
48
|
+
# runs BEST_OF times; per benchmark, the run with the highest upper
|
|
49
|
+
# bound stands (the capability estimate a fair gate compares).
|
|
50
|
+
def best_of(working_dir, label)
|
|
51
|
+
runs = Array.new(BEST_OF) { |i| run_report(working_dir, "#{label} ##{i + 1}") }
|
|
52
|
+
runs.first.fetch("benchmarks").keys.each_with_object({}) do |bench, merged|
|
|
53
|
+
best = runs.max_by { |r| r.fetch("benchmarks").fetch(bench).fetch("upper").to_f }
|
|
54
|
+
merged[bench] = best.fetch("benchmarks").fetch(bench)
|
|
55
|
+
end.then do |benchmarks|
|
|
56
|
+
runs.first.merge("benchmarks" => benchmarks)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
43
60
|
private
|
|
44
61
|
|
|
45
62
|
def clone_base
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: canon
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.117
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -173,8 +173,10 @@ files:
|
|
|
173
173
|
- docs/Gemfile
|
|
174
174
|
- docs/INDEX.adoc
|
|
175
175
|
- docs/_config.yml
|
|
176
|
+
- docs/_references/canonical-digests.adoc
|
|
176
177
|
- docs/_references/comparison-theory.adoc
|
|
177
178
|
- docs/_references/html-canonical-form.adoc
|
|
179
|
+
- docs/_references/jcs-rfc8785.adoc
|
|
178
180
|
- docs/_references/yaml-canonical-form.adoc
|
|
179
181
|
- docs/advanced/diff-classification.adoc
|
|
180
182
|
- docs/advanced/diff-pipeline.adoc
|