Repository navigation
feat(runtime): Python validator runner and enhanced validation enforcement - #402
Merged
Merged
Conversation
…orpus runValidators now runs fixtures/validation-conformance and passes it. New run-time rules: validator.numeric and validator.array bounds, the strict field.uri / field.inet format contract (@lenient opts out), and presence of an assigned primary key. Two existing rules are corrected to match the generated Zod schema: @maxlength x validator.length @max is strictest-wins, and an authored validator.length @min overrides the required-string floor. runtime-errors.json pins the exact failure list per rejected case so a second run-time runner can be held to the same structure and message text. runValidators is exported from the package root.
metaobjects.runtime.run_validators validates a data mapping against an entity's metadata: the Python port of the TypeScript runValidators, with the same rules, failure structure, message text and ordering. It never raises. ObjectManager.validate() returns the same result for a loaded entity. The runner runs fixtures/validation-conformance and asserts the failure list pinned in runtime-errors.json, the same file the TypeScript runner asserts. Docs: docs/ports/python.md, the corpus README, docs/CONFORMANCE.md (the case count was stale at 16; the corpus has 42), and CHANGELOG, which records the TypeScript behaviour change under Changed.
…nners A package-qualified @objectref on a value-object field resolved by bare name in the TypeScript runner, so with two same-named value objects in different packages it validated against the first one declared. It now matches the package-qualified key first, as the Python runner does. The Python runner printed a float in a failure message as Python does (1e-05, inf). It now follows ECMAScript Number::toString (0.00001, Infinity), so message text is identical to the TypeScript runner for every value. Both found by the independent branch review.
…olver in both runners
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Port a run-time validator runner to the Python port of metaobjects. Python has none today; TypeScript has
runValidatorsand Java has executable validators. A ported runner keeps the ports honest and lets Python consumers validate data against metadata without writing their own. Gated PR; the gate spend is approved.Where the TypeScript and Java runners disagree, follow TypeScript: never throw, collect every failure as {field, rule, message, expected, received} instead of failing on the first; TypeScript's message text; a whitespace-only string satisfies a required field; the @required and @maxlength field attributes count as well as validator children; no datatype default maximum length; and keep the TypeScript-only behaviour (value type checks, int64 string acceptance, the jsonb open-bag skip, value-object recursion, scalar-array element checks, partial-update mode, the storeFilled and @default exemptions). Enforce validator.numeric and validator.array at run time in BOTH the new Python runner and TypeScript's runValidators, with identical rules, error structure and message text, so both runners run and pass fixtures/validation-conformance. Leave Java alone. This is a behaviour change for TypeScript users: record it in the CHANGELOG under 1.1 as such.
What Changed
Python: new runtime validator runner —
run_validators(entity, data)validates a data mapping against entity metadata without generated code or database access. It is the port of TypeScript'srunValidators: never raises, collects all failures as{field, rule, message, expected, received}with identical rules and message text. Exported frommetaobjects.runtimeand available asObjectManager.validate(entity_name, data).TypeScript:
runValidatorsis exported and enforces 8 rules — the runner now validatesvalidator.numericbounds (@min/@maxon numeric fields),validator.arraybounds (element count),field.uri/field.inetformat, assigned primary keys without@default, and strictest-wins@maxLengthvsvalidator.lengthmax. Two failures now caught: a package-qualified@objectRefon a value-object field now resolves correctly, and@min: 0on a declared-required string admits the empty string. These are behaviour changes for users ofObjectManager(create, createMany, update, updateMany, validate).Conformance: both runners pass
fixtures/validation-conformance/, gated by newruntime-errors.jsonpinning the exact failure list each must produce. TS integration test added; Python unit and corpus tests added.Docs: Python documentation added to
docs/ports/python.md, conformance corpus documented indocs/CONFORMANCE.mdwith run-time runner notes.Risk Assessment
✅ Low: The fix is a bounded mechanical swap of both runners' VO-ref lookup to the shared ADR-0042 resolver with identical port semantics, gated behavior tests with real pre-fix failure semantics, and no scope beyond the prescribed remedy.
Testing
Baseline gate command (ts-fast + ts-unit, strict) ran green before this step. I stood both runners up the way a consumer does — loaded real metadata files through each port's standard loader and called the exported entry points (runValidators from @metaobjectsdev/runtime-ts; run_validators and ObjectManager.validate from metaobjects.runtime) — and drove 27 adversarial scenarios plus all 42 validation-conformance cases through both ports: the failure lists are byte-identical across all 69 drives (never-throws collect-all, validator.numeric/validator.array enforcement, whitespace-required, int64 string, jsonb open bag, VO recursion incl. arrays, scalar-array element paths, partial mode, storeFilled, @default, uri/inet strictness, @lenient, assigned PK, UTF-16 lengths, invalid-regex-pattern no-throw, and the two-package bare-ref case). Existing port suites pass (92 TS, 96 Python). Base commit confirmed to contain no numeric/array enforcement and no Python runner at all. The flagless full scripts/ci-local.sh regression is still executing (green through gates and the TypeScript lanes, 972 tests, zero failures); its log is the cited artifact. No scenario failed.
Evidence: Evidence README — scenario table, results, before/after proof
Evidence: TypeScript runValidators drive output (27 scenarios + 42 corpus cases + bigint)
Evidence: Python run_validators drive output (same drives + ObjectManager.validate)
Evidence: Parity scenario definitions (P01–P27)
Evidence: Adversarial metadata: same-named Address value objects in shipping and billing packages
~/.no-mistakes/evidence/01M44E60496HTN0A34DRM0ZY6X/ci-local-full.log)Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 2 issues found → auto-fixed ✅
server/typescript/packages/runtime-ts/src/validator-runner.ts:198- The rewritten resolveVoRef's bare-name fallbackobjects.find((o) => o.name === short)(Python twin: server/python/src/metaobjects/runtime/validator_runner.py:219-221) binds the FIRST object of that name across all packages, while the canonical ADR-0042 contract (resolveObjectRefin naming-refs.ts, exported from @metaobjectsdev/metadata;resolve_object_refin Python) resolves a bare ref in the REFERRER'S OWN package, then root-level — never cross-package. Concrete sequence: packagesshippingandbillingeach declareobject.value Address; an entity inbillingdeclaresfield.object @objectRef: "Address"(bare — legal, loads clean, and every other ref site resolves it to billing::Address). If the shipping file merges first, the runner validates the field against shipping::Address's members — the wrong ruleset applied, no error. Commit 1302573 fixed exactly this defect class for the qualified arm (billing::Address); the bare arm keeps it, in both runners. Remedy is in-scope and mechanical: call the sharedresolveObjectRef(root, ref, referrerPkg)/resolve_object_refinstead of the hand-rolled scan — naming-refs.ts states that resolver is 'the SINGLE resolver every object-ref site shares so the contract is uniform'. It also resolves every ref the loader itself accepted, so it cannot regress resolution.server/typescript/packages/runtime-ts/src/net-format.ts:8- The IPv4/IPv6 literal regexes now exist in three hand-maintained copies: codegen-ts/src/templates/net-regex.ts (generated Zod), runtime-ts/src/net-format.ts, and validator_runner.py. The corpus pins a finite probe set ('never total accept-set equality'), so drift outside it between the generated schema and the run-time runners is possible. Both copies name their source in comments and the duplication is tier-forced (codegen must not depend on runtime-ts); noting the accepted trade-off, no action required.🔧 Fix applied.
✅ Re-checked - no issues remain.
scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchainsscripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains (gate-configured baseline command, green)bun test packages/runtime-ts/test/validator-runner.test.ts packages/integration-tests/test/validation-conformance-runtime.test.ts — 92 passcd server/python && .venv/bin/python -m pytest tests/runtime/test_validation_conformance_runtime.py tests/runtime/test_validator_runner.py — 96 passbun drive_ts.ts and .venv python drive_py.py against the validation-conformance corpus + 27 parity scenarios, then deep-diff out-ts.json vs out-py.json — 69/69 identicalscripts/ci-local.sh (flagless full regression, all ports + gates + reactor + docker) — in progress at report time: gates, TS conformance and 972 TS unit tests green, mutation gate running, zero failures so far✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.