Skip to content

Commit cdd7680

Browse files
Merge pull request #12 from OpenStatSpec/agent/catalog-dolt-lifecycle-minimal
Recover minimal catalog and Dolt lifecycle integration
2 parents 2fb7e9e + 68cf61c commit cdd7680

42 files changed

Lines changed: 6633 additions & 1096 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/ci.yml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
uses: actions/checkout@v7
2121
with: &specification-checkout
2222
repository: OpenStatSpec/specification
23-
ref: e49252c00890aed76dcaabc5d1ab5121b45929db
23+
ref: f2fdf687d8cb32b944ca55a3e9e7215ffc603019
2424
path: openstatspec-specification
2525
- name: Checkout required SPSS engine
2626
uses: actions/checkout@v7
@@ -143,7 +143,7 @@ jobs:
143143
steps: *sql-test-steps
144144

145145
dolt-integration:
146-
name: Dolt ${{ matrix.dolt }} integration
146+
name: Dolt ${{ matrix.dolt }} read-only / fail-closed integration
147147
runs-on: ubuntu-latest
148148
strategy:
149149
fail-fast: false
@@ -217,7 +217,7 @@ jobs:
217217
connection.close()
218218
break
219219
PY
220-
- run: python -m pytest -m services
220+
- run: python -m pytest -m "services and not candidate_evidence"
221221
- name: Show Dolt logs on failure and stop server
222222
if: always()
223223
run: |

‎.github/workflows/release.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ jobs:
4747
uses: actions/checkout@v7
4848
with:
4949
repository: OpenStatSpec/specification
50-
ref: 79339ec3d8f8aa81789b7e85f6b8afa6f1374e50
50+
ref: f2fdf687d8cb32b944ca55a3e9e7215ffc603019
5151
path: openstatspec-specification
5252
- name: Checkout required SPSS engine
5353
uses: actions/checkout@v7

‎CHANGELOG.md‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,15 @@ Planned adapter release: `0.5.0`, after lifecycle integration and final specific
2828
format, and measurement level in both normative and compatibility catalogs.
2929
- Dolt still requires an exact branch, exact HEAD, and clean working set;
3030
successful apply leaves an inspectable diff and never calls `DOLT_COMMIT`.
31+
- Dolt declaration validation is implemented by the Python adapter. The
32+
specification remains a language-neutral source of JSON contracts and
33+
fixtures and is not installed as a Python distribution.
3134

3235
### Specification basis
3336

3437
- Release validation is pinned to the untagged OpenStatSpec specification
3538
release candidate at immutable commit
36-
`e49252c00890aed76dcaabc5d1ab5121b45929db`. Its
39+
`f2fdf687d8cb32b944ca55a3e9e7215ffc603019`. Its
3740
`specification_release` remains null until that commit receives a stable tag.
3841

3942
## 0.4.0 — 2026-07-31
@@ -143,6 +146,17 @@ SPSS profile.
143146
- Export of supported dataset semantics to SAV and ZSAV.
144147
- SQLite, PostgreSQL, MySQL, and MariaDB profiles, including service-backed
145148
PostgreSQL 17/18, MySQL 8.4/9.7, and MariaDB 11.4/11.8/12.3 CI coverage.
149+
- Read-only positive Dolt identity and working-set inspection. Operational
150+
Dolt writes use adapter-owned declaration validation and remain fail-closed
151+
because the adapter ships no concrete declaration claim. The specification
152+
remains language-neutral and is consumed only as JSON contracts and fixtures.
153+
- Added explicit packaged/directory conformance-source injection and exact
154+
single-match binding across active Dolt product version, adapter
155+
implementation and version, and specification commit before mutation.
156+
- Explicit catalog initialization/migration through `initialize_catalog` and
157+
`openstatspec init`; data operations never auto-create catalog relations.
158+
- Read-only Dolt working-set evidence through `dolt_state_snapshot` and
159+
`openstatspec dolt-state`; core performs no Dolt version-control mutations.
146160
- Preflight checks for target profile limits, atomic imports, validation, a
147161
command-line interface, and machine-readable capability and loss reports.
148162

@@ -155,3 +169,11 @@ SPSS profile.
155169
- Multiple-response sets, variable alignment, variable sets, and custom
156170
attributes have explicit capability diagnostics; see the SAV profile.
157171
- Encrypted SPSS files are not supported.
172+
- Dolt adapter-envelope values are not claims about Dolt server limits. Failure
173+
recovery is compensating and reports deterministic residual inventory; a
174+
verified catalog receives a best-effort failed-operation audit when cleanup
175+
itself fails.
176+
- Export publication uses a durable, audit-recorded prior-file backup through
177+
SQL finalization. Post-success backup retention is reported separately and
178+
does not rewrite a successful export as failed; pre-success failures restore
179+
the prior destination and close the running operation as failed.

‎docs/release-readiness.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ capability claim, or implied support.
114114
5. Confirm `openstatspec capabilities` reflects the intended support boundary.
115115
6. Confirm CI, release fixtures, and capabilities use the untagged OpenStatSpec
116116
specification release candidate at exact commit
117-
`e49252c00890aed76dcaabc5d1ab5121b45929db`, publish
117+
`f2fdf687d8cb32b944ca55a3e9e7215ffc603019`, publish
118118
`specification_status=release_candidate`, and leave
119119
`specification_release` null. If that exact commit receives a stable tag
120120
before this package is tagged, update the identity and re-run every gate.

‎pyproject.toml‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ openstatspec = "openstatspec.cli:main"
2222
[tool.pytest.ini_options]
2323
testpaths = ["tests"]
2424
addopts = "-q"
25-
markers = ["services: requires a configured PostgreSQL, MySQL, MariaDB, or Dolt service"]
25+
markers = [
26+
"services: requires a configured PostgreSQL, MySQL, MariaDB, or Dolt service",
27+
"candidate_evidence: non-claiming manual probe excluded from release gates",
28+
]
2629

2730
[tool.hatch.build.targets.wheel]
2831
packages = ["src/openstatspec"]

‎src/openstatspec/__init__.py‎

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@
22

33
from .api import (
44
apply_spss_in_place, apply_transformation_plan_in_place,
5-
capabilities, capability_matrix, derive_sql_dataset,
6-
execute_sql_transformation,
7-
export_sav, get_dataset, import_sav, inspect,
8-
install_in_place_transformation_schema, list_datasets,
5+
capabilities, capability_matrix, derive_sql_dataset, dolt_state_snapshot, execute_sql_transformation,
6+
export_sav, get_dataset, import_sav, initialize_catalog, inspect, list_datasets,
7+
install_in_place_transformation_schema,
98
register_sql_transformation, reconcile_derived_removals,
109
reconcile_sql_transformation_runs,
1110
remove_derived_physical_relation, retire_derived, validate, validate_derived,
1211
)
1312
from .core import CapabilityDeclaration, LossReport, UnsupportedOperationError
14-
from .sql.workflow import TransformationError
1513
from .frontends.spss import SpssFrontendCompilation, compile_spss_syntax
14+
from .sql import DoltConformanceSource
15+
from .sql.workflow import TransformationError
1616
from .transform import (
1717
AssignOperation, BooleanExpression, ComparisonExpression,
1818
ConditionalAssignOperation, ExecuteOperation, Operand, PredicateExpression,
@@ -27,19 +27,19 @@
2727
"AssignOperation", "BooleanExpression", "ComparisonExpression",
2828
"ConditionalAssignOperation", "ExecuteOperation", "Operand",
2929
"PredicateExpression",
30-
"CapabilityDeclaration", "LossReport", "SpssFrontendCompilation",
31-
"TransformationError", "TransformationFrontendError",
30+
"CapabilityDeclaration", "DoltConformanceSource", "LossReport",
31+
"SpssFrontendCompilation", "TransformationError",
32+
"TransformationFrontendError",
3233
"RecodeMatch", "RecodeOperation", "RecodeResult", "RecodeRule",
3334
"ReplaceValueLabelsOperation", "SetFormatOperation",
3435
"SetMeasurementLevelOperation", "SetVariableLabelOperation",
3536
"TransformationPlan", "TypedValue", "ValueLabel",
3637
"VariableDefinition", "VariableSchema", "transformation_plan_from_dict",
37-
"UnsupportedOperationError", "capabilities", "capability_matrix",
3838
"apply_spss_in_place", "apply_transformation_plan_in_place",
39-
"compile_spss_syntax", "derive_sql_dataset",
40-
"execute_sql_transformation", "export_sav",
41-
"get_dataset", "import_sav", "inspect",
42-
"install_in_place_transformation_schema", "list_datasets",
39+
"compile_spss_syntax", "install_in_place_transformation_schema",
40+
"UnsupportedOperationError", "capabilities", "capability_matrix",
41+
"derive_sql_dataset", "dolt_state_snapshot", "execute_sql_transformation", "export_sav", "get_dataset",
42+
"import_sav", "initialize_catalog", "inspect", "list_datasets",
4343
"register_sql_transformation", "reconcile_derived_removals",
4444
"reconcile_sql_transformation_runs",
4545
"remove_derived_physical_relation", "retire_derived", "validate",

‎src/openstatspec/api.py‎

Lines changed: 102 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@
77
from .core.results import result
88
from .spss import export_dataset, import_dataset, inspect_source
99
from .spss.sav import engine_identity
10-
from .sql import declared_profiles, validate_dataset
10+
from .sql import (
11+
DoltConformanceSource,
12+
declared_profiles,
13+
dolt_state_snapshot as _dolt_state_snapshot,
14+
initialize_catalog as _initialize_catalog,
15+
validate_dataset,
16+
)
1117
from .sql.catalog_api import catalog_dataset as _catalog_dataset, catalog_datasets as _catalog_datasets
1218
from .sql.workflow import (
1319
derive_dataset as _derive_dataset,
@@ -34,7 +40,11 @@
3440

3541

3642

37-
def capability_matrix(database_url: str | None = None) -> Mapping[str, Any]:
43+
def capability_matrix(
44+
database_url: str | None = None,
45+
*,
46+
dolt_conformance_source: DoltConformanceSource | None = None,
47+
) -> Mapping[str, Any]:
3848
"""Return the pyspssio-backed SAV/ZSAV fidelity boundary.
3949
4050
supported means that the adapter has a tested faithful path.
@@ -87,9 +97,18 @@ def capability_matrix(database_url: str | None = None) -> Mapping[str, Any]:
8797
"maximum_source_file_bytes": None,
8898
"limit_basis": "runtime memory and active SQL connection limits",
8999
},
90-
"active_connection": active_connection(database_url) if database_url else None,
100+
"active_connection": (
101+
active_connection(
102+
database_url,
103+
dolt_conformance_source=dolt_conformance_source,
104+
)
105+
if database_url else None
106+
),
91107
"catalog_binding": catalog_binding(database_url) if database_url else None,
92-
"sql_profiles": declared_profiles(database_url),
108+
"sql_profiles": declared_profiles(
109+
database_url,
110+
dolt_conformance_source=dolt_conformance_source,
111+
),
93112
"optional_profiles": {
94113
"sql_transformation_workflow": transformation_capabilities(database_url),
95114
"spss_in_place_transformation": (
@@ -99,26 +118,84 @@ def capability_matrix(database_url: str | None = None) -> Mapping[str, Any]:
99118
}
100119
return declaration
101120

102-
def capabilities(database_url: str | None = None) -> Mapping[str, Any]:
103-
return capability_matrix(database_url)
121+
def capabilities(
122+
database_url: str | None = None,
123+
*,
124+
dolt_conformance_source: DoltConformanceSource | None = None,
125+
) -> Mapping[str, Any]:
126+
return capability_matrix(
127+
database_url, dolt_conformance_source=dolt_conformance_source,
128+
)
104129

105130

106131
def inspect(source: str | Path, /, **options: Any) -> Mapping[str, Any]:
107132
return result(inspect_source(source, **options))
108133

109134

110-
def import_sav(source: str | Path, /, *, database_url: Any, dataset_id: str, **options: Any) -> Mapping[str, Any]:
135+
def dolt_state_snapshot(
136+
*,
137+
database_url: Any,
138+
dolt_conformance_source: DoltConformanceSource | None = None,
139+
) -> Mapping[str, Any]:
140+
"""Return read-only Dolt branch, HEAD, status, and diff-summary evidence."""
141+
return result(_dolt_state_snapshot(
142+
database_url=database_url,
143+
dolt_conformance_source=dolt_conformance_source,
144+
))
145+
146+
147+
def initialize_catalog(
148+
*,
149+
database_url: Any,
150+
dolt_conformance_source: DoltConformanceSource | None = None,
151+
) -> Mapping[str, Any]:
152+
"""Install or explicitly migrate a dedicated OpenStatSpec catalog."""
153+
return result(_initialize_catalog(
154+
database_url=database_url,
155+
dolt_conformance_source=dolt_conformance_source,
156+
))
157+
158+
159+
def import_sav(
160+
source: str | Path, /, *, database_url: Any, dataset_id: str,
161+
dolt_conformance_source: DoltConformanceSource | None = None,
162+
**options: Any,
163+
) -> Mapping[str, Any]:
111164
"""Import one source file into one dedicated wide SQL table."""
112-
return result(import_dataset(source, database_url=database_url, dataset_id=dataset_id, **options))
165+
return result(import_dataset(
166+
source, database_url=database_url, dataset_id=dataset_id,
167+
dolt_conformance_source=dolt_conformance_source,
168+
**options,
169+
))
113170

114171

115-
def export_sav(*, database_url: Any, dataset_id: str, destination: str | Path, **options: Any) -> Mapping[str, Any]:
172+
def export_sav(
173+
*, database_url: Any, dataset_id: str, destination: str | Path,
174+
dolt_conformance_source: DoltConformanceSource | None = None,
175+
**options: Any,
176+
) -> Mapping[str, Any]:
116177
"""Export one database-resident conforming dataset to SAV/ZSAV."""
117-
return result(export_dataset(database_url=database_url, dataset_id=dataset_id, destination=destination, **options))
178+
return result(export_dataset(
179+
database_url=database_url, dataset_id=dataset_id,
180+
destination=destination,
181+
dolt_conformance_source=dolt_conformance_source,
182+
**options,
183+
))
118184

119185

120-
def validate(*, database_url: Any, dataset_id: str, **options: Any) -> Mapping[str, Any]:
121-
return result(validate_dataset(database_url=database_url, dataset_id=dataset_id, **options))
186+
def validate(
187+
*,
188+
database_url: Any,
189+
dataset_id: str,
190+
dolt_conformance_source: DoltConformanceSource | None = None,
191+
**options: Any,
192+
) -> Mapping[str, Any]:
193+
return result(validate_dataset(
194+
database_url=database_url,
195+
dataset_id=dataset_id,
196+
dolt_conformance_source=dolt_conformance_source,
197+
**options,
198+
))
122199

123200

124201
def list_datasets(*, database_url: Any, kind: str | None = None) -> Mapping[str, Any]:
@@ -145,6 +222,7 @@ def apply_spss_in_place(
145222
*, database_url: Any, dataset_id: str, source_text: str,
146223
actor: str, expected_branch: str | None = None,
147224
expected_head: str | None = None,
225+
dolt_conformance_source: DoltConformanceSource | None = None,
148226
) -> Mapping[str, Any]:
149227
"""Apply bounded sequential SPSS syntax to one SQL dataset/table.
150228
@@ -157,6 +235,7 @@ def apply_spss_in_place(
157235
actor=actor,
158236
expected_branch=expected_branch,
159237
expected_head=expected_head,
238+
dolt_conformance_source=dolt_conformance_source,
160239
))
161240

162241

@@ -165,6 +244,7 @@ def apply_transformation_plan_in_place(
165244
plan: TransformationPlan | Mapping[str, Any],
166245
actor: str, expected_branch: str | None = None,
167246
expected_head: str | None = None,
247+
dolt_conformance_source: DoltConformanceSource | None = None,
168248
) -> Mapping[str, Any]:
169249
"""Apply a canonical plan to the same logical dataset and physical table."""
170250
return result(_apply_transformation_plan_in_place(
@@ -174,12 +254,20 @@ def apply_transformation_plan_in_place(
174254
actor=actor,
175255
expected_branch=expected_branch,
176256
expected_head=expected_head,
257+
dolt_conformance_source=dolt_conformance_source,
177258
))
178259

179260

180-
def install_in_place_transformation_schema(*, database_url: Any) -> None:
261+
def install_in_place_transformation_schema(
262+
*,
263+
database_url: Any,
264+
dolt_conformance_source: DoltConformanceSource | None = None,
265+
) -> None:
181266
"""Install the compact apply-audit relation before the first apply."""
182-
_install_in_place_schema(database_url=str(database_url))
267+
_install_in_place_schema(
268+
database_url=str(database_url),
269+
dolt_conformance_source=dolt_conformance_source,
270+
)
183271

184272

185273
def validate_derived(*, database_url: Any, derived_dataset_id: str) -> Mapping[str, Any]:

‎src/openstatspec/cli.py‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66

77
from .api import (
88
apply_spss_in_place, apply_transformation_plan_in_place,
9-
capability_matrix, derive_sql_dataset,
9+
capability_matrix, derive_sql_dataset, dolt_state_snapshot,
1010
execute_sql_transformation,
11-
export_sav, get_dataset, import_sav, inspect, list_datasets,
11+
export_sav, get_dataset, import_sav, initialize_catalog, inspect, list_datasets,
1212
install_in_place_transformation_schema, register_sql_transformation,
1313
validate, validate_derived,
1414
)
@@ -29,6 +29,10 @@ def main(argv: Sequence[str] | None = None) -> int:
2929
commands = parser.add_subparsers(dest="command", required=True)
3030
capability_parser = commands.add_parser("capabilities", help="show supported and lossy feature matrix")
3131
capability_parser.add_argument("--database-url", help="include active connection limits")
32+
dolt_state = commands.add_parser("dolt-state", help="show read-only Dolt branch, HEAD, status, and diff evidence")
33+
dolt_state.add_argument("--database-url", required=True)
34+
initializer = commands.add_parser("init", help="initialize or migrate a dedicated catalog")
35+
initializer.add_argument("--database-url", required=True)
3236
importer = commands.add_parser("import", help="import one SAV/ZSAV file")
3337
importer.add_argument("source")
3438
importer.add_argument("--database-url", required=True)
@@ -114,6 +118,10 @@ def main(argv: Sequence[str] | None = None) -> int:
114118
args = parser.parse_args(argv)
115119
if args.command == "capabilities":
116120
output = capability_matrix(database_url=args.database_url)
121+
elif args.command == "dolt-state":
122+
output = dolt_state_snapshot(database_url=args.database_url)
123+
elif args.command == "init":
124+
output = initialize_catalog(database_url=args.database_url)
117125
elif args.command == "import":
118126
output = import_sav(args.source, database_url=args.database_url, dataset_id=args.dataset_id)
119127
elif args.command == "export":

0 commit comments

Comments
 (0)