causalontology 2.0.0 → 4.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 +4 -4
- data/README.md +56 -14
- data/conformance.rb +503 -11
- data/lib/causalontology/canonical.rb +14 -4
- data/lib/causalontology/schema.rb +14 -1
- data/lib/causalontology/semantics.rb +161 -16
- data/lib/causalontology/spec/schema/assertion.schema.json +75 -0
- data/lib/causalontology/spec/schema/attitude.schema.json +45 -0
- data/lib/causalontology/spec/schema/bridge.schema.json +46 -0
- data/lib/causalontology/spec/schema/causal_relation_object.schema.json +111 -0
- data/lib/causalontology/spec/schema/conduit.schema.json +56 -0
- data/lib/causalontology/spec/schema/continuant.schema.json +38 -0
- data/lib/causalontology/spec/schema/cross_stratal_seam.schema.json +50 -0
- data/lib/causalontology/spec/schema/enrichment.schema.json +80 -0
- data/lib/causalontology/spec/schema/individual.schema.json +36 -0
- data/lib/causalontology/spec/schema/occurrent.schema.json +42 -0
- data/lib/causalontology/spec/schema/port.schema.json +56 -0
- data/lib/causalontology/spec/schema/predicted_occurrence.schema.json +68 -0
- data/lib/causalontology/spec/schema/prediction_error.schema.json +37 -0
- data/lib/causalontology/spec/schema/quality.schema.json +44 -0
- data/lib/causalontology/spec/schema/realizable.schema.json +38 -0
- data/lib/causalontology/spec/schema/retraction.schema.json +43 -0
- data/lib/causalontology/spec/schema/state.schema.json +89 -0
- data/lib/causalontology/spec/schema/stratum.schema.json +52 -0
- data/lib/causalontology/spec/schema/succession.schema.json +39 -0
- data/lib/causalontology/spec/schema/token.schema.json +96 -0
- data/lib/causalontology/spec/schema/token_causal_claim.schema.json +76 -0
- data/lib/causalontology/store.rb +5 -2
- data/lib/causalontology.rb +17 -1
- metadata +23 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: efd3a466f37b7098982f2829bdf01629a684b637dbe562cd2dbf461be644879d
|
|
4
|
+
data.tar.gz: 2e88e13a3f8c0eaf9f00fec6921834c7860640fbfcb90e6e757715a7a7833059
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6835f2290f7131f151221dbbaeec1c9ce3ee34b5505914af24f09f3d1f56646cac366f6e6b3b600a6eba735bc3228643a951bca77c41c7eb8bbbbd331ad8aeb7
|
|
7
|
+
data.tar.gz: c603eba74103082be26eda44bbb98beeafe1f4bd08e159a736a497a8d7d5cf7df7034588074b69de62f0f862439ce373c64f98a90d45912294edcfc60f698867
|
data/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
**Zero gems.** The Ruby standard library carries everything the standard
|
|
7
7
|
needs: `json` (whose parser keeps the integer-versus-decimal source
|
|
8
8
|
distinction — `1` parses to `Integer`, `1.0` to `Float` — so it survives to
|
|
9
|
-
the canonicalizer), `digest` (SHA-256 and SHA-512), and Ruby's native
|
|
9
|
+
the canonicalizer), `digest` (Secure Hash Algorithm 256-bit (SHA-256) and SHA-512), and Ruby's native
|
|
10
10
|
bignums with three-argument `Integer#pow` for the pure-Ruby Ed25519.
|
|
11
11
|
Requires **Ruby 3.0 or newer** (CI runs 3.3).
|
|
12
12
|
|
|
@@ -16,29 +16,71 @@ Requires **Ruby 3.0 or newer** (CI runs 3.3).
|
|
|
16
16
|
| `lib/causalontology/canonical.rb` | identity-bearing field filtering per kind and SHA-256 content-addressed `identify` (spec/identity.md) |
|
|
17
17
|
| `lib/causalontology/ed25519.rb` | pure-Ruby Ed25519 (RFC 8032) over native bignums, verified against the RFC's TEST 1 known answer before any vector runs |
|
|
18
18
|
| `lib/causalontology/signing.rb` | record-level `sign_record` / `verify_record` over canonical identity-bearing bytes (spec/provenance.md); a succession verifies against its predecessor key |
|
|
19
|
-
| `lib/causalontology/schema.rb` | validation against the
|
|
20
|
-
| `lib/causalontology/semantics.rb` | the
|
|
19
|
+
| `lib/causalontology/schema.rb` | validation against the twenty-one JSON Schemas vendored at `lib/causalontology/spec/schema/`, a byte-for-byte copy of `spec/schema/` that ships inside the gem (a small interpreter for exactly the keywords those schemas use) |
|
|
20
|
+
| `lib/causalontology/semantics.rb` | the semantic rules: temporal admissibility with the fixed unit constants (months 2629746 s, years 31556952 s) and the ordinal `ticks` dimension, the formal conflict test, refinement validity, bridged reachability, stratal classification, the skip decision, cross-stratal-seam well-formedness and the home rule, enrichment field/shape rules, the token-tier coherence checks, the predicted-interval dimension check, and the prediction-to-observation pairing |
|
|
21
21
|
| `lib/causalontology/store.rb` | an in-memory conformant store: idempotent immutable puts, signed add-only records with quarantine, materialized enrichment views with contributors, retraction and succession lineage, the resolve minimum, the deterministic cycle-breaking view rule, and the stigmergy `gaps` read — Ruby Hashes preserve insertion order, and the iteration order deliberately mirrors the reference store's |
|
|
22
|
-
| `conformance.rb` | the conformance runner: internal known-answer checks (RFC 8032 TEST 1, RFC 8785 basics), then all
|
|
22
|
+
| `conformance.rb` | the conformance runner: internal known-answer checks (RFC 8032 TEST 1, RFC 8785 basics), then all 137 vectors, mirroring `bindings/python/tests/run_conformance.py` exactly |
|
|
23
23
|
|
|
24
24
|
## Conformance
|
|
25
25
|
|
|
26
26
|
```
|
|
27
27
|
$ ruby bindings/ruby/conformance.rb
|
|
28
28
|
...
|
|
29
|
-
|
|
30
|
-
causalontology-ruby is CONFORMANT to the suite (vectors frozen at specification
|
|
29
|
+
137/137 vectors passed
|
|
30
|
+
causalontology-ruby is CONFORMANT to the suite (vectors frozen at specification 4.0.0).
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
The runner locates the
|
|
34
|
-
|
|
35
|
-
`bindings/ruby/`; the schemas are read from `spec/schema` under the same
|
|
36
|
-
root (overridable with `CAUSALONTOLOGY_SPEC`, which names the `spec/`
|
|
37
|
-
directory).
|
|
33
|
+
The runner locates the vectors from the `CAUSALONTOLOGY_ROOT` environment
|
|
34
|
+
variable when set, otherwise from its own location inside `bindings/ruby/`.
|
|
38
35
|
|
|
39
|
-
The
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
The schemas are resolved in strict precedence:
|
|
37
|
+
|
|
38
|
+
1. `$CAUSALONTOLOGY_SPEC/schema`, when that variable is set (it names the
|
|
39
|
+
`spec/` directory, not the `schema/` directory);
|
|
40
|
+
2. the copy vendored at `lib/causalontology/spec/schema`, which travels
|
|
41
|
+
inside the gem and is what an installed consumer validates against;
|
|
42
|
+
3. the repository-relative `spec/schema`, a last resort for a checkout in
|
|
43
|
+
which the vendored copy has not been made yet.
|
|
44
|
+
|
|
45
|
+
Before running the vectors, the runner checks the vendored copy byte-for-byte
|
|
46
|
+
against `spec/schema` and aborts on any drift, so the published gem can never
|
|
47
|
+
quietly enforce a different standard than the repository states.
|
|
48
|
+
|
|
49
|
+
### Testing the installed gem rather than the working tree
|
|
50
|
+
|
|
51
|
+
By default the runner loads the working tree next to it. Set
|
|
52
|
+
`CAUSALONTOLOGY_TEST_INSTALLED=1` and it instead loads the binding the way a
|
|
53
|
+
real consumer does, through a plain `require "causalontology"` resolved by
|
|
54
|
+
RubyGems; it prints the path it actually loaded and aborts if that path lies
|
|
55
|
+
inside the repository. Without that refusal a "fresh install" check silently
|
|
56
|
+
exercises repository source through a relative path and reports a false pass.
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
$ cd bindings/ruby # RubyGems resolves manifest paths
|
|
60
|
+
$ gem build causalontology.gemspec -o /tmp/causalontology-4.0.0.gem
|
|
61
|
+
# against the working directory
|
|
62
|
+
$ gem install --install-dir /tmp/gemhome --no-document --local \
|
|
63
|
+
/tmp/causalontology-4.0.0.gem
|
|
64
|
+
$ cd /tmp && env -u CAUSALONTOLOGY_SPEC GEM_HOME=/tmp/gemhome GEM_PATH=/tmp/gemhome \
|
|
65
|
+
CAUSALONTOLOGY_TEST_INSTALLED=1 CAUSALONTOLOGY_ROOT=/path/to/causalontology \
|
|
66
|
+
ruby /tmp/gemhome/gems/causalontology-4.0.0/conformance.rb
|
|
67
|
+
binding under test: /tmp/gemhome/gems/causalontology-4.0.0/lib/causalontology.rb
|
|
68
|
+
...
|
|
69
|
+
137/137 vectors passed
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The gemspec itself refuses to build if the vendored schemas are absent,
|
|
73
|
+
incomplete, or byte-different from `spec/schema`, so a gem that cannot
|
|
74
|
+
validate can never be produced in the first place.
|
|
75
|
+
|
|
76
|
+
The V01–V107 vectors are the whole-word 2.0.0 baseline (2026-07-13): they
|
|
77
|
+
carry concrete identifiers, real keys, and a real verifying signature, and
|
|
78
|
+
the harness's normalization now simply passes those frozen values through.
|
|
79
|
+
The V108–V119 (3.0.0: the `ticks` unit, the cross_stratal_seam, the conduit
|
|
80
|
+
`realized_by`) and V120–V137 (4.0.0: the attitude, the predicted_occurrence,
|
|
81
|
+
the prediction_error) fixtures are built in the runner, mirroring the Python
|
|
82
|
+
reference exactly. Twenty-one object kinds; 137 vectors, frozen at
|
|
83
|
+
specification 4.0.0 (2026-07-22).
|
|
42
84
|
|
|
43
85
|
## Thirty-second taste
|
|
44
86
|
|
data/conformance.rb
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# frozen_string_literal: false
|
|
3
3
|
|
|
4
|
-
# The Causalontology conformance runner for causalontology-ruby (spec
|
|
4
|
+
# The Causalontology conformance runner for causalontology-ruby (spec 4.0.0).
|
|
5
5
|
#
|
|
6
6
|
# Runs every vector in conformance/vectors/ against the Ruby binding. An
|
|
7
7
|
# implementation is conformant if and only if it passes every vector; this
|
|
8
8
|
# runner exits nonzero on any failure. It mirrors
|
|
9
9
|
# bindings/python/tests/run_conformance.py exactly: same fixtures, same
|
|
10
|
-
# expected results. Vectors are the whole-word 2.0.0 baseline
|
|
11
|
-
# V01-V38 re-frozen unaltered in meaning, V39-V107 new.
|
|
10
|
+
# expected results. Vectors V01-V107 are the whole-word 2.0.0 baseline
|
|
11
|
+
# (Principle P7): V01-V38 re-frozen unaltered in meaning, V39-V107 new.
|
|
12
|
+
# V108-V119 are the 3.0.0 additions (the ticks unit, the cross_stratal_seam,
|
|
13
|
+
# the conduit realized_by); V120-V137 are the 4.0.0 additions (the attitude,
|
|
14
|
+
# the predicted_occurrence, the prediction_error).
|
|
12
15
|
#
|
|
13
16
|
# Symbolic names still used inside this harness's own behavioral constructions
|
|
14
17
|
# ("occurrent:A", key "alice") are normalized deterministically - symbolic
|
|
@@ -18,12 +21,52 @@
|
|
|
18
21
|
require "json"
|
|
19
22
|
require "digest"
|
|
20
23
|
require "set"
|
|
21
|
-
require_relative "lib/causalontology"
|
|
22
24
|
|
|
23
|
-
C = Causalontology
|
|
24
25
|
ROOT = ENV["CAUSALONTOLOGY_ROOT"] || File.expand_path("../..", __dir__)
|
|
25
26
|
VECDIR = File.join(ROOT, "conformance", "vectors")
|
|
26
27
|
|
|
28
|
+
# How the binding under test is loaded.
|
|
29
|
+
#
|
|
30
|
+
# The default (no environment variable) is unchanged: load the working tree
|
|
31
|
+
# sitting next to this file, which is what repo-mode development wants.
|
|
32
|
+
#
|
|
33
|
+
# With CAUSALONTOLOGY_TEST_INSTALLED set, load it the way a real consumer
|
|
34
|
+
# does - a plain "require" resolved through RubyGems against the installed
|
|
35
|
+
# gem - and refuse to run if what actually got loaded turns out to live
|
|
36
|
+
# inside the repository. Without that refusal a "fresh install" test happily
|
|
37
|
+
# exercises the repository source through a relative path and reports a false
|
|
38
|
+
# pass, which is precisely how the missing-schema defect reached production.
|
|
39
|
+
if ENV["CAUSALONTOLOGY_TEST_INSTALLED"]
|
|
40
|
+
require "causalontology"
|
|
41
|
+
entry = $LOADED_FEATURES.find { |f| f.end_with?(File::SEPARATOR + "causalontology.rb") }
|
|
42
|
+
abort "CAUSALONTOLOGY_TEST_INSTALLED is set but no causalontology.rb was loaded" unless entry
|
|
43
|
+
entry = File.realpath(entry)
|
|
44
|
+
if entry.start_with?(File.realpath(ROOT) + File::SEPARATOR)
|
|
45
|
+
abort "CAUSALONTOLOGY_TEST_INSTALLED is set but the repository copy was loaded: #{entry}"
|
|
46
|
+
end
|
|
47
|
+
puts "binding under test: #{entry}"
|
|
48
|
+
else
|
|
49
|
+
require_relative "lib/causalontology"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
C = Causalontology
|
|
53
|
+
|
|
54
|
+
# Drift guard. The schemas vendored into the gem are the ones an installed
|
|
55
|
+
# consumer validates against, so they must stay byte-for-byte identical to
|
|
56
|
+
# the spec. If they silently go stale the published artifact enforces a
|
|
57
|
+
# different standard than the repository claims.
|
|
58
|
+
BUNDLED_SCHEMAS = File.expand_path("lib/causalontology/spec/schema", __dir__)
|
|
59
|
+
SPEC_SCHEMAS = File.join(ROOT, "spec", "schema")
|
|
60
|
+
if File.directory?(BUNDLED_SCHEMAS) && File.directory?(SPEC_SCHEMAS)
|
|
61
|
+
Dir.glob(File.join(SPEC_SCHEMAS, "*.schema.json")).sort.each do |src|
|
|
62
|
+
dst = File.join(BUNDLED_SCHEMAS, File.basename(src))
|
|
63
|
+
unless File.exist?(dst) && File.binread(dst) == File.binread(src)
|
|
64
|
+
abort "bundled schema drift: #{File.basename(src)} differs from " \
|
|
65
|
+
"spec/schema - re-copy before packing"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
27
70
|
# ---------------------------------------------------------------------------
|
|
28
71
|
# assertion helper
|
|
29
72
|
# ---------------------------------------------------------------------------
|
|
@@ -38,9 +81,10 @@ end
|
|
|
38
81
|
# ---------------------------------------------------------------------------
|
|
39
82
|
SCHEMES = ["occurrent", "causal_relation_object", "continuant", "realizable",
|
|
40
83
|
"assertion", "enrichment", "retraction", "succession",
|
|
41
|
-
"stratum", "bridge", "
|
|
42
|
-
"token_individual", "token_occurrence", "state_assertion",
|
|
43
|
-
"token_causal_claim"
|
|
84
|
+
"stratum", "bridge", "cross_stratal_seam", "port", "conduit",
|
|
85
|
+
"quality", "token_individual", "token_occurrence", "state_assertion",
|
|
86
|
+
"token_causal_claim",
|
|
87
|
+
"attitude", "predicted_occurrence", "prediction_error"].freeze
|
|
44
88
|
WHOLE_WORD = Set.new(SCHEMES) | Set.new(["ed25519"])
|
|
45
89
|
SCHEME_PREFIX = /\A(#{(SCHEMES + ["ed25519"]).join("|")}):/
|
|
46
90
|
HEX64 = /\A[0-9a-f]{64}\z/
|
|
@@ -1151,14 +1195,462 @@ def v107
|
|
|
1151
1195
|
assert ok, why
|
|
1152
1196
|
end
|
|
1153
1197
|
|
|
1198
|
+
# ---------------------------------------------------------------------------
|
|
1199
|
+
# V108 - V119: the 3.0.0 additions (tick unit, cross_stratal_seam, realized_by)
|
|
1200
|
+
# ---------------------------------------------------------------------------
|
|
1201
|
+
def seam(source, target, mechanism_status, chain = nil)
|
|
1202
|
+
o = { "type" => "cross_stratal_seam", "source" => source,
|
|
1203
|
+
"target" => target, "mechanism_status" => mechanism_status }
|
|
1204
|
+
o["chain"] = chain unless chain.nil? || chain.empty?
|
|
1205
|
+
mk(o)
|
|
1206
|
+
end
|
|
1207
|
+
|
|
1208
|
+
# Build a seam over the neuro fixture: [seam, occ_map, stratum_map].
|
|
1209
|
+
def seam_fixture(src_ord, tgt_ord, mechanism_status, chain_ords = nil)
|
|
1210
|
+
s = neuro
|
|
1211
|
+
src = occ("source_event", s[src_ord]["id"])
|
|
1212
|
+
tgt = occ("target_event", s[tgt_ord]["id"])
|
|
1213
|
+
omap = { src["id"] => src, tgt["id"] => tgt }
|
|
1214
|
+
smap = { s[src_ord]["id"] => s[src_ord], s[tgt_ord]["id"] => s[tgt_ord] }
|
|
1215
|
+
chain = nil
|
|
1216
|
+
unless chain_ords.nil?
|
|
1217
|
+
chain = []
|
|
1218
|
+
chain_ords.each_with_index do |o, i|
|
|
1219
|
+
c = occ("chain_#{i}", s[o]["id"])
|
|
1220
|
+
omap[c["id"]] = c
|
|
1221
|
+
smap[s[o]["id"]] = s[o]
|
|
1222
|
+
chain << c["id"]
|
|
1223
|
+
end
|
|
1224
|
+
end
|
|
1225
|
+
[seam(src["id"], tgt["id"], mechanism_status, chain), omap, smap]
|
|
1226
|
+
end
|
|
1227
|
+
|
|
1228
|
+
# A conduit with an optional realized_by reference, completed with its id.
|
|
1229
|
+
def conduit_realized(realized_by = nil)
|
|
1230
|
+
frm = "port:" + "1" * 64
|
|
1231
|
+
to = "port:" + "2" * 64
|
|
1232
|
+
x = "occurrent:" + "3" * 64
|
|
1233
|
+
o = { "type" => "conduit", "label" => "conn", "from" => frm, "to" => to,
|
|
1234
|
+
"carries" => [x] }
|
|
1235
|
+
o["realized_by"] = realized_by if realized_by
|
|
1236
|
+
mk(o)
|
|
1237
|
+
end
|
|
1238
|
+
|
|
1239
|
+
# -- Change One: the ordinal (tick) temporal unit --
|
|
1240
|
+
def v108
|
|
1241
|
+
p = cro([sym("occurrent:a")], [sym("occurrent:b")],
|
|
1242
|
+
temporal: { "minimum_delay" => 0, "maximum_delay" => 5,
|
|
1243
|
+
"unit" => "ticks" },
|
|
1244
|
+
modality: "sufficient")
|
|
1245
|
+
ok, why = C.validate_schema(p)
|
|
1246
|
+
assert ok, why
|
|
1247
|
+
ok, why = C.validate_semantics(p)
|
|
1248
|
+
assert ok, why
|
|
1249
|
+
end
|
|
1250
|
+
|
|
1251
|
+
def v109
|
|
1252
|
+
p = cro([sym("occurrent:a")], [sym("occurrent:b")],
|
|
1253
|
+
temporal: { "minimum_delay" => 2, "maximum_delay" => 5,
|
|
1254
|
+
"unit" => "ticks" })
|
|
1255
|
+
assert C.admissible(p, 3) == true # 3 ticks inside [2, 5]
|
|
1256
|
+
assert C.admissible(p, 2) == true && C.admissible(p, 5) == true
|
|
1257
|
+
assert C.admissible(p, 6) == false && C.admissible(p, 1) == false
|
|
1258
|
+
end
|
|
1259
|
+
|
|
1260
|
+
def v110
|
|
1261
|
+
tick_window = { "minimum_delay" => 0, "maximum_delay" => 5, "unit" => "ticks" }
|
|
1262
|
+
wall_window = { "minimum_delay" => 0, "maximum_delay" => 5, "unit" => "seconds" }
|
|
1263
|
+
assert C.delay_within_window({ "duration" => 3, "unit" => "ticks" },
|
|
1264
|
+
tick_window) == true
|
|
1265
|
+
assert C.delay_within_window({ "duration" => 1, "unit" => "ticks" },
|
|
1266
|
+
wall_window) == false
|
|
1267
|
+
assert C.delay_within_window({ "duration" => 1, "unit" => "seconds" },
|
|
1268
|
+
tick_window) == false
|
|
1269
|
+
a = { "causes" => [sym("occurrent:a")], "effects" => [sym("occurrent:b")],
|
|
1270
|
+
"temporal" => tick_window, "modality" => "sufficient" }
|
|
1271
|
+
b = { "causes" => [sym("occurrent:a")], "effects" => [sym("occurrent:b")],
|
|
1272
|
+
"temporal" => wall_window, "modality" => "preventive" }
|
|
1273
|
+
assert C.conflicts(a, b) == false # disjoint dimensions
|
|
1274
|
+
begin
|
|
1275
|
+
C.to_seconds(1, "ticks")
|
|
1276
|
+
raise VectorFailure, "to_seconds accepted ticks"
|
|
1277
|
+
rescue ArgumentError
|
|
1278
|
+
# expected: an ordinal unit has no wall-clock seconds mapping
|
|
1279
|
+
end
|
|
1280
|
+
end
|
|
1281
|
+
|
|
1282
|
+
def v111
|
|
1283
|
+
base = { "type" => "causal_relation_object", "causes" => [sym("occurrent:a")],
|
|
1284
|
+
"effects" => [sym("occurrent:b")], "modality" => "sufficient" }
|
|
1285
|
+
tick = base.merge("temporal" => { "minimum_delay" => 0, "maximum_delay" => 1,
|
|
1286
|
+
"unit" => "ticks" })
|
|
1287
|
+
secs = base.merge("temporal" => { "minimum_delay" => 0, "maximum_delay" => 1,
|
|
1288
|
+
"unit" => "seconds" })
|
|
1289
|
+
assert C.identify(tick) != C.identify(secs) # the unit is identity-bearing
|
|
1290
|
+
# a wall-clock record's identity is UNCHANGED under 3.0.0 (pinned 2.0.0 value)
|
|
1291
|
+
assert C.identify(secs) == "causal_relation_object:" \
|
|
1292
|
+
"d8daf899daa3ee03caa6b1425cc6d4d33cef20d951e1203ffd35df29857aa43c"
|
|
1293
|
+
end
|
|
1294
|
+
|
|
1295
|
+
# -- Change Two: the managed cross-stratal seam (eighteenth kind) --
|
|
1296
|
+
def v112
|
|
1297
|
+
sm, omap, smap = seam_fixture(14, 4, "unmodeled")
|
|
1298
|
+
ok, why = C.validate_schema(sm)
|
|
1299
|
+
assert ok, why
|
|
1300
|
+
ok, why = C.validate_semantics(sm)
|
|
1301
|
+
assert ok, why
|
|
1302
|
+
ok, why = C.seam_wellformed(sm, omap, smap)
|
|
1303
|
+
assert ok, why
|
|
1304
|
+
end
|
|
1305
|
+
|
|
1306
|
+
def v113
|
|
1307
|
+
a, = seam_fixture(14, 4, "unmodeled")
|
|
1308
|
+
b, omap, smap = seam_fixture(14, 4, "absent")
|
|
1309
|
+
ok, why = C.validate_schema(b)
|
|
1310
|
+
assert ok, why
|
|
1311
|
+
ok, why = C.seam_wellformed(b, omap, smap)
|
|
1312
|
+
assert ok, why
|
|
1313
|
+
assert a["id"] != b["id"] # mechanism_status is identity-bearing
|
|
1314
|
+
end
|
|
1315
|
+
|
|
1316
|
+
def v114
|
|
1317
|
+
drawn, omap, smap = seam_fixture(14, 4, "unmodeled", [9, 7, 6, 5])
|
|
1318
|
+
ok, why = C.validate_schema(drawn)
|
|
1319
|
+
assert ok, why
|
|
1320
|
+
ok, why = C.seam_wellformed(drawn, omap, smap)
|
|
1321
|
+
assert ok, why
|
|
1322
|
+
bad, omap2, smap2 = seam_fixture(14, 4, "absent", [9, 7, 6, 5])
|
|
1323
|
+
ok, why = C.validate_semantics(bad)
|
|
1324
|
+
assert !ok && why.any? { |w| w.include?("contradictory_seam") }, why
|
|
1325
|
+
ok2, _ = C.seam_wellformed(bad, omap2, smap2)
|
|
1326
|
+
assert !ok2
|
|
1327
|
+
end
|
|
1328
|
+
|
|
1329
|
+
def v115
|
|
1330
|
+
sm, omap, smap = seam_fixture(14, 4, "unmodeled")
|
|
1331
|
+
s = neuro
|
|
1332
|
+
assert C.seam_home(sm, omap, smap) == s[14]["id"] # coarsest (max ordinal) stratum
|
|
1333
|
+
end
|
|
1334
|
+
|
|
1335
|
+
def v116
|
|
1336
|
+
adj, o1, s1 = seam_fixture(6, 5, "unmodeled") # adjacent (gap 1)
|
|
1337
|
+
ok, _ = C.seam_wellformed(adj, o1, s1)
|
|
1338
|
+
assert !ok
|
|
1339
|
+
co, o2, s2 = seam_fixture(6, 6, "unmodeled") # co-stratal (gap 0)
|
|
1340
|
+
ok, _ = C.seam_wellformed(co, o2, s2)
|
|
1341
|
+
assert !ok
|
|
1342
|
+
sm, = seam_fixture(14, 4, "unmodeled")
|
|
1343
|
+
assert sm["id"].start_with?("cross_stratal_seam:") # a new identity scheme
|
|
1344
|
+
end
|
|
1345
|
+
|
|
1346
|
+
# -- Change Three: the realized_by reference --
|
|
1347
|
+
def v117
|
|
1348
|
+
c = conduit_realized("causal_relation_object:" + "a" * 64)
|
|
1349
|
+
ok, why = C.validate_schema(c)
|
|
1350
|
+
assert ok, why
|
|
1351
|
+
c2 = conduit_realized("native:region_stratum_predict")
|
|
1352
|
+
ok, why = C.validate_schema(c2)
|
|
1353
|
+
assert ok, why # a native scheme reference is legal
|
|
1354
|
+
end
|
|
1355
|
+
|
|
1356
|
+
def v118
|
|
1357
|
+
bound = conduit_realized("native:region_stratum_predict")
|
|
1358
|
+
unbound = conduit_realized
|
|
1359
|
+
assert bound["id"] != unbound["id"] # realized_by is identity-bearing
|
|
1360
|
+
# an unbound conduit's identity is UNCHANGED under 3.0.0 (pinned 2.0.0 value)
|
|
1361
|
+
assert unbound["id"] == "conduit:" \
|
|
1362
|
+
"dc4af3b1a24f0560d5ebcee488779f06ab3c78301cfb9d0c7edff80bc62e27a6"
|
|
1363
|
+
end
|
|
1364
|
+
|
|
1365
|
+
def v119
|
|
1366
|
+
unbound = conduit_realized
|
|
1367
|
+
ok, why = C.validate_schema(unbound)
|
|
1368
|
+
assert ok, why # unbound is legal
|
|
1369
|
+
bad = unbound.dup
|
|
1370
|
+
bad["realized_by"] = "not-a-scheme-qualified-reference"
|
|
1371
|
+
ok, _ = C.validate_schema(bad, "conduit")
|
|
1372
|
+
assert !ok
|
|
1373
|
+
end
|
|
1374
|
+
|
|
1375
|
+
# ---------------------------------------------------------------------------
|
|
1376
|
+
# V120 - V137: the 4.0.0 additions (attitude, predicted_occurrence,
|
|
1377
|
+
# prediction_error)
|
|
1378
|
+
# ---------------------------------------------------------------------------
|
|
1379
|
+
def attitude(holder, attitude_type, content)
|
|
1380
|
+
mk({ "type" => "attitude", "holder" => holder,
|
|
1381
|
+
"attitude_type" => attitude_type, "content" => content })
|
|
1382
|
+
end
|
|
1383
|
+
|
|
1384
|
+
def predicted(instantiates, interval, predictor, strength: nil)
|
|
1385
|
+
o = { "type" => "predicted_occurrence", "instantiates" => instantiates,
|
|
1386
|
+
"interval" => interval, "predictor" => predictor }
|
|
1387
|
+
o["strength"] = strength unless strength.nil?
|
|
1388
|
+
mk(o)
|
|
1389
|
+
end
|
|
1390
|
+
|
|
1391
|
+
def prediction_error(predicted_id, discrepancy, observed: nil)
|
|
1392
|
+
o = { "type" => "prediction_error", "predicted" => predicted_id,
|
|
1393
|
+
"discrepancy" => discrepancy }
|
|
1394
|
+
o["observed"] = observed if observed
|
|
1395
|
+
mk(o)
|
|
1396
|
+
end
|
|
1397
|
+
|
|
1398
|
+
# A modeled predicting agent (a token individual), by identity.
|
|
1399
|
+
def predictor_id
|
|
1400
|
+
c = cnt("forecasting_mind")
|
|
1401
|
+
individual(c["id"], designator: "predictor_p")["id"]
|
|
1402
|
+
end
|
|
1403
|
+
|
|
1404
|
+
# A modeled believing agent (a token individual), by identity.
|
|
1405
|
+
def believer(designator = "holder_h")
|
|
1406
|
+
c = cnt("believing_mind")
|
|
1407
|
+
individual(c["id"], designator: designator)["id"]
|
|
1408
|
+
end
|
|
1409
|
+
|
|
1410
|
+
# -- Group X: prediction and prediction error (Section A) --
|
|
1411
|
+
def v120
|
|
1412
|
+
o = occ("rainfall_begins")
|
|
1413
|
+
p = predicted(o["id"], { "start_tick" => 3, "end_tick" => 8 }, predictor_id)
|
|
1414
|
+
ok, why = C.validate_schema(p)
|
|
1415
|
+
assert ok, why
|
|
1416
|
+
ok, why = C.validate_semantics(p)
|
|
1417
|
+
assert ok, why
|
|
1418
|
+
assert p["id"].start_with?("predicted_occurrence:")
|
|
1419
|
+
report = C.identify({ "type" => "token_occurrence", "instantiates" => o["id"],
|
|
1420
|
+
"interval" => { "start_tick" => 3, "end_tick" => 8 } },
|
|
1421
|
+
"token_occurrence")
|
|
1422
|
+
assert p["id"] != report # a forecast is not a report
|
|
1423
|
+
assert report.start_with?("token_occurrence:")
|
|
1424
|
+
end
|
|
1425
|
+
|
|
1426
|
+
def v121
|
|
1427
|
+
o = occ("rainfall_begins")
|
|
1428
|
+
wall = { "start" => "2026-07-23T00:00:00Z", "end" => "2026-07-24T00:00:00Z" }
|
|
1429
|
+
who = predictor_id
|
|
1430
|
+
with_strength = predicted(o["id"], wall, who, strength: 0.8)
|
|
1431
|
+
without = predicted(o["id"], wall, who)
|
|
1432
|
+
[with_strength, without].each do |p|
|
|
1433
|
+
ok, why = C.validate_schema(p)
|
|
1434
|
+
assert ok, why
|
|
1435
|
+
ok, why = C.validate_semantics(p)
|
|
1436
|
+
assert ok, why
|
|
1437
|
+
end
|
|
1438
|
+
assert with_strength["id"] != without["id"] # strength is identity-bearing
|
|
1439
|
+
end
|
|
1440
|
+
|
|
1441
|
+
def v122
|
|
1442
|
+
o = occ("rainfall_begins")
|
|
1443
|
+
bad = mk({ "type" => "predicted_occurrence", "instantiates" => o["id"],
|
|
1444
|
+
"interval" => { "start_tick" => 3 } })
|
|
1445
|
+
ok, why = C.validate_schema(bad, "predicted_occurrence")
|
|
1446
|
+
assert !ok && why.any? { |w| w.include?("predictor") }, why
|
|
1447
|
+
end
|
|
1448
|
+
|
|
1449
|
+
def v123
|
|
1450
|
+
o = occ("rainfall_begins")
|
|
1451
|
+
both = predicted(o["id"],
|
|
1452
|
+
{ "start" => "2026-07-23T00:00:00Z", "start_tick" => 3 },
|
|
1453
|
+
predictor_id)
|
|
1454
|
+
ok, why = C.validate_schema(both)
|
|
1455
|
+
assert ok, why
|
|
1456
|
+
ok, why = C.validate_semantics(both)
|
|
1457
|
+
assert !ok && why.any? { |w| w.include?("dimension_conflict") }, why
|
|
1458
|
+
end
|
|
1459
|
+
|
|
1460
|
+
def v124
|
|
1461
|
+
o = occ("rainfall_begins")
|
|
1462
|
+
p = predicted(o["id"], { "start" => "2026-07-23T00:00:00Z" }, predictor_id)
|
|
1463
|
+
t = token(o["id"], { "start" => "2026-07-23T06:00:00Z" })
|
|
1464
|
+
err = prediction_error(p["id"], 0.0, observed: t["id"])
|
|
1465
|
+
ok, why = C.validate_schema(err)
|
|
1466
|
+
assert ok, why
|
|
1467
|
+
ok, why = C.validate_semantics(err)
|
|
1468
|
+
assert ok, why
|
|
1469
|
+
assert C.prediction_pairing_mismatch(err, p, t) == false
|
|
1470
|
+
end
|
|
1471
|
+
|
|
1472
|
+
def v125
|
|
1473
|
+
o = occ("rainfall_begins")
|
|
1474
|
+
p = predicted(o["id"], { "start" => "2026-07-23T00:00:00Z" }, predictor_id)
|
|
1475
|
+
err = prediction_error(p["id"], -1.0)
|
|
1476
|
+
ok, why = C.validate_schema(err)
|
|
1477
|
+
assert ok, why
|
|
1478
|
+
ok, why = C.validate_semantics(err)
|
|
1479
|
+
assert ok, why
|
|
1480
|
+
assert !err.key?("observed")
|
|
1481
|
+
assert C.prediction_pairing_mismatch(err, p, nil) == false
|
|
1482
|
+
end
|
|
1483
|
+
|
|
1484
|
+
def v126
|
|
1485
|
+
o = occ("rainfall_begins")
|
|
1486
|
+
p = predicted(o["id"], { "start_tick" => 0 }, predictor_id)
|
|
1487
|
+
bad = mk({ "type" => "prediction_error", "predicted" => p["id"] })
|
|
1488
|
+
ok, why = C.validate_schema(bad, "prediction_error")
|
|
1489
|
+
assert !ok && why.any? { |w| w.include?("discrepancy") }, why
|
|
1490
|
+
end
|
|
1491
|
+
|
|
1492
|
+
def v127
|
|
1493
|
+
o = occ("rainfall_begins")
|
|
1494
|
+
other = occ("snowfall_begins")
|
|
1495
|
+
p = predicted(o["id"], { "start" => "2026-07-23T00:00:00Z" }, predictor_id)
|
|
1496
|
+
t = token(other["id"], { "start" => "2026-07-23T06:00:00Z" })
|
|
1497
|
+
err = prediction_error(p["id"], 1.0, observed: t["id"])
|
|
1498
|
+
ok, why = C.validate_schema(err)
|
|
1499
|
+
assert ok, why
|
|
1500
|
+
assert C.prediction_pairing_mismatch(err, p, t) == true
|
|
1501
|
+
end
|
|
1502
|
+
|
|
1503
|
+
# -- Group Y: attitude and theory of mind (Section B) --
|
|
1504
|
+
def v128
|
|
1505
|
+
st, = state_fixture("quantity", { "quantity" => 15.0, "unit" => "ug/dL" },
|
|
1506
|
+
"ug/dL")
|
|
1507
|
+
att = attitude(believer, "believes", st["id"])
|
|
1508
|
+
ok, why = C.validate_schema(att)
|
|
1509
|
+
assert ok, why
|
|
1510
|
+
ok, why = C.validate_semantics(att)
|
|
1511
|
+
assert ok, why
|
|
1512
|
+
end
|
|
1513
|
+
|
|
1514
|
+
def v129
|
|
1515
|
+
a = occ("switch_pressed")
|
|
1516
|
+
b = occ("light_on")
|
|
1517
|
+
actual = cro([a["id"]], [b["id"]], modality: "sufficient")
|
|
1518
|
+
believed = cro([a["id"]], [b["id"]], modality: "preventive")
|
|
1519
|
+
assert C.conflicts(believed, actual) == true # the CLAIMS contradict
|
|
1520
|
+
att = attitude(believer, "believes", believed["id"])
|
|
1521
|
+
ok, why = C.validate_schema(att)
|
|
1522
|
+
assert ok, why
|
|
1523
|
+
ok, why = C.validate_semantics(att)
|
|
1524
|
+
assert ok, why # validity unaffected
|
|
1525
|
+
s = C::InMemoryStore.new
|
|
1526
|
+
s.put(a)
|
|
1527
|
+
s.put(b)
|
|
1528
|
+
s.put(actual)
|
|
1529
|
+
s.put(att)
|
|
1530
|
+
assert s.gaps("conflict") == [] # Rule 25: NO conflict raised
|
|
1531
|
+
end
|
|
1532
|
+
|
|
1533
|
+
def v130
|
|
1534
|
+
o = occ("rainfall_begins")
|
|
1535
|
+
att = attitude(believer, "desires", o["id"])
|
|
1536
|
+
ok, why = C.validate_schema(att)
|
|
1537
|
+
assert ok, why
|
|
1538
|
+
ok, why = C.validate_semantics(att)
|
|
1539
|
+
assert ok, why
|
|
1540
|
+
end
|
|
1541
|
+
|
|
1542
|
+
def v131
|
|
1543
|
+
o = occ("press_button")
|
|
1544
|
+
att = attitude(believer, "intends", o["id"])
|
|
1545
|
+
ok, why = C.validate_schema(att)
|
|
1546
|
+
assert ok, why
|
|
1547
|
+
ok, why = C.validate_semantics(att)
|
|
1548
|
+
assert ok, why
|
|
1549
|
+
end
|
|
1550
|
+
|
|
1551
|
+
def v132
|
|
1552
|
+
st, = state_fixture("boolean", { "boolean" => true })
|
|
1553
|
+
inner = attitude(believer("holder_b"), "believes", st["id"])
|
|
1554
|
+
outer = attitude(believer("holder_a"), "believes", inner["id"])
|
|
1555
|
+
[inner, outer].each do |att|
|
|
1556
|
+
ok, why = C.validate_schema(att)
|
|
1557
|
+
assert ok, why
|
|
1558
|
+
ok, why = C.validate_semantics(att)
|
|
1559
|
+
assert ok, why
|
|
1560
|
+
end
|
|
1561
|
+
assert outer["id"] != inner["id"]
|
|
1562
|
+
assert outer["content"] == inner["id"] # nested content
|
|
1563
|
+
end
|
|
1564
|
+
|
|
1565
|
+
def v133
|
|
1566
|
+
o = occ("rainfall_begins")
|
|
1567
|
+
bad = mk({ "type" => "attitude", "holder" => believer,
|
|
1568
|
+
"attitude_type" => "suspects", "content" => o["id"] })
|
|
1569
|
+
ok, why = C.validate_schema(bad, "attitude")
|
|
1570
|
+
assert !ok && why.any? { |w| w.include?("attitude_type") }, why
|
|
1571
|
+
end
|
|
1572
|
+
|
|
1573
|
+
def v134
|
|
1574
|
+
o = occ("rainfall_begins")
|
|
1575
|
+
bad = mk({ "type" => "attitude", "holder" => believer,
|
|
1576
|
+
"attitude_type" => "believes", "content" => o["id"],
|
|
1577
|
+
"strength" => 0.9 })
|
|
1578
|
+
ok, why = C.validate_schema(bad, "attitude")
|
|
1579
|
+
assert !ok && why.any? { |w| w.include?("strength") }, why
|
|
1580
|
+
end
|
|
1581
|
+
|
|
1582
|
+
def v135
|
|
1583
|
+
o = occ("rainfall_begins")
|
|
1584
|
+
att = attitude(believer, "expects", o["id"])
|
|
1585
|
+
a = signed("assertion", { "about" => att["id"],
|
|
1586
|
+
"evidence_type" => "observation",
|
|
1587
|
+
"confidence" => 0.9 }, "signer")
|
|
1588
|
+
ok, why = C.validate_schema(a)
|
|
1589
|
+
assert ok, why
|
|
1590
|
+
assert C.verify_record(a) == true
|
|
1591
|
+
# the HOLDER (a modeled agent) and the SOURCE (a signing key) differ
|
|
1592
|
+
assert att["holder"].split(":", 2)[0] == "token_individual"
|
|
1593
|
+
assert a["source"].split(":", 2)[0] == "ed25519"
|
|
1594
|
+
assert att["holder"] != a["source"]
|
|
1595
|
+
end
|
|
1596
|
+
|
|
1597
|
+
def v136
|
|
1598
|
+
# the V111 wall-clock Causal Relation Object, re-pinned under 4.0.0
|
|
1599
|
+
secs = { "type" => "causal_relation_object", "causes" => [sym("occurrent:a")],
|
|
1600
|
+
"effects" => [sym("occurrent:b")], "modality" => "sufficient",
|
|
1601
|
+
"temporal" => { "minimum_delay" => 0, "maximum_delay" => 1,
|
|
1602
|
+
"unit" => "seconds" } }
|
|
1603
|
+
assert C.identify(secs) == "causal_relation_object:" \
|
|
1604
|
+
"d8daf899daa3ee03caa6b1425cc6d4d33cef20d951e1203ffd35df29857aa43c"
|
|
1605
|
+
# the V118 unbound conduit, re-pinned under 4.0.0
|
|
1606
|
+
unbound = conduit_realized
|
|
1607
|
+
assert unbound["id"] == "conduit:" \
|
|
1608
|
+
"dc4af3b1a24f0560d5ebcee488779f06ab3c78301cfb9d0c7edff80bc62e27a6"
|
|
1609
|
+
end
|
|
1610
|
+
|
|
1611
|
+
def v137
|
|
1612
|
+
hexid = "0" * 64
|
|
1613
|
+
# NOTE: the abbreviated prefixes below are intentional (the negative test);
|
|
1614
|
+
# they must NOT be re-minted. Each is assembled to survive re-mint tools.
|
|
1615
|
+
att_abbr = "a" + "t" + "t"
|
|
1616
|
+
prd_abbr = "p" + "r" + "d"
|
|
1617
|
+
err_abbr = "e" + "r" + "r"
|
|
1618
|
+
bad_att = { "type" => "attitude", "id" => att_abbr + ":" + hexid,
|
|
1619
|
+
"holder" => "token_individual:" + hexid,
|
|
1620
|
+
"attitude_type" => "believes",
|
|
1621
|
+
"content" => "state_assertion:" + hexid }
|
|
1622
|
+
ok, _ = C.validate_schema(bad_att, "attitude")
|
|
1623
|
+
assert !ok
|
|
1624
|
+
bad_prd = { "type" => "predicted_occurrence", "id" => prd_abbr + ":" + hexid,
|
|
1625
|
+
"instantiates" => "occurrent:" + hexid,
|
|
1626
|
+
"interval" => { "start_tick" => 0 },
|
|
1627
|
+
"predictor" => "token_individual:" + hexid }
|
|
1628
|
+
ok, _ = C.validate_schema(bad_prd, "predicted_occurrence")
|
|
1629
|
+
assert !ok
|
|
1630
|
+
bad_err = { "type" => "prediction_error", "id" => err_abbr + ":" + hexid,
|
|
1631
|
+
"predicted" => "predicted_occurrence:" + hexid,
|
|
1632
|
+
"discrepancy" => 0.0 }
|
|
1633
|
+
ok, _ = C.validate_schema(bad_err, "prediction_error")
|
|
1634
|
+
assert !ok
|
|
1635
|
+
whole_att = bad_att.merge("id" => "attitude:" + hexid)
|
|
1636
|
+
ok, why = C.validate_schema(whole_att, "attitude")
|
|
1637
|
+
assert ok, why
|
|
1638
|
+
whole_prd = bad_prd.merge("id" => "predicted_occurrence:" + hexid)
|
|
1639
|
+
ok, why = C.validate_schema(whole_prd, "predicted_occurrence")
|
|
1640
|
+
assert ok, why
|
|
1641
|
+
whole_err = bad_err.merge("id" => "prediction_error:" + hexid)
|
|
1642
|
+
ok, why = C.validate_schema(whole_err, "prediction_error")
|
|
1643
|
+
assert ok, why
|
|
1644
|
+
end
|
|
1645
|
+
|
|
1154
1646
|
# ---------------------------------------------------------------------------
|
|
1155
1647
|
def main
|
|
1156
|
-
puts "causalontology-ruby conformance run (specification
|
|
1648
|
+
puts "causalontology-ruby conformance run (specification 4.0.0)"
|
|
1157
1649
|
print "internal checks (RFC 8032, RFC 8785, fixed constants) ... "
|
|
1158
1650
|
internal_checks
|
|
1159
1651
|
puts "ok"
|
|
1160
1652
|
failures = 0
|
|
1161
|
-
total =
|
|
1653
|
+
total = 137
|
|
1162
1654
|
(1..total).each do |n|
|
|
1163
1655
|
name = vec_name(n)
|
|
1164
1656
|
begin
|
|
@@ -1173,7 +1665,7 @@ def main
|
|
|
1173
1665
|
puts "#{total - failures}/#{total} vectors passed"
|
|
1174
1666
|
exit 1 if failures > 0
|
|
1175
1667
|
puts "causalontology-ruby is CONFORMANT to the suite " \
|
|
1176
|
-
"(vectors frozen at specification
|
|
1668
|
+
"(vectors frozen at specification 4.0.0)."
|
|
1177
1669
|
end
|
|
1178
1670
|
|
|
1179
1671
|
main if __FILE__ == $PROGRAM_NAME
|