Repository navigation
feat(codegen-ts): publish requirements in shared models and inherit them by consumer key - #553
Merged
Merged
Conversation
A publisher can now opt in to exporting the selected requirement.* nodes alongside its shapes (`requirements: true` in TypeScript, the `requirements` arg in Java and Kotlin, `Requirements` in C#). The default is unchanged: shapes only. template.* is never exported. The closure check now follows a selected requirement's @implementedBy claims (owner segment, in the requirement's effective package), so an exported requirement cannot claim a node the export leaves out. The shared-model corpus gains four cases and a pinned artifact, acme-common-v1-requirements.json, which the dependency corpus will consume. Python has no publisher; its serializer arm now takes every node the manifest lists, not only objects.
A new `requirements.inherit` key in .metaobjects/config.json names the dependencies whose requirements join this project's ledger. A pattern counts only when it names the dependency's package (the scope.include rule; `**` names nothing). It is a separate key from scope.include because that one also selects a package for code generation. An imported requirement not named there is loaded, so an overlay of it resolves, and is otherwise left alone: the requirement gate, its summary and lint, and the requirement-tests generator skip it. An imported requirement, inherited or not, never switches object coverage on, the rule a shipped library already follows. A new top-level requirement declared into a dependency's package is now refused with ERR_DEPENDENCY_PACKAGE_NOT_OWNED, as an object is. The dependency corpus gains seven cases and a ledger arm (expectLedger, expectCoverageMeasured).
…docs Java and C# read `requirements.inherit` and apply the same ledger rule as TypeScript and Python: a dependency's requirement joins the ledger only when the key names its package, an imported requirement never switches object coverage on, and a new requirement declared into a dependency's package is refused with ERR_DEPENDENCY_PACKAGE_NOT_OWNED. Both read the ledger from the dependency set already on the loaded root, so the requirement gate and the requirement-tests generators (JUnit, Kotlin, C#) need no new arguments. Kotlin uses Java's gate. Every port's dependency-conformance runner asserts the ledger arm through the gate's scan and through the requirement-tests walk. The `meta gen --list --probe` count for requirement-tests now uses the ledger too. Docs: the publisher's `requirements` option, a new "Inheriting a dependency's requirements" section, the corpus case schema and counts, and a CHANGELOG entry.
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.
What Changed
shared-modelgenerator acceptsrequirements: true(TypeScriptsharedModelFile, Java/Kotlin generator arg, C#SharedModelGenerator.Requirements). Default off, so existing exports keep their bytes. When on, the selectedrequirement.*nodes are published beside the model. A selected requirement's@implementedBytargets are closed likeextendsand ref attrs, and an unclosed claim fails the publish.template.*is never published.requirements.inheritin.metaobjects/config.json(zodConfigSchemainsdk, read by every port) lists the dependency packages whose requirements join the project ledger, using the same literal-package rule asscope.include. Dependency requirements not inherited still load for overlays but are left out of the requirement gate, summary, lint,requirement-testsgenerators and requirement pages. Inherited requirements never switch object coverage on. Ledger predicate isInLedger(C#),inLedger/in_ledger(TS, Python).ERR_DEPENDENCY_PACKAGE_NOT_OWNED. Overlays of that requirement are still accepted. Shared-model and dependency conformance fixtures and tests added in all ports, plusdocs/features/metadata-dependencies.md,docs/features/requirements.mdandCHANGELOG.md.Notes
requirements.inheritand applies the ledger rule, but it has no shared-model publisher today, so it cannot export requirements.requirementsFile()) and the agent requirements page now list only the project's ledger, so they agree with the gate and the docs-site explorer (review finding F5). The other ports have no requirements documentation page, so there was nothing to filter there.requirementTestIdentitiesand Pythonrequirement_test_identitiestake the collection's ledger predicate (inLedger/in_ledger), which the generators and the corpus runners pass. Called without it, they still return every loaded requirement, because a TS or Python root does not carry the dependency set.docs/features/metadata-dependencies.mdcarry the rule.schema_versionis unchanged.Validation
Risk Assessment
✅ Low: The opt-in publisher
requirementsoption (default off,template.*still excluded) and the separaterequirements.inheritconsumer key are implemented consistently in all five ports. The ledger filter is applied at every consumer I traced: the gate, the lint, the summary, requirement-tests, and the TS documentation generators. Cross-port corpus cases cover both sides. The only defect found was stale case counts in the docs prose, which I fixed inline. I did not run tests, per the review-phase rule.Testing
Drove the publish and consume flow live through the TS
metaCLI on disposable publisher and consumer projects (removed after). Default publish omits requirements, opt-in publish includes them, template is always excluded, and inherit by package name puts the dependency requirement in the ledger. Wildcard-only inherit and scope.include both leave it out. Per-package TS suites for sdk, codegen-ts and cli pass. Whole-server bun test timed out at 10 min, so it was not completed as a single run. Documentation generator output and the Python, Java and C# ports were not exercised live.meta genin disposable publisher projectmeta deps syncoutput: synced acme-common 1.1.0;meta deps listshows 3 nodesmeta verifyrequirementLedger lists only app::OrdersTaken, total 1meta verifyrequirementLedger lists acme::common::Customers (implementedBy Customer resolves) and app::OrdersTaken, total 2meta verifyledger total 1 (OrdersTaken only)meta verifyledger total 1 (OrdersTaken only)Evidence: publisher manifest with requirements opt-in (nodes: Customer, Customers, GreetingPayload)
Evidence: consumer verify ledger under inherit ** (only OrdersTaken listed)
Pipeline
Updates from git push no-mistakes
✅ **Intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
docs/features/metadata-dependencies.md:419- The documented consumer example was wrong. It used"requirements": { "inherit": ["acme::common"] }, and the rule bullet said thatacme::commonoracme::common::**inherits the package.explicitlyIncludesdrops the final segment throughpackageOfResolutionKey, so a bareacme::commonnames the packageacme. An adopter who copied the example would silently inherit nothing. Every fixture case usesacme::common::**, which agrees with the code, andsdk/src/config.tsdocuments the rule correctly. Changed the example toacme::common::**and rewrote the bullet (line 432): a bare package name names its parent package, and**names nothing.docs/features/metadata-dependencies.md:858- The 'Verified by' text said all four ports assert the ledger arm through the requirement gate and therequirement-testswalk. The TS (dependency-ledger-conformance.test.ts) and Python ledger arms assert onlyscanRequirements/scan_requirements. Only Java and C# also assert the walk. Reworded the text to state that split.server/typescript/packages/codegen-ts/src/requirement-walk.ts:73-requirementLedgerKeysat betweenwalkRequirements's JSDoc block andwalkRequirements, so the walk's documentation was attached to the wrong function. Moved the function above that JSDoc block. No code change.server/java/metadata/src/main/java/com/metaobjects/config/NeutralConfig.java:486- The newparseRequirementsInheritwas inserted under the/** {@code migrate.scope} ... */javadoc. That left the doc comment dangling, andparseMigrateScopelost its doc. Moved the migrate.scope javadoc back ontoparseMigrateScope.server/typescript/packages/sdk/src/config.ts:215- The new config doc says that an imported requirement not named inrequirements.inheritis "not listed, checked, summarised or given a generated test". The TS documentation generators still list it.requirementsFile()(codegen-ts/src/generators/requirements-file.ts:51) and the agent requirements page (agent-requirements-page.ts:114) build rows fromrequirementRows(ctx.loadedRoot), which callswalkRequirementsand never readsctx.inLedger. Concrete case: a consumer depends on a model published withrequirements: trueand does not inherit it. The consumer's generated requirements.md/.toon then shows the publisher's requirements with their statuses. The docs-site model explorer reads the filteredrequirementPayloadand leaves them out, so two documentation surfaces of one run disagree. (Resolved in this run: fixed as decided by the maintainer.)server/typescript/packages/codegen-ts/src/requirement-walk.ts:360- The ports now disagree on the public 'test identities' API. JavaRequirementTestIdentities.walkand C#RequirementTestIdentities.Walknow go throughcollectLedger/CollectLedger, which reads the dependency set on the root, so they return only ledger requirements. TSrequirementTestIdentities(root)and Pythonrequirement_test_identities(root)(server/python/src/metaobjects/codegen/requirement_walk.py:266) still walk every loaded requirement. Only the generators filter, throughctx.inLedger. Both TS and Python document the function as "every test the generator would emit". For a consumer with a requirements-carrying dependency it does not inherit, the TS/Python API returns identities the generator never writes. Java/C# do not. An adopter harness that checks witnesses against this list gets different answers per port. (Resolved in this run: fixed as decided by the maintainer.)server/typescript/packages/codegen-ts/src/shared-model/build-shared-model.ts:276- The closure check now follows@implementedBythroughresolveClaimTarget, which also resolves root-leveltemplate.*nodes. Requirements may legitimately claim prompts. A selected requirement that claims atemplate.prompttherefore fails with "include it or exclude the referrer", but a template can never be published, so 'include it' is impossible advice. Refusing is right: the claim would dangle in the consumer. When the target istemplate.*, the message should say that templates are never exported and the requirement must be excluded. Same message in JavaSharedModel.java(closure refusal) and C#SharedModel.cs(closure refusal).🔧 Fix applied.
1 info still open:
docs/CONFORMANCE.md:475- Fix-round 7355c32 added a seventh ledger case to dependency-conformance/cases.json. The matrix row and metadata-dependencies.md were updated to 36 resolution cases, 7 with a ledger arm, but the prose at lines 475-488 still said 35, 6, and 'A seventh case'. Corrected to 36, 7 and 'An eighth case', matching cases.json (47 cases total = 36 resolution + 11 check; 7 with expectLedger, plus the refusal case).✅ **Test** - passed
✅ No issues found.
meta genin disposable publisher projectmeta deps syncoutput: synced acme-common 1.1.0;meta deps listshows 3 nodesmeta verifyrequirementLedger lists only app::OrdersTaken, total 1meta verifyrequirementLedger lists acme::common::Customers (implementedBy Customer resolves) and app::OrdersTaken, total 2meta verifyledger total 1 (OrdersTaken only)meta verifyledger total 1 (OrdersTaken only)scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchainsPublisher:meta gen(sharedModelFile, default requirements off) in a disposable project, artifact nodes inspected: requirement.functional and template.output excluded.Publisher: same project withrequirements: true, artifact and manifest nodes inspected: requirement included, template.output still excluded.Consumer:meta deps syncagainst the published gen/ directory (new dependency, pinned lock).Consumer:meta verifywith no requirements.inherit -> ledger lists only the consumer's own OrdersTaken.Consumer:meta verifywith requirements.inherit ["acme::common::**"] -> ledger adds acme::common::Customers (live, resolves to Customer).Adversarial:requirements.inherit: ["**"]-> imported requirement NOT in ledger (wildcard names no package).Adversarial:scope.include: ["acme::common::**"]without inherit -> imported requirement NOT in ledger.cd server/typescript/packages/sdk && bun test-> 432 pass, 0 fail.cd server/typescript/packages/codegen-ts && bun test-> 3413 pass, 0 fail.cd server/typescript/packages/cli && bun test-> 2006 run, 0 fail (11 skipped).cd server/typescript && bun test(whole server suite) -> exceeded 10 min timeout, not completed; replaced by the per-package runs above.Baselinescripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains(passed before this step).✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.