isitright 0.1.0.dev0__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.
- isitright/__about__.py +4 -0
- isitright/__init__.py +34 -0
- isitright/api.py +46 -0
- isitright/cli.py +105 -0
- isitright/models.py +269 -0
- isitright/suite.py +43 -0
- isitright-0.1.0.dev0.dist-info/METADATA +105 -0
- isitright-0.1.0.dev0.dist-info/RECORD +11 -0
- isitright-0.1.0.dev0.dist-info/WHEEL +4 -0
- isitright-0.1.0.dev0.dist-info/entry_points.txt +2 -0
- isitright-0.1.0.dev0.dist-info/licenses/LICENSE +22 -0
isitright/__about__.py
ADDED
isitright/__init__.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Public package interface for isitright."""
|
|
2
|
+
|
|
3
|
+
from isitright.__about__ import __version__
|
|
4
|
+
from isitright.api import StageNotImplementedError, check, check_suite
|
|
5
|
+
from isitright.models import (
|
|
6
|
+
AttributionStatus,
|
|
7
|
+
CaseResult,
|
|
8
|
+
CheckConfig,
|
|
9
|
+
ColumnComparisonPolicy,
|
|
10
|
+
EquivalenceStatus,
|
|
11
|
+
FailOnPolicy,
|
|
12
|
+
OrderingPolicy,
|
|
13
|
+
SuiteCase,
|
|
14
|
+
SuiteResult,
|
|
15
|
+
ValidityStatus,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"AttributionStatus",
|
|
20
|
+
"CaseResult",
|
|
21
|
+
"CheckConfig",
|
|
22
|
+
"ColumnComparisonPolicy",
|
|
23
|
+
"EquivalenceStatus",
|
|
24
|
+
"FailOnPolicy",
|
|
25
|
+
"OrderingPolicy",
|
|
26
|
+
"StageNotImplementedError",
|
|
27
|
+
"SuiteCase",
|
|
28
|
+
"SuiteResult",
|
|
29
|
+
"ValidityStatus",
|
|
30
|
+
"__version__",
|
|
31
|
+
"check",
|
|
32
|
+
"check_suite",
|
|
33
|
+
]
|
|
34
|
+
|
isitright/api.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Reserved public API for the future analysis engine."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from os import PathLike
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from isitright.models import CaseResult, CheckConfig, SuiteResult
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class StageNotImplementedError(RuntimeError):
|
|
13
|
+
"""Raised when a post-Stage-0 capability is invoked."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def check(
|
|
17
|
+
*,
|
|
18
|
+
database: str | PathLike[str],
|
|
19
|
+
reference_sql: str,
|
|
20
|
+
candidate_sql: str,
|
|
21
|
+
question: str | None = None,
|
|
22
|
+
constraints_file: str | PathLike[str] | None = None,
|
|
23
|
+
config: CheckConfig | None = None,
|
|
24
|
+
validator: Callable[[Path], bool] | None = None,
|
|
25
|
+
) -> CaseResult:
|
|
26
|
+
"""Compare one SQL pair once the Stage 1+ engine is implemented."""
|
|
27
|
+
del database, reference_sql, candidate_sql, question, constraints_file, config, validator
|
|
28
|
+
raise StageNotImplementedError(
|
|
29
|
+
"check() is a Stage 1+ capability; Stage 0 defines its contract only"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def check_suite(
|
|
34
|
+
*,
|
|
35
|
+
database: str | PathLike[str],
|
|
36
|
+
cases: str | PathLike[str],
|
|
37
|
+
constraints_file: str | PathLike[str] | None = None,
|
|
38
|
+
config: CheckConfig | None = None,
|
|
39
|
+
validator: Callable[[Path], bool] | None = None,
|
|
40
|
+
) -> SuiteResult:
|
|
41
|
+
"""Compare a JSONL suite once the Stage 3 engine is implemented."""
|
|
42
|
+
del database, cases, constraints_file, config, validator
|
|
43
|
+
raise StageNotImplementedError(
|
|
44
|
+
"check_suite() is a Stage 3 capability; Stage 0 defines its contract only"
|
|
45
|
+
)
|
|
46
|
+
|
isitright/cli.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Stage 0 command-line contract."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Annotated
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
from isitright import __version__
|
|
12
|
+
from isitright.api import StageNotImplementedError, check, check_suite
|
|
13
|
+
from isitright.models import CheckConfig, FailOnPolicy
|
|
14
|
+
|
|
15
|
+
EXIT_INTERNAL_ERROR = 1
|
|
16
|
+
EXIT_INVALID_INPUT = 2
|
|
17
|
+
EXIT_POLICY_TRIGGERED = 3
|
|
18
|
+
|
|
19
|
+
app = typer.Typer(
|
|
20
|
+
name="isitright",
|
|
21
|
+
help="Generate evidence about text-to-SQL evaluation robustness.",
|
|
22
|
+
no_args_is_help=True,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def version_callback(value: bool) -> None:
|
|
27
|
+
if value:
|
|
28
|
+
typer.echo(f"isitright {__version__}")
|
|
29
|
+
raise typer.Exit()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@app.callback()
|
|
33
|
+
def main(
|
|
34
|
+
version: Annotated[
|
|
35
|
+
bool | None,
|
|
36
|
+
typer.Option("--version", callback=version_callback, is_eager=True, help="Show version."),
|
|
37
|
+
] = None,
|
|
38
|
+
) -> None:
|
|
39
|
+
"""Inspect the Stage 0 command surface."""
|
|
40
|
+
del version
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _not_implemented(action: Callable[[], object]) -> None:
|
|
44
|
+
try:
|
|
45
|
+
action()
|
|
46
|
+
except StageNotImplementedError as error:
|
|
47
|
+
typer.echo(str(error), err=True)
|
|
48
|
+
raise typer.Exit(EXIT_INTERNAL_ERROR) from error
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@app.command("check")
|
|
52
|
+
def check_command(
|
|
53
|
+
database: Annotated[Path, typer.Option(exists=True, dir_okay=False, readable=True)],
|
|
54
|
+
reference_sql: Annotated[Path, typer.Option("--reference", exists=True, dir_okay=False)],
|
|
55
|
+
candidate_sql: Annotated[Path, typer.Option("--candidate", exists=True, dir_okay=False)],
|
|
56
|
+
constraints: Annotated[
|
|
57
|
+
Path | None, typer.Option("--constraints", exists=True, dir_okay=False)
|
|
58
|
+
] = None,
|
|
59
|
+
seed: Annotated[int, typer.Option()] = 0,
|
|
60
|
+
max_attempts: Annotated[int, typer.Option(min=1)] = 100,
|
|
61
|
+
timeout: Annotated[float, typer.Option(min=0.001)] = 30.0,
|
|
62
|
+
output_dir: Annotated[Path, typer.Option()] = Path("artifacts/check"),
|
|
63
|
+
fail_on: Annotated[FailOnPolicy, typer.Option()] = FailOnPolicy.NONE,
|
|
64
|
+
) -> None:
|
|
65
|
+
"""Analyze one reference/candidate pair (reserved for Stage 1+)."""
|
|
66
|
+
del output_dir, fail_on
|
|
67
|
+
_not_implemented(
|
|
68
|
+
lambda: check(
|
|
69
|
+
database=database,
|
|
70
|
+
reference_sql=reference_sql.read_text(encoding="utf-8"),
|
|
71
|
+
candidate_sql=candidate_sql.read_text(encoding="utf-8"),
|
|
72
|
+
constraints_file=constraints,
|
|
73
|
+
config=CheckConfig(seed=seed, max_attempts=max_attempts, timeout_seconds=timeout),
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@app.command("suite")
|
|
79
|
+
def suite_command(
|
|
80
|
+
database: Annotated[Path, typer.Option(exists=True, dir_okay=False, readable=True)],
|
|
81
|
+
cases: Annotated[Path, typer.Option(exists=True, dir_okay=False, readable=True)],
|
|
82
|
+
constraints: Annotated[
|
|
83
|
+
Path | None, typer.Option("--constraints", exists=True, dir_okay=False)
|
|
84
|
+
] = None,
|
|
85
|
+
seed: Annotated[int, typer.Option()] = 0,
|
|
86
|
+
max_attempts: Annotated[int, typer.Option(min=1)] = 100,
|
|
87
|
+
timeout: Annotated[float, typer.Option(min=0.001)] = 30.0,
|
|
88
|
+
output_dir: Annotated[Path, typer.Option()] = Path("artifacts/suite"),
|
|
89
|
+
fail_on: Annotated[FailOnPolicy, typer.Option()] = FailOnPolicy.NONE,
|
|
90
|
+
) -> None:
|
|
91
|
+
"""Analyze a JSONL evaluation suite (reserved for Stage 3)."""
|
|
92
|
+
del output_dir, fail_on
|
|
93
|
+
_not_implemented(
|
|
94
|
+
lambda: check_suite(
|
|
95
|
+
database=database,
|
|
96
|
+
cases=cases,
|
|
97
|
+
constraints_file=constraints,
|
|
98
|
+
config=CheckConfig(seed=seed, max_attempts=max_attempts, timeout_seconds=timeout),
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
if __name__ == "__main__":
|
|
104
|
+
app()
|
|
105
|
+
|
isitright/models.py
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""Versioned Stage 0 data contracts.
|
|
2
|
+
|
|
3
|
+
These models describe evidence produced by later stages. They do not execute SQL.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
from enum import StrEnum
|
|
10
|
+
from typing import Any, Literal, Self
|
|
11
|
+
|
|
12
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
|
13
|
+
|
|
14
|
+
SCHEMA_VERSION: Literal["0.1"] = "0.1"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ContractModel(BaseModel):
|
|
18
|
+
"""Base class for closed, deterministic report contracts."""
|
|
19
|
+
|
|
20
|
+
model_config = ConfigDict(extra="forbid")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class EquivalenceStatus(StrEnum):
|
|
24
|
+
COUNTEREXAMPLE_FOUND = "COUNTEREXAMPLE_FOUND"
|
|
25
|
+
NOT_FOUND_WITHIN_BUDGET = "NOT_FOUND_WITHIN_BUDGET"
|
|
26
|
+
INCONCLUSIVE = "INCONCLUSIVE"
|
|
27
|
+
UNSUPPORTED = "UNSUPPORTED"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ValidityStatus(StrEnum):
|
|
31
|
+
DOMAIN_VALIDATED = "DOMAIN_VALIDATED"
|
|
32
|
+
SCHEMA_VALID_ONLY = "SCHEMA_VALID_ONLY"
|
|
33
|
+
INVALID = "INVALID"
|
|
34
|
+
UNKNOWN = "UNKNOWN"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AttributionStatus(StrEnum):
|
|
38
|
+
UNDETERMINED = "UNDETERMINED"
|
|
39
|
+
AGENT_DEFECT = "AGENT_DEFECT"
|
|
40
|
+
REFERENCE_DEFECT = "REFERENCE_DEFECT"
|
|
41
|
+
BOTH_DEFECTIVE = "BOTH_DEFECTIVE"
|
|
42
|
+
AMBIGUOUS_TASK = "AMBIGUOUS_TASK"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ColumnComparisonPolicy(StrEnum):
|
|
46
|
+
POSITIONAL = "positional"
|
|
47
|
+
BY_NAME = "by_name"
|
|
48
|
+
STRICT = "strict"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class OrderingPolicy(StrEnum):
|
|
52
|
+
AUTO = "auto"
|
|
53
|
+
ORDERED = "ordered"
|
|
54
|
+
UNORDERED = "unordered"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class FailOnPolicy(StrEnum):
|
|
58
|
+
NONE = "none"
|
|
59
|
+
DOMAIN_VALIDATED = "domain-validated"
|
|
60
|
+
ANY_COUNTEREXAMPLE = "any-counterexample"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class SQLiteStorageClass(StrEnum):
|
|
64
|
+
NULL = "null"
|
|
65
|
+
INTEGER = "integer"
|
|
66
|
+
REAL = "real"
|
|
67
|
+
TEXT = "text"
|
|
68
|
+
BLOB = "blob"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class CheckConfig(ContractModel):
|
|
72
|
+
"""Deterministic controls shared by single-case and suite analysis."""
|
|
73
|
+
|
|
74
|
+
seed: int = 0
|
|
75
|
+
max_attempts: int = Field(default=100, ge=1)
|
|
76
|
+
timeout_seconds: float = Field(default=30.0, gt=0)
|
|
77
|
+
column_policy: ColumnComparisonPolicy = ColumnComparisonPolicy.POSITIONAL
|
|
78
|
+
ordering_policy: OrderingPolicy = OrderingPolicy.AUTO
|
|
79
|
+
float_relative_tolerance: float = Field(default=1e-9, ge=0)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class SuiteCase(ContractModel):
|
|
83
|
+
"""One JSONL input record; unknown fields are retained as metadata."""
|
|
84
|
+
|
|
85
|
+
case_id: str = Field(min_length=1)
|
|
86
|
+
reference_sql: str = Field(min_length=1)
|
|
87
|
+
candidate_sql: str = Field(min_length=1)
|
|
88
|
+
question: str | None = None
|
|
89
|
+
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
90
|
+
|
|
91
|
+
@model_validator(mode="before")
|
|
92
|
+
@classmethod
|
|
93
|
+
def collect_extra_metadata(cls, value: Any) -> Any:
|
|
94
|
+
if not isinstance(value, dict):
|
|
95
|
+
return value
|
|
96
|
+
known = {"case_id", "reference_sql", "candidate_sql", "question", "metadata"}
|
|
97
|
+
result = dict(value)
|
|
98
|
+
metadata = dict(result.get("metadata") or {})
|
|
99
|
+
for key in list(result):
|
|
100
|
+
if key not in known:
|
|
101
|
+
metadata[key] = result.pop(key)
|
|
102
|
+
result["metadata"] = metadata
|
|
103
|
+
return result
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class ComparisonPolicy(ContractModel):
|
|
107
|
+
columns: ColumnComparisonPolicy = ColumnComparisonPolicy.POSITIONAL
|
|
108
|
+
ordering: OrderingPolicy = OrderingPolicy.AUTO
|
|
109
|
+
float_relative_tolerance: float = Field(default=1e-9, ge=0)
|
|
110
|
+
unordered_rows_are_multiset: Literal[True] = True
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class SQLiteValue(ContractModel):
|
|
114
|
+
"""A JSON-safe value tagged with its SQLite storage class.
|
|
115
|
+
|
|
116
|
+
Blob values are base64-encoded strings. Booleans are intentionally not accepted
|
|
117
|
+
as integers because reports preserve the observed SQLite storage class.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
storage_class: SQLiteStorageClass
|
|
121
|
+
value: int | float | str | None
|
|
122
|
+
|
|
123
|
+
@field_validator("value", mode="before")
|
|
124
|
+
@classmethod
|
|
125
|
+
def reject_boolean(cls, value: Any) -> Any:
|
|
126
|
+
if isinstance(value, bool):
|
|
127
|
+
raise ValueError("boolean is not a SQLite report value")
|
|
128
|
+
return value
|
|
129
|
+
|
|
130
|
+
@model_validator(mode="after")
|
|
131
|
+
def value_matches_storage_class(self) -> Self:
|
|
132
|
+
value = self.value
|
|
133
|
+
valid = {
|
|
134
|
+
SQLiteStorageClass.NULL: value is None,
|
|
135
|
+
SQLiteStorageClass.INTEGER: isinstance(value, int) and not isinstance(value, bool),
|
|
136
|
+
SQLiteStorageClass.REAL: isinstance(value, float),
|
|
137
|
+
SQLiteStorageClass.TEXT: isinstance(value, str),
|
|
138
|
+
SQLiteStorageClass.BLOB: isinstance(value, str),
|
|
139
|
+
}[self.storage_class]
|
|
140
|
+
if not valid:
|
|
141
|
+
raise ValueError(f"value does not match SQLite storage class {self.storage_class}")
|
|
142
|
+
return self
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class ResultColumn(ContractModel):
|
|
146
|
+
name: str
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class QueryResult(ContractModel):
|
|
150
|
+
columns: list[ResultColumn] = Field(default_factory=list)
|
|
151
|
+
rows: list[list[SQLiteValue]] = Field(default_factory=list)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class InputHashes(ContractModel):
|
|
155
|
+
database_sha256: str
|
|
156
|
+
reference_sql_sha256: str
|
|
157
|
+
candidate_sql_sha256: str
|
|
158
|
+
constraints_sha256: str | None = None
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class CaseInputs(ContractModel):
|
|
162
|
+
database: str
|
|
163
|
+
reference_sql: str
|
|
164
|
+
candidate_sql: str
|
|
165
|
+
question: str | None = None
|
|
166
|
+
constraints_file: str | None = None
|
|
167
|
+
hashes: InputHashes
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class OriginalStateResult(ContractModel):
|
|
171
|
+
outputs_match: bool | None
|
|
172
|
+
reference_result: QueryResult | None = None
|
|
173
|
+
candidate_result: QueryResult | None = None
|
|
174
|
+
note: str | None = None
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class SearchAccounting(ContractModel):
|
|
178
|
+
seed: int
|
|
179
|
+
attempts: int = Field(ge=0)
|
|
180
|
+
admitted_states: int = Field(ge=0)
|
|
181
|
+
rejected_states: dict[str, int] = Field(default_factory=dict)
|
|
182
|
+
used_generators: list[str] = Field(default_factory=list)
|
|
183
|
+
skipped_generators: dict[str, str] = Field(default_factory=dict)
|
|
184
|
+
coverage_notes: list[str] = Field(default_factory=list)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class CounterexampleEvidence(ContractModel):
|
|
188
|
+
generator: str
|
|
189
|
+
validity_status: ValidityStatus
|
|
190
|
+
reference_result: QueryResult
|
|
191
|
+
candidate_result: QueryResult
|
|
192
|
+
difference: str
|
|
193
|
+
reproducer_path: str
|
|
194
|
+
replay_command: str
|
|
195
|
+
original_injected_change: str
|
|
196
|
+
minimization_complete: bool
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class ErrorDetail(ContractModel):
|
|
200
|
+
code: str
|
|
201
|
+
message: str
|
|
202
|
+
phase: str
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class Timing(ContractModel):
|
|
206
|
+
elapsed_seconds: float = Field(ge=0)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class CaseResult(ContractModel):
|
|
210
|
+
schema_version: Literal["0.1"] = SCHEMA_VERSION
|
|
211
|
+
case_id: str | None = None
|
|
212
|
+
equivalence_status: EquivalenceStatus
|
|
213
|
+
validity_status: ValidityStatus
|
|
214
|
+
attribution_status: AttributionStatus = AttributionStatus.UNDETERMINED
|
|
215
|
+
headline: str
|
|
216
|
+
inputs: CaseInputs
|
|
217
|
+
comparison_policy: ComparisonPolicy = Field(default_factory=ComparisonPolicy)
|
|
218
|
+
original_state: OriginalStateResult
|
|
219
|
+
search: SearchAccounting
|
|
220
|
+
assumptions: list[str] = Field(default_factory=list)
|
|
221
|
+
checked_rules: list[str] = Field(default_factory=list)
|
|
222
|
+
evidence: CounterexampleEvidence | None = None
|
|
223
|
+
errors: list[ErrorDetail] = Field(default_factory=list)
|
|
224
|
+
timing: Timing
|
|
225
|
+
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
226
|
+
|
|
227
|
+
@field_validator("headline")
|
|
228
|
+
@classmethod
|
|
229
|
+
def headline_avoids_verdict_language(cls, value: str) -> str:
|
|
230
|
+
forbidden = re.compile(r"\b(pass|fail|correct|wrong)\b", re.IGNORECASE)
|
|
231
|
+
if forbidden.search(value):
|
|
232
|
+
raise ValueError("headline must describe evidence without verdict language")
|
|
233
|
+
return value
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class SuiteTotals(ContractModel):
|
|
237
|
+
cases: int = Field(ge=0)
|
|
238
|
+
by_equivalence_status: dict[EquivalenceStatus, int] = Field(default_factory=dict)
|
|
239
|
+
by_validity_status: dict[ValidityStatus, int] = Field(default_factory=dict)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class SuiteResult(ContractModel):
|
|
243
|
+
schema_version: Literal["0.1"] = SCHEMA_VERSION
|
|
244
|
+
cases: list[CaseResult]
|
|
245
|
+
totals: SuiteTotals
|
|
246
|
+
mutator_family_totals: dict[str, int] = Field(default_factory=dict)
|
|
247
|
+
timing: Timing
|
|
248
|
+
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
249
|
+
|
|
250
|
+
def ranked(self) -> list[CaseResult]:
|
|
251
|
+
"""Return a stable evidence-priority ordering without mutating the suite."""
|
|
252
|
+
|
|
253
|
+
def priority(case: CaseResult) -> int:
|
|
254
|
+
if case.equivalence_status is EquivalenceStatus.COUNTEREXAMPLE_FOUND:
|
|
255
|
+
if case.validity_status is ValidityStatus.DOMAIN_VALIDATED:
|
|
256
|
+
return 0
|
|
257
|
+
if case.validity_status is ValidityStatus.SCHEMA_VALID_ONLY:
|
|
258
|
+
return 1
|
|
259
|
+
if case.equivalence_status in {
|
|
260
|
+
EquivalenceStatus.INCONCLUSIVE,
|
|
261
|
+
EquivalenceStatus.UNSUPPORTED,
|
|
262
|
+
}:
|
|
263
|
+
return 2
|
|
264
|
+
if case.equivalence_status is EquivalenceStatus.NOT_FOUND_WITHIN_BUDGET:
|
|
265
|
+
return 3
|
|
266
|
+
return 2
|
|
267
|
+
|
|
268
|
+
return sorted(self.cases, key=priority)
|
|
269
|
+
|
isitright/suite.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""JSONL suite contract loading without executing cases."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from collections.abc import Iterable
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from pydantic import ValidationError
|
|
10
|
+
|
|
11
|
+
from isitright.models import SuiteCase
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SuiteInputError(ValueError):
|
|
15
|
+
"""A suite record is not valid under the v0.1 input contract."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def parse_suite_lines(lines: Iterable[str]) -> list[SuiteCase]:
|
|
19
|
+
"""Parse nonblank JSONL records and report their one-based line numbers."""
|
|
20
|
+
cases: list[SuiteCase] = []
|
|
21
|
+
seen_ids: set[str] = set()
|
|
22
|
+
for line_number, line in enumerate(lines, start=1):
|
|
23
|
+
if not line.strip():
|
|
24
|
+
continue
|
|
25
|
+
try:
|
|
26
|
+
raw = json.loads(line)
|
|
27
|
+
case = SuiteCase.model_validate(raw)
|
|
28
|
+
except (json.JSONDecodeError, ValidationError) as error:
|
|
29
|
+
raise SuiteInputError(f"invalid suite record on line {line_number}: {error}") from error
|
|
30
|
+
if case.case_id in seen_ids:
|
|
31
|
+
raise SuiteInputError(
|
|
32
|
+
f"duplicate case_id {case.case_id!r} on line {line_number}"
|
|
33
|
+
)
|
|
34
|
+
seen_ids.add(case.case_id)
|
|
35
|
+
cases.append(case)
|
|
36
|
+
return cases
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def load_suite_cases(path: str | Path) -> list[SuiteCase]:
|
|
40
|
+
"""Load a UTF-8 JSONL suite under the Stage 0 input contract."""
|
|
41
|
+
with Path(path).open(encoding="utf-8") as suite_file:
|
|
42
|
+
return parse_suite_lines(suite_file)
|
|
43
|
+
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: isitright
|
|
3
|
+
Version: 0.1.0.dev0
|
|
4
|
+
Summary: Evidence-first stress testing for text-to-SQL evaluations
|
|
5
|
+
Project-URL: Repository, https://github.com/isitright-ai/isitright
|
|
6
|
+
Author: isitright contributors
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2026 isitright contributors
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
31
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
32
|
+
Classifier: Programming Language :: Python :: 3
|
|
33
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
36
|
+
Requires-Python: >=3.11
|
|
37
|
+
Requires-Dist: pydantic<3,>=2.10
|
|
38
|
+
Requires-Dist: pyyaml<7,>=6.0.2
|
|
39
|
+
Requires-Dist: sqlglot<29,>=27
|
|
40
|
+
Requires-Dist: typer<1,>=0.15
|
|
41
|
+
Provides-Extra: dev
|
|
42
|
+
Requires-Dist: build<2,>=1.2.2; extra == 'dev'
|
|
43
|
+
Requires-Dist: mypy<2,>=1.14; extra == 'dev'
|
|
44
|
+
Requires-Dist: pytest-cov<8,>=6; extra == 'dev'
|
|
45
|
+
Requires-Dist: pytest<9,>=8.3; extra == 'dev'
|
|
46
|
+
Requires-Dist: ruff<1,>=0.9; extra == 'dev'
|
|
47
|
+
Requires-Dist: twine<7,>=6.1; extra == 'dev'
|
|
48
|
+
Description-Content-Type: text/markdown
|
|
49
|
+
|
|
50
|
+
# isitright
|
|
51
|
+
|
|
52
|
+
`isitright` is an evidence-first tool for stress-testing text-to-SQL evaluations.
|
|
53
|
+
Given reference and candidate SQL, the eventual v0.1 engine will search for a valid
|
|
54
|
+
database state on which their outputs differ and emit a minimized, replayable
|
|
55
|
+
counterexample. A disagreement is evidence, not automatic attribution of fault.
|
|
56
|
+
|
|
57
|
+
## Current status: Stage 0 review gate
|
|
58
|
+
|
|
59
|
+
This repository currently contains package scaffolding and specification contracts
|
|
60
|
+
only. SQL execution, database mutation, constraint validation, search, comparison,
|
|
61
|
+
and minimization are deliberately not implemented. The proposed fixtures under
|
|
62
|
+
`tests/fixtures/proposals` require owner approval before Stage 1 begins.
|
|
63
|
+
|
|
64
|
+
## Development
|
|
65
|
+
|
|
66
|
+
Python 3.11 or newer is required. With `uv` installed:
|
|
67
|
+
|
|
68
|
+
```console
|
|
69
|
+
uv sync --extra dev
|
|
70
|
+
uv run ruff check .
|
|
71
|
+
uv run mypy src
|
|
72
|
+
uv run pytest
|
|
73
|
+
uv build
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The package exposes the future command surface now:
|
|
77
|
+
|
|
78
|
+
```console
|
|
79
|
+
isitright --version
|
|
80
|
+
isitright check --help
|
|
81
|
+
isitright suite --help
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Calling `check` or `suite` during Stage 0 exits with the internal/not-implemented
|
|
85
|
+
code because no analysis engine exists yet.
|
|
86
|
+
|
|
87
|
+
The Stage 0 preview is versioned `0.1.0.dev0`. It is intentionally a development
|
|
88
|
+
release and must not be presented as an implemented analysis engine.
|
|
89
|
+
|
|
90
|
+
## Contract highlights
|
|
91
|
+
|
|
92
|
+
- Result statuses keep observed equivalence, state validity, and attribution separate.
|
|
93
|
+
- Unordered query results will be compared as multisets, preserving duplicates.
|
|
94
|
+
- Automatic ordering will honor a top-level `ORDER BY`; otherwise results are unordered.
|
|
95
|
+
- SQLite values are represented with explicit storage classes.
|
|
96
|
+
- Reports use schema version `0.1`; package versioning remains pre-release until an
|
|
97
|
+
engine is implemented.
|
|
98
|
+
|
|
99
|
+
See [the contract reference](docs/contracts.md) and
|
|
100
|
+
[the fixture proposal](tests/fixtures/proposals/README.md).
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
MIT
|
|
105
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
isitright/__about__.py,sha256=iNIRHlrBKAHdi2BELRNh4_PWDF2Qye6QR7JuMSluUD8,91
|
|
2
|
+
isitright/__init__.py,sha256=hywgNdYYhi2kIEQ66i-FcKFA-SezQyemuNTnNenJYM8,708
|
|
3
|
+
isitright/api.py,sha256=trhgccsxDSrXRj4x1GqMGD487F7XM0Y6nqeUWVzIpuM,1463
|
|
4
|
+
isitright/cli.py,sha256=_wPoycaXVDQjqKAAt0oItTABAgTiWEz5IC-NSRYoTPE,3484
|
|
5
|
+
isitright/models.py,sha256=CBrlkg_MobO9oycbl-ivQImq1ryh4DYxBxwIoGKeFOU,8441
|
|
6
|
+
isitright/suite.py,sha256=yVvhUw_qz6VfkzKVM9gbOJTzUxF74nTDskgO_h0dGNA,1408
|
|
7
|
+
isitright-0.1.0.dev0.dist-info/METADATA,sha256=nALCp31rVTM-_r0YHaUerRDGgntRpGp785jFx7wcuWs,4148
|
|
8
|
+
isitright-0.1.0.dev0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
9
|
+
isitright-0.1.0.dev0.dist-info/entry_points.txt,sha256=hwNIxaD5JYnFWLfxVHEyxj6M5bHJX48-82ofsREUsEA,48
|
|
10
|
+
isitright-0.1.0.dev0.dist-info/licenses/LICENSE,sha256=iHif5Dqqe1AlWATTIzsiRxsIOf1Z0Fe08ZVb1PqQmow,1080
|
|
11
|
+
isitright-0.1.0.dev0.dist-info/RECORD,,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 isitright contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|