alm108 commited on
Commit
413cc0f
·
1 Parent(s): 2a2189c

feat: ship portable Actors ontology v0.1

Browse files
portable_ontology/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Portable semantic ontology release contracts."""
portable_ontology/actors.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Validation for the clean Actors-family ontology release.
2
+
3
+ This module validates release artifacts and inventory candidates. It does not
4
+ rewrite an existing graph or admit semantic records; admission remains with the
5
+ instance human gate.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import hashlib
11
+ import json
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+
16
+ MANIFEST_PATH = Path(__file__).parent / "manifests" / "actors_family_v0_1.json"
17
+ CANONICAL_ACTOR_TYPES = ("person", "organization", "agent")
18
+ TAXONOMY_FIELDS = ("phylum", "class", "order", "genus", "species")
19
+ LEGACY_AGENT_NODE_TYPES = {
20
+ "agent_instance",
21
+ "agent_taxon",
22
+ "agent_phylum",
23
+ "agent_class",
24
+ "agent_order",
25
+ "agent_genus",
26
+ "agent_family",
27
+ "agent_species",
28
+ }
29
+
30
+
31
+ def load_manifest(path: Path = MANIFEST_PATH) -> dict[str, Any]:
32
+ return json.loads(path.read_text(encoding="utf-8"))
33
+
34
+
35
+ def manifest_digest(manifest: dict[str, Any]) -> str:
36
+ unsigned = json.loads(json.dumps(manifest))
37
+ unsigned["integrity"]["manifest_sha256"] = None
38
+ canonical = json.dumps(
39
+ unsigned, ensure_ascii=False, sort_keys=True, separators=(",", ":")
40
+ ).encode("utf-8")
41
+ return hashlib.sha256(canonical).hexdigest()
42
+
43
+
44
+ def validate_manifest(manifest: dict[str, Any]) -> list[str]:
45
+ errors: list[str] = []
46
+ if manifest.get("schema_version") != "mycelium-portable-ontology/v0.1":
47
+ errors.append("unexpected schema_version")
48
+ if manifest.get("release_id") != "actors_family_v0_1":
49
+ errors.append("unexpected release_id")
50
+ actors = manifest.get("actors_family", {})
51
+ if tuple(actors.get("canonical_types", ())) != CANONICAL_ACTOR_TYPES:
52
+ errors.append("Actors canonical types must be exactly person, organization, agent")
53
+ noncanonical = set(actors.get("noncanonical_agent_node_types", ()))
54
+ if not {"provider", "model", "lane", "persona", "instance", "agent_instance", "agent_taxon"} <= noncanonical:
55
+ errors.append("agent facet/noncanonical-type boundary is incomplete")
56
+
57
+ card = manifest.get("agent_registration_card", {})
58
+ if card.get("is_actor_type") is not False:
59
+ errors.append("registration card must not become a fourth Actors type")
60
+ if tuple(card.get("required_taxonomy_fields", ())) != TAXONOMY_FIELDS:
61
+ errors.append("registration card taxonomy must be phylum/class/order/genus/species")
62
+ if "Do not copy legacy agent_* taxonomy nodes" not in card.get("legacy_import_rule", ""):
63
+ errors.append("legacy taxonomy-node exclusion is missing")
64
+
65
+ relations = manifest.get("required_agent_topology", {}).get("relations", {})
66
+ organization = relations.get("organization", {})
67
+ registration = relations.get("registration_card", {})
68
+ lineage = relations.get("lineage", {})
69
+ if organization.get("minimum") != 1 or organization.get("evidence_backed") is not True:
70
+ errors.append("every agent must have an evidence-backed organization relation")
71
+ if registration.get("minimum") != 1 or registration.get("maximum") != 1:
72
+ errors.append("every agent must instantiate exactly one registration card")
73
+ if registration.get("evidence_backed") is not True:
74
+ errors.append("registration-card relation must be evidence-backed")
75
+ if lineage.get("relation_type") != "subagent_of" or lineage.get("queryable") is not True:
76
+ errors.append("parent/subagent lineage must be explicit and queryable")
77
+
78
+ growth = manifest.get("growth_admission", {})
79
+ if growth.get("instance_human_gate_is_only_admission_authority") is not True:
80
+ errors.append("instance human gate must remain the only admission authority")
81
+ if growth.get("credentials_are_content") is not False:
82
+ errors.append("credentials must not be ontology content")
83
+ if growth.get("cross_instance_publication_does_not_canonicalize_destination") is not True:
84
+ errors.append("cross-instance publication boundary is missing")
85
+
86
+ expected = manifest_digest(manifest)
87
+ if manifest.get("integrity", {}).get("manifest_sha256") != expected:
88
+ errors.append("integrity.manifest_sha256 digest mismatch")
89
+ return errors
90
+
91
+
92
+ def validate_agent_candidate(candidate: dict[str, Any]) -> list[str]:
93
+ """Validate a clean-instance agent inventory row before gate admission.
94
+
95
+ Relations are represented by IDs here so an inventory diff can use the
96
+ same contract without inventing or mutating graph edges.
97
+ """
98
+
99
+ errors: list[str] = []
100
+ if candidate.get("type") != "agent":
101
+ if candidate.get("type") in LEGACY_AGENT_NODE_TYPES:
102
+ errors.append("legacy agent taxonomy node types are not canonical")
103
+ else:
104
+ errors.append("candidate type must be agent")
105
+ return errors
106
+
107
+ agent_id = candidate.get("id")
108
+ if not isinstance(agent_id, str) or not agent_id.strip():
109
+ errors.append("agent id is required")
110
+ organizations = candidate.get("organization_ids")
111
+ if not isinstance(organizations, list) or not organizations or not all(
112
+ isinstance(value, str) and value.strip() for value in organizations
113
+ ):
114
+ errors.append("at least one organization_id is required")
115
+
116
+ card = candidate.get("registration_card")
117
+ if not isinstance(card, dict):
118
+ errors.append("one registration_card is required")
119
+ else:
120
+ missing = [field for field in TAXONOMY_FIELDS if not isinstance(card.get(field), str) or not card[field].strip()]
121
+ if missing:
122
+ errors.append("registration_card missing taxonomy fields: " + ", ".join(missing))
123
+
124
+ lineage_kind = candidate.get("lineage_kind")
125
+ parent_id = candidate.get("parent_agent_id")
126
+ if lineage_kind not in {"root", "subagent"}:
127
+ errors.append("lineage_kind must be root or subagent")
128
+ elif lineage_kind == "root" and parent_id is not None:
129
+ errors.append("root agent must not have parent_agent_id")
130
+ elif lineage_kind == "subagent" and (not isinstance(parent_id, str) or not parent_id.strip()):
131
+ errors.append("subagent requires parent_agent_id")
132
+ return errors
portable_ontology/manifests/actors_family_v0_1.json ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "schema_version": "mycelium-portable-ontology/v0.1",
3
+ "release_id": "actors_family_v0_1",
4
+ "release_status": "canonical_portable_release",
5
+ "released_at": "2026-08-17",
6
+ "scope": "Clean Blank Mycelium and Noam Spore instances only",
7
+ "actors_family": {
8
+ "canonical_types": [
9
+ "person",
10
+ "organization",
11
+ "agent"
12
+ ],
13
+ "definitions": {
14
+ "person": "One identifiable human being only.",
15
+ "organization": "An organization actor; product, model, agent, and organization identities remain distinct.",
16
+ "agent": "Any named AI operational identity that can participate in work or provenance."
17
+ },
18
+ "type_exclusions": {
19
+ "person": [
20
+ "AI or computational agent",
21
+ "organization",
22
+ "role",
23
+ "audience segment",
24
+ "fictional persona"
25
+ ],
26
+ "agent": [
27
+ "human being",
28
+ "organization",
29
+ "role",
30
+ "product",
31
+ "unnamed model class"
32
+ ]
33
+ },
34
+ "noncanonical_agent_node_types": [
35
+ "provider",
36
+ "model",
37
+ "lane",
38
+ "persona",
39
+ "instance",
40
+ "agent_instance",
41
+ "agent_taxon"
42
+ ]
43
+ },
44
+ "agent_registration_card": {
45
+ "record_kind": "reusable_registration_card",
46
+ "is_actor_type": false,
47
+ "required_taxonomy_fields": [
48
+ "phylum",
49
+ "class",
50
+ "order",
51
+ "genus",
52
+ "species"
53
+ ],
54
+ "reuse_rule": "One registration card may be instantiated by multiple individually tracked agents.",
55
+ "legacy_import_rule": "Do not copy legacy agent_* taxonomy nodes from Adi's live graph; map reviewed values into registration-card fields."
56
+ },
57
+ "required_agent_topology": {
58
+ "individually_trackable": true,
59
+ "relations": {
60
+ "organization": {
61
+ "relation_type": "associated_with_organization",
62
+ "target_type": "organization",
63
+ "minimum": 1,
64
+ "evidence_backed": true
65
+ },
66
+ "registration_card": {
67
+ "relation_type": "instantiates_registration_card",
68
+ "target_record_kind": "reusable_registration_card",
69
+ "minimum": 1,
70
+ "maximum": 1,
71
+ "evidence_backed": true
72
+ },
73
+ "lineage": {
74
+ "relation_type": "subagent_of",
75
+ "target_type": "agent",
76
+ "rule": "Every agent declares lineage_kind=root or lineage_kind=subagent; subagents require exactly one parent agent and roots require none.",
77
+ "queryable": true
78
+ }
79
+ },
80
+ "facets_preserved": [
81
+ "provider",
82
+ "model",
83
+ "lane",
84
+ "persona",
85
+ "instance"
86
+ ]
87
+ },
88
+ "growth_admission": {
89
+ "credentials_are_content": false,
90
+ "user_authorized_provider_credentials_may_open_bounded_candidate_lanes": true,
91
+ "agents_may_propose_names_and_registration_cards": true,
92
+ "instance_human_gate_is_only_admission_authority": true,
93
+ "cross_instance_publication_does_not_canonicalize_destination": true
94
+ },
95
+ "source_evidence": [
96
+ "ev_146b4d8547ed4585a80c07b313e39f6d",
97
+ "ev_afe89570b16740d8b078832a634e7ee9",
98
+ "ev_8a025c5dd6304957b019b172276dec83",
99
+ "ev_d99208305c6e46c59d7f75c3954c36f1",
100
+ "ev_dd7a27dbad984e4db32fad69767c06c4"
101
+ ],
102
+ "explicit_exclusions": [
103
+ "No Adi live-graph mutation",
104
+ "No legacy agent_* taxonomy-node import",
105
+ "No provider credentials or secrets",
106
+ "No automatic cross-instance canonicalization",
107
+ "No rewrite of existing noncompliant graphs"
108
+ ],
109
+ "integrity": {
110
+ "canonicalization": "UTF-8 JSON, recursively sorted object keys, compact separators, SHA-256; manifest_sha256 excludes itself",
111
+ "manifest_sha256": "90904b4e4c18de6b482e47d49cdcd7d7cecbf0e798caa407f965c16edf5ff1c2"
112
+ }
113
+ }
test_actors_ontology.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import copy
2
+ import unittest
3
+
4
+ from portable_ontology.actors import (
5
+ LEGACY_AGENT_NODE_TYPES,
6
+ load_manifest,
7
+ manifest_digest,
8
+ validate_agent_candidate,
9
+ validate_manifest,
10
+ )
11
+
12
+
13
+ VALID_ROOT = {
14
+ "id": "agent_example_001",
15
+ "type": "agent",
16
+ "organization_ids": ["org_example"],
17
+ "registration_card": {
18
+ "phylum": "ExampleProvider",
19
+ "class": "API",
20
+ "order": "weaver",
21
+ "genus": "shuttle",
22
+ "species": "example",
23
+ },
24
+ "lineage_kind": "root",
25
+ "parent_agent_id": None,
26
+ }
27
+
28
+
29
+ class ActorsOntologyTests(unittest.TestCase):
30
+ def test_release_manifest_and_digest(self):
31
+ manifest = load_manifest()
32
+ self.assertEqual([], validate_manifest(manifest))
33
+ self.assertEqual(manifest_digest(manifest), manifest["integrity"]["manifest_sha256"])
34
+
35
+ def test_valid_root_and_subagent(self):
36
+ self.assertEqual([], validate_agent_candidate(VALID_ROOT))
37
+ child = copy.deepcopy(VALID_ROOT)
38
+ child.update(id="agent_example_002", lineage_kind="subagent", parent_agent_id="agent_example_001")
39
+ self.assertEqual([], validate_agent_candidate(child))
40
+
41
+ def test_registration_card_can_be_reused_by_distinct_agents(self):
42
+ first = copy.deepcopy(VALID_ROOT)
43
+ second = copy.deepcopy(VALID_ROOT)
44
+ second["id"] = "agent_example_002"
45
+ self.assertEqual(first["registration_card"], second["registration_card"])
46
+ self.assertEqual([], validate_agent_candidate(first))
47
+ self.assertEqual([], validate_agent_candidate(second))
48
+
49
+ def test_missing_required_wiring_fails_closed(self):
50
+ for field in ("organization_ids", "registration_card", "lineage_kind"):
51
+ with self.subTest(field=field):
52
+ candidate = copy.deepcopy(VALID_ROOT)
53
+ candidate.pop(field)
54
+ self.assertTrue(validate_agent_candidate(candidate))
55
+
56
+ def test_legacy_taxonomy_node_types_are_rejected(self):
57
+ for legacy_type in LEGACY_AGENT_NODE_TYPES:
58
+ with self.subTest(legacy_type=legacy_type):
59
+ candidate = copy.deepcopy(VALID_ROOT)
60
+ candidate["type"] = legacy_type
61
+ self.assertIn(
62
+ "legacy agent taxonomy node types are not canonical",
63
+ validate_agent_candidate(candidate),
64
+ )
65
+
66
+ def test_manifest_contains_no_legacy_taxonomy_seed_ids(self):
67
+ manifest_text = __import__("json").dumps(load_manifest())
68
+ for prefix in ("agent_species_", "agent_genus_", "agent_order_", "agent_phylum_", "agent_class_"):
69
+ self.assertNotIn(prefix, manifest_text)
70
+
71
+
72
+ if __name__ == "__main__":
73
+ unittest.main()