fix: close Task 4 contract graph review findings
This commit is contained in:
@@ -132,6 +132,31 @@ def _schema(value: object, path: str) -> JsonSchema:
|
||||
return normalized
|
||||
|
||||
|
||||
def _is_named_schema_reference(value: object) -> bool:
|
||||
return (
|
||||
isinstance(value, Mapping)
|
||||
and set(value) == {"$ref"}
|
||||
and isinstance(value.get("$ref"), str)
|
||||
and value["$ref"].startswith("#/components/schemas/")
|
||||
)
|
||||
|
||||
|
||||
def _is_composed_named_result_schema(value: object) -> bool:
|
||||
if not isinstance(value, Mapping):
|
||||
return False
|
||||
|
||||
composition_keys = [key for key in ("allOf", "anyOf", "oneOf") if key in value]
|
||||
if len(composition_keys) != 1:
|
||||
return False
|
||||
|
||||
branches = value[composition_keys[0]]
|
||||
return (
|
||||
isinstance(branches, list)
|
||||
and bool(branches)
|
||||
and all(_is_named_schema_reference(branch) for branch in branches)
|
||||
)
|
||||
|
||||
|
||||
def _error_component(value: object, path: str) -> JsonValue:
|
||||
"""Normalize an OpenRPC error object, whose ``data`` member is a schema."""
|
||||
if not isinstance(value, Mapping):
|
||||
@@ -269,22 +294,9 @@ def manifest_from_openrpc(document: Mapping[str, object]) -> ContractManifest:
|
||||
f"{result_path}.schema", "missing success result schema"
|
||||
)
|
||||
raw_result_schema = result["schema"]
|
||||
if not isinstance(raw_result_schema, Mapping):
|
||||
raise ManifestError(
|
||||
f"{result_path}.schema",
|
||||
"success result must be a schema object",
|
||||
)
|
||||
named_result = (
|
||||
set(raw_result_schema) == {"$ref"}
|
||||
and isinstance(raw_result_schema.get("$ref"), str)
|
||||
and raw_result_schema["$ref"].startswith("#/components/schemas/")
|
||||
)
|
||||
# This inspection RPC returns either its inventory or the standard
|
||||
# revision-conflict workspace payload, so its OpenRPC result is a union.
|
||||
if (
|
||||
not named_result
|
||||
and method_name != "workflow.draft_workspaces.inspect_authoring_contract"
|
||||
):
|
||||
if not _is_named_schema_reference(
|
||||
raw_result_schema
|
||||
) and not _is_composed_named_result_schema(raw_result_schema):
|
||||
raise ManifestError(
|
||||
f"{result_path}.schema",
|
||||
"success result must reference a named schema component",
|
||||
|
||||
@@ -240,6 +240,43 @@ def test_rejects_invalid_result_schema_shape() -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_accepts_composed_result_with_named_component_branches() -> None:
|
||||
document = synthetic_openrpc_document()
|
||||
document["methods"][1]["result"]["schema"] = {
|
||||
"anyOf": [
|
||||
{"$ref": "#/components/schemas/AlphaResult"},
|
||||
{"$ref": "#/components/schemas/ZetaResult"},
|
||||
]
|
||||
}
|
||||
|
||||
manifest = manifest_from_openrpc(document)
|
||||
|
||||
assert manifest["operations"][0]["result"] == {
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{"$ref": "#/components/schemas/AlphaResult"},
|
||||
{"$ref": "#/components/schemas/ZetaResult"},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_rejects_composed_result_with_an_inline_branch() -> None:
|
||||
document = synthetic_openrpc_document()
|
||||
document["methods"][1]["result"]["schema"] = {
|
||||
"oneOf": [
|
||||
{"$ref": "#/components/schemas/AlphaResult"},
|
||||
{"type": "object"},
|
||||
]
|
||||
}
|
||||
|
||||
assert_manifest_error(
|
||||
document,
|
||||
"$.methods[1].result.schema",
|
||||
"success result must reference a named schema component",
|
||||
)
|
||||
|
||||
|
||||
def test_reports_a_missing_success_result_schema() -> None:
|
||||
document = synthetic_openrpc_document()
|
||||
del document["methods"][1]["result"]["schema"]
|
||||
|
||||
@@ -221,11 +221,22 @@ const authoringPathOption = {
|
||||
uses: ["step_input"],
|
||||
};
|
||||
|
||||
const conditionalRuntimeContextOption = {
|
||||
...authoringPathOption,
|
||||
path: "context.viewer_id",
|
||||
label: "Viewer ID",
|
||||
origin: "runtime_context",
|
||||
required: false,
|
||||
availability: "conditional",
|
||||
reason: "available when the selected capability accepts runtime context",
|
||||
uses: ["step_input"],
|
||||
};
|
||||
|
||||
const authoringContractInventory = {
|
||||
workspace_id: "console.demo",
|
||||
revision: 7,
|
||||
selected_step_id: "render",
|
||||
readable_sources: [authoringPathOption],
|
||||
readable_sources: [authoringPathOption, conditionalRuntimeContextOption],
|
||||
step_input_targets: [
|
||||
{
|
||||
...authoringPathOption,
|
||||
@@ -1113,6 +1124,16 @@ const parityReport = (): ParityReport => {
|
||||
};
|
||||
|
||||
describe("authored RPC and manifest schema parity", () => {
|
||||
it("covers conditional runtime context sources with a reason", () => {
|
||||
expect(authoringContractInventory.readable_sources).toContainEqual(
|
||||
expect.objectContaining({
|
||||
origin: "runtime_context",
|
||||
availability: "conditional",
|
||||
reason: "available when the selected capability accepts runtime context",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects empty segments in authored structural binding paths", () => {
|
||||
const cases: ReadonlyArray<{
|
||||
readonly method: keyof typeof authoredRpcSchemas;
|
||||
|
||||
Reference in New Issue
Block a user