Skip to content

feat: check shared model dependencies against publisher in Python and C# - #537

Merged
dmealing merged 2 commits into
mainfrom
fm/mo-deps-p2-s2s7
Nov 6, 2026
Merged

dmealing merged 2 commits into
mainfrom
fm/mo-deps-p2-s2s7

Conversation

@dmealing

@dmealing dmealing commented Nov 6, 2026 •

Copy link
Copy Markdown
Member

What Changed

  • Adds a read-only dependency check to Python and C#: metaobjects deps check and metaobjects verify --deps (Python), and dotnet meta verify --deps (C#). Each re-resolves a dependency's path, validates the publisher's metaobjects.pkg.json, hashes the artifact, and compares it with .metaobjects/deps.lock.json. Results are current, drifted or unresolved; drift or unresolved exits non-zero with ERR_DEPENDENCY_UPSTREAM_DRIFT.
  • Only the path transport is resolved. npm and python dependencies report unresolved and point to the Node meta deps check. The Node meta deps sync remains the only writer of the lock and snapshot. A bare verify does not run the check; it is opt-in via --deps.
  • Shares the fixtures/dependency-conformance/ expectCheck cases across Python and C# (11 cases), with new CLI tests and updated docs (docs/features/metadata-dependencies.md, docs/features/cli.md, docs/ports/python.md, docs/ports/csharp.md, docs/CONFORMANCE.md) and a CHANGELOG entry.
Validation

Risk Assessment

✅ Low: Read-only opt-in dependency check in Python and C# mirrors the TS reference; the fix-round corrections are verified against the code and covered by tests that would fail on the old behavior.

Testing

Drove the shared-model dependency check live in Python and C# on disposable projects built from the conformance corpus. Current, drifted, unresolved (no lock, not in lock) and corrupt non-UTF-8 manifest scenarios all report the documented state and exit codes, with no crash. Targeted Python and full C# suites pass. The full Python suite has 19 errors from the default env missing the integration extra (fastapi); those pass with --extra integration. Baseline TS checks were already green.

  • Live validation: ✅ go - 5 of 5 scenarios driven live against the product
Scenario Result Live Evidence
Current dependency: check reports current, exit 0 (Python and C#) ✅ pass live live/out/1-current-py.txt, 1-current-cs.txt
Drifted dependency: ERR_DEPENDENCY_UPSTREAM_DRIFT, exit 1 (Python and C#) ✅ pass live live/out/2-drifted-py.txt, 2-drifted-cs.txt
No lock file: unresolved first, then ERR_DEPENDENCY_SNAPSHOT_STALE exit 2 (C#); Python exit 1 ✅ pass live live/out/3-no-lock-unresolved-py.txt, 3-no-lock-unresolved-cs.txt
Dependency not yet in lock: unresolved, then stale-snapshot refusal (C#) or exit 1 (Python) ✅ pass live live/out/4-not-in-lock-unresolved-py.txt, 4-not-in-lock-unresolved-cs.txt
Adversarial: non-UTF-8 publisher manifest reported unresolved, not a crash (Python and C#) ✅ pass live live/out/5-badutf8-py.txt, 5-badutf8-cs.txt
  • Evidence: a live driver ran the real metaobjects deps check (Python) and dotnet meta verify --deps (C#) over fresh temp projects built from the corpus cases, with per-scenario CLI transcripts.

Pipeline

Updates from git push no-mistakes

✅ **Intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 2 issues found → auto-fixed ✅
  • ⚠️ server/csharp/MetaObjects.Cli/Program.cs:448 - The docs say that dotnet meta verify --deps reports a declared but not-yet-synced dependency as unresolved 'rather than as a stale snapshot', with the ERR_DEPENDENCY_UPSTREAM_DRIFT exit. The code does not do that. RunDepsGate prints the unresolved line and returns 1. The run then reaches ResolveMetadataDirOrExit, and VerifySnapshot throws ERR_DEPENDENCY_SNAPSHOT_STALE ('no deps.lock.json' or 'has no entry for it'). That path prints the stale error and calls Environment.Exit(2), which skips the Math.Max(..., depsExit) aggregation. Concrete case: the corpus case check-a-dependency-with-no-lock-file-is-unresolved prints acme-common: unresolved and then error: ERR_DEPENDENCY_SNAPSHOT_STALE ..., and exits 2, not 1. The test Verify_deps_reports_a_dependency_not_yet_synced_as_unresolved_not_stale only asserts NotEqual(0) and that 'unresolved' appears, so it passes either way. Narrowest remedy: correct the wording to say the check reports it as unresolved first, and the load still refuses the stale snapshot with exit 2. Other sites with the same claim: CHANGELOG.md:43-44 ('reported as such rather than as a stale snapshot'), docs/features/metadata-dependencies.md:382-385, docs/ports/csharp.md:124-125 ('the same ERR_DEPENDENCY_UPSTREAM_DRIFT exit'). Alternative: make a --deps-only run return depsExit when the snapshot resolve fails, so the behavior matches the docs.
  • ℹ️ server/python/src/metaobjects/config/dependency_check.py:83 - _read_manifest calls manifest_path.read_text(encoding="utf-8") under except OSError only. If a publisher's metaobjects.pkg.json is not valid UTF-8 (corrupt or binary), the call raises UnicodeDecodeError, which is a ValueError and not an OSError. Nothing in check_dependencies or _run_deps_check catches it, so metaobjects deps check / verify --deps crash with a traceback instead of reporting unresolved. TS (readFile utf8) and C# (File.ReadAllText) replace bad bytes, so the JSON parse fails and both report unresolved. Fix: catch UnicodeDecodeError there too, or read bytes and decode inside the existing ValueError handler, and report it as 'is not valid JSON'.

🔧 Fix applied.
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 5 of 5 scenarios driven live against the product
Scenario Result Live Evidence
Current dependency: check reports current, exit 0 (Python and C#) ✅ pass live live/out/1-current-py.txt, 1-current-cs.txt
Drifted dependency: ERR_DEPENDENCY_UPSTREAM_DRIFT, exit 1 (Python and C#) ✅ pass live live/out/2-drifted-py.txt, 2-drifted-cs.txt
No lock file: unresolved first, then ERR_DEPENDENCY_SNAPSHOT_STALE exit 2 (C#); Python exit 1 ✅ pass live live/out/3-no-lock-unresolved-py.txt, 3-no-lock-unresolved-cs.txt
Dependency not yet in lock: unresolved, then stale-snapshot refusal (C#) or exit 1 (Python) ✅ pass live live/out/4-not-in-lock-unresolved-py.txt, 4-not-in-lock-unresolved-cs.txt
Adversarial: non-UTF-8 publisher manifest reported unresolved, not a crash (Python and C#) ✅ pass live live/out/5-badutf8-py.txt, 5-badutf8-cs.txt
  • scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains
  • uv run --quiet pytest -q tests/codegen/test_cli_deps_check.py tests/conformance/test_dependency_conformance.py (50 passed)
  • dotnet test in server/csharp (Conformance 2319 passed, Cli.Tests 369 passed, Codegen.Tests 1301 passed, 1 skipped)
  • uv run --quiet pytest -q --ignore=tests/integration --ignore=tests/codegen/test_report_credential.py (4346 passed, 5 skipped, 19 errors from missing fastapi/integration extra)
  • uv run --quiet --extra integration pytest on test_duration_codegen, test_cli_deps_check, test_dependency_conformance (72 passed); test_tph_m2m_generated and test_report_credential (32 passed with --all-extras --group dev)
  • A live driver materialised corpus cases into fresh temp projects and ran the real metaobjects deps check (Python) and dotnet MetaObjects.Cli.dll verify --deps --no-field-lint (C#)
  • Live scenario 1 (current): both report acme-common: current, exit 0
  • Live scenario 2 (drifted): both report drifted with ERR_DEPENDENCY_UPSTREAM_DRIFT, exit 1
  • Live scenario 3 (no lock file): Python exit 1 with unresolved line; C# prints unresolved then ERR_DEPENDENCY_SNAPSHOT_STALE and exits 2, matching the corrected wording
  • Live scenario 4 (dependency not in lock): Python exit 1; C# unresolved then stale-snapshot exit 2
  • Live scenario 5 (non-UTF-8 manifest): Python and C# both report is not valid JSON, exit 1, no traceback
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

…23 Phase 2, S2+S7)

metaobjects deps check / verify --deps (Python) and dotnet meta verify --deps (C#)
re-resolve each dependency's path, hash the publisher's artifact and compare with
deps.lock.json: current, drifted or unresolved, with ERR_DEPENDENCY_UPSTREAM_DRIFT.
Both run the 11 expectCheck cases of fixtures/dependency-conformance.
@dmealing
dmealing merged commit 5a2ff16 into main Nov 6, 2026
9 checks passed
@dmealing
dmealing deleted the fm/mo-deps-p2-s2s7 branch November 6, 2026 01:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant