Skip to content

feat(csharp): consume shared dependency models at build time - #508

Merged
dmealing merged 7 commits into
mainfrom
fm/mo-deps-p2-s6
Oct 29, 2026
Merged

dmealing merged 7 commits into
mainfrom
fm/mo-deps-p2-s6

Conversation

@dmealing

Copy link
Copy Markdown
Member

What Changed

  • dotnet meta reads dependencies from .metaobjects/config.json on every source-ladder rung, including the explicit metadata directory. It verifies the committed snapshot against deps.lock.json offline and loads snapshots under dep:<name>/<artifact> source ids, with the same ERR_DEPENDENCY_* codes as the TypeScript and Python ports. The new Config/Dependencies.cs implements this.
  • Imported nodes are load-only. They are dropped at every selection point (codegen runner, report rows, shared enums, API-docs model, template scope walk, verify --codegen regeneration, requirement-gate coverage) unless scope.include names their package. An imported value object that a generated entity holds is still emitted, because the entity's property type needs it.
  • Adds dependency unit, conformance and CLI tests (DependencyUnitTests, DependencyConformanceTests, DependencyCodegenTests, DependencyCliTests). Updates docs, the README, and the agent-context codegen reference and its fixture copies to state that C# consumes shared models. Out of scope: publishing, Maven/NuGet transports, verify --deps (S7), and any C# writer of the lock or snapshot.
Validation

Risk Assessment

✅ Low: The change is additive and bounded: a project with no dependencies and no lock loads exactly as before. It mirrors the Python reference (lock validation, offline integrity check, collision and not-owned refusals, inScope formula). The import filter is applied at every selection point the intent lists. Shared-model artifacts never carry template.* nodes, so the template-tier seeds only ever see local templates. Corpus, unit, codegen and CLI tests cover the new paths.

Testing

Drove dotnet meta against a disposable app with a committed dependency snapshot and lock. Verified offline verify OK on a good lock. Verified stale snapshot, missing lock and metamodel-major mismatch fail with their ERR_DEPENDENCY_* codes. Verified gen drops imported Customer and emits imported Address, both directly and when nested in a local value object. Verified scope.include naming the package selects its nodes. Full C# suites pass: Codegen 1060 (1 skip), Cli 339, Render 336, Conformance 2167. Not driven live: ERR_DEPENDENCY_NODE_COLLISION (my attempt hit ERR_SOURCE_MULTIPLE_PRIMARY instead), and the explicit-rung error cases, which are covered only by conformance tests.

  • Live validation: ✅ go - 7 of 9 scenarios driven live against the product
Scenario Result Live Evidence
Verify with current lock and committed snapshot passes offline ✅ pass live dotnet meta verify --templates templates: OK in disposable app
Tampered committed snapshot is refused with ERR_DEPENDENCY_SNAPSHOT_STALE ✅ pass live verify output names hash mismatch and advises meta deps sync
Missing deps.lock.json with declared dependency is refused ✅ pass live ERR_DEPENDENCY_SNAPSHOT_STALE: no .metaobjects/deps.lock.json
Dependency with incompatible metamodel major is refused ✅ pass live ERR_DEPENDENCY_METAMODEL_INCOMPATIBLE: published against 2.0, toolchain speaks 1.2
gen emits local entity and the imported value object it holds, not imported entities ✅ pass live gen writes Order.g.cs and Address.g.cs; Customer not written
Adversarial: imported value object nested in local value object with no entity holding it is still emitted ✅ pass live gen writes Wrapper.g.cs and Address.g.cs; Wrapper references Address
scope.include naming acme::common selects imported nodes ✅ pass live gen writes Customer.g.cs with Order, Address, Wrapper
Imported entity/value collision ERR_DEPENDENCY_NODE_COLLISION fires live ⏸️ untested no Attempt with a local object reusing acme::common::Customer hit ERR_SOURCE_MULTIPLE_PRIMARY first, not the collision code. Covered by DependencyConformanceTests only.
Explicit metadata-directory rung refuses stale/colliding lock ⏸️ untested no Not driven live. Conformance test coverage only, which the reviewer flagged as a gap; the explicit-rung expectError branch is not exercised live here.

Pipeline

Updates from git push no-mistakes

✅ **Intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • ℹ️ docs/features/metadata-dependencies.md:590 - The C# paragraph said an imported value object's POCO is emitted only when a generated ENTITY holds it. Since the round-1 closure fix, the POCO is also emitted when a local value object holds it or when a local template's @payloadRef/@responseRef names it. Reworded the sentence to match the code (EntityGenerator.ReferencedValueObjects).
✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 7 of 9 scenarios driven live against the product
Scenario Result Live Evidence
Verify with current lock and committed snapshot passes offline ✅ pass live dotnet meta verify --templates templates: OK in disposable app
Tampered committed snapshot is refused with ERR_DEPENDENCY_SNAPSHOT_STALE ✅ pass live verify output names hash mismatch and advises meta deps sync
Missing deps.lock.json with declared dependency is refused ✅ pass live ERR_DEPENDENCY_SNAPSHOT_STALE: no .metaobjects/deps.lock.json
Dependency with incompatible metamodel major is refused ✅ pass live ERR_DEPENDENCY_METAMODEL_INCOMPATIBLE: published against 2.0, toolchain speaks 1.2
gen emits local entity and the imported value object it holds, not imported entities ✅ pass live gen writes Order.g.cs and Address.g.cs; Customer not written
Adversarial: imported value object nested in local value object with no entity holding it is still emitted ✅ pass live gen writes Wrapper.g.cs and Address.g.cs; Wrapper references Address
scope.include naming acme::common selects imported nodes ✅ pass live gen writes Customer.g.cs with Order, Address, Wrapper
Imported entity/value collision ERR_DEPENDENCY_NODE_COLLISION fires live ⏸️ untested no Attempt with a local object reusing acme::common::Customer hit ERR_SOURCE_MULTIPLE_PRIMARY first, not the collision code. Covered by DependencyConformanceTests only.
Explicit metadata-directory rung refuses stale/colliding lock ⏸️ untested no Not driven live. Conformance test coverage only, which the reviewer flagged as a gap; the explicit-rung expectError branch is not exercised live here.
  • scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains
  • dotnet build server/csharp/MetaObjects.Cli (branch CLI built)
  • dotnet test server/csharp/MetaObjects.Conformance.Tests/MetaObjects.Conformance.Tests.csproj --filter FullyQualifiedName~Dependency (57 pass)
  • Disposable app in mktemp dir: acme-common snapshot from fixtures/dependency-conformance/artifacts/acme-common-v1.json, lock with matching sha256, config.json dependency
  • live dotnet meta verify metaobjects --templates templates on current lock: OK
  • live tampered snapshot (appended byte): ERR_DEPENDENCY_SNAPSHOT_STALE with hash details
  • live missing deps.lock.json: ERR_DEPENDENCY_SNAPSHOT_STALE
  • live lock metamodelVersion 2.0 vs toolchain 1.2: ERR_DEPENDENCY_METAMODEL_INCOMPATIBLE
  • live dotnet meta init then dotnet meta gen metaobjects --out gen-out with EntityGenerator, codegen project pointed at branch MetaObjects.Codegen via ProjectReference (published 1.1.4 package lacks dependency support): Order.g.cs and Address.g.cs written, Customer and Audited not written
  • live adversarial: local object.value Wrapper with field.object @objectRef acme::common::Address and no entity holding Address: Address.g.cs emitted, Wrapper.g.cs references Address
  • live scope.include ["app::**","acme::common::**"]: Customer.g.cs also written
  • live bad dependency path ../nope with lock intact: verify OK (offline verify reads committed snapshot, by design)
  • dotnet test MetaObjects.Codegen.Tests (restore enabled): 1060 pass, 1 skip
  • dotnet test MetaObjects.Cli.Tests: 339 pass
  • dotnet test MetaObjects.Render.Tests: 336 pass
  • dotnet test MetaObjects.Conformance.Tests: 2167 pass
  • Scratch tree and /tmp files removed; git status clean
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

dotnet meta reads `dependencies` from .metaobjects/config.json at every rung of
its source ladder, verifies the committed snapshot against deps.lock.json
offline, and loads the snapshots ahead of the project's own files under
`dep:<name>/<artifact>` source ids. Same ERR_DEPENDENCY_* codes as TypeScript
and Python; the Node `meta deps sync` stays the only writer of lock and snapshot.

Imported nodes load but are not generated: the codegen runner, report rows,
shared enums, API-docs model, template scope walk, the verify --codegen
regeneration and the requirement gate's coverage denominator all leave them out
unless scope.include names their package. An imported value object a generated
entity holds is still emitted, since the entity's property type needs it.

C# runs all 23 cases of fixtures/dependency-conformance/ on the neutral ladder
and the explicit metadata-directory rung, plus codegen, CLI and unit tests.
…basing onto main

main's corpus now carries 11 deps-check cases (expectCheck) beside the 24
resolution cases; C# has no check yet, so its runner skips them. Update the
Phase 2 prose and the changelog count to match.
# Conflicts:
#	CHANGELOG.md
#	docs/CONFORMANCE.md
#	docs/features/metadata-dependencies.md
#	fixtures/dependency-conformance/README.md
… S6); stale "C# doesn't read dependencies" claims fixed in README, cli, metadata-sources, metadata-dependencies, spec/roadmap, and the agent-context codegen reference plus its five mirrored fixture copies.
@dmealing

Copy link
Copy Markdown
Member Author

no-mistakes runs: 3. Runs 1 and 2 ended on infrastructure failures (a git fetch and the rebase agent were killed with signal: terminated); run 1's review had already fixed its three findings. Run 3 passed every step with one info finding (a doc sentence, fixed inline). CI step skipped by design (ci_mode: local); full C# suites run locally before the run: Codegen 1060, Cli 339, Render 336, Conformance 2167.

@dmealing
dmealing merged commit 198d75d into main Oct 29, 2026
1 check passed
@dmealing
dmealing deleted the fm/mo-deps-p2-s6 branch October 29, 2026 02:49
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