engineering-argument-language 3.2.3__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.
- eal/__init__.py +3 -0
- eal/abstractions.py +144 -0
- eal/acquisition_coordination.py +80 -0
- eal/api_load_methods.py +77 -0
- eal/aspic.py +478 -0
- eal/aspic_compiler.py +445 -0
- eal/aspic_export.py +309 -0
- eal/builtin_methods.py +115 -0
- eal/catalogue.py +384 -0
- eal/cli.py +204 -0
- eal/client_transports.py +69 -0
- eal/collection_identity.py +28 -0
- eal/collection_scheduler.py +72 -0
- eal/command_process.py +118 -0
- eal/command_supervisor.py +112 -0
- eal/composition.py +531 -0
- eal/credentials.py +33 -0
- eal/dialectic.py +321 -0
- eal/discovery.py +127 -0
- eal/evaluator.py +619 -0
- eal/expressions.py +404 -0
- eal/extensions.py +38 -0
- eal/formatter.py +156 -0
- eal/generated/EALLexer.py +434 -0
- eal/generated/EALParser.py +6559 -0
- eal/generated/EALVisitor.py +393 -0
- eal/generated/__init__.py +1 -0
- eal/host.py +179 -0
- eal/host_redaction.py +51 -0
- eal/knowledge.py +96 -0
- eal/limits.py +88 -0
- eal/mcp_guard.py +56 -0
- eal/methods.py +463 -0
- eal/model.py +266 -0
- eal/model_context.py +55 -0
- eal/modes.py +121 -0
- eal/observation_reuse.py +128 -0
- eal/operation_contracts.py +25 -0
- eal/operations.py +155 -0
- eal/packets.py +604 -0
- eal/parser.py +407 -0
- eal/planning.py +136 -0
- eal/propositions.py +231 -0
- eal/reachability.py +85 -0
- eal/reasoning/__init__.py +25 -0
- eal/reasoning/abductive.py +45 -0
- eal/reasoning/analogical.py +41 -0
- eal/reasoning/causal.py +46 -0
- eal/reasoning/counterfactual.py +64 -0
- eal/reasoning/deductive.py +72 -0
- eal/reasoning/inductive.py +29 -0
- eal/reasoning/strategy.py +18 -0
- eal/reasoning/structured.py +11 -0
- eal/reasoning/temporal.py +47 -0
- eal/reasoning/validation.py +90 -0
- eal/registered_assessment.py +181 -0
- eal/runtime.py +454 -0
- eal/sampled_negative.py +131 -0
- eal/scope_transfer.py +59 -0
- eal/semantics.py +656 -0
- eal/server.py +79 -0
- eal/server_auth.py +37 -0
- eal/server_settings.py +258 -0
- eal/source_printer.py +129 -0
- eal/store.py +272 -0
- eal/tool_acquisition.py +383 -0
- engineering_argument_language-3.2.3.dist-info/METADATA +188 -0
- engineering_argument_language-3.2.3.dist-info/RECORD +72 -0
- engineering_argument_language-3.2.3.dist-info/WHEEL +5 -0
- engineering_argument_language-3.2.3.dist-info/entry_points.txt +4 -0
- engineering_argument_language-3.2.3.dist-info/licenses/LICENSE +24 -0
- engineering_argument_language-3.2.3.dist-info/top_level.txt +1 -0
eal/aspic_export.py
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"""Export a recomputed bounded ASPIC+ graph with supplied EAL provenance.
|
|
2
|
+
|
|
3
|
+
The solver owns formal semantics; the separate Vue application owns rendering.
|
|
4
|
+
This boundary checks source-map structure without authenticating source claims.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from copy import deepcopy
|
|
9
|
+
import json
|
|
10
|
+
|
|
11
|
+
from jsonschema import Draft202012Validator
|
|
12
|
+
|
|
13
|
+
from .aspic import ASPIC_CONTRACT, OUTPUT_SCHEMA
|
|
14
|
+
from .evaluator import canonical_digest
|
|
15
|
+
from .methods import execute_extension, schema_errors
|
|
16
|
+
from .limits import ExecutionLimits, current_limits, using_limits, bounded
|
|
17
|
+
|
|
18
|
+
VIEW_VERSION = "aspic-view/3"
|
|
19
|
+
AVAILABILITY_ISSUES = ("predicate_not_met", "missing_observation",
|
|
20
|
+
"stale_observation", "tool_error", "invalid_observation",
|
|
21
|
+
"out_of_scope")
|
|
22
|
+
MAX_INPUT_BYTES = 4 * 1024 * 1024
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@bounded
|
|
26
|
+
def export_aspic_view(result: dict, *, limits=None) -> dict:
|
|
27
|
+
"""Recompute and export one supplied theory/result, without collection.
|
|
28
|
+
|
|
29
|
+
Equality is exact, including argument order, labels, ranks and every defeat
|
|
30
|
+
witness. Older or edited results must be solved again before export.
|
|
31
|
+
Authored source maps remain supplied assertions even when well formed.
|
|
32
|
+
"""
|
|
33
|
+
try:
|
|
34
|
+
encoded = json.dumps(result, allow_nan=False, ensure_ascii=False)
|
|
35
|
+
if len(encoded.encode("utf-8")) > MAX_INPUT_BYTES:
|
|
36
|
+
raise ValueError("ASPIC+ result exceeds the 4 MiB export input limit")
|
|
37
|
+
# Detach from caller-owned mutable dictionaries and require JSON data.
|
|
38
|
+
result = json.loads(encoded)
|
|
39
|
+
except (TypeError, OverflowError, RecursionError, UnicodeError) as exc:
|
|
40
|
+
raise ValueError("ASPIC+ export requires bounded JSON data") from exc
|
|
41
|
+
if not isinstance(result, dict) or not isinstance(result.get("theory"), dict):
|
|
42
|
+
raise ValueError("Export requires a theory and its formal result")
|
|
43
|
+
theory, formal = result["theory"], result.get("formal")
|
|
44
|
+
supplied_limits = result.get('source_map', {}).get('execution_limits', current_limits().describe())
|
|
45
|
+
try:
|
|
46
|
+
selected_limits = ExecutionLimits(**supplied_limits)
|
|
47
|
+
except (ValueError, TypeError) as exc:
|
|
48
|
+
raise ValueError('Invalid snapshot execution budgets') from exc
|
|
49
|
+
if any(value > current_limits().describe()[key] for key, value in selected_limits.describe().items()):
|
|
50
|
+
raise ValueError('Snapshot budgets exceed the exporter host policy; configure explicit host limits')
|
|
51
|
+
errors = schema_errors(formal, OUTPUT_SCHEMA)
|
|
52
|
+
if errors:
|
|
53
|
+
raise ValueError("Invalid ASPIC+ result: " + "; ".join(errors[:4]))
|
|
54
|
+
if canonical_digest(theory) != formal["theory_sha256"]:
|
|
55
|
+
raise ValueError("Formal result does not match the supplied theory digest")
|
|
56
|
+
with using_limits(selected_limits):
|
|
57
|
+
computation = execute_extension(ASPIC_CONTRACT, {"theory": theory})
|
|
58
|
+
if computation["status"] != "supported":
|
|
59
|
+
raise ValueError("Bounded ASPIC recomputation failed: " +
|
|
60
|
+
"; ".join(computation["reasons"]))
|
|
61
|
+
if canonical_digest(formal) != canonical_digest(computation["details"]):
|
|
62
|
+
raise ValueError("Supplied formal result differs from bounded ASPIC recomputation")
|
|
63
|
+
arguments, defeats = formal["arguments"], formal["defeats"]
|
|
64
|
+
by_id = {arg["id"]: arg for arg in arguments}
|
|
65
|
+
premises = {item["atom"]: item for item in theory["premises"]}
|
|
66
|
+
rules = {item["id"]: item for item in theory["rules"]}
|
|
67
|
+
source = result.get("source_map")
|
|
68
|
+
if source is not None:
|
|
69
|
+
source_errors = list(Draft202012Validator(_SOURCE_SCHEMA).iter_errors(source))
|
|
70
|
+
if source_errors:
|
|
71
|
+
raise ValueError("Invalid source map structure: " + source_errors[0].message)
|
|
72
|
+
if (not isinstance(source, dict) or not isinstance(source.get("goal"), dict)
|
|
73
|
+
or source.get("theory_digest") != formal["theory_sha256"]
|
|
74
|
+
or source["goal"].get("atom") != theory["goal"]
|
|
75
|
+
or result.get("claim", source["goal"].get("claim")) != source["goal"].get("claim")
|
|
76
|
+
or result.get("source_digest", source.get("source_digest")) != source.get("source_digest")):
|
|
77
|
+
raise ValueError("Source map does not match the supplied theory and goal")
|
|
78
|
+
if (result.get("snapshot_digest", source.get("snapshot_digest"))
|
|
79
|
+
!= source.get("snapshot_digest")):
|
|
80
|
+
raise ValueError("Snapshot digest disagrees with the source map")
|
|
81
|
+
origin_by_rule = {}
|
|
82
|
+
origin_by_atom = {}
|
|
83
|
+
claim_by_atom = {}
|
|
84
|
+
missing = []
|
|
85
|
+
if source is not None:
|
|
86
|
+
def origins(kind: str) -> dict:
|
|
87
|
+
entries = source.get(kind, {})
|
|
88
|
+
if (not isinstance(entries, dict) or any(not isinstance(name, str)
|
|
89
|
+
or not isinstance(item, dict) for name, item in entries.items())):
|
|
90
|
+
raise ValueError(f"Source map {kind} must contain named declarations")
|
|
91
|
+
return entries
|
|
92
|
+
|
|
93
|
+
for name, item in origins("claims").items():
|
|
94
|
+
atom = item.get("atom")
|
|
95
|
+
if (atom in claim_by_atom or not isinstance(atom, str)
|
|
96
|
+
or not isinstance(item.get("statement"), str)):
|
|
97
|
+
raise ValueError("Source map claim identity is inconsistent")
|
|
98
|
+
claim_by_atom[atom] = {"name": name, "statement": item.get("statement")}
|
|
99
|
+
if (not isinstance(source["goal"].get("claim"), str)
|
|
100
|
+
or claim_by_atom.get(theory["goal"], {}).get("name") != source["goal"]["claim"]):
|
|
101
|
+
raise ValueError("Source map goal claim does not name the formal goal atom")
|
|
102
|
+
for kind in ("arguments", "assumptions", "objections"):
|
|
103
|
+
for name, item in origins(kind).items():
|
|
104
|
+
if item.get("emitted"):
|
|
105
|
+
rule_id = item.get("rule_id")
|
|
106
|
+
if (rule_id not in rules or rule_id in origin_by_rule
|
|
107
|
+
or (kind == "arguments" and
|
|
108
|
+
(not isinstance(item.get("reasoning"), str)
|
|
109
|
+
or not isinstance(item.get("rationale"), str)))):
|
|
110
|
+
raise ValueError("Source map rule identity is inconsistent")
|
|
111
|
+
rule = rules[rule_id]
|
|
112
|
+
if (item.get("conclusion_atom", item.get("atom")) != rule["consequent"]
|
|
113
|
+
or item.get("rule_kind", rule["kind"]) != rule["kind"]
|
|
114
|
+
or item.get("applicability_atom") != rule.get("name")
|
|
115
|
+
or item.get("rank") != rule.get("rank")):
|
|
116
|
+
raise ValueError("Source map rule fields disagree with the formal theory")
|
|
117
|
+
origin_by_rule[rule_id] = {"kind": kind[:-1], "name": name,
|
|
118
|
+
"span": item.get("span"),
|
|
119
|
+
"formal_review": (item.get("strict_annotation")
|
|
120
|
+
or item.get("rank_annotation")),
|
|
121
|
+
**({"rationale": item.get("rationale"),
|
|
122
|
+
"reasoning": item.get("reasoning")}
|
|
123
|
+
if kind == "arguments" else {})}
|
|
124
|
+
for key in ('source_file', 'scope_transfer'):
|
|
125
|
+
if key in item:
|
|
126
|
+
origin_by_rule[rule_id][key] = item[key]
|
|
127
|
+
for name, item in origins("evidence").items():
|
|
128
|
+
if item.get("available"):
|
|
129
|
+
atom = item.get("atom")
|
|
130
|
+
if (atom not in premises or premises[atom]["kind"] != "ordinary"
|
|
131
|
+
or atom in origin_by_atom or item.get("rank") != premises[atom]["rank"]):
|
|
132
|
+
raise ValueError("Source map evidence identity is inconsistent")
|
|
133
|
+
origin_by_atom[atom] = {"kind": "evidence", "name": name,
|
|
134
|
+
"span": item.get("span"),
|
|
135
|
+
"formal_review": item.get("rank_annotation"),
|
|
136
|
+
"observation": item.get("identity")}
|
|
137
|
+
if 'source_file' in item:
|
|
138
|
+
origin_by_atom[atom]['source_file'] = item['source_file']
|
|
139
|
+
else:
|
|
140
|
+
if (not isinstance(item.get("reasons", []), list)
|
|
141
|
+
or any(not isinstance(reason, str)
|
|
142
|
+
for reason in item.get("reasons", []))):
|
|
143
|
+
raise ValueError("Unavailable evidence reasons must be text")
|
|
144
|
+
issues = item.get("availability_issues")
|
|
145
|
+
if not issues:
|
|
146
|
+
raise ValueError("Unavailable evidence must have availability issues")
|
|
147
|
+
missing.append({"name": name, "reasons": item.get("reasons", []),
|
|
148
|
+
"availability_issues": issues, "span": item.get("span")})
|
|
149
|
+
basis = source.get("structural_basis")
|
|
150
|
+
if basis is not None:
|
|
151
|
+
if (basis["atom"] not in premises or premises[basis["atom"]]["kind"] != "axiom"
|
|
152
|
+
or basis["atom"] in origin_by_atom):
|
|
153
|
+
raise ValueError("Source map structural basis must name an unmapped axiom")
|
|
154
|
+
origin_by_atom[basis["atom"]] = {"kind": "structural basis", "name": "compiler_basis",
|
|
155
|
+
"meaning": basis["meaning"]}
|
|
156
|
+
|
|
157
|
+
contrary_origins = {}
|
|
158
|
+
contrary_pairs = {(item["attacker"], item["target"]) for item in theory["contraries"]}
|
|
159
|
+
if source is not None:
|
|
160
|
+
for item in source.get("contraries", []):
|
|
161
|
+
if isinstance(item, dict):
|
|
162
|
+
if (item["attacker"], item["target"]) not in contrary_pairs:
|
|
163
|
+
raise ValueError("Source map contrary is absent from the formal theory")
|
|
164
|
+
contrary_origins[(item.get("attacker"), item.get("target"))] = {
|
|
165
|
+
"objection": item.get("objection"),
|
|
166
|
+
"annotation": item.get("annotation")}
|
|
167
|
+
viewed_defeats = []
|
|
168
|
+
for event in defeats:
|
|
169
|
+
target = by_id[event["subargument"]]
|
|
170
|
+
attacked_atom = target.get("rule_name") if event["kind"] == "undercut" else target["conclusion"]
|
|
171
|
+
relation = contrary_origins.get((by_id[event["attacker"]]["conclusion"], attacked_atom))
|
|
172
|
+
viewed_defeats.append({**event, **({"relation_origin": relation} if relation else {})})
|
|
173
|
+
|
|
174
|
+
nodes = []
|
|
175
|
+
for arg in arguments:
|
|
176
|
+
node = dict(arg)
|
|
177
|
+
node["origin"] = (origin_by_rule.get(arg.get("rule_id")) if "rule_id" in arg
|
|
178
|
+
else origin_by_atom.get(arg["conclusion"])) or {
|
|
179
|
+
"kind": "formal rule" if "rule_id" in arg else "formal premise",
|
|
180
|
+
"name": arg.get("rule_id", arg["conclusion"])}
|
|
181
|
+
claim = claim_by_atom.get(arg["conclusion"])
|
|
182
|
+
node["display_conclusion"] = claim["name"] if claim else (
|
|
183
|
+
node["origin"]["name"] if node["origin"]["kind"] in
|
|
184
|
+
("evidence", "assumption", "objection") else arg["conclusion"])
|
|
185
|
+
if claim:
|
|
186
|
+
node["authored_statement"] = claim["statement"]
|
|
187
|
+
nodes.append(node)
|
|
188
|
+
view = {"schema": VIEW_VERSION, "theory_digest": formal["theory_sha256"],
|
|
189
|
+
"evaluated_at": source.get("assessed_at") if source else None,
|
|
190
|
+
"validation": {"formal_result": "recomputed", "provenance": "supplied"},
|
|
191
|
+
"profile": result.get("profile", "supplied-formal-result"),
|
|
192
|
+
"goal": theory["goal"], "goal_claim": source["goal"]["claim"] if source else result.get("claim"),
|
|
193
|
+
"formal_status": formal["grounded_status"],
|
|
194
|
+
**{key: formal[key] for key in ('semantics', 'query_mode', 'query_status', 'preference', 'extensions')},
|
|
195
|
+
'execution_limits': selected_limits.describe(),
|
|
196
|
+
'formal_directives': deepcopy(source.get('formal_directives', [])) if source else [],
|
|
197
|
+
"claim_status": result.get("claim_status"),
|
|
198
|
+
"authored_claim_status": result.get("authored_claim_status"),
|
|
199
|
+
"source_digest": result.get("source_digest"),
|
|
200
|
+
"snapshot_digest": source.get("snapshot_digest") if source else None,
|
|
201
|
+
"arguments": nodes, "defeats": viewed_defeats,
|
|
202
|
+
"unavailable_evidence": missing}
|
|
203
|
+
|
|
204
|
+
errors = list(Draft202012Validator(VIEW_SCHEMA).iter_errors(view))
|
|
205
|
+
if errors:
|
|
206
|
+
error = errors[0]
|
|
207
|
+
raise ValueError("Invalid ASPIC+ view metadata at " +
|
|
208
|
+
".".join(map(str, error.absolute_path)) + ": " + error.message)
|
|
209
|
+
return view
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _object(properties, required=None):
|
|
213
|
+
return {"type": "object", "properties": properties,
|
|
214
|
+
"required": list(properties) if required is None else required,
|
|
215
|
+
"additionalProperties": False}
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _nullable(schema):
|
|
219
|
+
return {"anyOf": [schema, {"type": "null"}]}
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
_TEXT = {"type": "string", "maxLength": 100_000}
|
|
223
|
+
_NAME = {"type": "string", "minLength": 1, "maxLength": 100_000}
|
|
224
|
+
_DIGEST = {"type": "string", "pattern": "^[0-9a-f]{64}$"}
|
|
225
|
+
_SPAN = _object({name: {"type": "integer", "minimum": 1}
|
|
226
|
+
for name in ("line", "column", "end_line", "end_column")})
|
|
227
|
+
_REVIEW = _object({
|
|
228
|
+
"kind": {"enum": ["strict", "rank", "contrary", "prefer"]}, "name": _NAME,
|
|
229
|
+
"other": _nullable(_NAME),
|
|
230
|
+
"rank": _nullable({"type": "integer", "minimum": 0, "maximum": 1000}),
|
|
231
|
+
"review": _NAME, "span": _nullable(_SPAN),
|
|
232
|
+
"target_kind": {"enum": ["argument", "assumption", "objection", "evidence", "claim"]},
|
|
233
|
+
'source_file': _nullable(_NAME),
|
|
234
|
+
})
|
|
235
|
+
_TRANSFER = _object({key: _NAME for key in ('source', 'target', 'assumption', 'review')})
|
|
236
|
+
_OBSERVATION = _object({
|
|
237
|
+
"run_id": _NAME, "data_digest": _DIGEST, "request_digest": _DIGEST,
|
|
238
|
+
"collected_at": _NAME, "tool_binding_digest": _DIGEST,
|
|
239
|
+
})
|
|
240
|
+
_ORIGIN = _object({
|
|
241
|
+
"kind": {"enum": ["argument", "assumption", "objection", "evidence",
|
|
242
|
+
"structural basis", "formal rule", "formal premise"]},
|
|
243
|
+
"name": _NAME, "span": _nullable(_SPAN), "formal_review": _nullable(_REVIEW),
|
|
244
|
+
"observation": _nullable(_OBSERVATION), "reasoning": _TEXT,
|
|
245
|
+
"rationale": _TEXT, "meaning": _TEXT,
|
|
246
|
+
'source_file': _NAME, 'scope_transfer': _TRANSFER,
|
|
247
|
+
}, required=["kind", "name"])
|
|
248
|
+
_NODE = deepcopy(OUTPUT_SCHEMA["properties"]["arguments"]["items"])
|
|
249
|
+
_NODE["properties"].update({"origin": _ORIGIN, "display_conclusion": _TEXT,
|
|
250
|
+
"authored_statement": _TEXT})
|
|
251
|
+
_NODE["required"].extend(["origin", "display_conclusion"])
|
|
252
|
+
_DEFEAT = deepcopy(OUTPUT_SCHEMA["properties"]["defeats"]["items"])
|
|
253
|
+
_DEFEAT["properties"]["relation_origin"] = _object({
|
|
254
|
+
"objection": _nullable(_NAME), "annotation": _nullable(_REVIEW)})
|
|
255
|
+
_STATUS = _nullable({"enum": ["supported", "unsupported", "contested", "out_of_scope"]})
|
|
256
|
+
VIEW_SCHEMA = {
|
|
257
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
258
|
+
"$id": "https://github.com/emmett08/earl/blob/main/docs/aspic-view.schema.json",
|
|
259
|
+
"title": "ASPIC+ graph export",
|
|
260
|
+
"description": "The Python exporter recomputes the formal result. Source metadata is supplied, not authenticated. Importing this document alone does not independently verify these assertions.",
|
|
261
|
+
**_object({
|
|
262
|
+
"schema": {"const": VIEW_VERSION}, "profile": _NAME, "goal": _NAME,
|
|
263
|
+
"goal_claim": _nullable(_NAME),
|
|
264
|
+
"formal_status": deepcopy(OUTPUT_SCHEMA["properties"]["grounded_status"]),
|
|
265
|
+
**{key: deepcopy(OUTPUT_SCHEMA['properties'][key]) for key in
|
|
266
|
+
('semantics', 'query_mode', 'query_status', 'preference', 'extensions')},
|
|
267
|
+
'execution_limits': _object({key: {'type': 'integer', 'minimum': 1} for key in ExecutionLimits().describe()}),
|
|
268
|
+
'formal_directives': {'type': 'array', 'items': _REVIEW},
|
|
269
|
+
"claim_status": _STATUS, "authored_claim_status": _STATUS,
|
|
270
|
+
"source_digest": _nullable(_DIGEST), "snapshot_digest": _nullable(_DIGEST),
|
|
271
|
+
"theory_digest": _DIGEST, "evaluated_at": _nullable(_NAME),
|
|
272
|
+
"validation": _object({"formal_result": {"const": "recomputed"},
|
|
273
|
+
"provenance": {"const": "supplied"}}),
|
|
274
|
+
"arguments": {"type": "array", "items": _NODE, "minItems": 1},
|
|
275
|
+
"defeats": {"type": "array", "items": _DEFEAT},
|
|
276
|
+
"unavailable_evidence": {"type": "array", "items": _object({
|
|
277
|
+
"name": _NAME, "reasons": {"type": "array", "items": _TEXT},
|
|
278
|
+
"availability_issues": {"type": "array", "minItems": 1,
|
|
279
|
+
"uniqueItems": True,
|
|
280
|
+
"items": {"enum": list(AVAILABILITY_ISSUES)}},
|
|
281
|
+
"span": _nullable(_SPAN)})},
|
|
282
|
+
}),
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
# Source-map checks concern types and correspondence only. They deliberately
|
|
286
|
+
# cannot establish whether a statement, review or observation is authentic.
|
|
287
|
+
_SOURCE_ENTRY = {
|
|
288
|
+
"type": "object", "properties": {
|
|
289
|
+
"atom": _NAME, "rule_id": _NAME, "conclusion_atom": _NAME,
|
|
290
|
+
"applicability_atom": _nullable(_NAME), "rule_kind": {"enum": ["strict", "defeasible"]},
|
|
291
|
+
"statement": _TEXT, "reasoning": _TEXT, "rationale": _TEXT,
|
|
292
|
+
"emitted": {"type": "boolean"}, "available": {"type": "boolean"},
|
|
293
|
+
"span": _nullable(_SPAN), "rank_annotation": _nullable(_REVIEW),
|
|
294
|
+
"strict_annotation": _nullable(_REVIEW), "identity": _nullable(_OBSERVATION),
|
|
295
|
+
"reasons": {"type": "array", "items": _TEXT},
|
|
296
|
+
"availability_issues": {"type": "array", "uniqueItems": True,
|
|
297
|
+
"items": {"enum": list(AVAILABILITY_ISSUES)}},
|
|
298
|
+
'source_file': _NAME, 'scope_transfer': _TRANSFER,
|
|
299
|
+
},
|
|
300
|
+
}
|
|
301
|
+
_SOURCE_SCHEMA = {"type": "object", "properties": {
|
|
302
|
+
'formal_directives': {'type': 'array', 'items': _REVIEW},
|
|
303
|
+
**{kind: {"type": "object", "additionalProperties": _SOURCE_ENTRY}
|
|
304
|
+
for kind in ("claims", "arguments", "assumptions", "objections", "evidence")},
|
|
305
|
+
"contraries": {"type": "array", "items": {"type": "object", "properties": {
|
|
306
|
+
"attacker": _NAME, "target": _NAME, "objection": _NAME,
|
|
307
|
+
"annotation": _nullable(_REVIEW)}, "required": ["attacker", "target"]}},
|
|
308
|
+
"structural_basis": _object({"atom": _NAME, "meaning": _TEXT}),
|
|
309
|
+
}}
|
eal/builtin_methods.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Versioned specifications for the built-in reasoning methods.
|
|
2
|
+
|
|
3
|
+
The algorithms live in ``reasoning`` strategy modules. This module owns their declared evidence,
|
|
4
|
+
input, query, output and quantity contracts; public discovery is generated
|
|
5
|
+
from these same specifications by ``methods.default_registry``.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _object(properties, required=None, additional=False):
|
|
13
|
+
return {'type': 'object', 'properties': properties,
|
|
14
|
+
'required': list(properties) if required is None else required,
|
|
15
|
+
'additionalProperties': additional}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class BuiltinMethodSpec:
|
|
20
|
+
identifier: str
|
|
21
|
+
evidence_kind: str | None
|
|
22
|
+
input_schema: dict
|
|
23
|
+
query_fields: tuple[str, ...]
|
|
24
|
+
outputs: dict[str, str]
|
|
25
|
+
quantities: tuple[str, ...] | None
|
|
26
|
+
exact_unit: bool = False
|
|
27
|
+
implementation_version: str = 'eal-builtin-2.1.0'
|
|
28
|
+
output_schema: dict | None = None
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def mode(self):
|
|
32
|
+
return self.identifier.rpartition('/')[0]
|
|
33
|
+
|
|
34
|
+
def contract_schemas(self):
|
|
35
|
+
"""Produce detached, typed query/output schemas from the declared fields."""
|
|
36
|
+
number = {'type': 'number', 'minimum': -1e100, 'maximum': 1e100}
|
|
37
|
+
boolean = {'type': 'boolean'}
|
|
38
|
+
query = _object({key: self.input_schema['properties'][key] for key in self.query_fields})
|
|
39
|
+
if self.output_schema is not None:
|
|
40
|
+
return query, self.output_schema
|
|
41
|
+
properties = {}
|
|
42
|
+
for path, kind in self.outputs.items():
|
|
43
|
+
if path.endswith('.*'):
|
|
44
|
+
properties[path[:-2]] = {'type': 'object', 'additionalProperties': number}
|
|
45
|
+
else:
|
|
46
|
+
properties[path] = boolean if kind == 'boolean' else number
|
|
47
|
+
return query, _object(properties, additional={'type': 'json'})
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
number = {'type': 'number', 'minimum': -1e100, 'maximum': 1e100}
|
|
51
|
+
integer = {'type': 'integer', 'minimum': 0, 'maximum': 1_000_000_000}
|
|
52
|
+
text = {'type': 'string', 'minLength': 1, 'maxLength': 4096}
|
|
53
|
+
anything = {'type': 'json'}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def array(item=number, low=1, high=10_000):
|
|
57
|
+
return {'type': 'array', 'items': item, 'minItems': low, 'maxItems': high}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# ``quantities=None`` means all declared non-proposition quantities; the
|
|
61
|
+
# quantity vocabulary remains defined by propositions rather than duplicated.
|
|
62
|
+
BUILTIN_SPECS = {
|
|
63
|
+
spec.mode: spec for spec in (
|
|
64
|
+
BuiltinMethodSpec('structured/1', None, _object({}), (), {}, (),
|
|
65
|
+
output_schema=_object({'authored': {'type': 'boolean'},
|
|
66
|
+
'mechanically_proved': {'type': 'boolean'}})),
|
|
67
|
+
BuiltinMethodSpec('deductive/1', 'logical_case',
|
|
68
|
+
_object({'premises': array(anything, 0, 128), 'conclusion': anything}),
|
|
69
|
+
('premises', 'conclusion'),
|
|
70
|
+
{'entailed': 'boolean', 'consistent_premises': 'boolean'}, ('proposition',)),
|
|
71
|
+
BuiltinMethodSpec('inductive/1', 'sample',
|
|
72
|
+
_object({'successes': integer, 'trials': {**integer, 'minimum': 1},
|
|
73
|
+
'confidence': {'type': 'number', 'minimum': .001, 'maximum': .999999}}),
|
|
74
|
+
('confidence',),
|
|
75
|
+
{'estimate': 'dimensionless', 'lower': 'dimensionless', 'upper': 'dimensionless'},
|
|
76
|
+
('probability',)),
|
|
77
|
+
BuiltinMethodSpec('abductive/1', 'hypotheses',
|
|
78
|
+
_object({'observed': text, 'candidates': array(_object({
|
|
79
|
+
'name': text, 'prior': number, 'likelihood': number}), 2, 128)}),
|
|
80
|
+
('observed', 'candidates'),
|
|
81
|
+
{'best_posterior': 'dimensionless', 'posterior.*': 'dimensionless'},
|
|
82
|
+
('probability',)),
|
|
83
|
+
BuiltinMethodSpec('causal/1', 'experiment',
|
|
84
|
+
_object({'assignment': {'type': 'string', 'enum': ['randomised']},
|
|
85
|
+
'treatment': array(number, 2), 'control': array(number, 2)}),
|
|
86
|
+
('assignment',),
|
|
87
|
+
{'estimate': 'basis', 'standard_error': 'basis',
|
|
88
|
+
'treatment_mean': 'basis', 'control_mean': 'basis'}, None),
|
|
89
|
+
BuiltinMethodSpec('counterfactual/1', 'causal_model',
|
|
90
|
+
_object({'variables': {'type': 'object', 'additionalProperties': _object({
|
|
91
|
+
'intercept': number, 'coefficients': {'type': 'object', 'additionalProperties': number},
|
|
92
|
+
'noise': number})},
|
|
93
|
+
'intervention': _object({'variable': text, 'value': number}),
|
|
94
|
+
'outcome': text}),
|
|
95
|
+
('variables', 'intervention', 'outcome'),
|
|
96
|
+
{'factual': 'basis', 'counterfactual': 'basis', 'difference': 'basis'},
|
|
97
|
+
None, exact_unit=True),
|
|
98
|
+
BuiltinMethodSpec('analogical/1', 'analogy',
|
|
99
|
+
_object({'relevant_features': array(text, 1, 256),
|
|
100
|
+
'source': {'type': 'object', 'additionalProperties': anything},
|
|
101
|
+
'target': {'type': 'object', 'additionalProperties': anything}}),
|
|
102
|
+
('relevant_features', 'source', 'target'),
|
|
103
|
+
{'match_fraction': 'dimensionless', 'complete': 'boolean'},
|
|
104
|
+
('dimensionless',)),
|
|
105
|
+
BuiltinMethodSpec('temporal/1', 'trace',
|
|
106
|
+
_object({'start': number, 'end': number, 'max_gap': number,
|
|
107
|
+
'events': array(_object({'time': number, 'value': number})),
|
|
108
|
+
'property': _object({'operator': {'type': 'string', 'enum': [
|
|
109
|
+
'lt', 'le', 'eq', 'ne', 'ge', 'gt']}, 'value': number}),
|
|
110
|
+
'semantics': {'type': 'string', 'enum': ['sampled']}}),
|
|
111
|
+
('start', 'end', 'max_gap', 'property', 'semantics'),
|
|
112
|
+
{'holds': 'boolean', 'coverage': 'boolean'},
|
|
113
|
+
None, exact_unit=True),
|
|
114
|
+
)
|
|
115
|
+
}
|