zeromodel-navigation 1.1.0__tar.gz
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.
- zeromodel_navigation-1.1.0/PKG-INFO +123 -0
- zeromodel_navigation-1.1.0/README.md +96 -0
- zeromodel_navigation-1.1.0/pyproject.toml +46 -0
- zeromodel_navigation-1.1.0/setup.cfg +4 -0
- zeromodel_navigation-1.1.0/src/zeromodel/navigation/__init__.py +40 -0
- zeromodel_navigation-1.1.0/src/zeromodel/navigation/compiler.py +362 -0
- zeromodel_navigation-1.1.0/src/zeromodel/navigation/dto.py +584 -0
- zeromodel_navigation-1.1.0/src/zeromodel/navigation/errors.py +11 -0
- zeromodel_navigation-1.1.0/src/zeromodel/navigation/rules.py +149 -0
- zeromodel_navigation-1.1.0/src/zeromodel/navigation/storage.py +162 -0
- zeromodel_navigation-1.1.0/src/zeromodel/navigation/traversal.py +174 -0
- zeromodel_navigation-1.1.0/src/zeromodel_navigation.egg-info/PKG-INFO +123 -0
- zeromodel_navigation-1.1.0/src/zeromodel_navigation.egg-info/SOURCES.txt +19 -0
- zeromodel_navigation-1.1.0/src/zeromodel_navigation.egg-info/dependency_links.txt +1 -0
- zeromodel_navigation-1.1.0/src/zeromodel_navigation.egg-info/requires.txt +3 -0
- zeromodel_navigation-1.1.0/src/zeromodel_navigation.egg-info/top_level.txt +1 -0
- zeromodel_navigation-1.1.0/tests/test_closure_over_corpus.py +212 -0
- zeromodel_navigation-1.1.0/tests/test_compile_and_closure.py +338 -0
- zeromodel_navigation-1.1.0/tests/test_load_integrity.py +148 -0
- zeromodel_navigation-1.1.0/tests/test_navigation_api_isolation.py +106 -0
- zeromodel_navigation-1.1.0/tests/test_traversal.py +255 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zeromodel-navigation
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: ZeroModel finite, deterministic artifact-corpus hierarchy compiler and traversal engine
|
|
5
|
+
Author-email: Ernan Hughes <ernanhughes@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ernanhughes/zeromodel
|
|
8
|
+
Project-URL: Repository, https://github.com/ernanhughes/zeromodel
|
|
9
|
+
Project-URL: Documentation, https://ernanhughes.github.io/zeromodel/
|
|
10
|
+
Keywords: visual-policy-map,vpm,hierarchy,traversal,navigation
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Requires-Dist: numpy>=1.23
|
|
25
|
+
Requires-Dist: zeromodel==1.1.0
|
|
26
|
+
Requires-Dist: zeromodel-artifacts==1.1.0
|
|
27
|
+
|
|
28
|
+
# zeromodel-navigation
|
|
29
|
+
|
|
30
|
+
A finite, deterministic hierarchy compiler and traversal engine over
|
|
31
|
+
identified artifacts.
|
|
32
|
+
|
|
33
|
+
## This is not Search
|
|
34
|
+
|
|
35
|
+
This package compiles and traverses a **finite, closed** hierarchy of
|
|
36
|
+
already-identified artifacts. It does not define similarity, relevance, or
|
|
37
|
+
nearest-neighbour retrieval. Traversal rules are stable, declared
|
|
38
|
+
descriptors with production implementations that route on explicit,
|
|
39
|
+
declared criteria (a request attribute, a declared priority, a fixed
|
|
40
|
+
target range) - never a learned or heuristic "closest match."
|
|
41
|
+
|
|
42
|
+
A later Search package may implement similarity-driven `TraversalRule`s
|
|
43
|
+
against the same `TraversalRule` protocol this package defines; that is out
|
|
44
|
+
of scope here.
|
|
45
|
+
|
|
46
|
+
## Two separate hierarchy concepts
|
|
47
|
+
|
|
48
|
+
- The existing **intra-artifact pyramid** (`zeromodel.analysis.hierarchy`,
|
|
49
|
+
`build_pyramid`) reduces a single VPM field into coarser levels of
|
|
50
|
+
itself. Untouched by this package.
|
|
51
|
+
- The **artifact corpus hierarchy** this package compiles is a tree over
|
|
52
|
+
*many* identified artifacts: a root tile, internal navigation tiles, and
|
|
53
|
+
leaf bindings to individual artifacts.
|
|
54
|
+
|
|
55
|
+
## Dependency rule
|
|
56
|
+
|
|
57
|
+
`navigation -> core + artifacts` only. Navigation does not depend on, or
|
|
58
|
+
import, Trust. Secure applications may compose Navigation and Trust
|
|
59
|
+
without coupling the packages directly - see the integration seam example
|
|
60
|
+
below.
|
|
61
|
+
|
|
62
|
+
## Persistence
|
|
63
|
+
|
|
64
|
+
Navigation defines no store or repository of its own. Every tile and leaf
|
|
65
|
+
binding is persisted as canonical bytes through the Artifacts package's
|
|
66
|
+
`ArtifactStore`/`ArtifactResolver` protocol; Navigation owns the DTUs'
|
|
67
|
+
semantics, Artifacts owns storage and identity.
|
|
68
|
+
|
|
69
|
+
## Public API
|
|
70
|
+
|
|
71
|
+
`HierarchyManifestDTO`, `NavigationTileDTO`, `HierarchyCompilerSpecDTO`,
|
|
72
|
+
`TraversalRule`, `TraversalRequestDTO`, `TraversalStepDTO`,
|
|
73
|
+
`TraversalResultDTO`, `TraversalReceiptDTO`, `compile_hierarchy`,
|
|
74
|
+
`validate_hierarchy`, `traverse`, `replay_traversal`.
|
|
75
|
+
|
|
76
|
+
Supporting DTOs (`TileCoverageDTO`, `TilePointerDTO`, `LeafBindingDTO`,
|
|
77
|
+
`TraversalRuleDescriptorDTO`, `TraversalFailureDTO`) are available from
|
|
78
|
+
`zeromodel.navigation.dto` for tests and advanced composition, but are not
|
|
79
|
+
part of the curated top-level surface.
|
|
80
|
+
|
|
81
|
+
## Claims boundary
|
|
82
|
+
|
|
83
|
+
The supported claim is: compiling and deterministically traversing a
|
|
84
|
+
finite, identified hierarchy with complete artifact resolution and a
|
|
85
|
+
replayable trace.
|
|
86
|
+
|
|
87
|
+
This package does **not** claim: planet-scale hierarchies, infinite
|
|
88
|
+
in-memory capacity, logarithmic-time guarantees, semantic search,
|
|
89
|
+
nearest-neighbour retrieval, "40-hop world navigation," or
|
|
90
|
+
storage-independent performance.
|
|
91
|
+
|
|
92
|
+
## Integration seam: composing with Trust (without depending on it)
|
|
93
|
+
|
|
94
|
+
Navigation never imports `zeromodel.trust`. A secure application composes
|
|
95
|
+
both packages at the call site:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from zeromodel.navigation import compile_hierarchy, validate_hierarchy, traverse
|
|
99
|
+
from zeromodel.trust import verify_artifact_for_scope, require_authorized
|
|
100
|
+
|
|
101
|
+
# 1. Resolve the hierarchy root (Navigation + Artifacts only).
|
|
102
|
+
root_manifest = ... # HierarchyManifestDTO, previously compiled
|
|
103
|
+
|
|
104
|
+
# 2. Verify the root/hierarchy is authorized before trusting its contents
|
|
105
|
+
# (Trust only - Navigation is not involved in this step).
|
|
106
|
+
decision = verify_artifact_for_scope(
|
|
107
|
+
artifact_ref=root_manifest.root_ref,
|
|
108
|
+
...,
|
|
109
|
+
)
|
|
110
|
+
require_authorized(decision) # raises ArtifactNotAuthorized if not
|
|
111
|
+
|
|
112
|
+
# 3. Validate structural closure (Navigation only).
|
|
113
|
+
validate_hierarchy(root_manifest, store=artifact_store)
|
|
114
|
+
|
|
115
|
+
# 4. Traverse (Navigation only).
|
|
116
|
+
result = traverse(manifest=root_manifest, store=artifact_store, rule=rule, request=request)
|
|
117
|
+
|
|
118
|
+
# 5. Optionally, verify each resolved leaf artifact through Trust before use.
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
This example is illustrative only - it does not execute a real controller
|
|
122
|
+
and makes no safety-certification claim. A safety-critical caller must not
|
|
123
|
+
skip step 2 or step 5.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# zeromodel-navigation
|
|
2
|
+
|
|
3
|
+
A finite, deterministic hierarchy compiler and traversal engine over
|
|
4
|
+
identified artifacts.
|
|
5
|
+
|
|
6
|
+
## This is not Search
|
|
7
|
+
|
|
8
|
+
This package compiles and traverses a **finite, closed** hierarchy of
|
|
9
|
+
already-identified artifacts. It does not define similarity, relevance, or
|
|
10
|
+
nearest-neighbour retrieval. Traversal rules are stable, declared
|
|
11
|
+
descriptors with production implementations that route on explicit,
|
|
12
|
+
declared criteria (a request attribute, a declared priority, a fixed
|
|
13
|
+
target range) - never a learned or heuristic "closest match."
|
|
14
|
+
|
|
15
|
+
A later Search package may implement similarity-driven `TraversalRule`s
|
|
16
|
+
against the same `TraversalRule` protocol this package defines; that is out
|
|
17
|
+
of scope here.
|
|
18
|
+
|
|
19
|
+
## Two separate hierarchy concepts
|
|
20
|
+
|
|
21
|
+
- The existing **intra-artifact pyramid** (`zeromodel.analysis.hierarchy`,
|
|
22
|
+
`build_pyramid`) reduces a single VPM field into coarser levels of
|
|
23
|
+
itself. Untouched by this package.
|
|
24
|
+
- The **artifact corpus hierarchy** this package compiles is a tree over
|
|
25
|
+
*many* identified artifacts: a root tile, internal navigation tiles, and
|
|
26
|
+
leaf bindings to individual artifacts.
|
|
27
|
+
|
|
28
|
+
## Dependency rule
|
|
29
|
+
|
|
30
|
+
`navigation -> core + artifacts` only. Navigation does not depend on, or
|
|
31
|
+
import, Trust. Secure applications may compose Navigation and Trust
|
|
32
|
+
without coupling the packages directly - see the integration seam example
|
|
33
|
+
below.
|
|
34
|
+
|
|
35
|
+
## Persistence
|
|
36
|
+
|
|
37
|
+
Navigation defines no store or repository of its own. Every tile and leaf
|
|
38
|
+
binding is persisted as canonical bytes through the Artifacts package's
|
|
39
|
+
`ArtifactStore`/`ArtifactResolver` protocol; Navigation owns the DTUs'
|
|
40
|
+
semantics, Artifacts owns storage and identity.
|
|
41
|
+
|
|
42
|
+
## Public API
|
|
43
|
+
|
|
44
|
+
`HierarchyManifestDTO`, `NavigationTileDTO`, `HierarchyCompilerSpecDTO`,
|
|
45
|
+
`TraversalRule`, `TraversalRequestDTO`, `TraversalStepDTO`,
|
|
46
|
+
`TraversalResultDTO`, `TraversalReceiptDTO`, `compile_hierarchy`,
|
|
47
|
+
`validate_hierarchy`, `traverse`, `replay_traversal`.
|
|
48
|
+
|
|
49
|
+
Supporting DTOs (`TileCoverageDTO`, `TilePointerDTO`, `LeafBindingDTO`,
|
|
50
|
+
`TraversalRuleDescriptorDTO`, `TraversalFailureDTO`) are available from
|
|
51
|
+
`zeromodel.navigation.dto` for tests and advanced composition, but are not
|
|
52
|
+
part of the curated top-level surface.
|
|
53
|
+
|
|
54
|
+
## Claims boundary
|
|
55
|
+
|
|
56
|
+
The supported claim is: compiling and deterministically traversing a
|
|
57
|
+
finite, identified hierarchy with complete artifact resolution and a
|
|
58
|
+
replayable trace.
|
|
59
|
+
|
|
60
|
+
This package does **not** claim: planet-scale hierarchies, infinite
|
|
61
|
+
in-memory capacity, logarithmic-time guarantees, semantic search,
|
|
62
|
+
nearest-neighbour retrieval, "40-hop world navigation," or
|
|
63
|
+
storage-independent performance.
|
|
64
|
+
|
|
65
|
+
## Integration seam: composing with Trust (without depending on it)
|
|
66
|
+
|
|
67
|
+
Navigation never imports `zeromodel.trust`. A secure application composes
|
|
68
|
+
both packages at the call site:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from zeromodel.navigation import compile_hierarchy, validate_hierarchy, traverse
|
|
72
|
+
from zeromodel.trust import verify_artifact_for_scope, require_authorized
|
|
73
|
+
|
|
74
|
+
# 1. Resolve the hierarchy root (Navigation + Artifacts only).
|
|
75
|
+
root_manifest = ... # HierarchyManifestDTO, previously compiled
|
|
76
|
+
|
|
77
|
+
# 2. Verify the root/hierarchy is authorized before trusting its contents
|
|
78
|
+
# (Trust only - Navigation is not involved in this step).
|
|
79
|
+
decision = verify_artifact_for_scope(
|
|
80
|
+
artifact_ref=root_manifest.root_ref,
|
|
81
|
+
...,
|
|
82
|
+
)
|
|
83
|
+
require_authorized(decision) # raises ArtifactNotAuthorized if not
|
|
84
|
+
|
|
85
|
+
# 3. Validate structural closure (Navigation only).
|
|
86
|
+
validate_hierarchy(root_manifest, store=artifact_store)
|
|
87
|
+
|
|
88
|
+
# 4. Traverse (Navigation only).
|
|
89
|
+
result = traverse(manifest=root_manifest, store=artifact_store, rule=rule, request=request)
|
|
90
|
+
|
|
91
|
+
# 5. Optionally, verify each resolved leaf artifact through Trust before use.
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
This example is illustrative only - it does not execute a real controller
|
|
95
|
+
and makes no safety-certification claim. A safety-critical caller must not
|
|
96
|
+
skip step 2 or step 5.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "zeromodel-navigation"
|
|
7
|
+
version = "1.1.0"
|
|
8
|
+
description = "ZeroModel finite, deterministic artifact-corpus hierarchy compiler and traversal engine"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [{name = "Ernan Hughes", email = "ernanhughes@gmail.com"}]
|
|
13
|
+
keywords = [
|
|
14
|
+
"visual-policy-map",
|
|
15
|
+
"vpm",
|
|
16
|
+
"hierarchy",
|
|
17
|
+
"traversal",
|
|
18
|
+
"navigation",
|
|
19
|
+
]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Development Status :: 4 - Beta",
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"Intended Audience :: Science/Research",
|
|
24
|
+
"License :: OSI Approved :: MIT License",
|
|
25
|
+
"Operating System :: OS Independent",
|
|
26
|
+
"Programming Language :: Python :: 3",
|
|
27
|
+
"Programming Language :: Python :: 3.10",
|
|
28
|
+
"Programming Language :: Python :: 3.11",
|
|
29
|
+
"Programming Language :: Python :: 3.12",
|
|
30
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
31
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
32
|
+
]
|
|
33
|
+
dependencies = [
|
|
34
|
+
"numpy>=1.23",
|
|
35
|
+
"zeromodel==1.1.0",
|
|
36
|
+
"zeromodel-artifacts==1.1.0",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://github.com/ernanhughes/zeromodel"
|
|
41
|
+
Repository = "https://github.com/ernanhughes/zeromodel"
|
|
42
|
+
Documentation = "https://ernanhughes.github.io/zeromodel/"
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.packages.find]
|
|
45
|
+
where = ["src"]
|
|
46
|
+
include = ["zeromodel.navigation*"]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""zeromodel-navigation: finite, deterministic artifact-corpus hierarchy.
|
|
2
|
+
|
|
3
|
+
Depends only on `zeromodel` (core) and `zeromodel-artifacts`. Does not
|
|
4
|
+
depend on, or import, `zeromodel.trust`.
|
|
5
|
+
|
|
6
|
+
The public surface is deliberately restricted to the 12 names in
|
|
7
|
+
`__all__`. Supporting DTOs (`TileCoverageDTO`, `TilePointerDTO`,
|
|
8
|
+
`LeafBindingDTO`, `TraversalRuleDescriptorDTO`, `TraversalFailureDTO`) and
|
|
9
|
+
the reference rule implementations remain available from
|
|
10
|
+
`zeromodel.navigation.dto` / `zeromodel.navigation.rules` for tests and
|
|
11
|
+
advanced composition.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from zeromodel.navigation.compiler import compile_hierarchy, validate_hierarchy
|
|
15
|
+
from zeromodel.navigation.dto import (
|
|
16
|
+
HierarchyCompilerSpecDTO,
|
|
17
|
+
HierarchyManifestDTO,
|
|
18
|
+
NavigationTileDTO,
|
|
19
|
+
TraversalReceiptDTO,
|
|
20
|
+
TraversalRequestDTO,
|
|
21
|
+
TraversalResultDTO,
|
|
22
|
+
TraversalStepDTO,
|
|
23
|
+
)
|
|
24
|
+
from zeromodel.navigation.rules import TraversalRule
|
|
25
|
+
from zeromodel.navigation.traversal import replay_traversal, traverse
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"HierarchyCompilerSpecDTO",
|
|
29
|
+
"HierarchyManifestDTO",
|
|
30
|
+
"NavigationTileDTO",
|
|
31
|
+
"TraversalRequestDTO",
|
|
32
|
+
"TraversalResultDTO",
|
|
33
|
+
"TraversalReceiptDTO",
|
|
34
|
+
"TraversalRule",
|
|
35
|
+
"TraversalStepDTO",
|
|
36
|
+
"compile_hierarchy",
|
|
37
|
+
"replay_traversal",
|
|
38
|
+
"traverse",
|
|
39
|
+
"validate_hierarchy",
|
|
40
|
+
]
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
"""Deterministic compilation and structural closure validation.
|
|
2
|
+
|
|
3
|
+
`compile_hierarchy` never reorders its input: identical `source_artifacts`
|
|
4
|
+
order plus an identical `spec` always yields an identical `HierarchyManifestDTO.
|
|
5
|
+
hierarchy_id`; a deliberately different order or a changed spec parameter
|
|
6
|
+
yields a different one. `validate_hierarchy` is an exhaustive, finite
|
|
7
|
+
structural walk of an already-compiled hierarchy - it performs no
|
|
8
|
+
similarity comparison and is not search.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import List, Optional, Sequence, Tuple
|
|
14
|
+
|
|
15
|
+
from zeromodel.artifacts import ArtifactRef, ArtifactResolver, ArtifactStore
|
|
16
|
+
|
|
17
|
+
from zeromodel.navigation.dto import (
|
|
18
|
+
HierarchyCompilerSpecDTO,
|
|
19
|
+
HierarchyManifestDTO,
|
|
20
|
+
NavigationTileDTO,
|
|
21
|
+
TileCoverageDTO,
|
|
22
|
+
TilePointerDTO,
|
|
23
|
+
compute_hierarchy_id,
|
|
24
|
+
compute_leaf_id,
|
|
25
|
+
compute_source_artifact_digest,
|
|
26
|
+
compute_tile_id,
|
|
27
|
+
)
|
|
28
|
+
from zeromodel.navigation.dto import LeafBindingDTO
|
|
29
|
+
from zeromodel.navigation.errors import HierarchyClosureError, HierarchyCompilationError
|
|
30
|
+
from zeromodel.navigation.storage import (
|
|
31
|
+
TILE_ARTIFACT_KIND,
|
|
32
|
+
leaf_binding_exists,
|
|
33
|
+
load_leaf_binding,
|
|
34
|
+
load_tile,
|
|
35
|
+
store_leaf_binding,
|
|
36
|
+
store_tile,
|
|
37
|
+
tile_exists,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _chunk(items: Sequence, size: int) -> List[Sequence]:
|
|
42
|
+
return [items[i : i + size] for i in range(0, len(items), size)]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _validate_corpus_contract(
|
|
46
|
+
spec: HierarchyCompilerSpecDTO, source_artifacts: Sequence[ArtifactRef]
|
|
47
|
+
) -> None:
|
|
48
|
+
if not source_artifacts:
|
|
49
|
+
raise HierarchyCompilationError(
|
|
50
|
+
"compile_hierarchy requires at least one source artifact"
|
|
51
|
+
)
|
|
52
|
+
seen: set = set()
|
|
53
|
+
for ref in source_artifacts:
|
|
54
|
+
if ref.artifact_kind != spec.corpus_artifact_kind:
|
|
55
|
+
raise HierarchyCompilationError(
|
|
56
|
+
f"artifact {ref.artifact_id} has kind {ref.artifact_kind!r}, "
|
|
57
|
+
f"but this corpus requires {spec.corpus_artifact_kind!r}"
|
|
58
|
+
)
|
|
59
|
+
key = (ref.artifact_kind, ref.artifact_id)
|
|
60
|
+
if key in seen:
|
|
61
|
+
raise HierarchyCompilationError(
|
|
62
|
+
f"duplicate source artifact {ref.artifact_id}: each source artifact "
|
|
63
|
+
"must appear at most once in one hierarchy"
|
|
64
|
+
)
|
|
65
|
+
seen.add(key)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _build_leaf_level(
|
|
69
|
+
spec: HierarchyCompilerSpecDTO,
|
|
70
|
+
source_artifacts: Sequence[ArtifactRef],
|
|
71
|
+
store: ArtifactStore,
|
|
72
|
+
) -> List[Tuple[TilePointerDTO, int]]:
|
|
73
|
+
"""Level 0: one leaf binding per source artifact, in declared order.
|
|
74
|
+
|
|
75
|
+
Each entry is (pointer, leaf_count_under_this_pointer) - leaf_count is 1
|
|
76
|
+
for every leaf and is summed up through parent levels for coverage.
|
|
77
|
+
"""
|
|
78
|
+
level: List[Tuple[TilePointerDTO, int]] = []
|
|
79
|
+
for index, ref in enumerate(source_artifacts):
|
|
80
|
+
leaf_id = compute_leaf_id(artifact_ref=ref, leaf_semantics=spec.leaf_semantics)
|
|
81
|
+
binding = LeafBindingDTO(
|
|
82
|
+
leaf_id=leaf_id, artifact_ref=ref, leaf_semantics=spec.leaf_semantics
|
|
83
|
+
)
|
|
84
|
+
store_leaf_binding(store, binding)
|
|
85
|
+
pointer = TilePointerDTO(
|
|
86
|
+
pointer_kind="leaf", target_id=leaf_id, order_key=f"{index:08d}"
|
|
87
|
+
)
|
|
88
|
+
level.append((pointer, 1))
|
|
89
|
+
return level
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _build_parent_level(
|
|
93
|
+
current: List[Tuple[TilePointerDTO, int]],
|
|
94
|
+
spec: HierarchyCompilerSpecDTO,
|
|
95
|
+
depth_from_leaves: int,
|
|
96
|
+
store: ArtifactStore,
|
|
97
|
+
) -> List[Tuple[TilePointerDTO, int]]:
|
|
98
|
+
next_level: List[Tuple[TilePointerDTO, int]] = []
|
|
99
|
+
for chunk_index, chunk_entries in enumerate(
|
|
100
|
+
_chunk(current, spec.max_children_per_tile)
|
|
101
|
+
):
|
|
102
|
+
children = tuple(entry[0] for entry in chunk_entries)
|
|
103
|
+
leaf_count = sum(entry[1] for entry in chunk_entries)
|
|
104
|
+
coverage = TileCoverageDTO(
|
|
105
|
+
corpus_id=spec.corpus_id,
|
|
106
|
+
partition_key=f"L{depth_from_leaves}-{chunk_index}",
|
|
107
|
+
child_count=len(children),
|
|
108
|
+
leaf_count=leaf_count,
|
|
109
|
+
)
|
|
110
|
+
tile_id = compute_tile_id(
|
|
111
|
+
depth=depth_from_leaves,
|
|
112
|
+
coverage=coverage,
|
|
113
|
+
children=children,
|
|
114
|
+
tie_rule=spec.tie_rule,
|
|
115
|
+
)
|
|
116
|
+
tile = NavigationTileDTO(
|
|
117
|
+
tile_id=tile_id,
|
|
118
|
+
depth=depth_from_leaves,
|
|
119
|
+
coverage=coverage,
|
|
120
|
+
children=children,
|
|
121
|
+
tie_rule=spec.tie_rule,
|
|
122
|
+
)
|
|
123
|
+
store_tile(store, tile)
|
|
124
|
+
pointer = TilePointerDTO(
|
|
125
|
+
pointer_kind="tile", target_id=tile_id, order_key=f"{chunk_index:08d}"
|
|
126
|
+
)
|
|
127
|
+
next_level.append((pointer, leaf_count))
|
|
128
|
+
return next_level
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _build_manifest(
|
|
132
|
+
spec: HierarchyCompilerSpecDTO, root_ref: ArtifactRef, source_artifact_digest: str
|
|
133
|
+
) -> HierarchyManifestDTO:
|
|
134
|
+
hierarchy_id = compute_hierarchy_id(
|
|
135
|
+
root_ref=root_ref, source_artifact_digest=source_artifact_digest, spec=spec
|
|
136
|
+
)
|
|
137
|
+
return HierarchyManifestDTO(
|
|
138
|
+
hierarchy_id=hierarchy_id,
|
|
139
|
+
root_ref=root_ref,
|
|
140
|
+
source_artifact_digest=source_artifact_digest,
|
|
141
|
+
compiler_id=spec.compiler_id,
|
|
142
|
+
compiler_version=spec.compiler_version,
|
|
143
|
+
corpus_id=spec.corpus_id,
|
|
144
|
+
corpus_artifact_kind=spec.corpus_artifact_kind,
|
|
145
|
+
leaf_semantics=spec.leaf_semantics,
|
|
146
|
+
max_children_per_tile=spec.max_children_per_tile,
|
|
147
|
+
max_depth=spec.max_depth,
|
|
148
|
+
tie_rule=spec.tie_rule,
|
|
149
|
+
failure_rule=spec.failure_rule,
|
|
150
|
+
navigation_rule_contract=spec.navigation_rule_contract,
|
|
151
|
+
child_ordering_rule=spec.child_ordering_rule,
|
|
152
|
+
partition_parameters=spec.partition_parameters,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def compile_hierarchy(
|
|
157
|
+
*,
|
|
158
|
+
spec: HierarchyCompilerSpecDTO,
|
|
159
|
+
source_artifacts: Sequence[ArtifactRef],
|
|
160
|
+
store: ArtifactStore,
|
|
161
|
+
) -> HierarchyManifestDTO:
|
|
162
|
+
_validate_corpus_contract(spec, source_artifacts)
|
|
163
|
+
|
|
164
|
+
current = _build_leaf_level(spec, source_artifacts, store)
|
|
165
|
+
depth_from_leaves = 0
|
|
166
|
+
while True:
|
|
167
|
+
next_level = _build_parent_level(current, spec, depth_from_leaves, store)
|
|
168
|
+
if len(next_level) == 1:
|
|
169
|
+
root_ref = ArtifactRef(
|
|
170
|
+
artifact_kind="navigation-tile", artifact_id=next_level[0][0].target_id
|
|
171
|
+
)
|
|
172
|
+
break
|
|
173
|
+
current = next_level
|
|
174
|
+
depth_from_leaves += 1
|
|
175
|
+
|
|
176
|
+
source_artifact_digest = compute_source_artifact_digest(tuple(source_artifacts))
|
|
177
|
+
manifest = _build_manifest(spec, root_ref, source_artifact_digest)
|
|
178
|
+
|
|
179
|
+
# Defense in depth: the compiler never hands back a hierarchy that
|
|
180
|
+
# would not itself pass closure validation (this also enforces
|
|
181
|
+
# max_depth against the tree the grouping loop actually produced).
|
|
182
|
+
try:
|
|
183
|
+
validate_hierarchy(manifest, store)
|
|
184
|
+
except HierarchyClosureError as exc:
|
|
185
|
+
raise HierarchyCompilationError(
|
|
186
|
+
f"compiled hierarchy failed closure validation: {exc}"
|
|
187
|
+
) from exc
|
|
188
|
+
return manifest
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _validate_root_reference(
|
|
192
|
+
manifest: HierarchyManifestDTO, store: ArtifactResolver
|
|
193
|
+
) -> None:
|
|
194
|
+
if manifest.root_ref.artifact_kind != TILE_ARTIFACT_KIND:
|
|
195
|
+
raise HierarchyClosureError(
|
|
196
|
+
f"hierarchy root reference must have kind {TILE_ARTIFACT_KIND!r}, "
|
|
197
|
+
f"got {manifest.root_ref.artifact_kind!r}"
|
|
198
|
+
)
|
|
199
|
+
if not tile_exists(store, manifest.root_ref.artifact_id):
|
|
200
|
+
raise HierarchyClosureError(
|
|
201
|
+
f"hierarchy root {manifest.root_ref.artifact_id} does not resolve"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _validate_tile_invariants(
|
|
206
|
+
tile: NavigationTileDTO,
|
|
207
|
+
tile_id: str,
|
|
208
|
+
manifest: HierarchyManifestDTO,
|
|
209
|
+
expected_depth_from_leaves: Optional[int],
|
|
210
|
+
) -> None:
|
|
211
|
+
if (
|
|
212
|
+
expected_depth_from_leaves is not None
|
|
213
|
+
and tile.depth != expected_depth_from_leaves
|
|
214
|
+
):
|
|
215
|
+
raise HierarchyClosureError(
|
|
216
|
+
f"tile {tile_id} declares depth={tile.depth}, but its position in the "
|
|
217
|
+
f"hierarchy requires depth={expected_depth_from_leaves}"
|
|
218
|
+
)
|
|
219
|
+
if tile.coverage.corpus_id != manifest.corpus_id:
|
|
220
|
+
raise HierarchyClosureError(
|
|
221
|
+
f"tile {tile_id} declares corpus_id={tile.coverage.corpus_id!r}, "
|
|
222
|
+
f"expected {manifest.corpus_id!r}"
|
|
223
|
+
)
|
|
224
|
+
if tile.coverage.child_count != len(tile.children):
|
|
225
|
+
raise HierarchyClosureError(
|
|
226
|
+
f"tile {tile_id} declares child_count that does not match its children"
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _validate_leaf_child(
|
|
231
|
+
*,
|
|
232
|
+
store: ArtifactResolver,
|
|
233
|
+
manifest: HierarchyManifestDTO,
|
|
234
|
+
tile_id: str,
|
|
235
|
+
tile: NavigationTileDTO,
|
|
236
|
+
child: TilePointerDTO,
|
|
237
|
+
) -> ArtifactRef:
|
|
238
|
+
"""Validate one leaf child and return its resolved source artifact ref."""
|
|
239
|
+
if tile.depth != 0:
|
|
240
|
+
raise HierarchyClosureError(
|
|
241
|
+
f"tile {tile_id} has a leaf child but declares depth={tile.depth} (expected 0)"
|
|
242
|
+
)
|
|
243
|
+
if not leaf_binding_exists(store, child.target_id):
|
|
244
|
+
raise HierarchyClosureError(
|
|
245
|
+
f"tile {tile_id} child leaf {child.target_id} does not resolve"
|
|
246
|
+
)
|
|
247
|
+
binding = load_leaf_binding(store, child.target_id)
|
|
248
|
+
if binding.leaf_semantics != manifest.leaf_semantics:
|
|
249
|
+
raise HierarchyClosureError(
|
|
250
|
+
f"leaf {child.target_id} declares semantics {binding.leaf_semantics!r}, "
|
|
251
|
+
f"expected {manifest.leaf_semantics!r}"
|
|
252
|
+
)
|
|
253
|
+
if binding.artifact_ref.artifact_kind != manifest.corpus_artifact_kind:
|
|
254
|
+
raise HierarchyClosureError(
|
|
255
|
+
f"leaf {child.target_id} artifact kind {binding.artifact_ref.artifact_kind!r} "
|
|
256
|
+
f"violates corpus contract {manifest.corpus_artifact_kind!r}"
|
|
257
|
+
)
|
|
258
|
+
if not store.has(binding.artifact_ref):
|
|
259
|
+
raise HierarchyClosureError(
|
|
260
|
+
f"leaf {child.target_id} references source artifact "
|
|
261
|
+
f"{binding.artifact_ref.artifact_id} which does not resolve - "
|
|
262
|
+
"closure over the routing structure is not closure over the corpus"
|
|
263
|
+
)
|
|
264
|
+
return binding.artifact_ref
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _reconcile_source_digest(
|
|
268
|
+
manifest: HierarchyManifestDTO, reachable_source_refs: List[ArtifactRef]
|
|
269
|
+
) -> None:
|
|
270
|
+
actual_source_digest = compute_source_artifact_digest(tuple(reachable_source_refs))
|
|
271
|
+
if actual_source_digest != manifest.source_artifact_digest:
|
|
272
|
+
raise HierarchyClosureError(
|
|
273
|
+
"the hierarchy's actually-reachable source artifacts do not match its "
|
|
274
|
+
"declared source_artifact_digest - a source artifact was added, removed, "
|
|
275
|
+
"or substituted since compilation"
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def validate_hierarchy(
|
|
280
|
+
manifest: HierarchyManifestDTO, store: ArtifactResolver
|
|
281
|
+
) -> Tuple[str, ...]:
|
|
282
|
+
"""Exhaustively validate structural closure. Returns every reachable
|
|
283
|
+
tile_id (root first) on success; raises `HierarchyClosureError` on the
|
|
284
|
+
first violation found.
|
|
285
|
+
|
|
286
|
+
This checks closure over the *routing structure* (tiles/leaf bindings
|
|
287
|
+
resolve, no cycles, bounded depth, consistent coverage/depth bookkeeping)
|
|
288
|
+
and over the *represented corpus* (every leaf's actual source artifact
|
|
289
|
+
resolves through the store, and the full reachable source set matches
|
|
290
|
+
the hierarchy's declared `source_artifact_digest` exactly - nothing
|
|
291
|
+
silently added, removed, or substituted).
|
|
292
|
+
"""
|
|
293
|
+
_validate_root_reference(manifest, store)
|
|
294
|
+
|
|
295
|
+
visited_tiles: List[str] = []
|
|
296
|
+
path_stack: set = set()
|
|
297
|
+
reachable_source_refs: List[ArtifactRef] = []
|
|
298
|
+
|
|
299
|
+
def _walk(
|
|
300
|
+
tile_id: str, depth_from_root: int, expected_depth_from_leaves: Optional[int]
|
|
301
|
+
) -> int:
|
|
302
|
+
"""Returns the number of leaves actually reachable under this tile."""
|
|
303
|
+
if tile_id in path_stack:
|
|
304
|
+
raise HierarchyClosureError(f"cycle detected at tile {tile_id}")
|
|
305
|
+
if depth_from_root > manifest.max_depth:
|
|
306
|
+
raise HierarchyClosureError(
|
|
307
|
+
f"hierarchy exceeds declared max_depth={manifest.max_depth} at tile {tile_id}"
|
|
308
|
+
)
|
|
309
|
+
tile = load_tile(store, tile_id)
|
|
310
|
+
_validate_tile_invariants(tile, tile_id, manifest, expected_depth_from_leaves)
|
|
311
|
+
visited_tiles.append(tile_id)
|
|
312
|
+
path_stack.add(tile_id)
|
|
313
|
+
|
|
314
|
+
actual_leaf_count = 0
|
|
315
|
+
seen_targets = set()
|
|
316
|
+
for child in tile.children:
|
|
317
|
+
if child.target_id == tile_id:
|
|
318
|
+
raise HierarchyClosureError(
|
|
319
|
+
f"tile {tile_id} references itself as a child"
|
|
320
|
+
)
|
|
321
|
+
if child.target_id in seen_targets:
|
|
322
|
+
raise HierarchyClosureError(
|
|
323
|
+
f"tile {tile_id} has a duplicate child reference {child.target_id}"
|
|
324
|
+
)
|
|
325
|
+
seen_targets.add(child.target_id)
|
|
326
|
+
|
|
327
|
+
if child.pointer_kind == "tile":
|
|
328
|
+
if not tile_exists(store, child.target_id):
|
|
329
|
+
raise HierarchyClosureError(
|
|
330
|
+
f"tile {tile_id} child tile {child.target_id} does not resolve"
|
|
331
|
+
)
|
|
332
|
+
actual_leaf_count += _walk(
|
|
333
|
+
child.target_id, depth_from_root + 1, tile.depth - 1
|
|
334
|
+
)
|
|
335
|
+
elif child.pointer_kind == "leaf":
|
|
336
|
+
reachable_source_refs.append(
|
|
337
|
+
_validate_leaf_child(
|
|
338
|
+
store=store,
|
|
339
|
+
manifest=manifest,
|
|
340
|
+
tile_id=tile_id,
|
|
341
|
+
tile=tile,
|
|
342
|
+
child=child,
|
|
343
|
+
)
|
|
344
|
+
)
|
|
345
|
+
actual_leaf_count += 1
|
|
346
|
+
else: # pragma: no cover - TilePointerDTO already restricts this
|
|
347
|
+
raise HierarchyClosureError(
|
|
348
|
+
f"tile {tile_id} has a child with an unknown pointer_kind"
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
path_stack.discard(tile_id)
|
|
352
|
+
|
|
353
|
+
if actual_leaf_count != tile.coverage.leaf_count:
|
|
354
|
+
raise HierarchyClosureError(
|
|
355
|
+
f"tile {tile_id} declares leaf_count={tile.coverage.leaf_count}, but "
|
|
356
|
+
f"{actual_leaf_count} leaves are actually reachable beneath it"
|
|
357
|
+
)
|
|
358
|
+
return actual_leaf_count
|
|
359
|
+
|
|
360
|
+
_walk(manifest.root_ref.artifact_id, 0, expected_depth_from_leaves=None)
|
|
361
|
+
_reconcile_source_digest(manifest, reachable_source_refs)
|
|
362
|
+
return tuple(visited_tiles)
|