Pass 4 / Finding F, commit 1 of 10 from BUILD_AZAZEL.md. Lays the
load-bearing foundation: classification discipline (property 11) at
the type system level, and unmanaged-destination opacity (property
12) via a Python sentinel. Every later commit in the Azazel build
inherits these defenses for free.
CREATED:
azazel_processor/
pyproject.toml — Poetry config matching
korban_processor; pythonpath
includes ../korban_processor/src
so the cross-package
classification-discipline test
can import both packages
src/azazel_processor/
__init__.py — public surface for commit 1:
AzazelStage, AzazelStaged,
AzazelStageError, ERETZ_GEZERAH,
is_eretz_gezerah
eretz_gezerah.py — the destination sentinel; module
__getattr__ refuses any
attribute beyond the two public
names so introspection cannot
discover further handles
stages.py — AzazelStage enum (six
checkpoints from the build doc:
STAGED → CONFESSED → ROUTED →
DESTROYED → ACKNOWLEDGED →
RECORDED), AzazelStaged frozen
dataclass, AzazelStageError
test/
test_eretz_gezerah.py — 11 tests covering: sentinel
identity stable across imports,
not-a-string, no public attrs,
repr leaks no destination info,
identity (is) not equality (==),
impostor with __eq__ rejected,
module __getattr__ refuses
unknown access, CI invariant
forbidding destination-handle
ASSIGNMENTS in source (catches
regressions; mention in
docstrings/__all__/getattr
allowlist is fine)
test_stages.py — 10 tests: intra-Azazel gating
(happy path, mismatch raises
naming both stages, frozen
dataclass, six distinct
checkpoints) and CROSS-PACKAGE
classification discipline:
KorbanStaged and AzazelStaged
are distinct types; enum value
spaces are disjoint; an Azazel
function refuses a KorbanStaged
input and vice versa; no
conversion function exists in
the public surface
PROPERTY COVERAGE THIS COMMIT:
Property 11 (classification discipline):
Defended by the type-distinctness of AzazelStaged vs.
korban_processor.stages.Staged. Cross-package tests verify the
two cannot be substituted at runtime; the absence of any
korban_to_azazel/to_korban/from_korban/convert function in the
public surface is verified by name-shape grep.
Property 12 (unmanaged destination):
Defended by the sentinel design: only object identity is exposed;
no string handle exists; module __getattr__ refuses introspection;
CI invariant catches assignment-shaped regressions across all of
azazel_processor/src/.
PENDING (commits 2-10 per BUILD_AZAZEL.md):
vidui (commit 2 — enumerative commit, property 3)
shaliach + tevilah (commit 3 — role-separated operator, property 4)
goral (commit 4 — blind routing, property 2)
azazel orchestrator + state machine (commit 5 — properties 5, 7, 10)
protocol envelope (commit 6 — protocol-restricted access, property 8)
L3 azazel_records (commit 7 — operator-internal half of property 9)
/peah/azazel_log endpoint (commit 8 — public half of property 9)
Korban→Azazel routing on empty-vital + chatzotzrot wiring
(commit 9 — properties 1, 6)
Closure pass (commit 10)
Tests: azazel_processor 21 passed. korban_processor unchanged at
203 passed, 1 skipped (live-API). No regressions across the boundary.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
210 lines
7.4 KiB
Python
210 lines
7.4 KiB
Python
"""
|
|
Tests for the eretz_gezerah sentinel — property 12 (unmanaged
|
|
destination opacity) from azazel_engineering_reading.md §14.
|
|
|
|
The destination MUST be opaque. There is no name, no URL, no queue
|
|
identifier, no string handle that encodes anything about where state
|
|
goes after disposal. The only legitimate reference is the sentinel
|
|
ERETZ_GEZERAH; identity comparison (`is`) is the only legitimate
|
|
predicate.
|
|
"""
|
|
|
|
import pathlib
|
|
|
|
import pytest
|
|
|
|
from azazel_processor.eretz_gezerah import ERETZ_GEZERAH, is_eretz_gezerah
|
|
|
|
|
|
# ============================================================
|
|
# The sentinel itself
|
|
# ============================================================
|
|
|
|
|
|
def test_sentinel_exists():
|
|
"""ERETZ_GEZERAH is importable and is the same object across imports."""
|
|
from azazel_processor.eretz_gezerah import ERETZ_GEZERAH as second_import
|
|
|
|
assert ERETZ_GEZERAH is second_import
|
|
|
|
|
|
def test_sentinel_is_a_plain_object_not_a_string():
|
|
"""A string would be searchable, interned, serializable. The sentinel
|
|
must NOT be a string. Property 12: opacity of name."""
|
|
assert not isinstance(ERETZ_GEZERAH, str)
|
|
assert not isinstance(ERETZ_GEZERAH, bytes)
|
|
|
|
|
|
def test_sentinel_has_no_meaningful_attributes():
|
|
"""A bare object() has no useful attributes for tooling to
|
|
discover. We don't grant the sentinel a name, a destination
|
|
string, or any other tracking handle."""
|
|
# Plain object() exposes only the dunder methods. No public attrs.
|
|
public = [a for a in dir(ERETZ_GEZERAH) if not a.startswith("_")]
|
|
assert public == []
|
|
|
|
|
|
def test_sentinel_repr_does_not_leak_destination_info():
|
|
"""The repr is just object()'s default — no operator-readable name
|
|
encoding 'where it goes'. Default Python repr is `<object object at
|
|
0x...>` which leaks only the address (changes per process)."""
|
|
r = repr(ERETZ_GEZERAH)
|
|
assert "<object object at 0x" in r
|
|
assert "azazel" not in r.lower()
|
|
assert "void" not in r.lower()
|
|
assert "null" not in r.lower()
|
|
|
|
|
|
# ============================================================
|
|
# is_eretz_gezerah/1
|
|
# ============================================================
|
|
|
|
|
|
def test_is_eretz_gezerah_recognizes_canonical_sentinel():
|
|
assert is_eretz_gezerah(ERETZ_GEZERAH)
|
|
|
|
|
|
def test_is_eretz_gezerah_rejects_other_objects():
|
|
assert not is_eretz_gezerah(None)
|
|
assert not is_eretz_gezerah(object())
|
|
assert not is_eretz_gezerah("eretz_gezerah")
|
|
assert not is_eretz_gezerah("ERETZ_GEZERAH")
|
|
assert not is_eretz_gezerah(0)
|
|
assert not is_eretz_gezerah([])
|
|
|
|
|
|
def test_is_eretz_gezerah_uses_identity_not_equality():
|
|
"""A class with __eq__ that returns True for anything must not
|
|
be confused with the sentinel. Identity is the gate; equality
|
|
would let any impostor in."""
|
|
|
|
class Impostor:
|
|
def __eq__(self, other):
|
|
return True
|
|
|
|
def __hash__(self):
|
|
return 0
|
|
|
|
impostor = Impostor()
|
|
assert impostor == ERETZ_GEZERAH # equality is True (impostor lies)
|
|
assert not is_eretz_gezerah(impostor) # but identity is not
|
|
|
|
|
|
# ============================================================
|
|
# Module-level __getattr__ refuses introspection
|
|
# ============================================================
|
|
|
|
|
|
def test_module_refuses_unknown_attribute_access():
|
|
"""The module exposes only ERETZ_GEZERAH and is_eretz_gezerah.
|
|
Any other attribute access must raise — there are no further
|
|
handles to discover."""
|
|
import azazel_processor.eretz_gezerah as mod
|
|
|
|
with pytest.raises(AttributeError) as exc:
|
|
mod.DESTINATION_URL # noqa: B018 — deliberate access for the test
|
|
|
|
assert "no further handles" in str(exc.value).lower() or \
|
|
"opaque" in str(exc.value).lower()
|
|
|
|
|
|
def test_module_refuses_invented_metadata_lookup():
|
|
import azazel_processor.eretz_gezerah as mod
|
|
|
|
for invented in ("destination", "endpoint", "queue_name", "host", "address"):
|
|
with pytest.raises(AttributeError):
|
|
getattr(mod, invented)
|
|
|
|
|
|
# ============================================================
|
|
# CI-style invariant: no destination string in the codebase
|
|
# ============================================================
|
|
#
|
|
# This is the grep-the-codebase check from BUILD_AZAZEL.md §12.
|
|
# It runs as a normal test so a regression at any layer of the
|
|
# build (a future contributor adding `DESTINATION_URL = "..."` to
|
|
# any module) fires immediately.
|
|
|
|
|
|
# Forbidden ASSIGNMENT-SHAPED patterns. The failure mode is someone
|
|
# adding a module-level constant that reintroduces a destination
|
|
# handle (e.g. `DESTINATION_URL = "..."`). Mere mention in a
|
|
# docstring, an `__all__` re-export, or a `__getattr__` allowlist is
|
|
# NOT the failure mode; what we defend against is binding a forbidden
|
|
# name to a value at module scope.
|
|
import re
|
|
|
|
_FORBIDDEN_ASSIGNMENT_PATTERNS = [
|
|
re.compile(rf"^\s*{name}\s*=", re.MULTILINE)
|
|
for name in (
|
|
"DESTINATION_URL",
|
|
"DESTINATION_HOST",
|
|
"AZAZEL_QUEUE",
|
|
"AZAZEL_ENDPOINT",
|
|
"DISPOSAL_HOST",
|
|
"DISPOSAL_URL",
|
|
"DISPOSAL_QUEUE",
|
|
"AZAZEL_RECIPIENT",
|
|
"DISPOSAL_RECIPIENT",
|
|
)
|
|
]
|
|
|
|
|
|
def _walk_source_files():
|
|
"""Yield every .py file under src/ — the source the operator has
|
|
to read to understand the channel. Tests are excluded (test
|
|
files legitimately reference forbidden patterns in assertions)."""
|
|
src_root = pathlib.Path(__file__).resolve().parent.parent / "src"
|
|
yield from src_root.rglob("*.py")
|
|
|
|
|
|
def test_no_destination_handle_assigned_in_source():
|
|
"""Property 12 enforced as a CI invariant. If a future contributor
|
|
adds `DESTINATION_URL = "..."` (or any of the other forbidden
|
|
handle-shaped names) to ANY module in azazel_processor/, this
|
|
test fails — they reintroduced the failure mode the sentinel
|
|
exists to prevent.
|
|
|
|
Catches assignment, not mention. Documenting "do not add
|
|
DESTINATION_URL" in a docstring is fine; `DESTINATION_URL = "x"`
|
|
is the regression.
|
|
"""
|
|
offenders = []
|
|
for path in _walk_source_files():
|
|
text = path.read_text()
|
|
for pattern in _FORBIDDEN_ASSIGNMENT_PATTERNS:
|
|
for match in pattern.finditer(text):
|
|
offenders.append((path.name, match.group().strip()))
|
|
|
|
assert offenders == [], (
|
|
f"Forbidden destination-handle assignment(s) in azazel_processor "
|
|
f"source: {offenders}. The destination is opaque by specification "
|
|
f"(property 12, azazel_engineering_reading.md §14). Use "
|
|
f"ERETZ_GEZERAH (the Python sentinel), not a string-valued "
|
|
f"module constant."
|
|
)
|
|
|
|
|
|
def test_sentinel_name_not_assigned_to_string_in_source():
|
|
"""A module-level `X = 'ERETZ_GEZERAH'` would treat the sentinel
|
|
name as if it were a string handle — re-introducing the very
|
|
string-tracking the sentinel exists to prevent. Mere mention in
|
|
`__all__` or `__getattr__`'s allowlist is fine; binding a name
|
|
to the string is not.
|
|
"""
|
|
string_assignment = re.compile(
|
|
r"""^\s*\w+\s*=\s*["']ERETZ_GEZERAH["']""", re.MULTILINE
|
|
)
|
|
offenders = []
|
|
for path in _walk_source_files():
|
|
text = path.read_text()
|
|
if string_assignment.search(text):
|
|
offenders.append(path.name)
|
|
|
|
assert offenders == [], (
|
|
f"Module-level assignment binding the string 'ERETZ_GEZERAH' "
|
|
f"to a name in: {offenders}. The sentinel is a Python object, "
|
|
f"not a string. Refer to it via the imported name, not via a "
|
|
f"string handle."
|
|
)
|