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.
@@ -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)