Skip to content

Commit f26ebcc

Browse files
Merge issue-35-django-parity: implement Django compatibility parity gate (#35)
2 parents a64af71 + 1f9e6af commit f26ebcc

30 files changed

Lines changed: 6600 additions & 1 deletion

Makefile

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
.PHONY: setup lint format format-check typecheck migrations-check django-check deployment-check \
22
test-core test test-compatibility compatibility-source-artifacts-check \
3-
compatibility-artifacts-check test-playwright-core test-playwright migrate run worker
3+
compatibility-artifacts-check check-links check-seo compatibility-real-gate-blocked-check \
4+
test-playwright-core test-playwright migrate run worker
45

56
ADOPTION_INTEGRATION_PYTHON = \
67
accounts/managers.py \
@@ -67,6 +68,26 @@ compatibility-artifacts-check:
6768
cmp _docs/compatibility/legacy-manifest-differences.json \
6869
.tmp/compatibility/legacy-manifest-differences.check.json
6970

71+
check-links:
72+
uv run pytest compatibility/tests/test_links.py compatibility/tests/test_runtime.py -q
73+
74+
check-seo:
75+
uv run pytest compatibility/tests/test_expectations.py compatibility/tests/test_report.py \
76+
compatibility/tests/test_target.py compatibility/tests/test_parity.py \
77+
compatibility/tests/test_monitoring.py -q
78+
79+
compatibility-real-gate-blocked-check:
80+
mkdir -p .tmp/compatibility
81+
rm -f .tmp/compatibility/checked-real-seo-parity-report.json
82+
@if DJANGO_SETTINGS_MODULE=website.settings.test uv run python manage.py compatibility_gate \
83+
--route-sha256 0000000000000000000000000000000000000000000000000000000000000000 \
84+
--asset-sha256 1111111111111111111111111111111111111111111111111111111111111111 \
85+
--projection-sha256 2222222222222222222222222222222222222222222222222222222222222222 \
86+
--output .tmp/compatibility/checked-real-seo-parity-report.json; then \
87+
echo "Unapproved checked inputs unexpectedly passed" >&2; exit 1; \
88+
fi
89+
uv run python -c 'import hashlib,json,pathlib; root=pathlib.Path("_docs/compatibility"); p=json.load(open(".tmp/compatibility/checked-real-seo-parity-report.json", encoding="utf-8")); digest=lambda name: hashlib.sha256((root/name).read_bytes()).hexdigest(); assert p["status"] == "BLOCKED" and p["expectation_count"] == 0; assert p["manifest_sha256"] == digest("legacy-manifest.jsonl"); assert p["differences_sha256"] == digest("legacy-manifest-differences.json"); assert p["public_contracts_sha256"] == digest("public-contracts.jsonl")'
90+
7091
test-playwright-core:
7192
DJANGO_SETTINGS_MODULE=website.settings.test DJANGO_ALLOW_ASYNC_UNSAFE=true uv run pytest playwright_tests -m core -v
7293

_docs/compatibility/README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,38 @@ SHA-256 digest is
8282
Every manifest row is still `preserve` with `review_state=proposed_preserve`; the comparison did
8383
not automatically approve parity, a redirect, or a retirement.
8484

85+
## Django parity gate
86+
87+
Issue #35 adds a separate approved-target contract rather than changing this observation evidence.
88+
`approved-expectation.schema.json` describes reviewed preserve, redirect, and retirement records
89+
bound to the exact manifest, difference-ledger, and public-contract digests. A preserve requires
90+
`approved_parity`; a redirect or retirement requires `approved_exception` plus an owner, reason,
91+
and focused test. Target observations and reports use `target-observation.schema.json` and
92+
`seo-parity-report.schema.json`.
93+
94+
The generic gate captures anonymous GET responses through Django without network access, retains
95+
the exact input path/query spelling, compares server-rendered metadata/content/links/assets and
96+
sitemap state, and emits only stable value-free findings. Its runtime registry exposes only
97+
approved one-hop 301/308 and direct 410 decisions; an unknown path remains a normal 404. Local
98+
captures suppress runtime monitoring through an internal context marker that an HTTP client cannot
99+
set. Runtime events use known contract IDs or low-cardinality unknown/external groups and never
100+
record raw queries, fragments, unknown paths, referrers, user IDs, or response bodies.
101+
102+
The checked real inputs intentionally have no approved expectation sidecar yet. This must remain a
103+
nonzero `BLOCKED` result until adapter issues produce independently reviewed expectations:
104+
105+
```console
106+
make check-links
107+
make check-seo
108+
make compatibility-real-gate-blocked-check
109+
```
110+
111+
The last target succeeds only when the management command exits nonzero and its report says
112+
`BLOCKED` with zero approved expectations. A fixture `PASS` is never whole-site or cutover approval.
113+
WSGI routes on decoded `PATH_INFO`; `raw_network_reference` preserves collector evidence, while any
114+
contract that depends on encoded separators still requires a deployed edge test. External URLs are
115+
compared byte-for-byte but are not contacted by this activation gate.
116+
85117
CI parses both checked artifacts strictly, validates their schemas, re-encodes the manifest,
86118
recomputes the comparison, verifies exact URL sets and digests, and checks source metadata
87119
invariants. The same verification is available offline:

0 commit comments

Comments
 (0)