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 ADDED
@@ -0,0 +1,4 @@
1
+ """Package metadata kept independent from runtime imports."""
2
+
3
+ __version__ = "0.1.0.dev0"
4
+
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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ isitright = isitright.cli:app
@@ -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
+