Repository navigation
chore(lint): sync the contract schema from the backend - #16
Conversation
The embedded lint schema was branched from soda-core's copy in May and has drifted from the schema the backend validates contracts against on ingestion, so `contract lint` passed files the backend then rejected. It is now the backend's canonical schema, which adds four dataset-level keys (check_attributes, diagnostics, failed_rows, reconciliation) and seven check types (aggregate_diff, duplicate_diff, freshness_diff, row_count_diff, group_by, hierarchy, reconciliation). No check type is lost; soda_runner and soda_agent are both upstream now, so the backwards-compatible alias from PLATL-650 survives the swap. One deliberate deviation: the root keeps additionalProperties: false where the backend has true. A linter exists to catch typo'd keys, and TestLintFile_InvalidProperty pins it — that test fails against a schema with the backend's permissive root. Verified on 15 real contracts (the backend's parser fixtures plus every complete contract example in the docs): verdicts are identical before and after, and the two that fail are incomplete snippets missing the required `columns`, which the old schema rejected too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ae3ffa2 to
1c08cb9
Compare
The upstream schema carries a beta.docs.soda.io URL for check attributes; that host does not resolve. The published page is docs.soda.io/soda-v4/manage-issues/check-and-dataset-attributes, which matches how every other link in this schema is formed. Also fixed upstream, so the next sync keeps it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
m1n0
left a comment
There was a problem hiding this comment.
Checked the swap: the embedded file is byte-equal to origin/master's soda-server/src/main/resources/contract/schema.json plus your one root edit, so the provenance claim holds. Built the binary and ran the contract from SCS-1441, where a customer put attributes on a metric check. Valid now, 6 errors before.
Two things before this lands.
The root additionalProperties: false deviation reintroduces the bug this PR fixes. A contract with a top-level kind: gets rejected by lint but is accepted by both Cloud and the engine:
kind: contract
dataset: ds/db/schema/t
columns:
- name: idlint says $: additional properties 'kind' not allowed, soda contract test says valid, and the canonical schema accepts it. kind is the collection dispatch key in soda-core at check_collections/base.py:237 and session.py:163, with values contract, data-standard, metric-monitoring. I'd keep the deviation, I agree a linter should catch typo'd keys, but add kind to the properties list so it's a two-line delta instead of one.
Second, nothing runs go test in CI. .github/workflows/ only has release.yml, tag-triggered, goreleaser. So TestLintFile_AcceptsDatasetLevelContractOptions never runs on a PR and the drift this fixes can come back without anyone noticing. A small test workflow here or straight after would close it.
Two smaller ones. The body says seven new check types, it's eleven. reconciliation is a top-level key rather than a check type, and fuzzy_duplicate, llm, metric_diff, reference_diff, rows_diff are missing from the list. And if the 15-contract corpus gives identical verdicts before and after, none of those fixtures exercised a newly-allowed key, so it's good regression evidence but doesn't show the fix works. The SCS-1441 contract would make a better fixture.
Nice catch on the dead ~/.soda/cache path, that had me looking too.
The sync kept additionalProperties: false at the root, on the reasoning that a linter should catch typo'd keys. The backend sets it to true so a contract written for a newer server does not fail against an older one, and the lint schema should not be stricter than the thing it predicts. The embedded schema is now byte-identical to the backend's, so the next sync is a plain copy with no edit to remember. TestLintFile_InvalidProperty asserted the opposite; it now covers a place the schema is still strict (an unknown key inside a check body), and TestLintFile_AllowsUnknownRootKeysLikeTheBackend documents the root behaviour. TestLintFiles_Mixed's invalid file relied on a missing required 'columns' rather than the unknown key it also carried, so it now says that plainly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This repo holds a copy of the backend's contract schema; what that schema means is the backend's business, and re-deriving it here buys nothing while making every sync a test-editing exercise. That cost was real: a legitimate upstream change failed CI here until someone came and loosened the assertions, which is part of why the copy sat four months stale. Removed, all of them assertions about schema content rather than about this repo's code: - AcceptsDatasetLevelContractOptions, AllowsUnknownRootKeysLikeTheBackend and RejectsUnknownKeyInsideACheck, added earlier in this PR - MissingColumnName, AcceptsSodaRunner, AcceptsSodaAgentLegacyAlias and RejectsBothSodaRunnerAndSodaAgent, which predate it (92ed81e, 284d7f3) What stays tests the lint command itself: the embedded schema compiles and a valid contract passes, malformed YAML and an empty file are reported at the document root, LintFiles returns one result per input in order, and segmentsToPath formats JSON paths. In place of the removed invalid-contract cases, ReportsErrorPathsForAnInvalidContract covers the jsonschema-to-LintError translation — that every error carries a message and a $-rooted path — without asserting which key is wrong. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
m1n0
left a comment
There was a problem hiding this comment.
Re-checked at bceaaba. kind is fixed, the schema is byte-identical to origin/master apart from the docs link, and my corpus is green: the SCS-1441 contract, attributes on metric and failed_rows at both levels, column_expression, remediation, group_by, check_attributes, the DET-107 additional: block. filter on a schema check is still correctly rejected. Good call fixing the link upstream in sodadata/soda#13496 too, that's the right order, though it's still open so a sync before it merges would revert your line.
The test deletions in bceaaba go too far.
I copied the old pre-sync schema over the new one and ran the current suite against it. It passes, all 6 tests. So nothing left in the suite can tell whether this PR's schema change happened at all. Revert the sync and the tests stay green.
TestLintFile_AcceptsSodaRunner, TestLintFile_AcceptsSodaAgentLegacyAlias and TestLintFile_RejectsBothSodaRunnerAndSodaAgent aren't assertions about the backend's schema. They're the soda_runner/soda_agent alias promise from PLATL-650, which is soda-cli's own commitment, and those tests are what stops a later sync quietly dropping it. I'd put all three back.
TestLintFile_AcceptsDatasetLevelContractOptions was the one test proving this PR does something. The SCS-1441 contract would be a good second fixture, it's four lines and it's the case that generated the ticket.
I verified all five deleted behaviours still hold at head, so this is lost coverage rather than a break. TestLintFile_ReportsErrorPathsForAnInvalidContract doesn't substitute: asserting that paths start with $ and messages are non-empty would pass even if the schema rejected every contract on earth.
Body's stale in the Change section. It still says the one deviation is the root keeping additionalProperties: false and cites TestLintFile_InvalidProperty as pinning it. b77622e undid both.
And still nothing runs go test in CI, which matters more now the suite is 6 tests instead of 11.
|
Thanks for the review @m1n0. I'll enable CI testing in a separate PR. Apart from that: I dropped contract validation tests since this is a copy of the schema from BE. Validation should live in one central place, not here. |
The offline CLI suite fails on main: TestContractLint/invalid_contract expects `bogus_field` at the contract root to exit 2, but lint accepts it and exits 0. Two PRs merged a minute apart caused this. #16 synced the contract JSON schema from the backend, which flips the root from "additionalProperties": false to true — unknown top-level keys are now allowed by design — and dropped its own TestLintFile_InvalidProperty accordingly. #17 branched before that landed and added a CLI test asserting the old behavior. Both were green against their own base; the merge of the two is not. The schema is generated backend-side, so pinning the root closed again would be reverted by the next sync and would reject contracts the backend accepts. Point the subtest at a check object instead: those are still closed, so it keeps CLI-level coverage of the "additional properties not allowed" path rather than deleting it. Refs: #16, #17 Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Problem
go/internal/lint/schema/soda_data_contract_json_schema_1_0_0.jsonwas branched from soda-core's copy in May and has drifted from the schema the backend validates every contract against on ingestion.sodacli contract linttherefore passed contracts that the backend then rejected — the opposite of what a linter is for.Change
The embedded schema is now the backend's canonical contract schema, byte-identical to it apart from one docs-link fix (below). What that adds over the old copy:
check_attributes,diagnostics,failed_rows,reconciliationaggregate_diff,duplicate_diff,freshness_diff,row_count_diff,group_by,hierarchy,reconciliationNo check type is lost. The 167 schema paths that disappear are all
$defsreorganization (generic_check_properties,format,numeric_range, and thethresholdif/thenconditionals), which the backend expresses differently.soda_runnerandsoda_agentare both in the upstream schema now, so the backwards-compatible alias from PLATL-650 survives the swap.Byte-identical is the point: the next sync is
cpwith nothing to remember. An earlier revision of this PR keptadditionalProperties: falseat the root, reasoning that a linter should catch typo'd keys; that deviation is gone. The backend sets it totrueso a contract written for a newer server does not fail against an older one, and the lint schema should not be stricter than the thing it predicts.Unknown keys deeper in the document are still rejected — the schema carries
additionalProperties: falsein 67 places, covering check bodies, thresholds and reconciliation blocks. Columns are permissive, because their definition is anallOfand afalseinside one branch would reject keys defined by sibling branches.Worth knowing what that leaves uncovered: soda-core's parser reads only the root keys it knows and silently ignores the rest (no warning at any log level — the sole exception is an unknown threshold key, which warns), and the backend accepts them by design. So a typo'd root key is reported by nothing. That is the backend's deliberate forward-compatibility trade, and this PR follows it rather than diverging from it.
The lint tests no longer assert what the schema means
This repo holds a copy of the backend's schema; what that schema means is the backend's business. Re-deriving it here bought nothing and made every sync a test-editing exercise — a real cost, since a legitimate upstream change fails CI here until someone comes and loosens the assertions. That is part of why the copy sat four months stale.
Seven tests are removed on those grounds. Three were added earlier in this PR (
AcceptsDatasetLevelContractOptions,AllowsUnknownRootKeysLikeTheBackend,RejectsUnknownKeyInsideACheck); four predate it (MissingColumnName,AcceptsSodaRunner,AcceptsSodaAgentLegacyAlias,RejectsBothSodaRunnerAndSodaAgent, from 92ed81e and 284d7f3). Thesoda_runnerpair shows the problem plainly: PLATL-650 added the key to this copy and a test for it, the backend has since added the key upstream, so those tests now pass for a reason unrelated to why they were written and would break if the backend restructured thatoneOf.Six tests remain, all covering this repo's code rather than the schema's content:
ValidContractYAMLParseErrorEmptyFileLintFiles_MixedReportsErrorPathsForAnInvalidContractLintErrortranslation: every error carries a message and a$-rooted pathSegmentsToPathReportsErrorPathsForAnInvalidContractreplaces what the removed invalid-contract cases incidentally covered. It asserts path and message shape, not which key is wrong, and leans only onrequired: ["dataset", "columns"]— the one thing identical across every copy and schema version compared here.One content fix on top of the sync
The upstream schema's check-attributes description pointed at
https://beta.docs.soda.io/…, a host that does not resolve at all. It now points at the published page,https://docs.soda.io/soda-v4/manage-issues/check-and-dataset-attributes#attributes-in-checks(verified 200, and the anchor matches the## Attributes in checksheading), which is the form every other link in the schema uses.Fixed upstream too — sodadata/soda#13496 — so the next sync keeps the correction instead of reverting it. Until that merges, this file differs from
master's copy by exactly that one line.Testing
go build ./...,go vet ./...andgo test ./...clean; the six remaining lint tests pass.Validated on a 15-contract corpus — the backend's own contract-parser fixtures plus every complete contract example in the docs — by building the binary before and after and diffing the verdicts: identical, re-confirmed after both the root-permissiveness change and the test removal. The two contracts that fail are incomplete documentation snippets missing the required
columns, which the old schema rejected too.