causalontology 2.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.
data/conformance.rb ADDED
@@ -0,0 +1,1179 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: false
3
+
4
+ # The Causalontology conformance runner for causalontology-ruby (spec 2.0.0).
5
+ #
6
+ # Runs every vector in conformance/vectors/ against the Ruby binding. An
7
+ # implementation is conformant if and only if it passes every vector; this
8
+ # runner exits nonzero on any failure. It mirrors
9
+ # bindings/python/tests/run_conformance.py exactly: same fixtures, same
10
+ # expected results. Vectors are the whole-word 2.0.0 baseline (Principle P7):
11
+ # V01-V38 re-frozen unaltered in meaning, V39-V107 new.
12
+ #
13
+ # Symbolic names still used inside this harness's own behavioral constructions
14
+ # ("occurrent:A", key "alice") are normalized deterministically - symbolic
15
+ # object ids become scheme:sha256(name), and symbolic key names become real
16
+ # Ed25519 keypairs seeded from sha256("key:" + name).
17
+
18
+ require "json"
19
+ require "digest"
20
+ require "set"
21
+ require_relative "lib/causalontology"
22
+
23
+ C = Causalontology
24
+ ROOT = ENV["CAUSALONTOLOGY_ROOT"] || File.expand_path("../..", __dir__)
25
+ VECDIR = File.join(ROOT, "conformance", "vectors")
26
+
27
+ # ---------------------------------------------------------------------------
28
+ # assertion helper
29
+ # ---------------------------------------------------------------------------
30
+ class VectorFailure < StandardError; end
31
+
32
+ def assert(cond, msg = "assertion failed")
33
+ raise VectorFailure, msg.to_s unless cond
34
+ end
35
+
36
+ # ---------------------------------------------------------------------------
37
+ # whole-word scheme normalization (Principle P7)
38
+ # ---------------------------------------------------------------------------
39
+ SCHEMES = ["occurrent", "causal_relation_object", "continuant", "realizable",
40
+ "assertion", "enrichment", "retraction", "succession",
41
+ "stratum", "bridge", "port", "conduit", "quality",
42
+ "token_individual", "token_occurrence", "state_assertion",
43
+ "token_causal_claim"].freeze
44
+ WHOLE_WORD = Set.new(SCHEMES) | Set.new(["ed25519"])
45
+ SCHEME_PREFIX = /\A(#{(SCHEMES + ["ed25519"]).join("|")}):/
46
+ HEX64 = /\A[0-9a-f]{64}\z/
47
+ KEYS = {}
48
+
49
+ # A real, deterministic Ed25519 keypair for a symbolic key name.
50
+ def key(name)
51
+ KEYS[name] ||= begin
52
+ seed = Digest::SHA256.digest("key:" + name)
53
+ C.keypair_from_seed(seed)
54
+ end
55
+ end
56
+
57
+ # Normalize one symbolic identifier to a well-formed one.
58
+ def sym(s)
59
+ scheme, name = s.split(":", 2)
60
+ if scheme == "ed25519"
61
+ return s if name.match?(HEX64) # a real key passes through
62
+ return key(name)[1]
63
+ end
64
+ return s if name.match?(HEX64)
65
+ scheme + ":" + Digest::SHA256.hexdigest(name)
66
+ end
67
+
68
+ # Recursively normalize symbolic identifiers and placeholders.
69
+ def normalize(x)
70
+ case x
71
+ when String
72
+ return "ab" * 64 if x == "<128 hex>"
73
+ return sym(x) if x.match?(SCHEME_PREFIX)
74
+ x
75
+ when Array
76
+ x.map { |v| normalize(v) }
77
+ when Hash
78
+ x.transform_values { |v| normalize(v) } # preserves key order
79
+ else
80
+ x
81
+ end
82
+ end
83
+
84
+ # Load vector n's JSON file (for its structured inputs).
85
+ def vec(n)
86
+ hits = Dir.glob(File.join(VECDIR, format("v%02d_*.json", n)))
87
+ assert hits.length == 1, "vector #{n} not found"
88
+ JSON.parse(File.read(hits[0]))
89
+ end
90
+
91
+ def vec_name(n)
92
+ File.basename(Dir.glob(File.join(VECDIR, format("v%02d_*.json", n)))[0], ".json")
93
+ end
94
+
95
+ TS = "2026-07-13T0%d:00:00Z"
96
+
97
+ # Build, timestamp, and sign a provenance record.
98
+ def signed(kind, body, who, ts_i = 0)
99
+ secret, pub = key(who)
100
+ rec = body.dup
101
+ rec["type"] = kind
102
+ rec["timestamp"] = format(TS, ts_i) unless rec.key?("timestamp")
103
+ if kind == "succession"
104
+ rec["predecessor"] = pub unless rec.key?("predecessor")
105
+ else
106
+ rec["source"] = pub
107
+ end
108
+ C.sign_record(rec, secret, kind)
109
+ end
110
+
111
+ # A content object completed with its real content-addressed id.
112
+ def mk(obj)
113
+ o = obj.dup
114
+ o["id"] = C.identify(o)
115
+ o
116
+ end
117
+
118
+ # builders -------------------------------------------------------------------
119
+ def stratum(label, scheme, ordinal, unit = nil, governs = nil)
120
+ o = { "type" => "stratum", "label" => label, "scheme" => scheme,
121
+ "ordinal" => ordinal }
122
+ o["unit"] = unit if unit
123
+ o["governs"] = governs if governs
124
+ mk(o)
125
+ end
126
+
127
+ def occ(label, stratum_id = nil, category = "event")
128
+ o = { "type" => "occurrent", "label" => label, "category" => category }
129
+ o["stratum"] = stratum_id if stratum_id
130
+ mk(o)
131
+ end
132
+
133
+ def cnt(label, category = "object")
134
+ mk({ "type" => "continuant", "label" => label, "category" => category })
135
+ end
136
+
137
+ def cro(causes, effects, **kw)
138
+ o = { "type" => "causal_relation_object", "causes" => causes,
139
+ "effects" => effects }
140
+ kw.each { |k, v| o[k.to_s] = v }
141
+ mk(o)
142
+ end
143
+
144
+ def bridge(coarse, fine, relation)
145
+ mk({ "type" => "bridge", "coarse" => coarse, "fine" => fine,
146
+ "relation" => relation })
147
+ end
148
+
149
+ def port(bearer, label, direction, accepts, realizable = nil)
150
+ o = { "type" => "port", "bearer" => bearer, "label" => label,
151
+ "direction" => direction, "accepts" => accepts }
152
+ o["realizable"] = realizable if realizable
153
+ mk(o)
154
+ end
155
+
156
+ def conduit(frm, to, carries, label: "conn", transform: nil)
157
+ o = { "type" => "conduit", "label" => label, "from" => frm, "to" => to,
158
+ "carries" => carries }
159
+ o["transform"] = transform if transform
160
+ mk(o)
161
+ end
162
+
163
+ def quality(label, datatype, unit = nil, stratum_id = nil)
164
+ o = { "type" => "quality", "label" => label, "datatype" => datatype }
165
+ o["unit"] = unit if unit
166
+ o["stratum"] = stratum_id if stratum_id
167
+ mk(o)
168
+ end
169
+
170
+ def individual(instantiates, designator: nil, part_of: nil)
171
+ o = { "type" => "token_individual", "instantiates" => instantiates }
172
+ o["designator"] = designator if designator
173
+ o["part_of"] = part_of if part_of
174
+ mk(o)
175
+ end
176
+
177
+ def token(instantiates, interval, participants: nil, locus: nil)
178
+ o = { "type" => "token_occurrence", "instantiates" => instantiates,
179
+ "interval" => interval }
180
+ o["participants"] = participants if participants
181
+ o["locus"] = locus if locus
182
+ mk(o)
183
+ end
184
+
185
+ def state(subject, qual, value, interval)
186
+ mk({ "type" => "state_assertion", "subject" => subject, "quality" => qual,
187
+ "value" => value, "interval" => interval })
188
+ end
189
+
190
+ def tcc(causes, effects, covering_law: nil, actual_delay: nil,
191
+ counterfactual: nil)
192
+ o = { "type" => "token_causal_claim", "causes" => causes,
193
+ "effects" => effects }
194
+ o["covering_law"] = covering_law if covering_law
195
+ o["actual_delay"] = actual_delay if actual_delay
196
+ o["counterfactual"] = counterfactual unless counterfactual.nil?
197
+ mk(o)
198
+ end
199
+
200
+ # ---------------------------------------------------------------------------
201
+ # internal sanity checks (not conformance vectors)
202
+ # ---------------------------------------------------------------------------
203
+ def internal_checks
204
+ # RFC 8032, TEST 1 known-answer
205
+ sk = ["9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60"].pack("H*")
206
+ pk = C::Ed25519.secret_to_public(sk)
207
+ assert pk.unpack1("H*") ==
208
+ "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
209
+ pk.unpack1("H*")
210
+ sig = C::Ed25519.sign(sk, "")
211
+ assert C::Ed25519.verify(pk, "", sig)
212
+ assert !C::Ed25519.verify(pk, "x", sig)
213
+ # JCS basics
214
+ assert C::Jcs.encode({ "b" => 2, "a" => 1 }) == '{"a":1,"b":2}'
215
+ assert C::Jcs.encode(1.0) == "1"
216
+ assert C::Jcs.encode(6.000) == "6"
217
+ assert C::Jcs.encode(0.7) == "0.7"
218
+ # fixed unit constants
219
+ assert C.to_seconds(1, "months") == 2629746
220
+ assert C.to_seconds(1, "years") == 31556952
221
+ end
222
+
223
+ # ---------------------------------------------------------------------------
224
+ # V01 - V38: the whole-word re-freeze of the 1.0.0 suite (unaltered in meaning)
225
+ # ---------------------------------------------------------------------------
226
+ def v01
227
+ inp = normalize(vec(1)["input"])
228
+ ok, why = C.validate_schema(inp)
229
+ assert ok, why
230
+ ok, why = C.validate_semantics(inp)
231
+ assert ok, why
232
+ end
233
+
234
+ def v02
235
+ inp = normalize(vec(2)["input"])
236
+ ok, _why = C.validate_schema(inp)
237
+ assert ok
238
+ ok, _why = C.validate_semantics(inp)
239
+ assert ok
240
+ partial, missing = C.is_partial(inp)
241
+ assert partial && missing == vec(2)["expect"]["missing"], missing
242
+ end
243
+
244
+ def schema_fails(n, must_mention)
245
+ inp = normalize(vec(n)["input"])
246
+ ok, why = C.validate_schema(inp)
247
+ assert !ok, "expected schema-invalid"
248
+ assert why.any? { |w| w.include?(must_mention) }, why
249
+ end
250
+
251
+ def v03; schema_fails(3, "effects"); end
252
+ def v04; schema_fails(4, "causes"); end
253
+ def v05; schema_fails(5, "modality"); end
254
+ def v06; schema_fails(6, "colour"); end
255
+ def v07; schema_fails(7, "causes"); end
256
+
257
+ def v08
258
+ ok, why = C.validate_schema(normalize(vec(8)["input"]))
259
+ assert ok, why
260
+ end
261
+
262
+ def v09; schema_fails(9, "label"); end
263
+ def v10; schema_fails(10, "category"); end
264
+
265
+ def v11
266
+ ok, why = C.validate_schema(normalize(vec(11)["input"]))
267
+ assert ok, why
268
+ end
269
+
270
+ def v12; schema_fails(12, "confidence"); end
271
+
272
+ def v13
273
+ inp = normalize(vec(13)["input"])
274
+ ok, why = C.validate_schema(inp)
275
+ assert ok, why
276
+ ok, why = C.validate_semantics(inp)
277
+ assert ok, why
278
+ end
279
+
280
+ def semantics_fails(n, must_mention)
281
+ inp = normalize(vec(n)["input"])
282
+ ok, why = C.validate_semantics(inp)
283
+ assert !ok, "expected semantically-invalid"
284
+ assert why.any? { |w| w.include?(must_mention) }, why
285
+ end
286
+
287
+ def v14
288
+ inp = normalize(vec(14)["input"])
289
+ ok, _why = C.validate_schema(inp)
290
+ assert ok
291
+ semantics_fails(14, "minimum_delay")
292
+ end
293
+
294
+ def v15; semantics_fails(15, "acyclic"); end
295
+ def v16; semantics_fails(16, "acyclic"); end
296
+
297
+ def v17
298
+ v = vec(17)
299
+ parent = normalize(v["given"]["parent"])
300
+ child = normalize(v["input"])
301
+ ok, reason = C.refinement_valid(child, parent)
302
+ assert !ok && reason.include?("rival"), reason
303
+ end
304
+
305
+ def v18; semantics_fails(18, "not a legal field"); end
306
+ def v19; semantics_fails(19, "language-tagged"); end
307
+
308
+ def v20
309
+ dog = sym("continuant:dog")
310
+ mam = sym("continuant:mammal")
311
+ ani = sym("continuant:animal")
312
+ enrich = lambda do |about, entry, i|
313
+ signed("enrichment",
314
+ { "about" => about, "field" => "subsumes", "entry" => entry },
315
+ "taxo", i)
316
+ end
317
+ s = C::InMemoryStore.new(enforcing: true)
318
+ s.put_record(enrich.call(dog, mam, 1))
319
+ s.put_record(enrich.call(mam, ani, 2))
320
+ begin
321
+ s.put_record(enrich.call(ani, dog, 3))
322
+ raise VectorFailure, "enforcing store accepted a cycle"
323
+ rescue C::RejectedWrite => e
324
+ assert e.message.include?("cycle"), e.message
325
+ end
326
+ s2 = C::InMemoryStore.new(enforcing: true)
327
+ s2.put_record(enrich.call(dog, mam, 1))
328
+ s2.put_record(enrich.call(mam, ani, 2))
329
+ bad = enrich.call(ani, dog, 3)
330
+ s2.force_merge_record(bad)
331
+ _active, excluded = s2.active_taxonomy_edges("subsumes")
332
+ assert excluded.length == 1 && excluded[0]["id"] == bad["id"]
333
+ repair = s2.gaps("inconsistent_hierarchy")
334
+ assert repair.any? { |g| g["id"] == bad["id"] }
335
+ end
336
+
337
+ def adm(n)
338
+ g = vec(n)["given"]
339
+ c = { "causes" => [sym("occurrent:c")], "effects" => [sym("occurrent:e")],
340
+ "temporal" => g["temporal"] }
341
+ C.admissible(c, g["elapsed_seconds"])
342
+ end
343
+
344
+ def v21; assert adm(21) == true; end
345
+ def v22; assert adm(22) == false; end
346
+ def v23; assert adm(23) == true; end
347
+
348
+ def v24
349
+ v = vec(24)
350
+ assert C.identify(normalize(v["inputA"])) == C.identify(normalize(v["inputB"]))
351
+ end
352
+
353
+ def v25
354
+ v = vec(25)
355
+ assert C.identify(normalize(v["inputA"])) == C.identify(normalize(v["inputB"]))
356
+ end
357
+
358
+ def v26
359
+ s = C::InMemoryStore.new
360
+ obj = { "type" => "occurrent", "label" => "press_button",
361
+ "category" => "action" }
362
+ a = s.put(obj.dup)
363
+ b = s.put(obj.dup)
364
+ assert a == b && s.objects.length == 1
365
+ end
366
+
367
+ def v27
368
+ s = C::InMemoryStore.new
369
+ occid = s.put({ "type" => "occurrent", "label" => "press_button",
370
+ "category" => "action" })
371
+ entry = { "lang" => "en", "text" => "press the button" }
372
+ r1 = signed("enrichment", { "about" => occid, "field" => "aliases",
373
+ "entry" => entry }, "alice", 1)
374
+ r2 = signed("enrichment", { "about" => occid, "field" => "aliases",
375
+ "entry" => entry }, "bob", 2)
376
+ assert s.put_record(r1) != s.put_record(r2)
377
+ view = s.get(occid)["enrichments"]["aliases"]
378
+ assert view.length == 1 && view[0]["contributors"].length == 2
379
+ end
380
+
381
+ def v28
382
+ s = C::InMemoryStore.new
383
+ claim = { "type" => "causal_relation_object", "causes" => [sym("occurrent:A")],
384
+ "effects" => [sym("occurrent:B")], "modality" => "sufficient" }
385
+ i1 = s.put(claim.dup)
386
+ i2 = s.put(claim.dup)
387
+ assert i1 == i2 && s.objects.length == 1
388
+ [["lab1", 1], ["lab2", 2]].each do |who, ts|
389
+ s.put_record(signed("assertion",
390
+ { "about" => i1, "evidence_type" => "observation",
391
+ "strength" => 0.8, "confidence" => 0.8 }, who, ts))
392
+ end
393
+ assert s.assertions_about(i1).length == 2
394
+ end
395
+
396
+ def v29
397
+ rec = signed("assertion", { "about" => sym("causal_relation_object:demo"),
398
+ "evidence_type" => "intervention",
399
+ "strength" => 0.7, "confidence" => 0.9 }, "signer")
400
+ assert C.verify_record(rec) == true
401
+ end
402
+
403
+ def v30
404
+ rec = signed("assertion", { "about" => sym("causal_relation_object:demo"),
405
+ "evidence_type" => "intervention",
406
+ "strength" => 0.7, "confidence" => 0.9 }, "signer")
407
+ tampered = rec.merge("confidence" => 0.1)
408
+ assert C.verify_record(tampered) == false
409
+ end
410
+
411
+ def v31
412
+ s = C::InMemoryStore.new
413
+ x = s.put({ "type" => "causal_relation_object", "causes" => [sym("occurrent:A")],
414
+ "effects" => [sym("occurrent:B")] })
415
+ a = signed("assertion", { "about" => x, "evidence_type" => "observation",
416
+ "confidence" => 0.8 }, "lab1", 1)
417
+ s.put_record(a)
418
+ s.put_record(signed("retraction", { "retracts" => a["id"] }, "lab1", 2))
419
+ assert s.assertions_about(x) == []
420
+ hist = s.assertions_about(x, include_retracted: true)
421
+ assert hist.length == 1 && hist[0]["retracted"] == true
422
+ foreign = signed("retraction", { "retracts" => a["id"] }, "mallory", 3)
423
+ begin
424
+ s.put_record(foreign)
425
+ raise VectorFailure, "foreign retraction accepted"
426
+ rescue C::RejectedWrite
427
+ # expected: only the source or its lineage may retract
428
+ end
429
+ end
430
+
431
+ def v32
432
+ s = C::InMemoryStore.new
433
+ occid = s.put({ "type" => "occurrent", "label" => "press_button",
434
+ "category" => "action" })
435
+ e = signed("enrichment", { "about" => occid, "field" => "aliases",
436
+ "entry" => { "lang" => "ja", "text" => "botan" } },
437
+ "bob", 1)
438
+ s.put_record(e)
439
+ before = s.get(occid)["enrichments"]["aliases"] || []
440
+ assert before.length == 1
441
+ s.put_record(signed("retraction", { "retracts" => e["id"] }, "bob", 2))
442
+ after = s.get(occid)["enrichments"]["aliases"] || []
443
+ assert after == []
444
+ hist = s.get(occid, view: "history")["enrichments"]["aliases"] || []
445
+ assert hist.length == 1
446
+ end
447
+
448
+ def v33
449
+ s = C::InMemoryStore.new
450
+ k1 = key("K1")[1]
451
+ k2 = key("K2")[1]
452
+ a = signed("assertion", { "about" => sym("causal_relation_object:claim"),
453
+ "evidence_type" => "observation",
454
+ "confidence" => 0.9 }, "K1", 1)
455
+ s.put_record(a)
456
+ s.put_record(signed("succession", { "successor" => k2 }, "K1", 2))
457
+ assert s.lineage(k2).include?(k1) && s.lineage(k1).include?(k2)
458
+ s.put_record(signed("retraction", { "retracts" => a["id"] }, "K2", 3))
459
+ assert s.assertions_about(sym("causal_relation_object:claim")) == []
460
+ end
461
+
462
+ def v34
463
+ g = normalize(vec(34)["given"])
464
+ assert C.conflicts(g["A"], g["B"]) == true
465
+ end
466
+
467
+ def v35
468
+ g = normalize(vec(35)["given"])
469
+ assert C.conflicts(g["A"], g["B"]) == false
470
+ end
471
+
472
+ def v36
473
+ a = sym("occurrent:A"); b = sym("occurrent:B")
474
+ c = sym("occurrent:C"); d = sym("occurrent:D")
475
+ m1 = { "id" => sym("causal_relation_object:m1"), "causes" => [a], "effects" => [b] }
476
+ m2 = { "id" => sym("causal_relation_object:m2"), "causes" => [b], "effects" => [c] }
477
+ m3 = { "id" => sym("causal_relation_object:m3"), "causes" => [d], "effects" => [c] }
478
+ parent = { "causes" => [a], "effects" => [c],
479
+ "mechanism" => [m1["id"], m2["id"]] }
480
+ assert C.hierarchy_consistent(parent, { m1["id"] => m1, m2["id"] => m2 }) == "consistent"
481
+ parent2 = parent.merge("mechanism" => [m1["id"], m3["id"]])
482
+ assert C.hierarchy_consistent(parent2, { m1["id"] => m1, m3["id"] => m3 }) == "inconsistent"
483
+ assert C.hierarchy_consistent(parent, { m1["id"] => m1 }) == "indeterminate"
484
+ end
485
+
486
+ def v37
487
+ s = C::InMemoryStore.new
488
+ occid = s.put({ "type" => "occurrent", "label" => "press_button",
489
+ "category" => "action" })
490
+ s.put_record(signed("enrichment",
491
+ { "about" => occid, "field" => "aliases",
492
+ "entry" => { "lang" => "en",
493
+ "text" => "Press the Button" } },
494
+ "alice", 1))
495
+ assert s.resolve("Press The Button", "en") == [occid]
496
+ assert s.resolve("press_button", "en")[0] == occid
497
+ end
498
+
499
+ def v38
500
+ s = C::InMemoryStore.new
501
+ parent = s.put({ "type" => "causal_relation_object", "causes" => [sym("occurrent:A")],
502
+ "effects" => [sym("occurrent:B")] })
503
+ gaps = s.gaps("missing_field").map { |g| g["id"] }
504
+ assert gaps.include?(parent)
505
+ refinement = s.put({ "type" => "causal_relation_object", "causes" => [sym("occurrent:A")],
506
+ "effects" => [sym("occurrent:B")],
507
+ "temporal" => { "minimum_delay" => 0, "maximum_delay" => 1,
508
+ "unit" => "seconds" },
509
+ "modality" => "sufficient", "refines" => parent })
510
+ gaps = s.gaps("missing_field").map { |g| g["id"] }
511
+ assert !gaps.include?(parent), "the gap did not close"
512
+ assert !gaps.include?(refinement), "the refinement itself must be complete"
513
+ end
514
+
515
+ # ---------------------------------------------------------------------------
516
+ # V39 - V107: the 2.0.0 additions
517
+ # ---------------------------------------------------------------------------
518
+ def neuro
519
+ labels = { 4 => "macromolecular", 5 => "subcellular", 6 => "cellular",
520
+ 7 => "synaptic", 9 => "region", 14 => "community_and_society" }
521
+ labels.each_with_object({}) { |(o, l), h| h[o] = stratum(l, "neuroendocrine", o) }
522
+ end
523
+
524
+ def v39
525
+ st = stratum("cellular", "neuroendocrine", 6, "cell", ["cell_biology"])
526
+ ok, why = C.validate_schema(st)
527
+ assert ok, why
528
+ end
529
+
530
+ def v40
531
+ bad = mk({ "type" => "stratum", "label" => "cellular", "ordinal" => 6 })
532
+ ok, why = C.validate_schema(bad, "stratum")
533
+ assert !ok && why.any? { |w| w.include?("scheme") }, why
534
+ end
535
+
536
+ def v41
537
+ a = stratum("cellular", "neuroendocrine", 6)
538
+ b = stratum("neuronal", "neuroendocrine", 6)
539
+ [a, b].each do |x|
540
+ ok, why = C.validate_schema(x)
541
+ assert ok, why
542
+ end
543
+ assert a["id"] != b["id"]
544
+ end
545
+
546
+ def v42
547
+ s = neuro
548
+ s4p = stratum("molecular", "physics", 4)
549
+ c = occ("chronic_social_subordination", s[14]["id"])
550
+ e = occ("gene_expression", s4p["id"])
551
+ smap = { s[14]["id"] => s[14], s4p["id"] => s4p }
552
+ omap = { c["id"] => c, e["id"] => e }
553
+ parent = cro([c["id"]], [e["id"]])
554
+ assert C.classify_cro(parent, omap, smap) == "scheme_mismatch"
555
+ end
556
+
557
+ def v43
558
+ [stratum("macromolecular", "neuroendocrine", 4),
559
+ stratum("region", "neuroendocrine", 9)].each do |x|
560
+ ok, why = C.validate_schema(x)
561
+ assert ok, why
562
+ end
563
+ end
564
+
565
+ def v44
566
+ st = stratum("cellular", "neuroendocrine", 6)
567
+ o = occ("neuron_fires", st["id"])
568
+ ok, why = C.validate_schema(o)
569
+ assert ok, why
570
+ ok, why = C.validate_semantics(o)
571
+ assert ok, why
572
+ end
573
+
574
+ def v45
575
+ o = occ("press_button")
576
+ ok, why = C.validate_schema(o)
577
+ assert ok, why
578
+ e = occ("light_on")
579
+ parent = cro([o["id"]], [e["id"]])
580
+ assert C.classify_cro(parent, { o["id"] => o, e["id"] => e }, {}) == "unclassifiable"
581
+ end
582
+
583
+ def v46
584
+ s = neuro
585
+ a = occ("depolarization", s[5]["id"])
586
+ b = occ("depolarization", s[6]["id"])
587
+ assert a["id"] != b["id"]
588
+ end
589
+
590
+ def bridge_fixture(relation)
591
+ s = neuro
592
+ coarse = occ("action_potential_fires", s[6]["id"])
593
+ fine = [occ("sodium_channels_open", s[4]["id"]),
594
+ occ("sodium_influx", s[4]["id"])]
595
+ b = bridge(coarse["id"], fine.map { |f| f["id"] }, relation)
596
+ omap = { coarse["id"] => coarse }
597
+ fine.each { |f| omap[f["id"]] = f }
598
+ smap = { s[4]["id"] => s[4], s[6]["id"] => s[6] }
599
+ [b, omap, smap]
600
+ end
601
+
602
+ def valid_bridge(relation)
603
+ b, omap, smap = bridge_fixture(relation)
604
+ ok, why = C.validate_schema(b)
605
+ assert ok, why
606
+ ok, why = C.bridge_wellformed(b, omap, smap)
607
+ assert ok, why
608
+ end
609
+
610
+ def v47; valid_bridge("constitutes"); end
611
+ def v48; valid_bridge("aggregates"); end
612
+ def v49; valid_bridge("realizes"); end
613
+ def v50; valid_bridge("supervenes_on"); end
614
+
615
+ def v51
616
+ s = neuro
617
+ coarse = occ("x_coarse", s[4]["id"])
618
+ fine = occ("x_fine", s[6]["id"])
619
+ b = bridge(coarse["id"], [fine["id"]], "constitutes")
620
+ omap = { coarse["id"] => coarse, fine["id"] => fine }
621
+ smap = { s[4]["id"] => s[4], s[6]["id"] => s[6] }
622
+ ok, _ = C.bridge_wellformed(b, omap, smap)
623
+ assert !ok
624
+ end
625
+
626
+ def v52
627
+ s = neuro
628
+ coarse = occ("c", s[6]["id"])
629
+ f1 = occ("f1", s[4]["id"])
630
+ f2 = occ("f2", s[5]["id"])
631
+ b = bridge(coarse["id"], [f1["id"], f2["id"]], "constitutes")
632
+ omap = { coarse["id"] => coarse, f1["id"] => f1, f2["id"] => f2 }
633
+ smap = { s[4]["id"] => s[4], s[5]["id"] => s[5], s[6]["id"] => s[6] }
634
+ ok, _ = C.bridge_wellformed(b, omap, smap)
635
+ assert !ok
636
+ end
637
+
638
+ def v53
639
+ x = sym("occurrent:x")
640
+ y = sym("occurrent:y")
641
+ b1 = bridge(x, [y], "constitutes")
642
+ b2 = bridge(y, [x], "constitutes")
643
+ edges = {}
644
+ [b1, b2].each do |b|
645
+ b["fine"].each { |f| (edges[f] ||= []) << b["coarse"] }
646
+ end
647
+ assert C.has_cycle(edges) == true
648
+ end
649
+
650
+ def v54
651
+ a = stratum("cellular", "neuroendocrine", 6)
652
+ b = stratum("molecular", "physics", 4)
653
+ coarse = occ("c", a["id"])
654
+ fine = occ("f", b["id"])
655
+ br = bridge(coarse["id"], [fine["id"]], "constitutes")
656
+ omap = { coarse["id"] => coarse, fine["id"] => fine }
657
+ smap = { a["id"] => a, b["id"] => b }
658
+ ok, _ = C.bridge_wellformed(br, omap, smap)
659
+ assert !ok
660
+ end
661
+
662
+ def v55
663
+ s = neuro
664
+ coarse = occ("decision_made", s[6]["id"])
665
+ f1 = occ("cascade_a", s[4]["id"])
666
+ f2 = occ("cascade_b", s[4]["id"])
667
+ b1 = bridge(coarse["id"], [f1["id"]], "realizes")
668
+ b2 = bridge(coarse["id"], [f2["id"]], "realizes")
669
+ assert b1["id"] != b2["id"]
670
+ [b1, b2].each do |b|
671
+ ok, why = C.validate_schema(b)
672
+ assert ok, why
673
+ end
674
+ end
675
+
676
+ def reach_fixture
677
+ s = neuro
678
+ ap = occ("action_potential_fires", s[6]["id"])
679
+ nt = occ("neurotransmitter_released", s[6]["id"])
680
+ fa = occ("calcium_enters", s[4]["id"])
681
+ fb = occ("vesicle_fuses", s[4]["id"])
682
+ m1 = cro([fa["id"]], [fb["id"]])
683
+ parent = cro([ap["id"]], [nt["id"]], mechanism: [m1["id"]])
684
+ bridges = [bridge(ap["id"], [fa["id"]], "constitutes"),
685
+ bridge(nt["id"], [fb["id"]], "constitutes")]
686
+ [parent, { m1["id"] => m1 }, bridges]
687
+ end
688
+
689
+ def v56
690
+ parent, members, bridges = reach_fixture
691
+ assert C.hierarchy_consistent(parent, members, bridges) == "consistent"
692
+ end
693
+
694
+ def v57
695
+ parent, members, _ = reach_fixture
696
+ assert C.hierarchy_consistent(parent, members, []) == "inconsistent"
697
+ end
698
+
699
+ def v58
700
+ parent, members, bridges = reach_fixture
701
+ literal = C.hierarchy_consistent(parent, members, [])
702
+ bridged = C.hierarchy_consistent(parent, members, bridges)
703
+ assert literal != "consistent" && bridged == "consistent"
704
+ end
705
+
706
+ def classify(cause_ord, effect_ord)
707
+ s = neuro
708
+ c = occ("c", s[cause_ord]["id"])
709
+ e = occ("e", s[effect_ord]["id"])
710
+ smap = { s[cause_ord]["id"] => s[cause_ord], s[effect_ord]["id"] => s[effect_ord] }
711
+ omap = { c["id"] => c, e["id"] => e }
712
+ C.classify_cro(cro([c["id"]], [e["id"]]), omap, smap)
713
+ end
714
+
715
+ def v59; assert classify(6, 6) == "intra_stratal"; end
716
+ def v60; assert classify(6, 5) == "adjacent_stratal"; end
717
+ def v61; assert classify(14, 4) == "skipping"; end
718
+
719
+ def skip_fixture(cause_ord, effect_ord, **kw)
720
+ s = neuro
721
+ c = occ("c", s[cause_ord]["id"])
722
+ e = occ("e", s[effect_ord]["id"])
723
+ smap = { s[cause_ord]["id"] => s[cause_ord], s[effect_ord]["id"] => s[effect_ord] }
724
+ omap = { c["id"] => c, e["id"] => e }
725
+ parent = cro([c["id"]], [e["id"]], **kw)
726
+ [parent, C.classify_cro(parent, omap, smap)]
727
+ end
728
+
729
+ def v62
730
+ parent, cls = skip_fixture(14, 4)
731
+ assert C.skip_gaps(parent, cls) == ["incomplete_mechanism"]
732
+ end
733
+
734
+ def v63
735
+ parent, cls = skip_fixture(14, 4, skips: true)
736
+ assert C.skip_gaps(parent, cls) == []
737
+ end
738
+
739
+ def v64
740
+ parent, cls = skip_fixture(14, 4, skips: true,
741
+ mechanism: [sym("causal_relation_object:m")])
742
+ assert C.skip_gaps(parent, cls) == ["contradictory_skip"]
743
+ ok, why = C.validate_semantics(parent)
744
+ assert !ok && why.any? { |w| w.include?("contradictory_skip") }
745
+ end
746
+
747
+ def v65
748
+ parent, cls = skip_fixture(6, 6, skips: true)
749
+ assert C.skip_gaps(parent, cls) == ["vacuous_skip"]
750
+ end
751
+
752
+ def v66
753
+ s = neuro
754
+ c = occ("c", s[14]["id"])
755
+ e = occ("e", s[4]["id"])
756
+ absent = cro([c["id"]], [e["id"]])
757
+ false_ = cro([c["id"]], [e["id"]], skips: false)
758
+ assert absent["id"] != false_["id"]
759
+ end
760
+
761
+ def v67
762
+ s = neuro
763
+ c1 = occ("c1", s[4]["id"])
764
+ c2 = occ("c2", s[6]["id"])
765
+ e = occ("e", s[6]["id"])
766
+ parent = cro([c1["id"], c2["id"]], [e["id"]])
767
+ assert C.endpoints_mixed(parent, { c1["id"] => c1, c2["id"] => c2, e["id"] => e }) == true
768
+ end
769
+
770
+ def v68
771
+ parent = cro([sym("occurrent:a")], [sym("occurrent:b")], modality: "enabling")
772
+ ok, why = C.validate_schema(parent)
773
+ assert ok, why
774
+ end
775
+
776
+ def v69
777
+ a = { "causes" => [sym("occurrent:a")], "effects" => [sym("occurrent:b")],
778
+ "modality" => "enabling" }
779
+ b = { "causes" => [sym("occurrent:a")], "effects" => [sym("occurrent:b")],
780
+ "modality" => "sufficient" }
781
+ assert C.conflicts(a, b) == false
782
+ end
783
+
784
+ def v70
785
+ a = { "causes" => [sym("occurrent:a")], "effects" => [sym("occurrent:b")],
786
+ "modality" => "enabling" }
787
+ b = { "causes" => [sym("occurrent:a")], "effects" => [sym("occurrent:b")],
788
+ "modality" => "preventive" }
789
+ assert C.conflicts(a, b) == true
790
+ end
791
+
792
+ def v71
793
+ b = cnt("hippocampus")
794
+ p = port(b["id"], "perforant_path", "in", [sym("occurrent:signal")])
795
+ ok, why = C.validate_schema(p)
796
+ assert ok, why
797
+ end
798
+
799
+ def v72
800
+ b = cnt("hippocampus")["id"]
801
+ x = sym("occurrent:signal")
802
+ assert port(b, "perforant_path", "in", [x])["id"] !=
803
+ port(b, "fornix", "in", [x])["id"]
804
+ end
805
+
806
+ def conduit_fixture(transform: false, bad_carry: false, in_from: false)
807
+ x = sym("occurrent:motor_command")
808
+ y = sym("occurrent:error_signal")
809
+ z = sym("occurrent:unrelated")
810
+ m1 = cnt("motor_cortex")["id"]
811
+ m2 = cnt("spinal_neuron")["id"]
812
+ frm = port(m1, "out_port", in_from ? "in" : "out", [x])
813
+ to = port(m2, "in_port", "in", transform ? [y] : [x])
814
+ carries = bad_carry ? [z] : [x]
815
+ xform = nil
816
+ cro_map = {}
817
+ if transform
818
+ law = cro([x], [y])
819
+ cro_map[law["id"]] = law
820
+ xform = law["id"]
821
+ end
822
+ c = conduit(frm["id"], to["id"], carries, transform: xform)
823
+ [c, { frm["id"] => frm, to["id"] => to }, cro_map]
824
+ end
825
+
826
+ def v73
827
+ c, pmap, _ = conduit_fixture
828
+ ok, why = C.validate_schema(c)
829
+ assert ok, why
830
+ ok, why = C.conduit_wellformed(c, pmap)
831
+ assert ok, why
832
+ end
833
+
834
+ def v74
835
+ c, pmap, cmap = conduit_fixture(transform: true)
836
+ ok, why = C.validate_schema(c)
837
+ assert ok, why
838
+ ok, why = C.conduit_wellformed(c, pmap, cmap)
839
+ assert ok, why
840
+ end
841
+
842
+ def v75
843
+ c, pmap, _ = conduit_fixture(bad_carry: true)
844
+ ok, _ = C.conduit_wellformed(c, pmap)
845
+ assert !ok
846
+ end
847
+
848
+ def v76
849
+ c, pmap, _ = conduit_fixture(in_from: true)
850
+ ok, _ = C.conduit_wellformed(c, pmap)
851
+ assert !ok
852
+ end
853
+
854
+ def v77
855
+ c, pmap, cmap = conduit_fixture(transform: true)
856
+ ok, why = C.conduit_wellformed(c, pmap, cmap)
857
+ assert ok, why
858
+ law = cmap.values[0]
859
+ assert !c["carries"].include?(law["effects"][0])
860
+ end
861
+
862
+ def rlz(bearer, kind, label = nil)
863
+ o = { "type" => "realizable", "kind" => kind, "bearer" => bearer }
864
+ o["label"] = label if label
865
+ mk(o)
866
+ end
867
+
868
+ def v78
869
+ b = cnt("hippocampus")["id"]
870
+ assert rlz(b, "disposition", "long_term_potentiation")["id"] !=
871
+ rlz(b, "disposition", "pattern_separation")["id"]
872
+ end
873
+
874
+ def v79
875
+ b = cnt("hippocampus")["id"]
876
+ u1 = rlz(b, "disposition")
877
+ u2 = rlz(b, "disposition")
878
+ ok, why = C.validate_schema(u1)
879
+ assert ok, why
880
+ assert u1["id"] == u2["id"]
881
+ assert rlz(b, "disposition", "some_function")["id"] != u1["id"]
882
+ end
883
+
884
+ def v80
885
+ parent = occ("fires")
886
+ child = occ("fires_action_potential")
887
+ e = { "type" => "enrichment", "about" => child["id"],
888
+ "field" => "occurrent_subsumes", "entry" => parent["id"] }
889
+ ok, why = C.validate_semantics(e)
890
+ assert ok, why
891
+ end
892
+
893
+ def v81
894
+ a = sym("occurrent:a")
895
+ b = sym("occurrent:b")
896
+ assert C.has_cycle({ a => [b], b => [a] }) == true
897
+ end
898
+
899
+ def v82
900
+ whole = occ("eat")
901
+ part = occ("chew")
902
+ e = { "type" => "enrichment", "about" => part["id"],
903
+ "field" => "occurrent_part_of", "entry" => whole["id"] }
904
+ ok, why = C.validate_semantics(e)
905
+ assert ok, why
906
+ end
907
+
908
+ def v83
909
+ legal_kinds, shape = C::Semantics::ENRICHMENT_FIELDS["occurrent_part_of"]
910
+ assert shape == "occurrent" && legal_kinds == ["occurrent"]
911
+ s = C::InMemoryStore.new
912
+ s.put(occ("eat"))
913
+ s.put(occ("chew"))
914
+ assert s.objects.values.none? { |o| o["type"] == "causal_relation_object" }
915
+ end
916
+
917
+ def v84
918
+ s = neuro
919
+ a = occ("run", s[9]["id"])
920
+ b = occ("sprint", s[6]["id"])
921
+ assert a["stratum"] != b["stratum"]
922
+ end
923
+
924
+ def v85
925
+ c = cnt("human_patient")
926
+ ti = individual(c["id"], designator: "salted_hash_abc123")
927
+ ok, why = C.validate_schema(ti)
928
+ assert ok, why
929
+ end
930
+
931
+ def v86
932
+ bad = mk({ "type" => "token_individual", "designator" => "x" })
933
+ ok, why = C.validate_schema(bad, "token_individual")
934
+ assert !ok && why.any? { |w| w.include?("instantiates") }, why
935
+ end
936
+
937
+ def v87
938
+ c = cnt("human_patient")["id"]
939
+ assert individual(c, designator: "hash_a")["id"] !=
940
+ individual(c, designator: "hash_b")["id"]
941
+ end
942
+
943
+ def v88
944
+ o = occ("bilateral_hippocampal_resection")
945
+ t = token(o["id"], { "start" => "1953-08-25T00:00:00Z",
946
+ "end" => "1953-08-25T00:00:00Z" })
947
+ ok, why = C.validate_schema(t)
948
+ assert ok, why
949
+ end
950
+
951
+ def v89
952
+ o = occ("amnesia_onset")["id"]
953
+ bounded = token(o, { "start" => "1953-08-25T00:00:00Z",
954
+ "end" => "1953-08-26T00:00:00Z" })
955
+ instantaneous = token(o, { "start" => "1953-08-25T00:00:00Z" })
956
+ ongoing = token(o, { "start" => "1953-08-25T00:00:00Z", "open" => true })
957
+ assert Set.new([bounded["id"], instantaneous["id"], ongoing["id"]]).length == 3
958
+ end
959
+
960
+ def v90
961
+ o = occ("resection")["id"]
962
+ c = cnt("human_patient")["id"]
963
+ patient = individual(c, designator: "p")["id"]
964
+ surgeon = individual(c, designator: "s")["id"]
965
+ t = token(o, { "start" => "1953-08-25T00:00:00Z" },
966
+ participants: [{ "role" => "patient", "filler" => patient },
967
+ { "role" => "agent", "filler" => surgeon }])
968
+ ok, why = C.validate_schema(t)
969
+ assert ok, why
970
+ end
971
+
972
+ def v91
973
+ q = quality("cortisol_concentration", "quantity", "ug/dL")
974
+ ok, why = C.validate_schema(q)
975
+ assert ok, why
976
+ end
977
+
978
+ def state_fixture(datatype, value, unit = nil)
979
+ q = quality("cortisol_concentration", datatype, unit)
980
+ c = cnt("human_patient")["id"]
981
+ subj = individual(c, designator: "p")["id"]
982
+ st = state(subj, q["id"], value,
983
+ { "start" => "2026-01-01T00:00:00Z", "end" => "2026-01-01T01:00:00Z" })
984
+ [st, q]
985
+ end
986
+
987
+ def v92
988
+ st, q = state_fixture("quantity", { "quantity" => 15.0, "unit" => "ug/dL" },
989
+ "ug/dL")
990
+ ok, why = C.validate_schema(st)
991
+ assert ok, why
992
+ assert C.state_gaps(st, q) == []
993
+ end
994
+
995
+ def v93
996
+ st, q = state_fixture("categorical", { "categorical" => "elevated" })
997
+ ok, why = C.validate_schema(st)
998
+ assert ok, why
999
+ assert C.state_gaps(st, q) == []
1000
+ end
1001
+
1002
+ def v94
1003
+ st, q = state_fixture("boolean", { "boolean" => true })
1004
+ ok, why = C.validate_schema(st)
1005
+ assert ok, why
1006
+ assert C.state_gaps(st, q) == []
1007
+ end
1008
+
1009
+ def v95
1010
+ st, q = state_fixture("quantity", { "categorical" => "elevated" }, "ug/dL")
1011
+ assert C.state_gaps(st, q) == ["value_type_mismatch"]
1012
+ end
1013
+
1014
+ def v96
1015
+ st, q = state_fixture("quantity", { "quantity" => 15.0, "unit" => "mg/dL" },
1016
+ "ug/dL")
1017
+ assert C.state_gaps(st, q) == ["unit_mismatch"]
1018
+ end
1019
+
1020
+ def law_and_tokens
1021
+ o_cause = occ("resection")
1022
+ o_effect = occ("amnesia_onset")
1023
+ law = cro([o_cause["id"]], [o_effect["id"]],
1024
+ temporal: { "minimum_delay" => 0, "maximum_delay" => 1, "unit" => "days" },
1025
+ modality: "sufficient")
1026
+ t_cause = token(o_cause["id"], { "start" => "1953-08-25T00:00:00Z" })
1027
+ t_effect = token(o_effect["id"], { "start" => "1953-08-25T00:00:00Z",
1028
+ "open" => true })
1029
+ [law, o_cause, o_effect, t_cause, t_effect]
1030
+ end
1031
+
1032
+ def v97
1033
+ law, _, _, tc, te = law_and_tokens
1034
+ claim = tcc([tc["id"]], [te["id"]], covering_law: law["id"],
1035
+ actual_delay: { "duration" => 0, "unit" => "instant" },
1036
+ counterfactual: true)
1037
+ ok, why = C.validate_schema(claim)
1038
+ assert ok, why
1039
+ end
1040
+
1041
+ def v98
1042
+ _, _, _, tc, te = law_and_tokens
1043
+ claim = tcc([tc["id"]], [te["id"]])
1044
+ ok, why = C.validate_schema(claim)
1045
+ assert ok, why
1046
+ assert !claim.key?("covering_law")
1047
+ end
1048
+
1049
+ def v99
1050
+ law, _, _, _, _ = law_and_tokens
1051
+ assert C.delay_within_window({ "duration" => 0, "unit" => "instant" },
1052
+ law["temporal"]) == true
1053
+ end
1054
+
1055
+ def v100
1056
+ temporal = { "minimum_delay" => 0, "maximum_delay" => 1, "unit" => "hours" }
1057
+ assert C.delay_within_window({ "duration" => 5, "unit" => "days" }, temporal) == false
1058
+ end
1059
+
1060
+ def v101
1061
+ o = occ("x")["id"]
1062
+ cause = token(o, { "start" => "2026-01-02T00:00:00Z" })
1063
+ effect = token(o, { "start" => "2026-01-01T00:00:00Z" })
1064
+ claim = tcc([cause["id"]], [effect["id"]])
1065
+ assert C.retrocausal(claim, { cause["id"] => cause, effect["id"] => effect }) == true
1066
+ end
1067
+
1068
+ def v102
1069
+ other = cro([sym("occurrent:foo")], [sym("occurrent:bar")])
1070
+ _, _, _, tc, te = law_and_tokens
1071
+ claim = tcc([tc["id"]], [te["id"]], covering_law: other["id"])
1072
+ assert C.covering_law_mismatch(claim, { tc["id"] => tc, te["id"] => te }, other) == true
1073
+ end
1074
+
1075
+ def v103
1076
+ a = signed("assertion", { "about" => sym("token_occurrence:t"),
1077
+ "evidence_type" => "observation",
1078
+ "confidence" => 0.9 }, "signer")
1079
+ ok, why = C.validate_schema(a)
1080
+ assert ok, why
1081
+ end
1082
+
1083
+ def v104
1084
+ ev = [sym("token_occurrence:t1"), sym("token_causal_claim:c1")]
1085
+ base = { "type" => "assertion", "about" => sym("causal_relation_object:law"),
1086
+ "source" => key("signer")[1], "evidence_type" => "intervention",
1087
+ "strength" => 0.95, "confidence" => 0.99,
1088
+ "timestamp" => "2026-07-14T00:00:00Z" }
1089
+ a = base.merge("evidenced_by" => ev)
1090
+ ok, why = C.validate_schema(a.merge("id" => C.identify(a)))
1091
+ assert ok, why
1092
+ assert C.identify(a) != C.identify(base) # evidenced_by is identity-bearing
1093
+ end
1094
+
1095
+ def v105
1096
+ a = signed("assertion", { "about" => sym("causal_relation_object:law"),
1097
+ "evidence_type" => "simulation",
1098
+ "confidence" => 0.5 }, "signer")
1099
+ ok, why = C.validate_schema(a)
1100
+ assert ok, why
1101
+ rank = { "intervention" => 0, "observation" => 1, "simulation" => 2 }
1102
+ assert rank["intervention"] < rank["observation"] &&
1103
+ rank["observation"] < rank["simulation"]
1104
+ end
1105
+
1106
+ def v106
1107
+ scan = nil
1108
+ scan = lambda do |node, ids|
1109
+ case node
1110
+ when String
1111
+ m = node.match(/\A([a-z0-9_]+):[0-9a-f]{64}\z/)
1112
+ ids << m[1] if m
1113
+ when Array
1114
+ node.each { |x| scan.call(x, ids) }
1115
+ when Hash
1116
+ node.each_value { |x| scan.call(x, ids) }
1117
+ end
1118
+ end
1119
+ (1..38).each do |n|
1120
+ ids = []
1121
+ scan.call(vec(n), ids)
1122
+ ids.each do |scheme|
1123
+ assert WHOLE_WORD.include?(scheme),
1124
+ "V106: abbreviated scheme #{scheme.inspect} in vector #{n}"
1125
+ end
1126
+ end
1127
+ rec = { "type" => "occurrent", "label" => "press_button", "category" => "action" }
1128
+ assert C.identify(rec) == C.identify(rec)
1129
+ assert C.identify(rec).split(":", 2)[0] == "occurrent"
1130
+ end
1131
+
1132
+ def v107
1133
+ hexid = "0" * 64
1134
+ # NOTE: the abbreviated prefix below is intentional (the negative test);
1135
+ # it must NOT be re-minted. "c" "r" "o" is assembled to survive re-mint tools.
1136
+ cro_abbr = "c" + "r" + "o"
1137
+ abbreviated = { "type" => "causal_relation_object", "id" => cro_abbr + ":" + hexid,
1138
+ "causes" => ["occurrent:" + hexid],
1139
+ "effects" => ["occurrent:" + hexid] }
1140
+ ok, _ = C.validate_schema(abbreviated, "causal_relation_object")
1141
+ assert !ok, "abbreviated scheme must be rejected"
1142
+ abbr_str = { "type" => "stratum", "id" => "str:" + hexid, "label" => "cellular",
1143
+ "scheme" => "neuroendocrine", "ordinal" => 6 }
1144
+ ok, _ = C.validate_schema(abbr_str, "stratum")
1145
+ assert !ok
1146
+ whole = { "type" => "causal_relation_object",
1147
+ "id" => "causal_relation_object:" + hexid,
1148
+ "causes" => ["occurrent:" + hexid],
1149
+ "effects" => ["occurrent:" + hexid] }
1150
+ ok, why = C.validate_schema(whole, "causal_relation_object")
1151
+ assert ok, why
1152
+ end
1153
+
1154
+ # ---------------------------------------------------------------------------
1155
+ def main
1156
+ puts "causalontology-ruby conformance run (specification 2.0.0)"
1157
+ print "internal checks (RFC 8032, RFC 8785, fixed constants) ... "
1158
+ internal_checks
1159
+ puts "ok"
1160
+ failures = 0
1161
+ total = 107
1162
+ (1..total).each do |n|
1163
+ name = vec_name(n)
1164
+ begin
1165
+ send(format("v%02d", n))
1166
+ puts "PASS #{name}"
1167
+ rescue StandardError => e
1168
+ failures += 1
1169
+ puts "FAIL #{name} :: #{e.class}: #{e.message}"
1170
+ end
1171
+ end
1172
+ puts "-" * 60
1173
+ puts "#{total - failures}/#{total} vectors passed"
1174
+ exit 1 if failures > 0
1175
+ puts "causalontology-ruby is CONFORMANT to the suite " \
1176
+ "(vectors frozen at specification 2.0.0)."
1177
+ end
1178
+
1179
+ main if __FILE__ == $PROGRAM_NAME