openverify 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openverify/__init__.py +38 -0
- openverify/adapters/__init__.py +28 -0
- openverify/adapters/demo.py +102 -0
- openverify/authoring/__init__.py +6 -0
- openverify/authoring/graph.py +262 -0
- openverify/authoring/references.py +21 -0
- openverify/cli.py +58 -0
- openverify/conformance/__init__.py +21 -0
- openverify/data/profile.json +97 -0
- openverify/data/schemas/activation.schema.json +24 -0
- openverify/data/schemas/admission.schema.json +25 -0
- openverify/data/schemas/artifact-ref.schema.json +43 -0
- openverify/data/schemas/cancellation-event.schema.json +29 -0
- openverify/data/schemas/decision.schema.json +58 -0
- openverify/data/schemas/evidence.schema.json +53 -0
- openverify/data/schemas/execution-observation.schema.json +51 -0
- openverify/data/schemas/extension.schema.json +28 -0
- openverify/data/schemas/graph.schema.json +158 -0
- openverify/data/schemas/input-port.schema.json +50 -0
- openverify/data/schemas/input-resolution.schema.json +31 -0
- openverify/data/schemas/operation-requirement.schema.json +22 -0
- openverify/data/schemas/output-port.schema.json +32 -0
- openverify/data/schemas/policy-evaluation.schema.json +67 -0
- openverify/data/schemas/profile.schema.json +77 -0
- openverify/data/schemas/provider-binding.schema.json +33 -0
- openverify/data/schemas/resource-dimension.schema.json +35 -0
- openverify/data/schemas/resource.schema.json +59 -0
- openverify/data/schemas/result.schema.json +145 -0
- openverify/data/schemas/run-record.schema.json +269 -0
- openverify/data/schemas/selection.schema.json +23 -0
- openverify/data/schemas/severity-domain.schema.json +37 -0
- openverify/data/schemas/value-source.schema.json +76 -0
- openverify/data/schemas/verification-definition.schema.json +235 -0
- openverify/dataflow.py +487 -0
- openverify/engine/__init__.py +295 -0
- openverify/engine/admission.py +30 -0
- openverify/engine/aggregation.py +98 -0
- openverify/engine/binding.py +69 -0
- openverify/engine/policy.py +80 -0
- openverify/error_codes.py +41 -0
- openverify/errors.py +32 -0
- openverify/fingerprint/__init__.py +11 -0
- openverify/inspection.py +68 -0
- openverify/json_schema.py +156 -0
- openverify/lifecycle.py +84 -0
- openverify/model/__init__.py +340 -0
- openverify/policy.py +84 -0
- openverify/provenance/__init__.py +41 -0
- openverify/provider/__init__.py +81 -0
- openverify/provider/conformance.py +67 -0
- openverify/provider/data/schemas/cancel-request.schema.json +28 -0
- openverify/provider/data/schemas/capability.schema.json +80 -0
- openverify/provider/data/schemas/initialize-request.schema.json +38 -0
- openverify/provider/data/schemas/initialize-result.schema.json +27 -0
- openverify/provider/data/schemas/invocation.schema.json +60 -0
- openverify/provider/data/schemas/outcome.schema.json +92 -0
- openverify/provider/data/schemas/provider-error.schema.json +38 -0
- openverify/provider/data/schemas/provider-event.schema.json +131 -0
- openverify/provider/data/schemas/provider-manifest.schema.json +56 -0
- openverify/provider/lifecycle.py +229 -0
- openverify/provider/model.py +107 -0
- openverify/provider/stdio.py +264 -0
- openverify/provider/validation.py +366 -0
- openverify/provider_codes.py +14 -0
- openverify/py.typed +0 -0
- openverify/relay/__init__.py +8 -0
- openverify/relay/adapters/__init__.py +1 -0
- openverify/relay/adapters/binder.py +31 -0
- openverify/relay/adapters/catalog.py +83 -0
- openverify/relay/adapters/inprocess.py +55 -0
- openverify/relay/adapters/jsonl.py +28 -0
- openverify/relay/adapters/local.py +110 -0
- openverify/relay/adapters/logging.py +25 -0
- openverify/relay/adapters/memory.py +21 -0
- openverify/relay/adapters/process.py +124 -0
- openverify/relay/adapters/server.py +179 -0
- openverify/relay/adapters/stdio.py +154 -0
- openverify/relay/adapters/windows_job.py +81 -0
- openverify/relay/api.py +165 -0
- openverify/relay/application/__init__.py +1 -0
- openverify/relay/application/binding.py +58 -0
- openverify/relay/application/contract.py +37 -0
- openverify/relay/application/execution.py +198 -0
- openverify/relay/application/policies.py +123 -0
- openverify/relay/application/preparation.py +39 -0
- openverify/relay/application/run.py +310 -0
- openverify/relay/config.py +21 -0
- openverify/relay/core/__init__.py +1 -0
- openverify/relay/core/aggregation.py +99 -0
- openverify/relay/core/readiness.py +20 -0
- openverify/relay/core/state.py +25 -0
- openverify/relay/entrypoints/__init__.py +1 -0
- openverify/relay/entrypoints/cli.py +208 -0
- openverify/relay/entrypoints/http.py +54 -0
- openverify/relay/entrypoints/mcp.py +38 -0
- openverify/relay/entrypoints/service.py +59 -0
- openverify/relay/error_codes.py +16 -0
- openverify/relay/errors.py +17 -0
- openverify/relay/ports/__init__.py +75 -0
- openverify/relay/ports/binding.py +28 -0
- openverify/relay/providers.py +166 -0
- openverify/relay/templates.py +129 -0
- openverify/serialization/__init__.py +134 -0
- openverify/standard/__init__.py +68 -0
- openverify/standard/astgrep.py +75 -0
- openverify/standard/command.py +86 -0
- openverify/standard/common.py +64 -0
- openverify/standard/control.py +53 -0
- openverify/standard/filesystem.py +124 -0
- openverify/standard/git.py +210 -0
- openverify/standard/paths.py +26 -0
- openverify/standard/pattern_worker.py +16 -0
- openverify/standard/pytest.py +44 -0
- openverify/standard/ruff.py +86 -0
- openverify/standard/scope.py +105 -0
- openverify/standard/structured.py +37 -0
- openverify/standard/text.py +86 -0
- openverify/standard/tools.py +52 -0
- openverify/validation/__init__.py +644 -0
- openverify-0.1.0.dist-info/METADATA +481 -0
- openverify-0.1.0.dist-info/RECORD +125 -0
- openverify-0.1.0.dist-info/WHEEL +5 -0
- openverify-0.1.0.dist-info/entry_points.txt +2 -0
- openverify-0.1.0.dist-info/licenses/LICENSE +22 -0
- openverify-0.1.0.dist-info/top_level.txt +1 -0
openverify/__init__.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Draft domain-neutral verification protocol implementation."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0"
|
|
4
|
+
|
|
5
|
+
from openverify.authoring import Graph
|
|
6
|
+
from openverify.dataflow import validate_run_inputs
|
|
7
|
+
from openverify.errors import ProtocolError
|
|
8
|
+
from openverify.fingerprint import fingerprint
|
|
9
|
+
from openverify.inspection import flattened_status, inspect_definition
|
|
10
|
+
from openverify.relay import Relay
|
|
11
|
+
from openverify.serialization import serialize
|
|
12
|
+
from openverify.validation import (
|
|
13
|
+
load_schema,
|
|
14
|
+
parse_definition,
|
|
15
|
+
parse_result,
|
|
16
|
+
parse_run_record,
|
|
17
|
+
validate_definition,
|
|
18
|
+
validate_executable_definition,
|
|
19
|
+
validate_result,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"Graph",
|
|
24
|
+
"Relay",
|
|
25
|
+
"ProtocolError",
|
|
26
|
+
"fingerprint",
|
|
27
|
+
"flattened_status",
|
|
28
|
+
"inspect_definition",
|
|
29
|
+
"load_schema",
|
|
30
|
+
"parse_definition",
|
|
31
|
+
"parse_result",
|
|
32
|
+
"parse_run_record",
|
|
33
|
+
"serialize",
|
|
34
|
+
"validate_definition",
|
|
35
|
+
"validate_executable_definition",
|
|
36
|
+
"validate_result",
|
|
37
|
+
"validate_run_inputs",
|
|
38
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Trusted local implementations bound to exact capability ID/version requirements."""
|
|
2
|
+
|
|
3
|
+
from typing import Protocol
|
|
4
|
+
|
|
5
|
+
from openverify.model import Context, Decision, ExecutionObservation, Result
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Adapter(Protocol):
|
|
9
|
+
def execute(
|
|
10
|
+
self, inputs: Context, config: Context, /
|
|
11
|
+
) -> ExecutionObservation | Context | Result | Decision: ...
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Registry:
|
|
15
|
+
def __init__(self) -> None:
|
|
16
|
+
self.operations: dict[tuple[str, str], Adapter] = {}
|
|
17
|
+
|
|
18
|
+
def register(self, capability_id: str, capability_version: str, adapter: Adapter) -> None:
|
|
19
|
+
self.operations[(capability_id, capability_version)] = adapter
|
|
20
|
+
|
|
21
|
+
def register_derive(self, name: str, adapter: Adapter, *, version: str = "1") -> None:
|
|
22
|
+
self.register(name, version, adapter)
|
|
23
|
+
|
|
24
|
+
def register_evaluate(self, name: str, adapter: Adapter, *, version: str = "1") -> None:
|
|
25
|
+
self.register(name, version, adapter)
|
|
26
|
+
|
|
27
|
+
def register_control(self, name: str, adapter: Adapter, *, version: str = "1") -> None:
|
|
28
|
+
self.register(name, version, adapter)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Small local examples, not protocol primitives or an untrusted code runner."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import cast
|
|
5
|
+
|
|
6
|
+
from openverify.adapters import Registry
|
|
7
|
+
from openverify.errors import ProtocolError
|
|
8
|
+
from openverify.model import Context, Decision, Evidence, Result
|
|
9
|
+
from openverify.validation import parse_result
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def known(verdict: str | None = None, score: float | None = None) -> Result:
|
|
13
|
+
return parse_result(
|
|
14
|
+
{
|
|
15
|
+
"protocol": "draft",
|
|
16
|
+
"disposition": "EXECUTED",
|
|
17
|
+
"execution_outcome": "SUCCESS",
|
|
18
|
+
"applicability": "APPLICABLE",
|
|
19
|
+
"determination": "KNOWN",
|
|
20
|
+
"verdict": verdict,
|
|
21
|
+
"score": score,
|
|
22
|
+
}
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ConstantEvaluator:
|
|
27
|
+
version = "1"
|
|
28
|
+
|
|
29
|
+
def execute(self, inputs: Context, config: Context) -> Result:
|
|
30
|
+
return parse_result(config["result"])
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class RegexEvaluator:
|
|
34
|
+
version = "1"
|
|
35
|
+
|
|
36
|
+
def execute(self, inputs: Context, config: Context) -> Result:
|
|
37
|
+
text = inputs[cast(str, config["key"])]
|
|
38
|
+
pattern = config["pattern"]
|
|
39
|
+
must_match = config["must_match"]
|
|
40
|
+
if (
|
|
41
|
+
not isinstance(text, str)
|
|
42
|
+
or not isinstance(pattern, str)
|
|
43
|
+
or type(must_match) is not bool
|
|
44
|
+
):
|
|
45
|
+
raise ProtocolError(
|
|
46
|
+
"EXECUTOR_ERROR", "Regex demo needs text, pattern, and boolean must_match"
|
|
47
|
+
)
|
|
48
|
+
matched = re.search(pattern, text) is not None
|
|
49
|
+
result = known("PASS" if matched == must_match else "FAIL")
|
|
50
|
+
return result.model_copy(
|
|
51
|
+
update={"evidence": [Evidence(message=f"Pattern matched: {matched}")]}
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class RangeEvaluator:
|
|
56
|
+
version = "1"
|
|
57
|
+
|
|
58
|
+
def execute(self, inputs: Context, config: Context) -> Result:
|
|
59
|
+
value = inputs[cast(str, config["key"])]
|
|
60
|
+
low, high = config["minimum"], config["maximum"]
|
|
61
|
+
if any(type(v) not in (int, float) for v in (value, low, high)):
|
|
62
|
+
raise ProtocolError("EXECUTOR_ERROR", "Range demo requires numeric values")
|
|
63
|
+
if cast(float, low) > cast(float, high):
|
|
64
|
+
raise ProtocolError("EXECUTOR_ERROR", "Inverted range")
|
|
65
|
+
return known(
|
|
66
|
+
"PASS" if cast(float, low) <= cast(float, value) <= cast(float, high) else "FAIL"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class LengthDeriver:
|
|
71
|
+
version = "1"
|
|
72
|
+
|
|
73
|
+
def execute(self, inputs: Context, config: Context) -> Context:
|
|
74
|
+
value = inputs[cast(str, config["key"])]
|
|
75
|
+
if not isinstance(value, str):
|
|
76
|
+
raise ProtocolError("EXECUTOR_ERROR", "Length demo requires text")
|
|
77
|
+
return {cast(str, config["output_key"]): len(value)}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class UnknownRouter:
|
|
81
|
+
version = "1"
|
|
82
|
+
|
|
83
|
+
def execute(self, inputs: Context, config: Context) -> Decision:
|
|
84
|
+
if any(
|
|
85
|
+
isinstance(r, dict) and r.get("determination") == "UNKNOWN" for r in inputs.values()
|
|
86
|
+
):
|
|
87
|
+
return Decision(
|
|
88
|
+
kind="ROUTE",
|
|
89
|
+
destination=cast(str, config["destination"]),
|
|
90
|
+
reason="Uncertain evaluation",
|
|
91
|
+
)
|
|
92
|
+
return Decision(kind="CONTINUE")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def demo_registry() -> Registry:
|
|
96
|
+
registry = Registry()
|
|
97
|
+
registry.register_evaluate("com.example.demo.constant", ConstantEvaluator())
|
|
98
|
+
registry.register_evaluate("com.example.demo.regex", RegexEvaluator())
|
|
99
|
+
registry.register_evaluate("com.example.demo.range", RangeEvaluator())
|
|
100
|
+
registry.register_derive("com.example.demo.length", LengthDeriver())
|
|
101
|
+
registry.register_control("com.example.demo.unknown_route", UnknownRouter())
|
|
102
|
+
return registry
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""An explicit, small VCP builder. build() always returns a validated SDK definition."""
|
|
2
|
+
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Literal, TypedDict, Unpack, cast
|
|
6
|
+
|
|
7
|
+
from pydantic import JsonValue
|
|
8
|
+
|
|
9
|
+
from openverify.authoring.references import Ref, constant
|
|
10
|
+
from openverify.inspection import inspect_definition
|
|
11
|
+
from openverify.model import (
|
|
12
|
+
AdmissionPolicy,
|
|
13
|
+
ArtifactRef,
|
|
14
|
+
Capability,
|
|
15
|
+
Constant,
|
|
16
|
+
Context,
|
|
17
|
+
Criterion,
|
|
18
|
+
Edge,
|
|
19
|
+
ExecutionPolicy,
|
|
20
|
+
Guard,
|
|
21
|
+
InputPort,
|
|
22
|
+
JsonSchema,
|
|
23
|
+
Node,
|
|
24
|
+
OperationRequirement,
|
|
25
|
+
OutputPort,
|
|
26
|
+
ResourceVector,
|
|
27
|
+
RunInput,
|
|
28
|
+
UpstreamOutput,
|
|
29
|
+
VerificationDefinition,
|
|
30
|
+
)
|
|
31
|
+
from openverify.model import (
|
|
32
|
+
Graph as VCPGraph,
|
|
33
|
+
)
|
|
34
|
+
from openverify.serialization import serialize, to_data
|
|
35
|
+
from openverify.validation import HANDLING, parse_definition
|
|
36
|
+
|
|
37
|
+
type NodeKind = Literal["DERIVE", "EVALUATE", "CONTROL", "AGGREGATE"]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class NodeRef:
|
|
42
|
+
id: str
|
|
43
|
+
outputs: dict[str, Ref]
|
|
44
|
+
|
|
45
|
+
def __getitem__(self, name: str) -> Ref:
|
|
46
|
+
return self.outputs[name]
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def result(self) -> Ref:
|
|
50
|
+
return self["result"]
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def decision(self) -> Ref:
|
|
54
|
+
return self["decision"]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class NodeOptions(TypedDict, total=False):
|
|
58
|
+
version: str
|
|
59
|
+
inputs: dict[str, Ref] | None
|
|
60
|
+
outputs: dict[str, JsonSchema] | None
|
|
61
|
+
config: Context | None
|
|
62
|
+
config_schema: JsonSchema | None
|
|
63
|
+
dependencies: list[NodeRef] | None
|
|
64
|
+
input_ports: list[InputPort] | None
|
|
65
|
+
aggregate_inputs: list[str] | None
|
|
66
|
+
resource_estimate: ResourceVector | None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class Graph:
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
id: str,
|
|
73
|
+
*,
|
|
74
|
+
version: str = "1",
|
|
75
|
+
subject: ArtifactRef | None = None,
|
|
76
|
+
criterion: Criterion | None = None,
|
|
77
|
+
judgment_schema: JsonSchema = True,
|
|
78
|
+
execution_policy: ExecutionPolicy | None = None,
|
|
79
|
+
) -> None:
|
|
80
|
+
self.id = id
|
|
81
|
+
self.version = version
|
|
82
|
+
self.subject = subject or ArtifactRef(id=id, type="declared-input", locator={"graph": id})
|
|
83
|
+
self.criterion = criterion or Criterion(id=id, description=f"Verification defined by {id}")
|
|
84
|
+
self.judgment_schema = judgment_schema
|
|
85
|
+
self.execution_policy = execution_policy or ExecutionPolicy(
|
|
86
|
+
activation=Capability(executor_ref="MANUAL"),
|
|
87
|
+
selection=Capability(executor_ref="EXPLICIT"),
|
|
88
|
+
admission=AdmissionPolicy(strategy="ALWAYS"),
|
|
89
|
+
)
|
|
90
|
+
self._inputs: list[InputPort] = []
|
|
91
|
+
self._nodes: list[Node] = []
|
|
92
|
+
self._edges: list[Edge] = []
|
|
93
|
+
|
|
94
|
+
def input(
|
|
95
|
+
self,
|
|
96
|
+
name: str,
|
|
97
|
+
schema: JsonSchema,
|
|
98
|
+
*,
|
|
99
|
+
required: bool = True,
|
|
100
|
+
default: Constant | None = None,
|
|
101
|
+
) -> Ref:
|
|
102
|
+
self._inputs.append(
|
|
103
|
+
InputPort(
|
|
104
|
+
name=name,
|
|
105
|
+
schema=deepcopy(schema),
|
|
106
|
+
required=required,
|
|
107
|
+
on_unavailable="BLOCK" if required else "DEFAULT" if default else "OMIT",
|
|
108
|
+
default=default,
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
return Ref(RunInput(name=name), deepcopy(schema))
|
|
112
|
+
|
|
113
|
+
def node(
|
|
114
|
+
self,
|
|
115
|
+
id: str,
|
|
116
|
+
kind: NodeKind,
|
|
117
|
+
capability: str,
|
|
118
|
+
*,
|
|
119
|
+
version: str = "1",
|
|
120
|
+
inputs: dict[str, Ref] | None = None,
|
|
121
|
+
outputs: dict[str, JsonSchema] | None = None,
|
|
122
|
+
config: Context | None = None,
|
|
123
|
+
config_schema: JsonSchema | None = None,
|
|
124
|
+
dependencies: list[NodeRef] | None = None,
|
|
125
|
+
input_ports: list[InputPort] | None = None,
|
|
126
|
+
aggregate_inputs: list[str] | None = None,
|
|
127
|
+
resource_estimate: ResourceVector | None = None,
|
|
128
|
+
) -> NodeRef:
|
|
129
|
+
bindings = dict(inputs or {})
|
|
130
|
+
if kind in ("EVALUATE", "AGGREGATE"):
|
|
131
|
+
if "subject" in bindings or "criterion" in bindings:
|
|
132
|
+
raise ValueError(
|
|
133
|
+
"Builder binds definition subject and criterion; "
|
|
134
|
+
"use raw VCP for explicit declarations"
|
|
135
|
+
)
|
|
136
|
+
bindings.update(
|
|
137
|
+
subject=constant(to_data(self.subject), {"type": "object"}),
|
|
138
|
+
criterion=constant(to_data(self.criterion), {"type": "object"}),
|
|
139
|
+
)
|
|
140
|
+
ports = {p.name: p for p in input_ports or []}
|
|
141
|
+
for name, ref in bindings.items():
|
|
142
|
+
if name not in ports:
|
|
143
|
+
ports[name] = InputPort(
|
|
144
|
+
name=name, schema=deepcopy(ref.schema), required=True, on_unavailable="BLOCK"
|
|
145
|
+
)
|
|
146
|
+
declared = [
|
|
147
|
+
OutputPort(name=name, schema=deepcopy(schema), role="INFORMATION")
|
|
148
|
+
for name, schema in (outputs or {}).items()
|
|
149
|
+
]
|
|
150
|
+
if kind in ("EVALUATE", "AGGREGATE", "CONTROL"):
|
|
151
|
+
role = "DECISION" if kind == "CONTROL" else "RESULT"
|
|
152
|
+
declared.append(OutputPort(name=role.lower(), schema={"type": "object"}, role=role))
|
|
153
|
+
self._nodes.append(
|
|
154
|
+
Node(
|
|
155
|
+
id=id,
|
|
156
|
+
kind=kind,
|
|
157
|
+
operation=OperationRequirement(
|
|
158
|
+
capability_id=capability, capability_version=version
|
|
159
|
+
),
|
|
160
|
+
inputs=list(ports.values()),
|
|
161
|
+
outputs=declared,
|
|
162
|
+
bindings={n: deepcopy(r.source) for n, r in bindings.items()},
|
|
163
|
+
aggregate_inputs=aggregate_inputs or [],
|
|
164
|
+
config=deepcopy(config or {}),
|
|
165
|
+
config_schema=deepcopy(config_schema),
|
|
166
|
+
resource_estimate=deepcopy(resource_estimate or {}),
|
|
167
|
+
)
|
|
168
|
+
)
|
|
169
|
+
predecessors = [
|
|
170
|
+
r.source.node_id for r in bindings.values() if isinstance(r.source, UpstreamOutput)
|
|
171
|
+
]
|
|
172
|
+
predecessors.extend(n.id for n in dependencies or [])
|
|
173
|
+
for predecessor in dict.fromkeys(predecessors):
|
|
174
|
+
self._edges.append(Edge(from_node=predecessor, to_node=id))
|
|
175
|
+
return NodeRef(
|
|
176
|
+
id,
|
|
177
|
+
{
|
|
178
|
+
p.name: Ref(UpstreamOutput(node_id=id, output=p.name), p.value_schema)
|
|
179
|
+
for p in declared
|
|
180
|
+
},
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
def derive(self, id: str, capability: str, **kwargs: Unpack[NodeOptions]) -> NodeRef:
|
|
184
|
+
return self.node(id, "DERIVE", capability, **kwargs)
|
|
185
|
+
|
|
186
|
+
def evaluate(self, id: str, capability: str, **kwargs: Unpack[NodeOptions]) -> NodeRef:
|
|
187
|
+
return self.node(id, "EVALUATE", capability, **kwargs)
|
|
188
|
+
|
|
189
|
+
def control(self, id: str, capability: str, **kwargs: Unpack[NodeOptions]) -> NodeRef:
|
|
190
|
+
return self.node(id, "CONTROL", capability, **kwargs)
|
|
191
|
+
|
|
192
|
+
def aggregate(
|
|
193
|
+
self,
|
|
194
|
+
id: str,
|
|
195
|
+
members: dict[str, Ref],
|
|
196
|
+
*,
|
|
197
|
+
reducer: str = "ALL",
|
|
198
|
+
handling: dict[str, str] | None = None,
|
|
199
|
+
config: Context | None = None,
|
|
200
|
+
capability: str = "openverify.aggregate",
|
|
201
|
+
version: str = "1",
|
|
202
|
+
) -> NodeRef:
|
|
203
|
+
names = list(members)
|
|
204
|
+
if set(names) & {"subject", "criterion", "members"}:
|
|
205
|
+
raise ValueError("Reserved aggregate member name")
|
|
206
|
+
settings: Context = dict(config or {})
|
|
207
|
+
if capability == "openverify.aggregate":
|
|
208
|
+
settings.update(
|
|
209
|
+
reducer=reducer, handling=dict(handling or {k: "REJECT" for k in sorted(HANDLING)})
|
|
210
|
+
)
|
|
211
|
+
return self.node(
|
|
212
|
+
id,
|
|
213
|
+
"AGGREGATE",
|
|
214
|
+
capability,
|
|
215
|
+
version=version,
|
|
216
|
+
inputs={
|
|
217
|
+
**members,
|
|
218
|
+
"members": constant(
|
|
219
|
+
cast(JsonValue, names), {"type": "array", "items": {"type": "string"}}
|
|
220
|
+
),
|
|
221
|
+
},
|
|
222
|
+
aggregate_inputs=names,
|
|
223
|
+
config=settings,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
def edge(self, predecessor: NodeRef, successor: NodeRef, *, guard: Guard | None = None) -> None:
|
|
227
|
+
self._edges = [
|
|
228
|
+
e for e in self._edges if (e.from_node, e.to_node) != (predecessor.id, successor.id)
|
|
229
|
+
]
|
|
230
|
+
self._edges.append(Edge(from_node=predecessor.id, to_node=successor.id, guard=guard))
|
|
231
|
+
|
|
232
|
+
def add_node(self, node: Node) -> NodeRef:
|
|
233
|
+
self._nodes.append(deepcopy(node))
|
|
234
|
+
return NodeRef(
|
|
235
|
+
node.id,
|
|
236
|
+
{
|
|
237
|
+
p.name: Ref(UpstreamOutput(node_id=node.id, output=p.name), p.value_schema)
|
|
238
|
+
for p in node.outputs
|
|
239
|
+
},
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
def build(self) -> VerificationDefinition:
|
|
243
|
+
definition = VerificationDefinition(
|
|
244
|
+
id=self.id,
|
|
245
|
+
version=self.version,
|
|
246
|
+
subject=self.subject,
|
|
247
|
+
criterion=self.criterion,
|
|
248
|
+
judgment_schema=self.judgment_schema,
|
|
249
|
+
run_inputs=self._inputs,
|
|
250
|
+
graph=VCPGraph(nodes=self._nodes, edges=self._edges),
|
|
251
|
+
execution_policy=self.execution_policy,
|
|
252
|
+
)
|
|
253
|
+
return parse_definition(serialize(definition))
|
|
254
|
+
|
|
255
|
+
def validate(self) -> None:
|
|
256
|
+
self.build()
|
|
257
|
+
|
|
258
|
+
def inspect(self) -> Context:
|
|
259
|
+
return inspect_definition(self.build())
|
|
260
|
+
|
|
261
|
+
def to_json(self) -> str:
|
|
262
|
+
return serialize(self.build())
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Typed references compile directly into VCP ValueSource objects."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from pydantic import JsonValue
|
|
6
|
+
|
|
7
|
+
from openverify.model import ArtifactRef, Constant, ExternalReference, JsonSchema, ValueSource
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class Ref:
|
|
12
|
+
source: ValueSource
|
|
13
|
+
schema: JsonSchema
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def constant(value: JsonValue, schema: JsonSchema = True) -> Ref:
|
|
17
|
+
return Ref(Constant(value=value), schema)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def external(reference: ArtifactRef) -> Ref:
|
|
21
|
+
return Ref(ExternalReference(reference=reference), {"type": "object"})
|
openverify/cli.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Non-normative local convenience commands using an explicit demo registry."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import cast
|
|
7
|
+
|
|
8
|
+
from openverify.adapters.demo import demo_registry
|
|
9
|
+
from openverify.engine import Engine
|
|
10
|
+
from openverify.errors import ProtocolError
|
|
11
|
+
from openverify.inspection import inspect_definition
|
|
12
|
+
from openverify.model import Context
|
|
13
|
+
from openverify.serialization import decode, serialize
|
|
14
|
+
from openverify.validation import parse_definition, parse_object
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def main() -> int:
|
|
18
|
+
parser = argparse.ArgumentParser(description="Draft Openverify local SDK/reference commands")
|
|
19
|
+
commands = parser.add_subparsers(dest="command", required=True)
|
|
20
|
+
validate = commands.add_parser("validate")
|
|
21
|
+
validate.add_argument("file", type=Path)
|
|
22
|
+
validate.add_argument("--type", default="verification-definition")
|
|
23
|
+
inspect = commands.add_parser("inspect")
|
|
24
|
+
inspect.add_argument("file", type=Path)
|
|
25
|
+
run = commands.add_parser("run")
|
|
26
|
+
run.add_argument("file", type=Path)
|
|
27
|
+
run.add_argument("--run-inputs", type=Path, required=True)
|
|
28
|
+
run.add_argument("--opportunity-id", required=True)
|
|
29
|
+
run.add_argument("--output", type=Path)
|
|
30
|
+
args = parser.parse_args()
|
|
31
|
+
try:
|
|
32
|
+
data = cast(Path, args.file).read_bytes()
|
|
33
|
+
if args.command == "validate":
|
|
34
|
+
parse_object(args.type, data)
|
|
35
|
+
print(json.dumps({"valid": True}))
|
|
36
|
+
elif args.command == "inspect":
|
|
37
|
+
print(json.dumps(inspect_definition(parse_definition(data)), indent=2))
|
|
38
|
+
else:
|
|
39
|
+
run_inputs = decode(cast(Path, args.run_inputs).read_bytes())
|
|
40
|
+
if not isinstance(run_inputs, dict):
|
|
41
|
+
raise ProtocolError("PROTOCOL_STRUCTURE", "Run inputs must be a mapping")
|
|
42
|
+
record = Engine(demo_registry()).run(
|
|
43
|
+
parse_definition(data),
|
|
44
|
+
cast(Context, run_inputs),
|
|
45
|
+
opportunity_id=args.opportunity_id,
|
|
46
|
+
)
|
|
47
|
+
output = serialize(record)
|
|
48
|
+
if args.output:
|
|
49
|
+
cast(Path, args.output).write_text(output + "\n", encoding="utf-8")
|
|
50
|
+
else:
|
|
51
|
+
print(output)
|
|
52
|
+
return 0
|
|
53
|
+
except ProtocolError as exc:
|
|
54
|
+
print(json.dumps(exc.as_dict()))
|
|
55
|
+
return 1
|
|
56
|
+
except OSError as exc:
|
|
57
|
+
print(json.dumps({"code": "IO_ERROR", "message": str(exc)}))
|
|
58
|
+
return 1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Run a portable manifest using this SDK; no engine execution is required."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from openverify.errors import ProtocolError
|
|
7
|
+
from openverify.validation import parse_object
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def check_manifest(directory: Path) -> list[str]:
|
|
11
|
+
failures: list[str] = []
|
|
12
|
+
manifest = json.loads((directory / "manifest.json").read_text(encoding="utf-8"))
|
|
13
|
+
for case in manifest["cases"]:
|
|
14
|
+
try:
|
|
15
|
+
parse_object(case["object_type"], (directory / case["input"]).read_bytes())
|
|
16
|
+
code = None
|
|
17
|
+
except ProtocolError as exc:
|
|
18
|
+
code = exc.code
|
|
19
|
+
if code != case.get("error_code"):
|
|
20
|
+
failures.append(case["id"])
|
|
21
|
+
return failures
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
{
|
|
2
|
+
"protocol": "draft",
|
|
3
|
+
"id": "standard-draft",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"flattened_statuses": [
|
|
6
|
+
"PASS",
|
|
7
|
+
"FAIL",
|
|
8
|
+
"UNKNOWN",
|
|
9
|
+
"ERROR",
|
|
10
|
+
"SKIP"
|
|
11
|
+
],
|
|
12
|
+
"severity_domain": {
|
|
13
|
+
"id": "standard-draft",
|
|
14
|
+
"levels": [
|
|
15
|
+
{
|
|
16
|
+
"label": "INFO",
|
|
17
|
+
"rank": 10
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"label": "WARNING",
|
|
21
|
+
"rank": 20
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"label": "ERROR",
|
|
25
|
+
"rank": 30
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"label": "BLOCKER",
|
|
29
|
+
"rank": 40
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
"aggregators": [
|
|
34
|
+
"ALL",
|
|
35
|
+
"ANY",
|
|
36
|
+
"MAJORITY",
|
|
37
|
+
"MIN_SCORE",
|
|
38
|
+
"MAX_SCORE",
|
|
39
|
+
"AVERAGE",
|
|
40
|
+
"WEIGHTED_AVERAGE",
|
|
41
|
+
"WORST_SEVERITY"
|
|
42
|
+
],
|
|
43
|
+
"admission_policies": [
|
|
44
|
+
"ALWAYS",
|
|
45
|
+
"PROBABILITY"
|
|
46
|
+
],
|
|
47
|
+
"decisions": [
|
|
48
|
+
"CONTINUE",
|
|
49
|
+
"STOP_SUCCESS",
|
|
50
|
+
"STOP_FAILURE",
|
|
51
|
+
"ROUTE",
|
|
52
|
+
"RETRY",
|
|
53
|
+
"DEFER"
|
|
54
|
+
],
|
|
55
|
+
"resource_dimensions": [
|
|
56
|
+
{
|
|
57
|
+
"id": "wall_time",
|
|
58
|
+
"unit": "s",
|
|
59
|
+
"description": "Elapsed wall-clock duration"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"id": "money",
|
|
63
|
+
"unit": "currency",
|
|
64
|
+
"description": "Monetary usage; measurement supplies the currency unit"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": "input_tokens",
|
|
68
|
+
"unit": "token",
|
|
69
|
+
"description": "Input tokens consumed"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"id": "output_tokens",
|
|
73
|
+
"unit": "token",
|
|
74
|
+
"description": "Output tokens consumed"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"id": "cpu_time",
|
|
78
|
+
"unit": "s",
|
|
79
|
+
"description": "CPU time consumed"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"id": "gpu_time",
|
|
83
|
+
"unit": "s",
|
|
84
|
+
"description": "GPU time consumed"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"id": "human_time",
|
|
88
|
+
"unit": "s",
|
|
89
|
+
"description": "Reviewer time consumed"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"id": "network_bytes",
|
|
93
|
+
"unit": "byte",
|
|
94
|
+
"description": "Network bytes transferred"
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:verification:draft:activation",
|
|
4
|
+
"description": "Typed explicit executable dataflow; see spec/draft/dataflow.md.",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"outcome": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"enum": [
|
|
10
|
+
"ACTIVATE",
|
|
11
|
+
"DO_NOT_ACTIVATE"
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"reason": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"minLength": 1
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"required": [
|
|
20
|
+
"outcome",
|
|
21
|
+
"reason"
|
|
22
|
+
],
|
|
23
|
+
"additionalProperties": false
|
|
24
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:verification:draft:admission",
|
|
4
|
+
"description": "Typed explicit executable dataflow; see spec/draft/dataflow.md.",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"outcome": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"enum": [
|
|
10
|
+
"RUN",
|
|
11
|
+
"SKIP",
|
|
12
|
+
"DEFER"
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
"reason": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"minLength": 1
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"required": [
|
|
21
|
+
"outcome",
|
|
22
|
+
"reason"
|
|
23
|
+
],
|
|
24
|
+
"additionalProperties": false
|
|
25
|
+
}
|