Block conflicting extension write selectors
This commit is contained in:
@@ -47447,6 +47447,18 @@ def repository_apply_gate(payload: dict[str, Any], method: str, mode: str) -> di
|
||||
"schema": "onec_repository_write_gate.v1", "method": method,
|
||||
"base_id": payload.get("base_id"), **context_error,
|
||||
}
|
||||
layer_block = development_layer_write_block(payload)
|
||||
if layer_block:
|
||||
return {
|
||||
"schema": "onec_repository_write_gate.v1",
|
||||
"method": method,
|
||||
"status": "blocked",
|
||||
"base_id": payload.get("base_id"),
|
||||
**layer_block,
|
||||
"diagnostics": {
|
||||
"message": "Saved-state apply is blocked because the public extension selector did not resolve to one matching development layer.",
|
||||
},
|
||||
}
|
||||
gate = repository_control.write_gate(payload)
|
||||
support = live_sql_support_gate(payload)
|
||||
if gate.get("allowed") is True and support.get("allowed") is True:
|
||||
@@ -47491,9 +47503,12 @@ def metadata_write_preflight(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
}
|
||||
repository_gate = repository_control.write_gate(gate_payload)
|
||||
support_gate = live_sql_support_gate(gate_payload)
|
||||
layer_block = development_layer_write_block(payload)
|
||||
schedule_status = str(schedule_plan.get("status") or "blocked")
|
||||
status = "ready" if schedule_status in {"planned", "unchanged"} else schedule_status
|
||||
if repository_gate.get("allowed") is not True:
|
||||
if layer_block:
|
||||
status = "blocked"
|
||||
elif repository_gate.get("allowed") is not True:
|
||||
status = str(repository_gate.get("status") or "blocked")
|
||||
elif support_gate.get("allowed") is not True:
|
||||
status = str(support_gate.get("status") or "blocked")
|
||||
@@ -47526,6 +47541,11 @@ def metadata_write_preflight(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
},
|
||||
"repository": repository_gate,
|
||||
"support": support_gate,
|
||||
**(
|
||||
{"development_layer_resolution": payload["development_layer_resolution"]}
|
||||
if isinstance(payload.get("development_layer_resolution"), dict)
|
||||
else {}
|
||||
),
|
||||
"guards": {
|
||||
"required": ["allow_saved_state_write", "expected_sha1", "backup", "readback_verification"],
|
||||
"requires_saved_state_prepare": schedule_status == "needs_prepare",
|
||||
@@ -47649,7 +47669,10 @@ def metadata_write_preflight(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
gate_payload["target"] = {**caller_target, **plan_target}
|
||||
repository_gate = repository_control.write_gate(gate_payload)
|
||||
support_gate = live_sql_support_gate(gate_payload)
|
||||
if repository_gate.get("allowed") is not True:
|
||||
layer_block = development_layer_write_block(payload)
|
||||
if layer_block:
|
||||
status = "blocked"
|
||||
elif repository_gate.get("allowed") is not True:
|
||||
status = str(repository_gate.get("status") or "blocked")
|
||||
elif support_gate.get("allowed") is not True:
|
||||
status = str(support_gate.get("status") or "blocked")
|
||||
@@ -47682,6 +47705,11 @@ def metadata_write_preflight(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
"saved_state": saved_state,
|
||||
"repository": repository_gate,
|
||||
"support": support_gate,
|
||||
**(
|
||||
{"development_layer_resolution": payload["development_layer_resolution"]}
|
||||
if isinstance(payload.get("development_layer_resolution"), dict)
|
||||
else {}
|
||||
),
|
||||
**({"owner_resolution": payload["owner_resolution"]} if isinstance(payload.get("owner_resolution"), dict) else {}),
|
||||
"guards": guards,
|
||||
"plan": {
|
||||
@@ -54672,6 +54700,20 @@ def repository_resolve_form_guid_sql(*, method: str, base_id: str, form_guid: st
|
||||
_FORM_MODULE_FILE_RE = re.compile(r"^(?:(?P<extension>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})__)?(?P<form>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.0$", re.IGNORECASE)
|
||||
|
||||
|
||||
def development_layer_write_block(payload: dict[str, Any]) -> dict[str, Any] | None:
|
||||
resolution = payload.get("development_layer_resolution") if isinstance(payload.get("development_layer_resolution"), dict) else {}
|
||||
status = str(resolution.get("status") or "").strip().casefold()
|
||||
if not status or status == "resolved":
|
||||
return None
|
||||
diagnostics = resolution.get("diagnostics") if isinstance(resolution.get("diagnostics"), dict) else {}
|
||||
error = str(diagnostics.get("error") or "extension_layer_not_resolved")
|
||||
return {
|
||||
"error": error,
|
||||
"layer_id": resolution.get("layer_id") or "extension:unresolved",
|
||||
"development_layer_resolution": resolution,
|
||||
}
|
||||
|
||||
|
||||
def resolve_write_gate_context(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Derive the exact development layer and form ownership before gates.
|
||||
|
||||
@@ -54705,16 +54747,47 @@ def resolve_write_gate_context(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
else:
|
||||
requested_extension = str(requested_extension_value or "").strip()
|
||||
base_id = str(payload.get("base_id") or "").strip()
|
||||
resolved_extension_guid = explicit_extension_guid
|
||||
resolved_extension_guid = ""
|
||||
extension_error: dict[str, Any] | None = None
|
||||
if not resolved_extension_guid and requested_extension and base_id:
|
||||
resolved_extension_guid, extension_error = extension_filter_to_guid(
|
||||
selector_conflict: dict[str, Any] | None = None
|
||||
if explicit_extension_guid and not is_guid_text(explicit_extension_guid):
|
||||
selector_conflict = {
|
||||
"status": "invalid_argument",
|
||||
"error": "invalid_extension_guid",
|
||||
"extension_guid": explicit_extension_guid,
|
||||
}
|
||||
resolved_name_guid: str | None = None
|
||||
if requested_extension and base_id and selector_conflict is None:
|
||||
resolved_name_guid, extension_error = extension_filter_to_guid(
|
||||
base_id,
|
||||
requested_extension,
|
||||
method="write.gate_context",
|
||||
)
|
||||
if extension_error:
|
||||
selector_conflict = extension_error
|
||||
elif explicit_extension_guid and resolved_name_guid != explicit_extension_guid:
|
||||
selector_conflict = {
|
||||
"status": "conflict",
|
||||
"error": "extension_selector_conflict",
|
||||
"base_id": base_id,
|
||||
"query": {
|
||||
"extension": requested_extension,
|
||||
"extension_guid": explicit_extension_guid,
|
||||
},
|
||||
"resolved_extension_guid": resolved_name_guid,
|
||||
"diagnostics": {
|
||||
"message": "The public extension name resolves to a different GUID than the supplied write context.",
|
||||
},
|
||||
}
|
||||
elif requested_extension and not base_id and selector_conflict is None:
|
||||
selector_conflict = {
|
||||
"status": "not_resolved",
|
||||
"error": "extension_resolution_requires_base_id",
|
||||
"base_id": None,
|
||||
}
|
||||
if selector_conflict is None:
|
||||
resolved_extension_guid = str(resolved_name_guid or explicit_extension_guid or "").lower()
|
||||
if resolved_extension_guid:
|
||||
resolved_extension_guid = resolved_extension_guid.lower()
|
||||
result["extension_guid"] = resolved_extension_guid
|
||||
target["extension_guid"] = resolved_extension_guid
|
||||
target["origin"] = {
|
||||
@@ -54736,18 +54809,24 @@ def resolve_write_gate_context(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
},
|
||||
"layer_id": f"extension:{resolved_extension_guid}",
|
||||
}
|
||||
elif requested_extension:
|
||||
# Never let an unresolved public extension selector inherit the base
|
||||
# repository/support policy. The synthetic layer cannot match a
|
||||
# configured GUID layer and therefore keeps every write gate closed.
|
||||
elif requested_extension or selector_conflict:
|
||||
# An explicit preflight/apply block consumes this resolution. The
|
||||
# synthetic layer also prevents accidental inheritance of base policy.
|
||||
result.pop("extension_guid", None)
|
||||
target.pop("extension_guid", None)
|
||||
result["layer_id"] = "extension:unresolved"
|
||||
target["layer_id"] = "extension:unresolved"
|
||||
unresolved_status = "conflict" if str((selector_conflict or {}).get("error") or "") == "extension_selector_conflict" else "not_resolved"
|
||||
result["development_layer_resolution"] = {
|
||||
"status": "not_resolved",
|
||||
"status": unresolved_status,
|
||||
"source": "extension_name",
|
||||
"extension": {"name": requested_extension},
|
||||
"extension": {
|
||||
**({"name": requested_extension} if requested_extension else {}),
|
||||
**({"supplied_guid": explicit_extension_guid} if explicit_extension_guid else {}),
|
||||
},
|
||||
"layer_id": "extension:unresolved",
|
||||
"diagnostics": extension_error
|
||||
"diagnostics": selector_conflict
|
||||
or extension_error
|
||||
or {
|
||||
"status": "not_resolved",
|
||||
"error": "extension_resolution_requires_base_id",
|
||||
@@ -54756,6 +54835,8 @@ def resolve_write_gate_context(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
}
|
||||
if target:
|
||||
result["target"] = target
|
||||
if development_layer_write_block(result):
|
||||
return result
|
||||
existing = payload.get("owner_resolution") if isinstance(payload.get("owner_resolution"), dict) else {}
|
||||
if existing.get("status") == "resolved" and str(existing.get("repository_object") or "").strip():
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user