cjm-dev-graph-schema 0.0.11__py3-none-any.whl
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.
- cjm_dev_graph_schema/__init__.py +1 -0
- cjm_dev_graph_schema/aliases.py +62 -0
- cjm_dev_graph_schema/identity.py +181 -0
- cjm_dev_graph_schema/nodes.py +882 -0
- cjm_dev_graph_schema/predicates.py +219 -0
- cjm_dev_graph_schema/vocab.py +100 -0
- cjm_dev_graph_schema-0.0.11.dist-info/METADATA +166 -0
- cjm_dev_graph_schema-0.0.11.dist-info/RECORD +11 -0
- cjm_dev_graph_schema-0.0.11.dist-info/WHEEL +5 -0
- cjm_dev_graph_schema-0.0.11.dist-info/licenses/LICENSE +201 -0
- cjm_dev_graph_schema-0.0.11.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.11"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Rename-stable subject resolution (the A+aliases identity machinery).
|
|
2
|
+
|
|
3
|
+
An Entity's identity `key` is a durable, name-INDEPENDENT conceptual slug (e.g.
|
|
4
|
+
`torch-utils`); the current repo name is a `name` property; prior names + variant
|
|
5
|
+
link-slugs are `aliases`. So a fact ABOUT an entity that itself got renamed keeps
|
|
6
|
+
one stable subject — the historical "keep cjm-torch-plugin-utils" claim and the
|
|
7
|
+
current "cjm-substrate-torch-utils" both resolve to the SAME entity.
|
|
8
|
+
|
|
9
|
+
This is the CONFIRMED-equivalence resolution structure. A CANDIDATE equivalence
|
|
10
|
+
(fuzzy slug-drift) becomes an alias via the propose/confirm worklist, never auto-
|
|
11
|
+
guessed here. Pure: builds an index from entity node forms and looks subjects up.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from typing import Any, Dict, Iterable, List, Optional
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _props(node: Any) -> Dict[str, Any]:
|
|
18
|
+
"""Properties dict from an entity wire dict / GraphNode (tolerant access)."""
|
|
19
|
+
p = getattr(node, "properties", None)
|
|
20
|
+
if p is None and isinstance(node, dict):
|
|
21
|
+
p = node.get("properties")
|
|
22
|
+
return p or {}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _node_id(node: Any) -> Optional[str]:
|
|
26
|
+
"""A node's id (typed GraphNode or wire dict)."""
|
|
27
|
+
if isinstance(node, dict):
|
|
28
|
+
return node.get("id")
|
|
29
|
+
return getattr(node, "id", None)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _canon(name: str) -> str:
|
|
33
|
+
"""Canonical lookup key for a subject name (case/space-insensitive)."""
|
|
34
|
+
return str(name).strip().lower()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def build_alias_index(
|
|
38
|
+
entities: Iterable[Any], # Entity node wire dicts / GraphNodes
|
|
39
|
+
) -> Dict[str, str]: # canon(key|name|alias) -> entity node id
|
|
40
|
+
"""Index every entity by its key, current name, and each alias.
|
|
41
|
+
|
|
42
|
+
First writer wins on a collision (`setdefault`) — the durable `key` is added
|
|
43
|
+
first, so a name shared transiently can't hijack a key-resolution."""
|
|
44
|
+
index: Dict[str, str] = {}
|
|
45
|
+
for e in entities:
|
|
46
|
+
nid = _node_id(e)
|
|
47
|
+
if not nid:
|
|
48
|
+
continue
|
|
49
|
+
p = _props(e)
|
|
50
|
+
names: List[Any] = [p.get("key"), p.get("name")] + list(p.get("aliases") or [])
|
|
51
|
+
for n in names:
|
|
52
|
+
if n:
|
|
53
|
+
index.setdefault(_canon(n), nid)
|
|
54
|
+
return index
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def resolve_subject_id(
|
|
58
|
+
index: Dict[str, str], # An index from `build_alias_index`
|
|
59
|
+
name: str, # A subject name / key / alias
|
|
60
|
+
) -> Optional[str]: # The entity node id, or None when unresolved
|
|
61
|
+
"""Resolve a subject name to its entity id via the alias index (no guessing)."""
|
|
62
|
+
return index.get(_canon(name))
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""Deterministic node-id helpers for the dev/decision-provenance domain.
|
|
2
|
+
|
|
3
|
+
Thin domain-specific wrappers over the layer's `derive_node_id` (UUIDv5 over a
|
|
4
|
+
kind + identity tuple). A node's id derives from what makes it THE same node
|
|
5
|
+
across re-derivation (its stable slug/key), never from its correctable content —
|
|
6
|
+
so re-decomposing the same corpus reproduces ids and `extend_graph` collides
|
|
7
|
+
re-emissions into verified no-ops instead of duplicating.
|
|
8
|
+
|
|
9
|
+
The coarse-tier `note_node_id` lands first. Fine-tier id helpers (decisions,
|
|
10
|
+
fact-slots keyed on (subject, predicate), sessions, …) are added as those kinds
|
|
11
|
+
are implemented.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from cjm_context_graph_layer.identity import derive_node_id
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def note_node_id(
|
|
18
|
+
slug: str, # Stable note slug (memory frontmatter `name`, else the corpus-relative path)
|
|
19
|
+
) -> str: # Deterministic Note node id
|
|
20
|
+
"""Note identity = its stable slug.
|
|
21
|
+
|
|
22
|
+
For memory files the slug is the frontmatter `name` (already kebab-case and
|
|
23
|
+
stable across edits); for general markdown without a `name` it is the
|
|
24
|
+
corpus-relative path. Either way the slug is what `[[wiki-links]]` resolve
|
|
25
|
+
against, so a `REFERENCES` edge can target a note's id without first reading
|
|
26
|
+
the target file."""
|
|
27
|
+
return derive_node_id("note", slug)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def topic_node_id(
|
|
31
|
+
name: str, # The topic/category name (already normalized to its stable slug key)
|
|
32
|
+
) -> str: # Deterministic Topic node id
|
|
33
|
+
"""Topic identity = its normalized name slug.
|
|
34
|
+
|
|
35
|
+
A category/tag is shared across notes, so independent `TAGGED` edges from
|
|
36
|
+
different notes converge on one Topic node — the same name (case- and
|
|
37
|
+
separator-normalized to a kebab slug by the markdown core's harvester) always
|
|
38
|
+
derives the same id, the way `[[wiki-links]]` converge on one Note."""
|
|
39
|
+
return derive_node_id("topic", name)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def series_node_id(
|
|
43
|
+
key: str, # The series' stable key (its slug/permalink stem, e.g. "education-notes")
|
|
44
|
+
) -> str: # Deterministic Series node id
|
|
45
|
+
"""Series identity = its stable key.
|
|
46
|
+
|
|
47
|
+
A series (an ordered collection a note belongs to) is shared across its member
|
|
48
|
+
notes, so each member's `IN_SERIES` edge converges on one Series node. The key
|
|
49
|
+
is the series' durable slug (e.g. the `/series/.../<key>.html` permalink stem),
|
|
50
|
+
independent of any member."""
|
|
51
|
+
return derive_node_id("series", key)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def section_node_id(
|
|
55
|
+
note_id: str, # The enclosing Note node id
|
|
56
|
+
anchor: str, # The heading's slug anchor (disambiguated; e.g. "loading-the-model")
|
|
57
|
+
) -> str: # Deterministic Section node id
|
|
58
|
+
"""Section identity = (enclosing Note, heading anchor slug).
|
|
59
|
+
|
|
60
|
+
The anchor is the slugified heading (`## Loading the Model` -> `loading-the-model`,
|
|
61
|
+
duplicate headings disambiguated `-1/-2`) — the SAME slug a cross-post `#anchor`
|
|
62
|
+
link targets, so an inbound anchored REFERENCES resolves to this id by
|
|
63
|
+
construction (no lookup). Derives off the note id so a section belongs to exactly
|
|
64
|
+
one note; the anchor is stable across edits that don't rename the heading, mirroring
|
|
65
|
+
`code_text_node_id` (a region keyed on what it leads with)."""
|
|
66
|
+
return derive_node_id("section", note_id, anchor)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def entity_node_id(
|
|
70
|
+
kind: str, # Entity sub-kind discriminator (e.g. "repo", "stage", "capability", "term")
|
|
71
|
+
key: str, # Stable key within that sub-kind (e.g. the repo name, the stage number)
|
|
72
|
+
) -> str: # Deterministic Entity node id
|
|
73
|
+
"""Entity identity = (sub-kind, stable key).
|
|
74
|
+
|
|
75
|
+
Subject identity is mechanical — deterministic ids on entities/stages/etc.
|
|
76
|
+
are the half of the slot-identity unlock that needs no resolution (the other
|
|
77
|
+
half, the predicate vocabulary, is curated). Reserved here; used as the fine
|
|
78
|
+
tier introduces Fact-slots whose subject is an Entity."""
|
|
79
|
+
return derive_node_id("entity", kind, key)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def factslot_node_id(
|
|
83
|
+
subject_id: str, # The subject node's id (an Entity, usually)
|
|
84
|
+
predicate_slug: str, # The curated predicate slug
|
|
85
|
+
) -> str: # Deterministic Fact-slot node id
|
|
86
|
+
"""Fact-slot identity = (subject, predicate).
|
|
87
|
+
|
|
88
|
+
THE slot-identity unlock made mechanical: the same subject + predicate always
|
|
89
|
+
derives the same slot id, so independent assertions about one fact converge on
|
|
90
|
+
one slot (rather than splintering into parallel free-floating questions)."""
|
|
91
|
+
return derive_node_id("factslot", subject_id, predicate_slug)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def assertion_node_id(
|
|
95
|
+
slot_id: str, # The Fact-slot this value is claimed for
|
|
96
|
+
canonical_value: str, # The value's canonical form (see `predicates.canonical_value`)
|
|
97
|
+
actor: str, # Who claimed it (the assertion is identified by WHAT is claimed, by whom)
|
|
98
|
+
) -> str: # Deterministic Assertion node id
|
|
99
|
+
"""Assertion identity = (slot, canonical value, actor).
|
|
100
|
+
|
|
101
|
+
An assertion is identified by WHAT is claimed, NOT by its why/when/evidence —
|
|
102
|
+
so re-asserting the same value (same actor) is an idempotent no-op, while a
|
|
103
|
+
DIFFERENT value mints a new node (the potential conflict). The when
|
|
104
|
+
(`asserted_at`) and evidence (edges) are content, never identity."""
|
|
105
|
+
return derive_node_id("assertion", slot_id, canonical_value, actor)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def decision_node_id(
|
|
109
|
+
statement_key: str, # The decision's canonical statement (its stable key)
|
|
110
|
+
) -> str: # Deterministic Decision node id
|
|
111
|
+
"""Decision identity = its canonical statement (idempotent re-records)."""
|
|
112
|
+
return derive_node_id("decision", statement_key)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def check_node_id(
|
|
116
|
+
item_id: str, # The work item the check gates closure of
|
|
117
|
+
text_key: str, # The check's canonical text (its stable key)
|
|
118
|
+
) -> str: # Deterministic Check node id
|
|
119
|
+
"""Check identity = (its work item, canonical text) — the same wording on two
|
|
120
|
+
items is two checks (a DoD is scoped to what it closes)."""
|
|
121
|
+
return derive_node_id("check", item_id, text_key)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def session_node_id(
|
|
125
|
+
key: str, # Stable session key (e.g. the session timestamp/id)
|
|
126
|
+
) -> str: # Deterministic Session node id
|
|
127
|
+
"""Session identity = its stable key (so DECIDED_IN/PRODUCED_IN converge)."""
|
|
128
|
+
return derive_node_id("session", key)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def code_module_node_id(
|
|
132
|
+
repo_key: str, # The repo's durable conceptual slug (the rename-stable Entity key; the federation anchor)
|
|
133
|
+
module_path: str, # The module's import-style or repo-relative path (e.g. "cjm_dev_graph_schema/nodes.py")
|
|
134
|
+
) -> str: # Deterministic CodeModule node id
|
|
135
|
+
"""Code-module identity = (repo_key, module_path).
|
|
136
|
+
|
|
137
|
+
Keyed on the repo's DURABLE conceptual slug (not its directory name) + the
|
|
138
|
+
module's repo-relative path, so the id is reproducible in ANY graph that
|
|
139
|
+
decomposes the repo — the cross-graph/federation anchor that lets a different
|
|
140
|
+
project's graph reference this module by its stable id."""
|
|
141
|
+
return derive_node_id("code_module", repo_key, module_path)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def code_symbol_node_id(
|
|
145
|
+
module_id: str, # The enclosing CodeModule node id (already repo+path-stable)
|
|
146
|
+
qualname: str, # The symbol's qualified name within the module (e.g. "EntityNode.to_graph_node")
|
|
147
|
+
) -> str: # Deterministic CodeSymbol node id
|
|
148
|
+
"""Code-symbol identity = (enclosing module, qualified name).
|
|
149
|
+
|
|
150
|
+
Derives off the module id (itself repo+path-stable), so a symbol has the same
|
|
151
|
+
id across re-decomposition and across graphs. Qualname carries nesting
|
|
152
|
+
(`Class.method`), so a method and a same-named free function never collide."""
|
|
153
|
+
return derive_node_id("code_symbol", module_id, qualname)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def cell_node_id(
|
|
157
|
+
module_id: str, # The enclosing notebook CodeModule node id
|
|
158
|
+
cell_key: str, # Stable cell key: the nbformat cell `id` when present, else the positional index
|
|
159
|
+
) -> str: # Deterministic Cell node id
|
|
160
|
+
"""Cell identity = (notebook module, stable cell key).
|
|
161
|
+
|
|
162
|
+
Prefer the nbformat cell `id` (stable across reorder/insert, nbformat >= 4.5) as
|
|
163
|
+
the key; fall back to the positional index when absent. Derives off the notebook
|
|
164
|
+
module id so a cell belongs to exactly one notebook."""
|
|
165
|
+
return derive_node_id("cell", module_id, cell_key)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def code_text_node_id(
|
|
169
|
+
module_id: str, # The enclosing CodeModule node id
|
|
170
|
+
region_key: str, # Stable key for the region (the leading line-anchor of its first statement)
|
|
171
|
+
) -> str: # Deterministic CodeText node id
|
|
172
|
+
"""Code-text-region identity = (module, region key).
|
|
173
|
+
|
|
174
|
+
A `CodeText` is a non-def top-level region of a plain-`.py` module (imports,
|
|
175
|
+
module docstring, constants, `__all__`, `if __name__`) — the verbatim substrate
|
|
176
|
+
BETWEEN the def/class regions that a faithful round-trip must hold. The region
|
|
177
|
+
key anchors on the region's first statement (its leading dotted symbol/keyword),
|
|
178
|
+
so a region keeps its id across edits that don't change what it leads with;
|
|
179
|
+
derives off the module id so a region belongs to exactly one module. Mirrors
|
|
180
|
+
`cell_node_id` (a notebook's verbatim substrate) for the plain-`.py` case."""
|
|
181
|
+
return derive_node_id("code_text", module_id, region_key)
|