causalontology 1.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 +7 -0
- data/LICENSE +203 -0
- data/README.md +73 -0
- data/conformance.rb +453 -0
- data/lib/causalontology/canonical.rb +85 -0
- data/lib/causalontology/ed25519.rb +158 -0
- data/lib/causalontology/jcs.rb +95 -0
- data/lib/causalontology/schema.rb +169 -0
- data/lib/causalontology/semantics.rb +204 -0
- data/lib/causalontology/signing.rb +74 -0
- data/lib/causalontology/store.rb +355 -0
- data/lib/causalontology.rb +93 -0
- metadata +62 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
# An in-memory conformant store.
|
|
4
|
+
#
|
|
5
|
+
# Implements the store side of the abstract operation set (spec/store.md):
|
|
6
|
+
# immutable content objects with idempotent put; signed, add-only provenance
|
|
7
|
+
# records; materialized enrichment views with contributors; retraction handling
|
|
8
|
+
# in default views; succession lineage; the resolve minimum; the deterministic
|
|
9
|
+
# cycle-breaking view rule; and the stigmergy gap read.
|
|
10
|
+
#
|
|
11
|
+
# Ruby Hashes preserve insertion order, exactly like Python dicts, so every
|
|
12
|
+
# iteration below deliberately mirrors the reference store's iteration order.
|
|
13
|
+
|
|
14
|
+
require "set"
|
|
15
|
+
require_relative "canonical"
|
|
16
|
+
require_relative "schema"
|
|
17
|
+
require_relative "semantics"
|
|
18
|
+
require_relative "signing"
|
|
19
|
+
|
|
20
|
+
module Causalontology
|
|
21
|
+
CONTENT_KINDS = Set.new(["occurrent", "cro", "continuant", "realizable"]).freeze
|
|
22
|
+
RECORD_KINDS = Set.new(["assertion", "enrichment", "retraction", "succession"]).freeze
|
|
23
|
+
|
|
24
|
+
# An enforcing store refused a write, with the reason as the message.
|
|
25
|
+
class RejectedWrite < StandardError; end
|
|
26
|
+
|
|
27
|
+
class InMemoryStore
|
|
28
|
+
attr_reader :enforcing, :objects, :records, :quarantine
|
|
29
|
+
|
|
30
|
+
def initialize(enforcing: true)
|
|
31
|
+
@enforcing = enforcing
|
|
32
|
+
@objects = {} # id -> content object
|
|
33
|
+
@records = {} # id -> provenance record
|
|
34
|
+
@quarantine = {} # id -> record (unsigned / unverifiable)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# ---------------------------------------------------------------- put
|
|
38
|
+
|
|
39
|
+
# Write a content object; idempotent; returns the identifier.
|
|
40
|
+
def put(obj, kind = nil)
|
|
41
|
+
kind ||= Canonical.infer_kind(obj)
|
|
42
|
+
unless CONTENT_KINDS.include?(kind)
|
|
43
|
+
raise ArgumentError, "put() takes content objects; use put_record()"
|
|
44
|
+
end
|
|
45
|
+
obj = obj.dup
|
|
46
|
+
obj["type"] = kind unless obj.key?("type")
|
|
47
|
+
obj["id"] = Canonical.identify(obj, kind) unless obj.key?("id")
|
|
48
|
+
return obj["id"] if @objects.key?(obj["id"]) # immutable: identical identity is a no-op
|
|
49
|
+
ok, why = Schema.validate_schema(obj, kind)
|
|
50
|
+
raise RejectedWrite, why.join("; ") unless ok
|
|
51
|
+
ok, why = Semantics.validate_semantics(obj, kind)
|
|
52
|
+
raise RejectedWrite, why.join("; ") unless ok
|
|
53
|
+
@objects[obj["id"]] = obj
|
|
54
|
+
obj["id"]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Write a signed provenance record; returns the identifier.
|
|
58
|
+
def put_record(record, kind = nil, force: false)
|
|
59
|
+
kind ||= Canonical.infer_kind(record)
|
|
60
|
+
unless RECORD_KINDS.include?(kind)
|
|
61
|
+
raise ArgumentError, "put_record() takes provenance records"
|
|
62
|
+
end
|
|
63
|
+
record = record.dup
|
|
64
|
+
record["type"] = kind unless record.key?("type")
|
|
65
|
+
rid = record["id"]
|
|
66
|
+
rid = Canonical.identify(record, kind) if rid.nil? || rid.empty?
|
|
67
|
+
record["id"] = rid
|
|
68
|
+
return rid if @records.key?(rid) # add-only and idempotent
|
|
69
|
+
unless Signing.verify_record(record, kind)
|
|
70
|
+
@quarantine[rid] = record
|
|
71
|
+
raise RejectedWrite, "unsigned or unverifiable record: quarantined"
|
|
72
|
+
end
|
|
73
|
+
ok, why = Semantics.validate_semantics(record, kind)
|
|
74
|
+
raise RejectedWrite, why.join("; ") unless ok
|
|
75
|
+
if kind == "retraction" && !retraction_source_ok(record)
|
|
76
|
+
raise RejectedWrite,
|
|
77
|
+
"a retraction is valid only from the retracted record's " \
|
|
78
|
+
"source or its succession lineage"
|
|
79
|
+
end
|
|
80
|
+
if kind == "enrichment" && @enforcing && !force
|
|
81
|
+
if ["subsumes", "part_of"].include?(record["field"]) && would_cycle(record)
|
|
82
|
+
raise RejectedWrite,
|
|
83
|
+
"would create a cycle in the materialized " \
|
|
84
|
+
"#{record["field"]} graph"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
@records[rid] = record
|
|
88
|
+
rid
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Simulate a decentralized replica merge (no enforcement gate).
|
|
92
|
+
def force_merge_record(record, kind = nil)
|
|
93
|
+
put_record(record, kind, force: true)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# ----------------------------------------------------- record queries
|
|
97
|
+
|
|
98
|
+
def records_of(kind)
|
|
99
|
+
@records.values.select { |r| r["type"] == kind }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def retracted_ids
|
|
103
|
+
out = Set.new
|
|
104
|
+
records_of("retraction").each { |r| out << r["retracts"] }
|
|
105
|
+
out
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def retraction_source_ok(retraction)
|
|
109
|
+
target = @records[retraction["retracts"]]
|
|
110
|
+
return true if target.nil? # open world: the target may arrive later
|
|
111
|
+
lineage(target["source"]).include?(retraction["source"])
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# The succession chain closure containing key (includes key).
|
|
115
|
+
def lineage(key)
|
|
116
|
+
succ = {}
|
|
117
|
+
pred = {}
|
|
118
|
+
records_of("succession").each do |s|
|
|
119
|
+
succ[s["predecessor"]] = s["successor"]
|
|
120
|
+
pred[s["successor"]] = s["predecessor"]
|
|
121
|
+
end
|
|
122
|
+
chain = Set.new([key])
|
|
123
|
+
cursor = key
|
|
124
|
+
while pred.key?(cursor)
|
|
125
|
+
cursor = pred[cursor]
|
|
126
|
+
chain << cursor
|
|
127
|
+
end
|
|
128
|
+
cursor = key
|
|
129
|
+
while succ.key?(cursor)
|
|
130
|
+
cursor = succ[cursor]
|
|
131
|
+
chain << cursor
|
|
132
|
+
end
|
|
133
|
+
chain
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def assertions_about(identifier, include_retracted: false)
|
|
137
|
+
retracted = retracted_ids
|
|
138
|
+
out = []
|
|
139
|
+
records_of("assertion").each do |r|
|
|
140
|
+
next unless r["about"] == identifier
|
|
141
|
+
if retracted.include?(r["id"])
|
|
142
|
+
out << r.merge("retracted" => true) if include_retracted
|
|
143
|
+
next
|
|
144
|
+
end
|
|
145
|
+
out << r
|
|
146
|
+
end
|
|
147
|
+
out
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def enrichments_about(identifier, include_retracted: false)
|
|
151
|
+
retracted = retracted_ids
|
|
152
|
+
out = []
|
|
153
|
+
records_of("enrichment").each do |r|
|
|
154
|
+
next unless r["about"] == identifier
|
|
155
|
+
next if retracted.include?(r["id"]) && !include_retracted
|
|
156
|
+
out << r
|
|
157
|
+
end
|
|
158
|
+
out
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# --------------------------------------------------- materialized views
|
|
162
|
+
|
|
163
|
+
# [edges, excluded] for subsumes/part_of after rule 13 cycle-breaking.
|
|
164
|
+
def active_taxonomy_edges(field)
|
|
165
|
+
retracted = retracted_ids
|
|
166
|
+
recs = records_of("enrichment").select do |r|
|
|
167
|
+
r["field"] == field && !retracted.include?(r["id"])
|
|
168
|
+
end
|
|
169
|
+
active = recs.dup
|
|
170
|
+
excluded = []
|
|
171
|
+
loop do
|
|
172
|
+
cyc = self.class.find_cycle_records(active)
|
|
173
|
+
break if cyc.empty?
|
|
174
|
+
# exclude the cycle-completing record with the LATEST timestamp,
|
|
175
|
+
# ties broken by lexicographic record identifier (deterministic)
|
|
176
|
+
loser = cyc.max_by { |r| [r["timestamp"], r["id"]] }
|
|
177
|
+
active.delete_at(active.index(loser))
|
|
178
|
+
excluded << loser
|
|
179
|
+
end
|
|
180
|
+
[active, excluded]
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# The records forming one directed cycle among the given enrichment
|
|
184
|
+
# records, or an empty array when the graph they draw is acyclic.
|
|
185
|
+
def self.find_cycle_records(recs)
|
|
186
|
+
edges = {}
|
|
187
|
+
recs.each { |r| (edges[r["about"]] ||= []) << [r["entry"], r] }
|
|
188
|
+
state = Hash.new(0)
|
|
189
|
+
cycle = []
|
|
190
|
+
|
|
191
|
+
dfs = nil
|
|
192
|
+
dfs = lambda do |node, path_records|
|
|
193
|
+
state[node] = 1
|
|
194
|
+
(edges[node] || []).each do |nxt, rec|
|
|
195
|
+
if state[nxt] == 1
|
|
196
|
+
cycle.concat(path_records + [rec])
|
|
197
|
+
return true
|
|
198
|
+
end
|
|
199
|
+
if state[nxt] == 0
|
|
200
|
+
return true if dfs.call(nxt, path_records + [rec])
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
state[node] = 2
|
|
204
|
+
false
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
edges.keys.each do |start|
|
|
208
|
+
return cycle if state[start] == 0 && dfs.call(start, [])
|
|
209
|
+
end
|
|
210
|
+
[]
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def would_cycle(record)
|
|
214
|
+
retracted = retracted_ids
|
|
215
|
+
recs = records_of("enrichment").select do |r|
|
|
216
|
+
r["field"] == record["field"] && !retracted.include?(r["id"])
|
|
217
|
+
end
|
|
218
|
+
!self.class.find_cycle_records(recs + [record]).empty?
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# The object with its materialized enrichment sets and contributors.
|
|
222
|
+
def get(identifier, view: "default")
|
|
223
|
+
obj = @objects[identifier]
|
|
224
|
+
return nil if obj.nil?
|
|
225
|
+
include_retracted = (view == "history")
|
|
226
|
+
excluded_ids = Set.new
|
|
227
|
+
["subsumes", "part_of"].each do |field|
|
|
228
|
+
_, excluded = active_taxonomy_edges(field)
|
|
229
|
+
excluded.each { |r| excluded_ids << r["id"] }
|
|
230
|
+
end
|
|
231
|
+
fields = {}
|
|
232
|
+
enrichments_about(identifier, include_retracted: include_retracted).each do |rec|
|
|
233
|
+
next if excluded_ids.include?(rec["id"]) && view != "history"
|
|
234
|
+
entry = rec["entry"]
|
|
235
|
+
entry_key = [rec["field"],
|
|
236
|
+
entry.is_a?(Hash) ? entry.to_a.sort : entry]
|
|
237
|
+
slot = (fields[rec["field"]] ||= {})
|
|
238
|
+
bucket = (slot[entry_key] ||= { "entry" => entry, "contributors" => [] })
|
|
239
|
+
bucket["contributors"] << {
|
|
240
|
+
"source" => rec["source"], "timestamp" => rec["timestamp"]
|
|
241
|
+
}
|
|
242
|
+
end
|
|
243
|
+
enrichments = fields.transform_values(&:values)
|
|
244
|
+
return { "object" => obj } if view == "raw"
|
|
245
|
+
{ "object" => obj, "enrichments" => enrichments }
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# -------------------------------------------------------------- resolve
|
|
249
|
+
|
|
250
|
+
def self.canon_label(text)
|
|
251
|
+
text.strip.downcase.split.join("_")
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def self.norm_alias(text)
|
|
255
|
+
text.split.join(" ").downcase(:fold)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# The conformance minimum: exact label, then alias, then nothing.
|
|
259
|
+
def resolve(text, lang = nil)
|
|
260
|
+
label_hits = []
|
|
261
|
+
alias_hits = []
|
|
262
|
+
wanted_label = self.class.canon_label(text)
|
|
263
|
+
wanted_alias = self.class.norm_alias(text)
|
|
264
|
+
retracted = retracted_ids
|
|
265
|
+
@objects.each do |oid, obj|
|
|
266
|
+
next unless ["occurrent", "continuant"].include?(obj["type"])
|
|
267
|
+
if obj["label"] == wanted_label
|
|
268
|
+
label_hits << oid
|
|
269
|
+
next
|
|
270
|
+
end
|
|
271
|
+
records_of("enrichment").each do |rec|
|
|
272
|
+
next unless rec["about"] == oid && rec["field"] == "aliases"
|
|
273
|
+
next if retracted.include?(rec["id"])
|
|
274
|
+
entry = rec["entry"]
|
|
275
|
+
next if !lang.nil? && entry["lang"] != lang
|
|
276
|
+
if self.class.norm_alias(entry["text"] || "") == wanted_alias
|
|
277
|
+
alias_hits << oid
|
|
278
|
+
break
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
label_hits + alias_hits
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# ---------------------------------------------------------------- gaps
|
|
286
|
+
|
|
287
|
+
# The stigmergy read. Gap kinds per spec/store.md.
|
|
288
|
+
def gaps(kind = nil)
|
|
289
|
+
out = []
|
|
290
|
+
refined = Set.new
|
|
291
|
+
@objects.each_value do |obj|
|
|
292
|
+
next unless obj["type"] == "cro" && obj["refines"]
|
|
293
|
+
parent = @objects[obj["refines"]]
|
|
294
|
+
next if parent.nil?
|
|
295
|
+
ok, _reason = Semantics.refinement_valid(obj, parent)
|
|
296
|
+
refined << parent["id"] if ok
|
|
297
|
+
end
|
|
298
|
+
@objects.each do |oid, obj|
|
|
299
|
+
next unless obj["type"] == "cro"
|
|
300
|
+
# missing_field: lacking the temporal window or the modality -
|
|
301
|
+
# mechanism and context may legitimately stay unspecified forever
|
|
302
|
+
# (empty_mechanism is its own kind; absent context = context-free).
|
|
303
|
+
if (!obj.key?("temporal") || !obj.key?("modality")) &&
|
|
304
|
+
!refined.include?(oid)
|
|
305
|
+
out << { "id" => oid, "kind" => "missing_field",
|
|
306
|
+
"missing" => Semantics.is_partial(obj)[1] }
|
|
307
|
+
end
|
|
308
|
+
if !obj.key?("mechanism") || obj["mechanism"] == []
|
|
309
|
+
unless refined.include?(oid)
|
|
310
|
+
out << { "id" => oid, "kind" => "empty_mechanism" }
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
["subsumes", "part_of"].each do |field|
|
|
315
|
+
_, excluded = active_taxonomy_edges(field)
|
|
316
|
+
excluded.each do |rec|
|
|
317
|
+
out << { "id" => rec["id"], "kind" => "inconsistent_hierarchy",
|
|
318
|
+
"note" => "excluded by the deterministic " \
|
|
319
|
+
"cycle-breaking view rule" }
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
# dangling_reference: a reference to an object absent from the store -
|
|
323
|
+
# the red link that says "this page is wanted".
|
|
324
|
+
@objects.each do |oid, obj|
|
|
325
|
+
refs = []
|
|
326
|
+
if obj["type"] == "cro"
|
|
327
|
+
refs = (obj["causes"] || []).to_a +
|
|
328
|
+
(obj["effects"] || []).to_a +
|
|
329
|
+
(obj["context"] || []).to_a +
|
|
330
|
+
(obj["mechanism"] || []).to_a
|
|
331
|
+
refs << obj["refines"] if obj["refines"]
|
|
332
|
+
elsif obj["type"] == "realizable"
|
|
333
|
+
refs = [obj["bearer"]]
|
|
334
|
+
end
|
|
335
|
+
refs.each do |ref|
|
|
336
|
+
if ref && !@objects.key?(ref)
|
|
337
|
+
out << { "id" => oid, "kind" => "dangling_reference", "ref" => ref }
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
# conflict: pairs of claims satisfying the formal test (rule 6).
|
|
342
|
+
cros = @objects.values.select { |o| o["type"] == "cro" }
|
|
343
|
+
(0...cros.length).each do |i|
|
|
344
|
+
((i + 1)...cros.length).each do |j|
|
|
345
|
+
if Semantics.conflicts(cros[i], cros[j])
|
|
346
|
+
out << { "kind" => "conflict",
|
|
347
|
+
"a" => cros[i]["id"], "b" => cros[j]["id"] }
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
out = out.select { |g| g["kind"] == kind } unless kind.nil?
|
|
352
|
+
out
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
# causalontology - the Ruby binding of the Causalontology standard.
|
|
4
|
+
#
|
|
5
|
+
# A faithful port of causalontology-py, proving language independence:
|
|
6
|
+
# standard library only (json, digest), conformant when it passes every
|
|
7
|
+
# vector in conformance/vectors/ (run bindings/ruby/conformance.rb).
|
|
8
|
+
#
|
|
9
|
+
# Causalontology is a verb-first noun-hosting ontology: reality is what
|
|
10
|
+
# happens, and things are its participants.
|
|
11
|
+
|
|
12
|
+
require "json"
|
|
13
|
+
require "digest"
|
|
14
|
+
require "set"
|
|
15
|
+
|
|
16
|
+
require_relative "causalontology/jcs"
|
|
17
|
+
require_relative "causalontology/canonical"
|
|
18
|
+
require_relative "causalontology/ed25519"
|
|
19
|
+
require_relative "causalontology/signing"
|
|
20
|
+
require_relative "causalontology/schema"
|
|
21
|
+
require_relative "causalontology/semantics"
|
|
22
|
+
require_relative "causalontology/store"
|
|
23
|
+
|
|
24
|
+
module Causalontology
|
|
25
|
+
VERSION = "1.0.0" # specification 1.0.0 (vectors frozen 2026-07-13)
|
|
26
|
+
|
|
27
|
+
# Rule 4's fixed unit-conversion constants, re-exported at the top level.
|
|
28
|
+
UNIT_SECONDS = Semantics::UNIT_SECONDS
|
|
29
|
+
|
|
30
|
+
module_function
|
|
31
|
+
|
|
32
|
+
# -- canonicalization and identity (spec/identity.md) ---------------------
|
|
33
|
+
|
|
34
|
+
def canonicalize(obj, kind = nil)
|
|
35
|
+
Canonical.canonicalize(obj, kind)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def identify(obj, kind = nil)
|
|
39
|
+
Canonical.identify(obj, kind)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def identity_bearing(obj, kind = nil)
|
|
43
|
+
Canonical.identity_bearing(obj, kind)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def infer_kind(obj)
|
|
47
|
+
Canonical.infer_kind(obj)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# -- validation (spec/schema/, spec/semantics.md) --------------------------
|
|
51
|
+
|
|
52
|
+
def validate_schema(obj, kind = nil)
|
|
53
|
+
Schema.validate_schema(obj, kind)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def validate_semantics(obj, kind = nil)
|
|
57
|
+
Semantics.validate_semantics(obj, kind)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def is_partial(cro)
|
|
61
|
+
Semantics.is_partial(cro)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def admissible(cro, elapsed_seconds)
|
|
65
|
+
Semantics.admissible(cro, elapsed_seconds)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def conflicts(a, b)
|
|
69
|
+
Semantics.conflicts(a, b)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def refinement_valid(child, parent)
|
|
73
|
+
Semantics.refinement_valid(child, parent)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def hierarchy_consistent(parent, members)
|
|
77
|
+
Semantics.hierarchy_consistent(parent, members)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# -- provenance (spec/provenance.md) ---------------------------------------
|
|
81
|
+
|
|
82
|
+
def keypair_from_seed(seed32)
|
|
83
|
+
Signing.keypair_from_seed(seed32)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def sign_record(record, secret, kind = nil)
|
|
87
|
+
Signing.sign_record(record, secret, kind)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def verify_record(record, kind = nil)
|
|
91
|
+
Signing.verify_record(record, kind)
|
|
92
|
+
end
|
|
93
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: causalontology
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- AI University (AIU)
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-07-14 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: 'The Ruby binding of the Causalontology standard - a verb-first noun-hosting
|
|
14
|
+
ontology; a language-neutral standard and shared commons for reified causation.
|
|
15
|
+
Zero dependencies: RFC 8785 canonicalization, SHA-256 identity, pure-Ruby Ed25519
|
|
16
|
+
(RFC 8032), schema and semantics validation, and an in-memory conformant store.'
|
|
17
|
+
email:
|
|
18
|
+
- ai.university.aiu@gmail.com
|
|
19
|
+
executables: []
|
|
20
|
+
extensions: []
|
|
21
|
+
extra_rdoc_files: []
|
|
22
|
+
files:
|
|
23
|
+
- LICENSE
|
|
24
|
+
- README.md
|
|
25
|
+
- conformance.rb
|
|
26
|
+
- lib/causalontology.rb
|
|
27
|
+
- lib/causalontology/canonical.rb
|
|
28
|
+
- lib/causalontology/ed25519.rb
|
|
29
|
+
- lib/causalontology/jcs.rb
|
|
30
|
+
- lib/causalontology/schema.rb
|
|
31
|
+
- lib/causalontology/semantics.rb
|
|
32
|
+
- lib/causalontology/signing.rb
|
|
33
|
+
- lib/causalontology/store.rb
|
|
34
|
+
homepage: https://github.com/ai-university-aiu/causalontology
|
|
35
|
+
licenses:
|
|
36
|
+
- Nonstandard
|
|
37
|
+
metadata:
|
|
38
|
+
homepage_uri: https://github.com/ai-university-aiu/causalontology
|
|
39
|
+
source_code_uri: https://github.com/ai-university-aiu/causalontology/tree/main/bindings/ruby
|
|
40
|
+
documentation_uri: https://github.com/ai-university-aiu/causalontology/tree/main/spec
|
|
41
|
+
license_note: The attribution always; no profit, no problem license. (Apache License
|
|
42
|
+
2.0 text) - see LICENSE and NOTICE in the repository root.
|
|
43
|
+
post_install_message:
|
|
44
|
+
rdoc_options: []
|
|
45
|
+
require_paths:
|
|
46
|
+
- lib
|
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
48
|
+
requirements:
|
|
49
|
+
- - ">="
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '3.0'
|
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
53
|
+
requirements:
|
|
54
|
+
- - ">="
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
version: '0'
|
|
57
|
+
requirements: []
|
|
58
|
+
rubygems_version: 3.5.22
|
|
59
|
+
signing_key:
|
|
60
|
+
specification_version: 4
|
|
61
|
+
summary: The Ruby binding of the Causalontology standard
|
|
62
|
+
test_files: []
|