speclogician 0.0.0b1__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.
- speclogician/__init__.py +0 -0
- speclogician/commands/__init__.py +15 -0
- speclogician/commands/cmd_ch.py +616 -0
- speclogician/commands/cmd_find.py +256 -0
- speclogician/commands/cmd_view.py +202 -0
- speclogician/commands/runner.py +149 -0
- speclogician/commands/utils.py +101 -0
- speclogician/data/__init__.py +0 -0
- speclogician/data/artifact.py +63 -0
- speclogician/data/container.py +402 -0
- speclogician/data/mapping.py +88 -0
- speclogician/data/refs.py +24 -0
- speclogician/data/traces.py +26 -0
- speclogician/demos/.DS_Store +0 -0
- speclogician/demos/cmd_demo.py +278 -0
- speclogician/demos/loader.py +135 -0
- speclogician/demos/model.py +27 -0
- speclogician/demos/runner.py +51 -0
- speclogician/logic/__init__.py +11 -0
- speclogician/logic/api/__init__.py +29 -0
- speclogician/logic/api/client.py +606 -0
- speclogician/logic/api/decomp.py +67 -0
- speclogician/logic/api/scenario.py +102 -0
- speclogician/logic/api/traces.py +59 -0
- speclogician/logic/lib/__init__.py +19 -0
- speclogician/logic/lib/complement.py +107 -0
- speclogician/logic/lib/domain_model.py +59 -0
- speclogician/logic/lib/predicates.py +151 -0
- speclogician/logic/lib/scenarios.py +369 -0
- speclogician/logic/lib/traces.py +114 -0
- speclogician/logic/lib/transitions.py +104 -0
- speclogician/logic/main.py +246 -0
- speclogician/logic/strings.py +194 -0
- speclogician/logic/utils.py +135 -0
- speclogician/main.py +139 -0
- speclogician/modeling/__init__.py +31 -0
- speclogician/modeling/complement.py +104 -0
- speclogician/modeling/component.py +71 -0
- speclogician/modeling/conflict.py +26 -0
- speclogician/modeling/domain.py +349 -0
- speclogician/modeling/predicates.py +59 -0
- speclogician/modeling/scenario.py +162 -0
- speclogician/modeling/spec.py +306 -0
- speclogician/modeling/spec_stats.py +39 -0
- speclogician/presentation/__init__.py +0 -0
- speclogician/presentation/api.py +244 -0
- speclogician/presentation/builders/__init__.py +0 -0
- speclogician/presentation/builders/_links.py +44 -0
- speclogician/presentation/builders/container.py +53 -0
- speclogician/presentation/builders/data_artifact.py +42 -0
- speclogician/presentation/builders/domain.py +54 -0
- speclogician/presentation/builders/instances_list.py +38 -0
- speclogician/presentation/builders/predicate.py +51 -0
- speclogician/presentation/builders/recommendations.py +41 -0
- speclogician/presentation/builders/scenario.py +41 -0
- speclogician/presentation/builders/scenario_complement.py +82 -0
- speclogician/presentation/builders/smart_find.py +39 -0
- speclogician/presentation/builders/spec.py +39 -0
- speclogician/presentation/builders/state_diff.py +150 -0
- speclogician/presentation/builders/state_instance.py +42 -0
- speclogician/presentation/builders/state_instance_summary.py +84 -0
- speclogician/presentation/builders/trace.py +58 -0
- speclogician/presentation/ctx.py +38 -0
- speclogician/presentation/models/__init__.py +0 -0
- speclogician/presentation/models/container.py +44 -0
- speclogician/presentation/models/data_artifact.py +33 -0
- speclogician/presentation/models/domain.py +50 -0
- speclogician/presentation/models/instances_list.py +23 -0
- speclogician/presentation/models/predicate.py +60 -0
- speclogician/presentation/models/recommendations.py +34 -0
- speclogician/presentation/models/scenario.py +31 -0
- speclogician/presentation/models/scenario_complement.py +40 -0
- speclogician/presentation/models/smart_find.py +34 -0
- speclogician/presentation/models/spec.py +32 -0
- speclogician/presentation/models/state_diff.py +34 -0
- speclogician/presentation/models/state_instance.py +31 -0
- speclogician/presentation/models/state_instance_summary.py +102 -0
- speclogician/presentation/models/trace.py +42 -0
- speclogician/presentation/preview/__init__.py +13 -0
- speclogician/presentation/preview/cli.py +50 -0
- speclogician/presentation/preview/fixtures/__init__.py +205 -0
- speclogician/presentation/preview/fixtures/artifact_container.py +150 -0
- speclogician/presentation/preview/fixtures/data_artifact.py +144 -0
- speclogician/presentation/preview/fixtures/domain_model.py +162 -0
- speclogician/presentation/preview/fixtures/instances_list.py +162 -0
- speclogician/presentation/preview/fixtures/predicate.py +184 -0
- speclogician/presentation/preview/fixtures/scenario.py +84 -0
- speclogician/presentation/preview/fixtures/scenario_complement.py +81 -0
- speclogician/presentation/preview/fixtures/smart_find.py +140 -0
- speclogician/presentation/preview/fixtures/spec.py +95 -0
- speclogician/presentation/preview/fixtures/state_diff.py +158 -0
- speclogician/presentation/preview/fixtures/state_instance.py +128 -0
- speclogician/presentation/preview/fixtures/state_instance_summary.py +80 -0
- speclogician/presentation/preview/fixtures/trace.py +206 -0
- speclogician/presentation/preview/registry.py +42 -0
- speclogician/presentation/renderers/__init__.py +24 -0
- speclogician/presentation/renderers/container.py +136 -0
- speclogician/presentation/renderers/data_artifact.py +144 -0
- speclogician/presentation/renderers/domain.py +123 -0
- speclogician/presentation/renderers/instances_list.py +120 -0
- speclogician/presentation/renderers/predicate.py +180 -0
- speclogician/presentation/renderers/recommendations.py +90 -0
- speclogician/presentation/renderers/scenario.py +94 -0
- speclogician/presentation/renderers/scenario_complement.py +59 -0
- speclogician/presentation/renderers/smart_find.py +307 -0
- speclogician/presentation/renderers/spec.py +105 -0
- speclogician/presentation/renderers/state_diff.py +102 -0
- speclogician/presentation/renderers/state_instance.py +82 -0
- speclogician/presentation/renderers/state_instance_summary.py +143 -0
- speclogician/presentation/renderers/trace.py +122 -0
- speclogician/py.typed +0 -0
- speclogician/shell/app.py +170 -0
- speclogician/shell/shell_ch.py +263 -0
- speclogician/shell/shell_view.py +153 -0
- speclogician/state/__init__.py +0 -0
- speclogician/state/change.py +428 -0
- speclogician/state/change_result.py +32 -0
- speclogician/state/diff.py +191 -0
- speclogician/state/inst.py +574 -0
- speclogician/state/recommendation.py +13 -0
- speclogician/state/recommender.py +577 -0
- speclogician/state/state.py +465 -0
- speclogician/state/state_stats.py +133 -0
- speclogician/tui/__init__.py +0 -0
- speclogician/tui/app.py +257 -0
- speclogician/tui/app.tcss +160 -0
- speclogician/tui/demo.py +45 -0
- speclogician/tui/images/speclogician-full.png +0 -0
- speclogician/tui/images/speclogician-minimal.png +0 -0
- speclogician/tui/main_screen.py +454 -0
- speclogician/tui/splash_screen.py +51 -0
- speclogician/tui/stats_screen.py +125 -0
- speclogician/utils/__init__.py +78 -0
- speclogician/utils/load.py +166 -0
- speclogician/utils/prompt.md +325 -0
- speclogician/utils/testing.py +151 -0
- speclogician-0.0.0b1.dist-info/METADATA +116 -0
- speclogician-0.0.0b1.dist-info/RECORD +139 -0
- speclogician-0.0.0b1.dist-info/WHEEL +4 -0
speclogician/main.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Imandra Inc.
|
|
3
|
+
#
|
|
4
|
+
# speclogician/main.py
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
__version__ = "1.0"
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from typing import Final
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from speclogician.commands import ch_app, view_app, find_app
|
|
14
|
+
from speclogician.presentation.preview import preview_app
|
|
15
|
+
from speclogician.demos.cmd_demo import app as demo_app
|
|
16
|
+
from speclogician.utils import console, require_imandra_key
|
|
17
|
+
from speclogician.utils.load import load_state
|
|
18
|
+
from speclogician.state.state import State
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
SL_HELP : Final = """
|
|
22
|
+
:robot: [bold deep_sky_blue1][italic]SpecLogician[/italic] is an AI framework for data-driven
|
|
23
|
+
formal program specification synthesis, verification, and analysis.[/bold deep_sky_blue1] :rocket:
|
|
24
|
+
|
|
25
|
+
SpecLogician provides an LLM-native alternative to classical predicate abstraction and
|
|
26
|
+
Counterexample-Guided Abstraction Refinement (CEGAR).
|
|
27
|
+
|
|
28
|
+
Instead of requiring users or agents to discover and maintain global Boolean abstractions,
|
|
29
|
+
SpecLogician models behavior directly using structured scenarios composed from state and
|
|
30
|
+
action predicates. Specifications are built incrementally by defining a domain base
|
|
31
|
+
(types and core invariants), predicates, transition functions, and scenarios that capture
|
|
32
|
+
intent explicitly.
|
|
33
|
+
|
|
34
|
+
Each change creates a new immutable state, triggers precise logical analysis, and produces
|
|
35
|
+
a semantic diff against the previous state. Using ImandraX’s exact symbolic reasoning and
|
|
36
|
+
region decomposition, SpecLogician computes coverage, conflicts, reachability, and
|
|
37
|
+
behavioral complements—identifying real, reachable behaviors not yet explained by any
|
|
38
|
+
scenario.
|
|
39
|
+
|
|
40
|
+
Rather than reacting to spurious counterexamples, users and AI agents receive positive,
|
|
41
|
+
actionable refinement signals in the form of uncovered or inconsistent behavioral regions.
|
|
42
|
+
This enables scalable, explainable refinement toward correctness and completeness across
|
|
43
|
+
source code, tests, documentation, and real-world traces—using small, explicit change
|
|
44
|
+
commands instead of manual file editing.
|
|
45
|
+
|
|
46
|
+
Learn more at [bold italic deep_sky_blue1]https://www.speclogician.dev![/bold italic deep_sky_blue1]
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
app = typer.Typer(
|
|
50
|
+
name="SpecLogician",
|
|
51
|
+
help=SL_HELP,
|
|
52
|
+
rich_markup_mode="rich",
|
|
53
|
+
no_args_is_help=True
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
class AppCtx:
|
|
57
|
+
state_json_path: Path | None = None
|
|
58
|
+
state: State | None = None
|
|
59
|
+
|
|
60
|
+
@app.callback()
|
|
61
|
+
def _cli_entry() -> None:
|
|
62
|
+
"""
|
|
63
|
+
SpecLogician CLI entry point.
|
|
64
|
+
"""
|
|
65
|
+
require_imandra_key()
|
|
66
|
+
|
|
67
|
+
@app.callback()
|
|
68
|
+
def main(ctx: typer.Context, state_json: Path | None = typer.Option(None, "--state-json", help="...")):
|
|
69
|
+
ctx.obj = ctx.ensure_object(AppCtx)
|
|
70
|
+
|
|
71
|
+
ctx.obj.state_json_path = state_json
|
|
72
|
+
|
|
73
|
+
if state_json:
|
|
74
|
+
try:
|
|
75
|
+
ctx.obj.state = load_state(path=state_json)
|
|
76
|
+
except Exception:
|
|
77
|
+
ctx.obj.state = None
|
|
78
|
+
else:
|
|
79
|
+
ctx.obj.state = None # ✅ ensure attribute exists
|
|
80
|
+
|
|
81
|
+
app.add_typer(view_app , name="view" , help="View components of the state")
|
|
82
|
+
app.add_typer(ch_app , name="ch" , help="Apply change to the state")
|
|
83
|
+
app.add_typer(find_app , name="find" , help="Search for a components of the state")
|
|
84
|
+
app.add_typer(demo_app , name="demo" , help="A collection of SpecLogician demos")
|
|
85
|
+
app.add_typer(preview_app , name="preview" , hidden=True) # This is an internal tool for manually inspecting renderers
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@app.command(help="Run the TUI")
|
|
89
|
+
def tui ():
|
|
90
|
+
""" Run the TUI to help us navigate and explore the state """
|
|
91
|
+
from speclogician.tui.app import SpecLogicianApp
|
|
92
|
+
tui_app = SpecLogicianApp()
|
|
93
|
+
tui_app.run()
|
|
94
|
+
|
|
95
|
+
@app.command(help="Generate a prompt for helping agents use SpecLogician")
|
|
96
|
+
def prompt(
|
|
97
|
+
out: Path | None = typer.Option(
|
|
98
|
+
None,
|
|
99
|
+
"--out",
|
|
100
|
+
help="Write the prompt to a file instead of stdout.",
|
|
101
|
+
),
|
|
102
|
+
):
|
|
103
|
+
"""
|
|
104
|
+
Generate a prompt for helping CLI agents use SpecLogician.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
here = Path(__file__).resolve()
|
|
108
|
+
repo_root = here.parents[0]
|
|
109
|
+
prompt_path = repo_root / "utils" / "prompt.md"
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
txt = prompt_path.read_text()
|
|
113
|
+
except Exception as e:
|
|
114
|
+
console.print(f"🛑 Failed to read prompt file at {prompt_path}: {e}")
|
|
115
|
+
raise typer.Exit(2)
|
|
116
|
+
|
|
117
|
+
if out:
|
|
118
|
+
try:
|
|
119
|
+
out.write_text(txt)
|
|
120
|
+
except Exception as e:
|
|
121
|
+
console.print(f"🛑 Failed to write prompt to {out}: {e}")
|
|
122
|
+
raise typer.Exit(2)
|
|
123
|
+
typer.echo(str(out))
|
|
124
|
+
else:
|
|
125
|
+
typer.echo(txt)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@app.command()
|
|
129
|
+
def version(
|
|
130
|
+
json_only: bool = typer.Option(False, "--json", help="Output JSON"),
|
|
131
|
+
):
|
|
132
|
+
"""Print SpecLogician version"""
|
|
133
|
+
if json_only:
|
|
134
|
+
typer.echo(f'{{"version": "{__version__}"}}')
|
|
135
|
+
else:
|
|
136
|
+
typer.echo(__version__)
|
|
137
|
+
|
|
138
|
+
if __name__ == '__main__':
|
|
139
|
+
app()
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Imandra Inc.
|
|
3
|
+
#
|
|
4
|
+
# speclogician/modeling/__init__.py
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
from typing import List
|
|
10
|
+
|
|
11
|
+
IML_TYPE_REGEX = re.compile(
|
|
12
|
+
r"""
|
|
13
|
+
^\s*type\s+ # 'type' keyword
|
|
14
|
+
(?P<name>[a-zA-Z_][a-zA-Z0-9_]*) # type name
|
|
15
|
+
\s*= # '=' sign
|
|
16
|
+
""",
|
|
17
|
+
re.MULTILINE | re.VERBOSE,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
def strip_iml_comments(text: str) -> str:
|
|
21
|
+
"""Remove OCaml-style (* ... *) and // comments."""
|
|
22
|
+
# Remove block comments
|
|
23
|
+
text = re.sub(r"\(\*.*?\*\)", "", text, flags=re.DOTALL)
|
|
24
|
+
# Remove line comments
|
|
25
|
+
text = re.sub(r"//.*", "", text)
|
|
26
|
+
return text
|
|
27
|
+
|
|
28
|
+
def extract_declared_types(iml_text: str) -> List[str]:
|
|
29
|
+
"""Return all declared type names in an IML file."""
|
|
30
|
+
clean = strip_iml_comments(iml_text)
|
|
31
|
+
return [m.group("name") for m in IML_TYPE_REGEX.finditer(clean)]
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Imandra Inc.
|
|
3
|
+
#
|
|
4
|
+
# speclogician/modeling/complement.py
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import hashlib
|
|
10
|
+
import json
|
|
11
|
+
from typing import Sequence, Iterable
|
|
12
|
+
|
|
13
|
+
from pydantic import BaseModel
|
|
14
|
+
from imandrax_api_models import RegionStr
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ScenarioComplement(BaseModel):
|
|
18
|
+
"""Result of running region decomposition on all the combined scenarios."""
|
|
19
|
+
regions: Sequence[RegionStr]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ScenarioComplementDiff(BaseModel):
|
|
23
|
+
"""
|
|
24
|
+
Difference between two scenario complements (before -> after),
|
|
25
|
+
computed using stable fingerprints of regions.
|
|
26
|
+
|
|
27
|
+
We store only fingerprints by default (regions can be huge).
|
|
28
|
+
"""
|
|
29
|
+
num_regions_before: int
|
|
30
|
+
num_regions_after: int
|
|
31
|
+
regions_added: list[str] # fingerprints that appear in after but not before
|
|
32
|
+
regions_removed: list[str] # fingerprints that appear in before but not after
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _canon_region_str(r: RegionStr) -> str:
|
|
36
|
+
"""
|
|
37
|
+
Produce a deterministic string representation of a RegionStr.
|
|
38
|
+
|
|
39
|
+
We prefer a canonical JSON-ish blob rather than str(r) because:
|
|
40
|
+
- __repr__ / __str__ may change
|
|
41
|
+
- dict ordering may vary
|
|
42
|
+
"""
|
|
43
|
+
# constraints_str is typically a list of strings
|
|
44
|
+
constraints = list(getattr(r, "constraints_str", []) or [])
|
|
45
|
+
|
|
46
|
+
invariant = getattr(r, "invariant_str", "") or ""
|
|
47
|
+
model_eval = getattr(r, "model_eval_str", "") or ""
|
|
48
|
+
|
|
49
|
+
# model_str can be dict-like (often {'s': '{x = 0}'}). Canonicalize ordering.
|
|
50
|
+
model = getattr(r, "model_str", None)
|
|
51
|
+
if isinstance(model, dict):
|
|
52
|
+
# stringify values to avoid non-jsonable shapes
|
|
53
|
+
model_items = {str(k): str(v) for k, v in sorted(model.items(), key=lambda kv: str(kv[0]))}
|
|
54
|
+
elif model is None:
|
|
55
|
+
model_items = {}
|
|
56
|
+
else:
|
|
57
|
+
# fallback: stringify
|
|
58
|
+
model_items = {"_": str(model)}
|
|
59
|
+
|
|
60
|
+
payload = {
|
|
61
|
+
"constraints": constraints,
|
|
62
|
+
"invariant": invariant,
|
|
63
|
+
"model": model_items,
|
|
64
|
+
"model_eval": model_eval,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# separators for stable compact encoding
|
|
68
|
+
return json.dumps(payload, sort_keys=True, separators=(",", ":"))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _fingerprint_region(r: RegionStr | str) -> str:
|
|
72
|
+
"""
|
|
73
|
+
Stable-ish fingerprint for a RegionStr (or a raw string region).
|
|
74
|
+
"""
|
|
75
|
+
if isinstance(r, RegionStr):
|
|
76
|
+
s = _canon_region_str(r)
|
|
77
|
+
else:
|
|
78
|
+
s = str(r)
|
|
79
|
+
|
|
80
|
+
h = hashlib.sha256(s.encode("utf-8")).hexdigest()
|
|
81
|
+
# short prefix is plenty for diffs + UI, but keep full if you prefer
|
|
82
|
+
return h[:16]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _fingerprints(regions: Iterable[RegionStr | str]) -> set[str]:
|
|
86
|
+
return {_fingerprint_region(r) for r in regions}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def diff_scenario_complement(before: ScenarioComplement, after: ScenarioComplement) -> ScenarioComplementDiff:
|
|
90
|
+
before_regions = list(before.regions or [])
|
|
91
|
+
after_regions = list(after.regions or [])
|
|
92
|
+
|
|
93
|
+
bf = _fingerprints(before_regions)
|
|
94
|
+
af = _fingerprints(after_regions)
|
|
95
|
+
|
|
96
|
+
added = sorted(list(af - bf))
|
|
97
|
+
removed = sorted(list(bf - af))
|
|
98
|
+
|
|
99
|
+
return ScenarioComplementDiff(
|
|
100
|
+
num_regions_before=len(before_regions),
|
|
101
|
+
num_regions_after=len(after_regions),
|
|
102
|
+
regions_added=added,
|
|
103
|
+
regions_removed=removed,
|
|
104
|
+
)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Imandra Inc.
|
|
3
|
+
#
|
|
4
|
+
# speclogician/modeling/component.py
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
import uuid
|
|
8
|
+
import re
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
from pydantic import BaseModel, Field, field_validator, ConfigDict
|
|
11
|
+
from ..utils import IMLValidity
|
|
12
|
+
|
|
13
|
+
_NAME_RE = re.compile(r"^[a-z][a-z0-9_]*$")
|
|
14
|
+
|
|
15
|
+
# Conservative reserved set (OCaml-ish + common IML surface syntax).
|
|
16
|
+
# Add more as you run into them.
|
|
17
|
+
_IML_RESERVED: set[str] = {
|
|
18
|
+
# core keywords / syntax
|
|
19
|
+
"let", "in", "and", "rec",
|
|
20
|
+
"type", "match", "with", "function",
|
|
21
|
+
"if", "then", "else",
|
|
22
|
+
"try", "raise",
|
|
23
|
+
"module", "open", "include",
|
|
24
|
+
"begin", "end",
|
|
25
|
+
"as", "of", "when",
|
|
26
|
+
|
|
27
|
+
# booleans / common literals
|
|
28
|
+
"true", "false",
|
|
29
|
+
|
|
30
|
+
# common types / constructors that are often special-cased
|
|
31
|
+
"some", "none",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
def validate_iml_identifier(name: str) -> str:
|
|
35
|
+
if not _NAME_RE.fullmatch(name):
|
|
36
|
+
raise ValueError(
|
|
37
|
+
f"Invalid name '{name}'. Must start with a lowercase letter and contain only "
|
|
38
|
+
"lowercase letters, digits, and underscores."
|
|
39
|
+
)
|
|
40
|
+
if name in _IML_RESERVED:
|
|
41
|
+
raise ValueError(f"Invalid name '{name}': reserved keyword in IML/OCaml surface syntax.")
|
|
42
|
+
return name
|
|
43
|
+
|
|
44
|
+
class ModelComponent(BaseModel):
|
|
45
|
+
"""
|
|
46
|
+
Base class for all model components (Predicates, transitions and scenarios)
|
|
47
|
+
"""
|
|
48
|
+
model_config = ConfigDict(validate_assignment=True) # optional but recommended
|
|
49
|
+
|
|
50
|
+
comp_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
51
|
+
name: str
|
|
52
|
+
|
|
53
|
+
# Track last update time (UTC)
|
|
54
|
+
last_updated: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
55
|
+
|
|
56
|
+
@field_validator("name")
|
|
57
|
+
@classmethod
|
|
58
|
+
def validate_name(cls, v: str) -> str:
|
|
59
|
+
return validate_iml_identifier(v)
|
|
60
|
+
|
|
61
|
+
def touch(self) -> None:
|
|
62
|
+
self.last_updated = datetime.now(timezone.utc)
|
|
63
|
+
|
|
64
|
+
class SrcComponent(ModelComponent):
|
|
65
|
+
src_code: str = ""
|
|
66
|
+
is_iml_valid : IMLValidity = IMLValidity.UNKNOWN
|
|
67
|
+
iml_error: str | None = None
|
|
68
|
+
|
|
69
|
+
def set_src_code(self, src_code: str) -> None:
|
|
70
|
+
self.src_code = src_code
|
|
71
|
+
self.touch()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Imandra Inc.
|
|
3
|
+
#
|
|
4
|
+
# speclogician/modeling/conflict.py
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
from typing import Literal, TypeAlias
|
|
9
|
+
|
|
10
|
+
class ScenarioOverlap(BaseModel):
|
|
11
|
+
""" Two scenarios overlap in constraints """
|
|
12
|
+
kind: Literal["overlap"] = "overlap"
|
|
13
|
+
scenario_name1 : str
|
|
14
|
+
scenario_name2 : str
|
|
15
|
+
|
|
16
|
+
details : str
|
|
17
|
+
|
|
18
|
+
class ScenarioConsumed(BaseModel):
|
|
19
|
+
""" One scenario fully absorbs another """
|
|
20
|
+
kind: Literal["consumed"] = "consumed"
|
|
21
|
+
scenario_name1 : str
|
|
22
|
+
scenario_name2 : str
|
|
23
|
+
|
|
24
|
+
details : str
|
|
25
|
+
|
|
26
|
+
ScenarioConflict : TypeAlias = ScenarioOverlap | ScenarioConsumed
|