Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
test-noio test-db test-coverage store-durations diff-coverage fmt clean help \
migrate-status migrate-apply migrate-new migrate-hash precommit precommit-run \
arch-check arch-show docs-stage docs-build docs-serve openapi-snapshot \
mutmut-audit mutmut-browse restore-drill
record-dispositions mutmut-audit mutmut-browse restore-drill

API_DIR := apps/api
COMPOSE := docker compose -f infra/docker-compose.yml
Expand Down Expand Up @@ -36,6 +36,7 @@ help:
@echo " arch-check Tach dependency contract + architecture fitness-function tests"
@echo " arch-show Open the dependency graph (tach show)"
@echo " openapi-snapshot Regenerate apps/api/openapi.json from create_app()"
@echo " record-dispositions Regenerate the record-export redaction table"
@echo " mutmut-audit Run mutmut against Access BC deciders/evolver (audit cadence, ~5-15 min)"
@echo " mutmut-browse Open mutmut's interactive TUI to triage surviving mutants"
@echo " precommit Install pre-commit hooks (one-time per clone)"
Expand All @@ -62,15 +63,15 @@ db-reset:
$(COMPOSE) up -d postgres

lint:
cd $(API_DIR) && uv run ruff check src tests
cd $(API_DIR) && uv run ruff format --check src tests
cd $(API_DIR) && uv run ruff check src tests tools
cd $(API_DIR) && uv run ruff format --check src tests tools

fmt:
cd $(API_DIR) && uv run ruff check --fix src tests
cd $(API_DIR) && uv run ruff format src tests
cd $(API_DIR) && uv run ruff check --fix src tests tools
cd $(API_DIR) && uv run ruff format src tests tools

typecheck:
cd $(API_DIR) && uv run pyright src tests
cd $(API_DIR) && uv run pyright src tests tools

# pytest-xdist with `--dist=worksteal -n 4`: worksteal is the
# scheduler-of-choice for mixed-duration suites (50ms unit alongside
Expand Down Expand Up @@ -138,6 +139,19 @@ openapi-snapshot:
cd $(API_DIR) && APP_ENV=test uv run python -c "import json; from cora.api.main import create_app; \
f = open('openapi.json', 'w'); json.dump(create_app().openapi(), f, indent=2, sort_keys=True); f.write('\n'); f.close()"

# Regenerate the record-export redaction disposition table after adding
# an event type, adding a field, or changing a field's declared type.
# The drift test (tests/architecture/test_record_dispositions_drift.py)
# fails until this is run and the diff is reviewed in the PR, and that
# diff is the list of what a published record would disclose.
#
# The generator ABORTS on an annotation it cannot classify. That is the
# design, not a bug: an unrecognised type is a question about the model.
# Pass --survey to list every such annotation in one pass instead of
# hitting them one exception at a time.
record-dispositions:
cd $(API_DIR) && uv run python tools/gen_record_dispositions.py

# Mutation testing audit. Pure-logic scope via the CLI wildcard pattern
# (Access BC deciders + evolver only). [tool.mutmut] in apps/api/pyproject.toml
# carries the test-selection + runner config. Audit-only — not per-PR.
Expand Down
7 changes: 5 additions & 2 deletions apps/api/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,10 @@ packages = ["src/cora"]
[tool.ruff]
line-length = 100
target-version = "py313"
src = ["src", "tests"]
# `tools` holds build-time generators that MAY import every bounded
# context; they live outside `src` so nothing shippable can import them,
# but they are still first-party code and get linted and type-checked.
src = ["src", "tests", "tools"]
# Parked directories are work-in-progress slices the user explicitly
# shelved (suffix `.parked`); mirrors the same exclusion in [tool.pyright].
extend-exclude = ["**/*.parked"]
Expand Down Expand Up @@ -298,7 +301,7 @@ indent-style = "space"
known-first-party = ["cora", "tests"]

[tool.pyright]
include = ["src", "tests"]
include = ["src", "tests", "tools"]
# Parked directories are work-in-progress slices the user explicitly
# shelved (suffix `.parked`); they shouldn't block type-check on the
# active codebase. Same convention applies to ruff (below).
Expand Down
120 changes: 120 additions & 0 deletions apps/api/src/cora/infrastructure/record_export/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"""Record export: CORA's record as a hashed, offline-verifiable artifact.

The design is `project_record_export_v3.md`, locked for the no-beam
commissioning scope. This package holds the exporter and the generated
redaction disposition table it reads.

It lives at `cora.infrastructure`, whose `tach.toml` entry allows
`cora.shared` and nothing else, and that constraint is the guarantee
rather than the obstacle: the exporter composes nothing (column-driven
over raw rows, a registry of `str -> str` pairs, and a table read as
data), so the layering rule ENFORCES the zero-bounded-context-import
property that makes the standalone-verifiability claim honest. If a
change here wants to import an aggregate, the design has drifted.

Two things this package must never do, both learned from review rather
than from first principles:

- Drive extraction, redaction or parity from `LogbookSchema`.
`LogbookFieldType` is closed over six scalars, so no schema can name a
jsonb column, and jsonb is where the doing lives. The schema travels
with the export as documentation.
- Describe the published record as anonymous. It is pseudonymous:
timestamps plus a facility's own published beamtime schedule
re-identify without the token map.
"""

from cora.infrastructure.record_export._export import (
EmptyExportError,
ExportedRecord,
capture_watermark,
export_record,
)
from cora.infrastructure.record_export._hashing import (
LOGBOOKS_PAYLOAD_TYPE,
RECORD_PAYLOAD_TYPE,
REDACTION_PROFILE_PAYLOAD_TYPE,
STREAMS_PAYLOAD_TYPE,
hash_logbooks,
hash_record,
hash_redaction_profile,
hash_streams,
)
from cora.infrastructure.record_export._manifest import Manifest, build_manifest, capture_git_commit
from cora.infrastructure.record_export._redact_tier1 import (
Tier1Redactor,
UnknownEventTypeError,
redact_tier1_payload,
)
from cora.infrastructure.record_export._redact_tier2 import (
TIER2_DISPOSITIONS,
TIER2_JSONB_CLEARED_POINTERS,
TIER2_JSONB_DROPPED_COLUMNS,
UnfiredClearanceError,
ensure_all_clearances_fired,
redact_tier2_row,
)
from cora.infrastructure.record_export._redaction import (
RedactedRecord,
RedactionProfileMismatchError,
RedactionResult,
redact_record,
)
from cora.infrastructure.record_export._registry import (
EntriesReader,
EntriesTableSpec,
UnknownLogbookKindError,
all_specs,
registered_envelope_classes,
resolve,
)
from cora.infrastructure.record_export._render import render_row, render_value
from cora.infrastructure.record_export._stream_types import (
KNOWN_STREAM_TYPES,
UnknownStreamTypeError,
ensure_stream_type_known,
)
from cora.infrastructure.record_export._tokens import TokenMap

__all__ = [
"KNOWN_STREAM_TYPES",
"LOGBOOKS_PAYLOAD_TYPE",
"RECORD_PAYLOAD_TYPE",
"REDACTION_PROFILE_PAYLOAD_TYPE",
"STREAMS_PAYLOAD_TYPE",
"TIER2_DISPOSITIONS",
"TIER2_JSONB_CLEARED_POINTERS",
"TIER2_JSONB_DROPPED_COLUMNS",
"EmptyExportError",
"EntriesReader",
"EntriesTableSpec",
"ExportedRecord",
"Manifest",
"RedactedRecord",
"RedactionProfileMismatchError",
"RedactionResult",
"Tier1Redactor",
"TokenMap",
"UnfiredClearanceError",
"UnknownEventTypeError",
"UnknownLogbookKindError",
"UnknownStreamTypeError",
"all_specs",
"build_manifest",
"capture_git_commit",
"capture_watermark",
"ensure_all_clearances_fired",
"ensure_stream_type_known",
"export_record",
"hash_logbooks",
"hash_record",
"hash_redaction_profile",
"hash_streams",
"redact_record",
"redact_tier1_payload",
"redact_tier2_row",
"registered_envelope_classes",
"render_row",
"render_value",
"resolve",
]
Loading
Loading