fix: close Task 4 contract graph review findings

This commit is contained in:
lda
2026-08-14 17:58:13 +07:00 Verified
parent bd226a2fa7
commit 804946531a
3 changed files with 87 additions and 17 deletions
+28 -16
View File
@@ -132,6 +132,31 @@ def _schema(value: object, path: str) -> JsonSchema:
return normalized 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: def _error_component(value: object, path: str) -> JsonValue:
"""Normalize an OpenRPC error object, whose ``data`` member is a schema.""" """Normalize an OpenRPC error object, whose ``data`` member is a schema."""
if not isinstance(value, Mapping): 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" f"{result_path}.schema", "missing success result schema"
) )
raw_result_schema = result["schema"] raw_result_schema = result["schema"]
if not isinstance(raw_result_schema, Mapping): if not _is_named_schema_reference(
raise ManifestError( raw_result_schema
f"{result_path}.schema", ) and not _is_composed_named_result_schema(raw_result_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"
):
raise ManifestError( raise ManifestError(
f"{result_path}.schema", f"{result_path}.schema",
"success result must reference a named schema component", "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: def test_reports_a_missing_success_result_schema() -> None:
document = synthetic_openrpc_document() document = synthetic_openrpc_document()
del document["methods"][1]["result"]["schema"] del document["methods"][1]["result"]["schema"]
@@ -221,11 +221,22 @@ const authoringPathOption = {
uses: ["step_input"], 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 = { const authoringContractInventory = {
workspace_id: "console.demo", workspace_id: "console.demo",
revision: 7, revision: 7,
selected_step_id: "render", selected_step_id: "render",
readable_sources: [authoringPathOption], readable_sources: [authoringPathOption, conditionalRuntimeContextOption],
step_input_targets: [ step_input_targets: [
{ {
...authoringPathOption, ...authoringPathOption,
@@ -1113,6 +1124,16 @@ const parityReport = (): ParityReport => {
}; };
describe("authored RPC and manifest schema parity", () => { 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", () => { it("rejects empty segments in authored structural binding paths", () => {
const cases: ReadonlyArray<{ const cases: ReadonlyArray<{
readonly method: keyof typeof authoredRpcSchemas; readonly method: keyof typeof authoredRpcSchemas;