unidpp 0.1.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 +7 -0
- data/lib/unidpp/annexb.rb +296 -0
- data/lib/unidpp/canonical_json.rb +37 -0
- data/lib/unidpp/event.rb +103 -0
- data/lib/unidpp/event_log.rb +225 -0
- data/lib/unidpp/gs1.rb +105 -0
- data/lib/unidpp/identifier.rb +240 -0
- data/lib/unidpp/link.rb +197 -0
- data/lib/unidpp/manifest.rb +328 -0
- data/lib/unidpp/quantity.rb +150 -0
- data/lib/unidpp/registry/applicability.rb +66 -0
- data/lib/unidpp/registry/client.rb +141 -0
- data/lib/unidpp/registry/file_store.rb +121 -0
- data/lib/unidpp/registry/item.rb +132 -0
- data/lib/unidpp/registry/item_version.rb +66 -0
- data/lib/unidpp/registry/register.rb +36 -0
- data/lib/unidpp/registry/store.rb +26 -0
- data/lib/unidpp/registry.rb +21 -0
- data/lib/unidpp/verdict.rb +72 -0
- data/lib/unidpp/version.rb +5 -0
- data/lib/unidpp.rb +31 -0
- metadata +108 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "yaml"
|
|
5
|
+
|
|
6
|
+
module Unidpp
|
|
7
|
+
module Registry
|
|
8
|
+
# Local JSON/YAML store adapter: registry content read from a
|
|
9
|
+
# directory or a single consolidated document. All (de)serialization
|
|
10
|
+
# of items, bindings and register metadata goes through lutaml-model
|
|
11
|
+
# (Item.from_yaml / from_json, etc.); this adapter only locates and
|
|
12
|
+
# reads raw documents.
|
|
13
|
+
#
|
|
14
|
+
# Directory layout (glossarist-dataset style, one item per file):
|
|
15
|
+
#
|
|
16
|
+
# <path>/register.yaml Register metadata
|
|
17
|
+
# <path>/items/*.yaml one Item per file
|
|
18
|
+
# <path>/bindings/*.yaml one ApplicabilityBinding or a
|
|
19
|
+
# bindings: [...] list per file
|
|
20
|
+
#
|
|
21
|
+
# Single-file layout (JSON or YAML): one document with the keys
|
|
22
|
+
# +register+, +items+, +applicability+.
|
|
23
|
+
#
|
|
24
|
+
# Item version entries are immutable per version and perfectly
|
|
25
|
+
# cacheable — only register status is mutable — so a FileStore may
|
|
26
|
+
# also serve as an offline mirror of a remote register.
|
|
27
|
+
class FileStore < Store
|
|
28
|
+
class FormatError < Unidpp::Error; end
|
|
29
|
+
|
|
30
|
+
ITEM_GLOB = "items/*.{yaml,yml,json}".freeze
|
|
31
|
+
BINDING_GLOB = "bindings/*.{yaml,yml,json}".freeze
|
|
32
|
+
|
|
33
|
+
# @param path [String] directory or single document file
|
|
34
|
+
def initialize(path)
|
|
35
|
+
@path = File.expand_path(path)
|
|
36
|
+
@directory = File.directory?(@path)
|
|
37
|
+
freeze_content
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @return [Array<Item>]
|
|
41
|
+
def items
|
|
42
|
+
content[:items]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @return [Array<ApplicabilityBinding>]
|
|
46
|
+
def bindings
|
|
47
|
+
content[:bindings]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @return [Register, nil]
|
|
51
|
+
def register
|
|
52
|
+
content[:register]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def content
|
|
58
|
+
@content
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def freeze_content
|
|
62
|
+
@content =
|
|
63
|
+
if @directory
|
|
64
|
+
load_directory
|
|
65
|
+
else
|
|
66
|
+
load_single_file
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def load_directory
|
|
71
|
+
{
|
|
72
|
+
register: load_register,
|
|
73
|
+
items: Dir[File.join(@path, ITEM_GLOB)].sort.map { |f| load_item(f) },
|
|
74
|
+
bindings: Dir[File.join(@path, BINDING_GLOB)].sort.flat_map { |f| load_bindings(f) },
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def load_single_file
|
|
79
|
+
doc = read_document(@path)
|
|
80
|
+
unless doc.is_a?(Hash)
|
|
81
|
+
raise FormatError, "registry document must be a mapping: #{@path}"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
{
|
|
85
|
+
register: doc["register"] && Register.from_hash(doc["register"]),
|
|
86
|
+
items: Array(doc["items"]).map { |i| Item.from_hash(i) },
|
|
87
|
+
bindings: Array(doc["applicability"] || doc["bindings"])
|
|
88
|
+
.map { |b| ApplicabilityBinding.from_hash(b) },
|
|
89
|
+
}
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def load_register
|
|
93
|
+
path = Dir[File.join(@path, "register.{yaml,yml,json}")].first
|
|
94
|
+
doc = path && read_document(path)
|
|
95
|
+
doc && Register.from_hash(doc)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def load_item(path)
|
|
99
|
+
Item.from_hash(read_document(path))
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def load_bindings(path)
|
|
103
|
+
doc = read_document(path)
|
|
104
|
+
list = doc.is_a?(Hash) ? doc["bindings"] : doc
|
|
105
|
+
Array(list).map { |b| ApplicabilityBinding.from_hash(b) }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Reads a raw JSON or YAML document (plain data; YAML timestamps are
|
|
109
|
+
# permitted and cast to ISO strings by the lutaml DateTime type).
|
|
110
|
+
def read_document(path)
|
|
111
|
+
if File.extname(path) == ".json"
|
|
112
|
+
JSON.parse(File.read(path))
|
|
113
|
+
else
|
|
114
|
+
YAML.safe_load(File.read(path),
|
|
115
|
+
permitted_classes: [Time, Date, DateTime],
|
|
116
|
+
aliases: true)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unidpp
|
|
4
|
+
module Registry
|
|
5
|
+
# A registry item: the single definition (I2 — single definition, many
|
|
6
|
+
# constraints) of a data element, profile, transform, crypto suite,
|
|
7
|
+
# trust anchor or unit. Item references are (register, item, version).
|
|
8
|
+
# Profiles constrain and compose; they never redefine — where regions
|
|
9
|
+
# regulate differently they register distinct items.
|
|
10
|
+
#
|
|
11
|
+
# Profile items embed their ProfileManifest, version-pinned to one of
|
|
12
|
+
# the item's versions (seam S4: version pinning at the profile↔registry
|
|
13
|
+
# join).
|
|
14
|
+
class Item < Lutaml::Model::Serializable
|
|
15
|
+
ITEM_CLASSES = %w[
|
|
16
|
+
data-element profile crypto-suite transform trust-anchor unit
|
|
17
|
+
].freeze
|
|
18
|
+
|
|
19
|
+
class UnknownVersionError < Unidpp::Error; end
|
|
20
|
+
|
|
21
|
+
attribute :identifier, :string
|
|
22
|
+
attribute :register, :string
|
|
23
|
+
attribute :item_class, :string, values: ITEM_CLASSES
|
|
24
|
+
attribute :title, :string
|
|
25
|
+
# ISO 19135 submitting organization for this item.
|
|
26
|
+
attribute :submitting_organization, :string
|
|
27
|
+
attribute :versions, ItemVersion, collection: true, default: -> { [] }
|
|
28
|
+
attribute :manifest, ProfileManifest
|
|
29
|
+
|
|
30
|
+
json do
|
|
31
|
+
map "identifier", to: :identifier
|
|
32
|
+
map "register", to: :register, render_nil: false
|
|
33
|
+
map "item_class", to: :item_class
|
|
34
|
+
map "title", to: :title, render_nil: false
|
|
35
|
+
map "submitting_organization", to: :submitting_organization, render_nil: false
|
|
36
|
+
map "versions", to: :versions
|
|
37
|
+
map "manifest", to: :manifest, render_nil: false
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
yaml do
|
|
41
|
+
map "identifier", to: :identifier
|
|
42
|
+
map "register", to: :register, render_nil: false
|
|
43
|
+
map "item_class", to: :item_class
|
|
44
|
+
map "title", to: :title, render_nil: false
|
|
45
|
+
map "submitting_organization", to: :submitting_organization, render_nil: false
|
|
46
|
+
map "versions", to: :versions
|
|
47
|
+
map "manifest", to: :manifest, render_nil: false
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# item_class predicates (profile? / unit? / ...) are generated by
|
|
51
|
+
# lutaml-model from the item_class value enumeration.
|
|
52
|
+
|
|
53
|
+
# @param number [String]
|
|
54
|
+
# @return [ItemVersion, nil]
|
|
55
|
+
def version(number)
|
|
56
|
+
versions.find { |v| v.version == number.to_s }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @param number [String]
|
|
60
|
+
# @return [ItemVersion]
|
|
61
|
+
# @raise [UnknownVersionError]
|
|
62
|
+
def version!(number)
|
|
63
|
+
version(number) ||
|
|
64
|
+
raise(UnknownVersionError, "#{identifier} has no version #{number}")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Versions in registration order (by effective_from, then version).
|
|
68
|
+
#
|
|
69
|
+
# @return [Array<ItemVersion>]
|
|
70
|
+
def ordered_versions
|
|
71
|
+
versions.sort_by do |v|
|
|
72
|
+
[v.effective_from || DateTime.new(0), v.version.to_s]
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# The version currently in force: latest effective version with
|
|
77
|
+
# status "valid".
|
|
78
|
+
#
|
|
79
|
+
# @return [ItemVersion, nil]
|
|
80
|
+
def current_version
|
|
81
|
+
reversed = ordered_versions.reverse
|
|
82
|
+
reversed.find(&:valid?) || reversed.find { |v| !v.retired? }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Follows supersession links from the earliest version carrying one
|
|
86
|
+
# (or from +from+, when given) to the terminal version.
|
|
87
|
+
#
|
|
88
|
+
# @param from [String, nil] version number to start from
|
|
89
|
+
# @return [Array<ItemVersion>] the chain, in supersession order
|
|
90
|
+
def supersession_chain(from: nil)
|
|
91
|
+
start = from ? version!(from) : ordered_versions.first
|
|
92
|
+
chain = [start]
|
|
93
|
+
while (next_number = chain.last.superseded_by_version)
|
|
94
|
+
chain << version!(next_number)
|
|
95
|
+
end
|
|
96
|
+
chain
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Resolved effective window end: explicit effective_until, else the
|
|
100
|
+
# successor's effective_from, else open (nil).
|
|
101
|
+
#
|
|
102
|
+
# @param version_number [String]
|
|
103
|
+
# @return [DateTime, nil]
|
|
104
|
+
def window_until(version_number)
|
|
105
|
+
v = version!(version_number)
|
|
106
|
+
return v.effective_until if v.effective_until
|
|
107
|
+
return nil if v.superseded_by_version.nil?
|
|
108
|
+
|
|
109
|
+
version(v.superseded_by_version)&.effective_from
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# The version in force at time +t+ (point-in-time registry state).
|
|
113
|
+
#
|
|
114
|
+
# @param t [Time, DateTime]
|
|
115
|
+
# @return [ItemVersion, nil]
|
|
116
|
+
def in_force_at(t)
|
|
117
|
+
t = t.is_a?(DateTime) ? t : DateTime.parse(t.to_s)
|
|
118
|
+
ordered_versions
|
|
119
|
+
.select { |v| v.in_force_at?(t, until_time: window_until(v.version)) }
|
|
120
|
+
.last
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Seam S4 discipline: an embedded manifest must be pinned to a
|
|
124
|
+
# version of this item.
|
|
125
|
+
#
|
|
126
|
+
# @return [Boolean]
|
|
127
|
+
def manifest_version_pinned?
|
|
128
|
+
manifest.nil? || !version(manifest.version).nil?
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unidpp
|
|
4
|
+
module Registry
|
|
5
|
+
# One version of a registry item with its ISO 19135 lifecycle status.
|
|
6
|
+
# Versions are immutable once registered; status and supersession
|
|
7
|
+
# links express the lifecycle (valid / superseded / retired).
|
|
8
|
+
class ItemVersion < Lutaml::Model::Serializable
|
|
9
|
+
STATUSES = %w[valid superseded retired].freeze
|
|
10
|
+
|
|
11
|
+
attribute :version, :string
|
|
12
|
+
attribute :status, :string, values: STATUSES
|
|
13
|
+
# Effective window of this version. An open effective_until is
|
|
14
|
+
# derived from the successor's effective_from when absent.
|
|
15
|
+
attribute :effective_from, "Lutaml::Model::Type::DateTime"
|
|
16
|
+
attribute :effective_until, "Lutaml::Model::Type::DateTime"
|
|
17
|
+
attribute :registered_at, "Lutaml::Model::Type::DateTime"
|
|
18
|
+
attribute :superseded_by_version, :string
|
|
19
|
+
attribute :notes, :string
|
|
20
|
+
|
|
21
|
+
json do
|
|
22
|
+
map "version", to: :version
|
|
23
|
+
map "status", to: :status
|
|
24
|
+
map "effective_from", to: :effective_from, render_nil: false
|
|
25
|
+
map "effective_until", to: :effective_until, render_nil: false
|
|
26
|
+
map "registered_at", to: :registered_at, render_nil: false
|
|
27
|
+
map "superseded_by_version", to: :superseded_by_version, render_nil: false
|
|
28
|
+
map "notes", to: :notes, render_nil: false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
yaml do
|
|
32
|
+
map "version", to: :version
|
|
33
|
+
map "status", to: :status
|
|
34
|
+
map "effective_from", to: :effective_from, render_nil: false
|
|
35
|
+
map "effective_until", to: :effective_until, render_nil: false
|
|
36
|
+
map "registered_at", to: :registered_at, render_nil: false
|
|
37
|
+
map "superseded_by_version", to: :superseded_by_version, render_nil: false
|
|
38
|
+
map "notes", to: :notes, render_nil: false
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def valid?
|
|
42
|
+
status == "valid"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def superseded?
|
|
46
|
+
status == "superseded"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def retired?
|
|
50
|
+
status == "retired"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @param t [Time, DateTime]
|
|
54
|
+
# @param until_time [DateTime, nil] resolved window end (explicit or
|
|
55
|
+
# derived from the successor)
|
|
56
|
+
# @return [Boolean] this version was in force at +t+
|
|
57
|
+
def in_force_at?(t, until_time: effective_until)
|
|
58
|
+
t = t.is_a?(DateTime) ? t : DateTime.parse(t.to_s)
|
|
59
|
+
return false if effective_from && t < effective_from
|
|
60
|
+
return false if until_time && t >= until_time
|
|
61
|
+
|
|
62
|
+
true
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unidpp
|
|
4
|
+
module Registry
|
|
5
|
+
# Register metadata with ISO 19135 governance roles: the register
|
|
6
|
+
# owner, register manager and control body for this (sub)register.
|
|
7
|
+
# FERIN registers federate as siblings — no register is the universal
|
|
8
|
+
# envelope.
|
|
9
|
+
class Register < Lutaml::Model::Serializable
|
|
10
|
+
attribute :register_id, :string # FERIN register identifier
|
|
11
|
+
attribute :name, :string
|
|
12
|
+
attribute :register_owner, :string
|
|
13
|
+
attribute :register_manager, :string
|
|
14
|
+
attribute :control_body, :string
|
|
15
|
+
attribute :homepage, :string
|
|
16
|
+
|
|
17
|
+
json do
|
|
18
|
+
map "register_id", to: :register_id
|
|
19
|
+
map "name", to: :name, render_nil: false
|
|
20
|
+
map "register_owner", to: :register_owner, render_nil: false
|
|
21
|
+
map "register_manager", to: :register_manager, render_nil: false
|
|
22
|
+
map "control_body", to: :control_body, render_nil: false
|
|
23
|
+
map "homepage", to: :homepage, render_nil: false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
yaml do
|
|
27
|
+
map "register_id", to: :register_id
|
|
28
|
+
map "name", to: :name, render_nil: false
|
|
29
|
+
map "register_owner", to: :register_owner, render_nil: false
|
|
30
|
+
map "register_manager", to: :register_manager, render_nil: false
|
|
31
|
+
map "control_body", to: :control_body, render_nil: false
|
|
32
|
+
map "homepage", to: :homepage, render_nil: false
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unidpp
|
|
4
|
+
module Registry
|
|
5
|
+
# Store interface — the seam between the registry client and a source
|
|
6
|
+
# of register content (local files for tests and offline mirrors; an
|
|
7
|
+
# HTTP adapter against a live FERIN register endpoint can implement
|
|
8
|
+
# the same three methods without touching the client).
|
|
9
|
+
class Store
|
|
10
|
+
# @return [Array<Item>] all items in the store
|
|
11
|
+
def items
|
|
12
|
+
raise NotImplementedError, "#{self.class} must implement #items"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @return [Array<ApplicabilityBinding>] all applicability bindings
|
|
16
|
+
def bindings
|
|
17
|
+
raise NotImplementedError, "#{self.class} must implement #bindings"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @return [Register, nil] register metadata when available
|
|
21
|
+
def register
|
|
22
|
+
raise NotImplementedError, "#{self.class} must implement #register"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unidpp
|
|
4
|
+
# FERIN / ISO 19135-style federated registry access (framework layer L3,
|
|
5
|
+
# seam S4/S5): every data point, profile, transform, crypto suite and
|
|
6
|
+
# unit is defined ONCE in a register — principal register plus
|
|
7
|
+
# federated subregisters with 19135 governance roles — and every item
|
|
8
|
+
# has a lifecycle (valid / superseded / retired) and versioned
|
|
9
|
+
# supersession chains. Applicability bindings carry effective windows
|
|
10
|
+
# with retroactivity flags, which is what makes "which profiles applied
|
|
11
|
+
# at T" answerable as a legal as-of query.
|
|
12
|
+
module Registry
|
|
13
|
+
autoload :Register, "unidpp/registry/register"
|
|
14
|
+
autoload :ItemVersion, "unidpp/registry/item_version"
|
|
15
|
+
autoload :Item, "unidpp/registry/item"
|
|
16
|
+
autoload :ApplicabilityBinding, "unidpp/registry/applicability"
|
|
17
|
+
autoload :Store, "unidpp/registry/store"
|
|
18
|
+
autoload :FileStore, "unidpp/registry/file_store"
|
|
19
|
+
autoload :Client, "unidpp/registry/client"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unidpp
|
|
4
|
+
# Verification verdict (I9 / I13): graded, never boolean. A verdict
|
|
5
|
+
# states which verification *reading* it answers, the *freshness* class
|
|
6
|
+
# of the underlying state, and a coverage report (which data points the
|
|
7
|
+
# verdict covers and which are missing).
|
|
8
|
+
#
|
|
9
|
+
# The three readings (because law needs all three):
|
|
10
|
+
# evidentiary what could a diligent verifier know at T, given the
|
|
11
|
+
# then-current trust state (protects good-faith actors)
|
|
12
|
+
# current_state fraud voids ab initio — prevents laundering
|
|
13
|
+
# cryptographic signature/chain validity alone
|
|
14
|
+
#
|
|
15
|
+
# Freshness verdicts degrade explicitly, never silently:
|
|
16
|
+
# fresh within the profile's freshness window
|
|
17
|
+
# stale past the window; still served, marked
|
|
18
|
+
# offline_degraded served from Tier-A carrier / cache, as-of stamped
|
|
19
|
+
# unavailable no current state could be established
|
|
20
|
+
class Verdict < Lutaml::Model::Serializable
|
|
21
|
+
READINGS = %w[evidentiary current_state cryptographic].freeze
|
|
22
|
+
FRESHNESS = %w[fresh stale offline_degraded unavailable].freeze
|
|
23
|
+
|
|
24
|
+
attribute :reading, :string, values: READINGS
|
|
25
|
+
attribute :freshness, :string, values: FRESHNESS
|
|
26
|
+
attribute :rendered_at, "Lutaml::Model::Type::DateTime"
|
|
27
|
+
# Coverage report: data point URIs covered by this verdict and still
|
|
28
|
+
# missing for the profile that asked.
|
|
29
|
+
attribute :covered, :string, collection: true, default: -> { [] }
|
|
30
|
+
attribute :missing, :string, collection: true, default: -> { [] }
|
|
31
|
+
attribute :notes, :string, collection: true, default: -> { [] }
|
|
32
|
+
|
|
33
|
+
json do
|
|
34
|
+
map "reading", to: :reading
|
|
35
|
+
map "freshness", to: :freshness
|
|
36
|
+
map "rendered_at", to: :rendered_at, render_nil: false
|
|
37
|
+
map "covered", to: :covered, render_nil: false
|
|
38
|
+
map "missing", to: :missing, render_nil: false
|
|
39
|
+
map "notes", to: :notes, render_nil: false
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
yaml do
|
|
43
|
+
map "reading", to: :reading
|
|
44
|
+
map "freshness", to: :freshness
|
|
45
|
+
map "rendered_at", to: :rendered_at, render_nil: false
|
|
46
|
+
map "covered", to: :covered, render_nil: false
|
|
47
|
+
map "missing", to: :missing, render_nil: false
|
|
48
|
+
map "notes", to: :notes, render_nil: false
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Reading predicates (evidentiary? / current_state? / cryptographic?)
|
|
52
|
+
# are generated by lutaml-model from the reading value enumeration.
|
|
53
|
+
|
|
54
|
+
# I13: degradation is explicit — anything not fresh is degraded and
|
|
55
|
+
# must be marked, never silently passed.
|
|
56
|
+
def degraded?
|
|
57
|
+
freshness != "fresh"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def complete?
|
|
61
|
+
missing.empty?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @return [Rational, nil] covered / (covered + missing), nil when empty
|
|
65
|
+
def coverage_ratio
|
|
66
|
+
total = covered.size + missing.size
|
|
67
|
+
return nil if total.zero?
|
|
68
|
+
|
|
69
|
+
Rational(covered.size, total)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
data/lib/unidpp.rb
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# UniDPP — Ruby library for the international Digital Product Passport
|
|
4
|
+
# framework (the UniDPP design framework invariants I1–I14; see https://github.com/unidpp).
|
|
5
|
+
#
|
|
6
|
+
# Loading discipline: this file is the only require point. Every other
|
|
7
|
+
# constant is lazily loaded through autoload entries declared here (and,
|
|
8
|
+
# for the Unidpp::Registry namespace, in unidpp/registry.rb) — no
|
|
9
|
+
# require_relative anywhere inside lib/.
|
|
10
|
+
require "lutaml/model"
|
|
11
|
+
|
|
12
|
+
module Unidpp
|
|
13
|
+
class Error < StandardError; end
|
|
14
|
+
|
|
15
|
+
autoload :VERSION, "unidpp/version"
|
|
16
|
+
autoload :CanonicalJson, "unidpp/canonical_json"
|
|
17
|
+
autoload :AnnexB, "unidpp/annexb"
|
|
18
|
+
autoload :Gs1, "unidpp/gs1"
|
|
19
|
+
autoload :ProductIdentifier, "unidpp/identifier"
|
|
20
|
+
autoload :ProfileManifest, "unidpp/manifest"
|
|
21
|
+
autoload :ProfileAxes, "unidpp/manifest"
|
|
22
|
+
autoload :TriggerPredicate, "unidpp/manifest"
|
|
23
|
+
autoload :CryptoSuiteBinding, "unidpp/manifest"
|
|
24
|
+
autoload :PassportLink, "unidpp/link"
|
|
25
|
+
autoload :LinkInterval, "unidpp/link"
|
|
26
|
+
autoload :Event, "unidpp/event"
|
|
27
|
+
autoload :EventLog, "unidpp/event_log"
|
|
28
|
+
autoload :Verdict, "unidpp/verdict"
|
|
29
|
+
autoload :Quantity, "unidpp/quantity"
|
|
30
|
+
autoload :Registry, "unidpp/registry"
|
|
31
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: unidpp
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- UniDPP
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: lutaml-model
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.8'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.8'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rake
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '13.0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '13.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rspec
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '3.13'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '3.13'
|
|
54
|
+
description: 'Ruby library for the UniDPP international Digital Product Passport framework:
|
|
55
|
+
scheme-agnostic product identifiers (GS1 Digital Link, ISO/IEC 15459, GB/T 33993-style,
|
|
56
|
+
URN), profile manifests (jurisdiction x sector x characteristic axes, trigger predicates,
|
|
57
|
+
capability classes S0-S3, crypto-suite bindings), typed relationship edges with
|
|
58
|
+
visibility classes, hash-chained append-only event logs with salted commitments,
|
|
59
|
+
graded-trust verdicts, quantities with registered unit URIs, and a federated-registry
|
|
60
|
+
client (FERIN/ISO 19135 items, statuses, supersession chains, applicability as-of
|
|
61
|
+
queries).'
|
|
62
|
+
executables: []
|
|
63
|
+
extensions: []
|
|
64
|
+
extra_rdoc_files: []
|
|
65
|
+
files:
|
|
66
|
+
- lib/unidpp.rb
|
|
67
|
+
- lib/unidpp/annexb.rb
|
|
68
|
+
- lib/unidpp/canonical_json.rb
|
|
69
|
+
- lib/unidpp/event.rb
|
|
70
|
+
- lib/unidpp/event_log.rb
|
|
71
|
+
- lib/unidpp/gs1.rb
|
|
72
|
+
- lib/unidpp/identifier.rb
|
|
73
|
+
- lib/unidpp/link.rb
|
|
74
|
+
- lib/unidpp/manifest.rb
|
|
75
|
+
- lib/unidpp/quantity.rb
|
|
76
|
+
- lib/unidpp/registry.rb
|
|
77
|
+
- lib/unidpp/registry/applicability.rb
|
|
78
|
+
- lib/unidpp/registry/client.rb
|
|
79
|
+
- lib/unidpp/registry/file_store.rb
|
|
80
|
+
- lib/unidpp/registry/item.rb
|
|
81
|
+
- lib/unidpp/registry/item_version.rb
|
|
82
|
+
- lib/unidpp/registry/register.rb
|
|
83
|
+
- lib/unidpp/registry/store.rb
|
|
84
|
+
- lib/unidpp/verdict.rb
|
|
85
|
+
- lib/unidpp/version.rb
|
|
86
|
+
homepage: https://github.com/unidpp/unidpp-rb
|
|
87
|
+
licenses:
|
|
88
|
+
- MIT
|
|
89
|
+
metadata: {}
|
|
90
|
+
rdoc_options: []
|
|
91
|
+
require_paths:
|
|
92
|
+
- lib
|
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
94
|
+
requirements:
|
|
95
|
+
- - ">="
|
|
96
|
+
- !ruby/object:Gem::Version
|
|
97
|
+
version: '3.0'
|
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - ">="
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '0'
|
|
103
|
+
requirements: []
|
|
104
|
+
rubygems_version: 4.0.23
|
|
105
|
+
specification_version: 4
|
|
106
|
+
summary: UniDPP — Digital Product Passport model, event log and FERIN/ISO 19135 registry
|
|
107
|
+
client
|
|
108
|
+
test_files: []
|