Close HUB-WP-0009 conformance gaps (C2, C7, C9, C10); mark blocked workplans
Implements the four residual conformance checks left open by the T04
minimal vertical:
- C2: GET /ports/registry/registrations/{hub_slug} resolves missing (404),
ambiguous (shared reuse_surface_id across hub_slugs), and stale
(deprecated/retired descriptor) registrations; a new .../audit route
exposes queryable registration history from the existing in-memory
history and the PostgreSQL runtime_audit_ledger.
- C7: harness proof that disabled compatibility groups deny access
(404) with no fixture credentials involved, matching the existing
fail-closed compat router behavior.
- C9: harness proof plus a dedicated test that /readyz degrades only on
an unavailable configured dependency while unrelated disabled
projections stay non-blocking.
- C10: ContractValidator now negotiates contract_version_min/max against
the runtime's contract version and rejects incompatible or inverted
ranges with an explicit 422 instead of silently accepting them.
HUB-WP-0009 is now finished. HUB-WP-0006 is marked blocked: its only open
task (T06) has no remaining hub-core code path and waits on an external
Forgejo identity/production deployment gate. HUB-WP-0011 is marked
blocked: T02/T03 already waited on external credential/deployment
review, and T01 needs a source/destination ownership and retention
decision against live message data before it can be implemented safely.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 310936@bnt-lap001
Assistant-Session: 00cd9abe-09a0-416b-88e0-f907b9101629
This commit is contained in:
parent
b0e89592c6
commit
e89d621f18
11 changed files with 496 additions and 20 deletions
|
|
@ -163,8 +163,105 @@ class ConformanceHarness:
|
|||
),
|
||||
)
|
||||
)
|
||||
|
||||
ambiguous_slug = f"{self.package['descriptor']['hub_slug']}-conformance-ambiguous"
|
||||
ambiguous_package = _with_hub_slug(self.package, ambiguous_slug)
|
||||
self.target.post(
|
||||
"/ports/registry/registrations",
|
||||
headers={"X-Correlation-ID": str(uuid4())},
|
||||
json=ambiguous_package,
|
||||
)
|
||||
resolution = self.target.get(
|
||||
f"/ports/registry/registrations/{self.package['descriptor']['hub_slug']}"
|
||||
)
|
||||
missing = self.target.get("/ports/registry/registrations/hub-that-was-never-registered")
|
||||
audit = self.target.get(
|
||||
f"/ports/registry/registrations/{self.package['descriptor']['hub_slug']}/audit"
|
||||
)
|
||||
results.append(
|
||||
self._check(
|
||||
"C2",
|
||||
2,
|
||||
"registry resolution reports missing, ambiguous, and audited registrations",
|
||||
lambda: _assert_registry_resolution(resolution, missing, audit, ambiguous_slug),
|
||||
)
|
||||
)
|
||||
|
||||
results.append(
|
||||
self._check(
|
||||
"C7",
|
||||
2,
|
||||
"disabled compatibility groups deny access without needing fixture credentials",
|
||||
self._raw_port_policy_denies_by_default,
|
||||
)
|
||||
)
|
||||
|
||||
results.append(
|
||||
self._check(
|
||||
"C9",
|
||||
2,
|
||||
"readiness reports each dependency without blocking on unrelated disabled groups",
|
||||
self._dependency_aware_readiness,
|
||||
)
|
||||
)
|
||||
|
||||
incompatible_package = _with_contract_version_range(
|
||||
self.package, "9.9.9", "9.9.9", hub_slug=f"{ambiguous_slug}-incompatible"
|
||||
)
|
||||
incompatible_response = self.target.post(
|
||||
"/ports/registry/registrations",
|
||||
headers={"X-Correlation-ID": str(uuid4())},
|
||||
json=incompatible_package,
|
||||
)
|
||||
results.append(
|
||||
self._check(
|
||||
"C10",
|
||||
2,
|
||||
"registration outside the runtime's supported contract range is rejected",
|
||||
lambda: _assert_status_and_incompatibility(incompatible_response),
|
||||
)
|
||||
)
|
||||
|
||||
return ConformanceReport(contract_version=CONTRACT_VERSION, checks=tuple(results))
|
||||
|
||||
def _raw_port_policy_denies_by_default(self) -> None:
|
||||
read_only_group = self.target.get("/api/v2/hubs")
|
||||
operator_console = self.target.get("/console")
|
||||
if read_only_group.status_code != 404:
|
||||
raise AssertionError(
|
||||
"disabled registry compatibility group did not deny access "
|
||||
f"(status {read_only_group.status_code})"
|
||||
)
|
||||
if operator_console.status_code != 404:
|
||||
raise AssertionError(
|
||||
"disabled operator compatibility group did not deny access "
|
||||
f"(status {operator_console.status_code})"
|
||||
)
|
||||
|
||||
def _dependency_aware_readiness(self) -> None:
|
||||
response = self.target.get("/readyz")
|
||||
if response.status_code not in {200, 503}:
|
||||
raise AssertionError(f"/readyz returned unexpected status {response.status_code}")
|
||||
body = response.json()
|
||||
checks = body.get("checks", {})
|
||||
dependency_keys = ("database", "repo_manager_projection", "workload_projection")
|
||||
dependency_values = {}
|
||||
for required in dependency_keys:
|
||||
if required not in checks:
|
||||
raise AssertionError(f"readiness checks missing '{required}'")
|
||||
if checks[required] not in {"ok", "stale", "unavailable", "not_applicable"}:
|
||||
raise AssertionError(f"readiness check '{required}' has unexpected value")
|
||||
dependency_values[required] = checks[required]
|
||||
if checks.get("authorization") not in {"ok", "unavailable"}:
|
||||
raise AssertionError("readiness check 'authorization' has unexpected value")
|
||||
all_non_blocking = checks.get("authorization") == "ok" and all(
|
||||
value in {"ok", "not_applicable"} for value in dependency_values.values()
|
||||
)
|
||||
if all_non_blocking and body.get("status") != "ok":
|
||||
raise AssertionError("readiness reported degraded with no failing dependency")
|
||||
if not all_non_blocking and body.get("status") != "degraded":
|
||||
raise AssertionError("readiness reported ok while a dependency is unavailable or stale")
|
||||
|
||||
def _schema_validate(self) -> None:
|
||||
_validator(self.schema_root.joinpath("hub-descriptor.schema.json")).validate(
|
||||
self.package["descriptor"]
|
||||
|
|
@ -318,6 +415,49 @@ def _assert_projection_rebuild(
|
|||
raise AssertionError("message authority fixture missing from message projection")
|
||||
|
||||
|
||||
def _with_hub_slug(package: Mapping[str, Any], hub_slug: str) -> dict[str, Any]:
|
||||
descriptor = {**package["descriptor"], "hub_slug": hub_slug}
|
||||
return {**package, "descriptor": descriptor}
|
||||
|
||||
|
||||
def _with_contract_version_range(
|
||||
package: Mapping[str, Any],
|
||||
version_min: str,
|
||||
version_max: str,
|
||||
*,
|
||||
hub_slug: str,
|
||||
) -> dict[str, Any]:
|
||||
descriptor = {
|
||||
**package["descriptor"],
|
||||
"hub_slug": hub_slug,
|
||||
"contract_version_min": version_min,
|
||||
"contract_version_max": version_max,
|
||||
}
|
||||
return {**package, "descriptor": descriptor}
|
||||
|
||||
|
||||
def _assert_registry_resolution(
|
||||
resolution: ResponseLike,
|
||||
missing: ResponseLike,
|
||||
audit: ResponseLike,
|
||||
ambiguous_slug: str,
|
||||
) -> None:
|
||||
_expect_status(missing, 404, "unregistered hub_slug resolution")
|
||||
_expect_status(resolution, 200, "registered hub_slug resolution")
|
||||
data = resolution.json().get("data", {})
|
||||
if data.get("resolution") != "ambiguous":
|
||||
raise AssertionError("second registration sharing reuse_surface_id was not flagged ambiguous")
|
||||
if ambiguous_slug not in data.get("ambiguous_with", []):
|
||||
raise AssertionError("ambiguous resolution did not name the conflicting hub_slug")
|
||||
_expect_status(audit, 200, "registration audit history")
|
||||
if not audit.json().get("items"):
|
||||
raise AssertionError("registration audit history is empty")
|
||||
|
||||
|
||||
def _assert_status_and_incompatibility(response: ResponseLike) -> None:
|
||||
_expect_status(response, 422, "out-of-range contract version registration")
|
||||
|
||||
|
||||
def _projection_items(response: ResponseLike, projection_id: str) -> list[dict[str, Any]]:
|
||||
_expect_status(response, 200, f"{projection_id} projection")
|
||||
items = response.json().get("data", {}).get("items")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue