fix(calm-suite): adopt canonical nested relationship-type in CALMGuard for cross-tool interop#2683
fix(calm-suite): adopt canonical nested relationship-type in CALMGuard for cross-tool interop#2683eddie-knight wants to merge 4 commits into
Conversation
…d for cross-tool interop CALMGuard modeled `relationship-type` as a flat string discriminant with a sibling variant key, which is not canonical CALM 1.2. As a result it rejected nested documents authored by CALM Studio / the CLI / Hub at parse time, and its own PR write-backs were schema-invalid to those tools. - Rewrite the Zod schema to the canonical nested `relationship-type` object, enforcing exactly-one-variant via superRefine, and relax `node-type` to an open string so extension node-types (e.g. aws:lambda) interoperate. - Add getRelationshipVariant() and route all readers (calm extractor, calm-to-flow graph mapping, learning extractor) through the nested shape. - Emit the nested form from the v1.0 normalizer. - Add a compile-time conformance check against @finos/calm-models types (gated by tsc, not vitest), plus interop tests that parse real canonical fixtures authored by other CALM tools and reject the legacy flat form. - Convert demo examples and inline test fixtures to nested; update AGENTS.md. Adds @finos/calm-models as a direct dependency of calmguard. Signed-off-by: Eddie Knight <knight@linux.com>
Slim launcher wrapping the existing npm workspace scripts: guard / guard-docs / studio / studio-desktop dev servers, builds, and tests. Runs from the repo root and builds @calmstudio/calm-core before starting Studio (it ships from dist). Signed-off-by: Eddie Knight <knight@linux.com>
…nd build CALMGuard's compile-time conformance check (src/lib/calm/conformance.ts) imports types from @finos/calm-models. That package ships from dist/ and has no prepare script, so a clean `npm ci` does not build it — the Lint/Typecheck and Build jobs then fail to resolve `@finos/calm-models/types` (TS2307). Build it first in both jobs, matching how the calm-studio workflow already sequences it. Signed-off-by: Eddie Knight <knight@linux.com>
|
Thanks @eddie-knight — this stack (#2683, #2691, #2692, #2693) is squarely in the direction of the convergence epic #2600; canonical CALM interop across the suite is exactly the end-state we're aiming for. My one concern before merging is sequence. Each PR overlaps a track that's already scoped as an issue:
The fixes are real, and some of the work is a genuine down-payment on the tracks (e.g. the Two asks:
|
gjs-opsflo
left a comment
There was a problem hiding this comment.
Strong PR — schema migration is clean and the compile-time conformance.ts gate vs @finos/calm-models is a great move. New interop.test.ts covering real cross-tool fixtures (complex.architecture.json, all 4 concrete variants) is the right anchor test. Normalizer leniency on input + strict canonical output is exactly the contract we need for round-trip with Studio and the CLI.
Requesting one change before merge
calm-suite/calm-guard/package.json:45 — "@finos/calm-models": "file:../../calm-models"
calm-models is already a root npm workspace, so this should use the workspace protocol so the package resolves via the workspace symlink rather than a relative file path:
"@finos/calm-models": "*"Why this matters:
file:bypasses npm's workspace resolution and creates a hard relative-path coupling. Ifcalm-suite/is ever restructured (or if calmguard is consumed outside the monorepo), this breaks in non-obvious ways.- Renovate / dependabot treat
file:deps differently from workspace deps. - The CI workflow's explicit
Build @finos/calm-modelsstep (workflow lines added in this PR) is correct either way — the change is purely about how the dep is declared.
Non-blocking observations (no action needed, just calling out)
-
node-typewidening is silent at the UI layer. AGENTS.md is honest about "9 well-known values get first-class UI, any string accepted", but the React Flow custom-node mapping and the compliance heat-map are still keyed off the original 9-value enum, so anaws:lambdanode will parse but fall through to a default renderer / bucket as "unknown" in the heat-map. The user story (parse, don't reject) is fixed here — render-side work is a reasonable follow-up issue. -
CI test job does not build
@finos/calm-models. Workflow adds the build step before lint and before build, but not before test. Currently safe because tests don't import calm-models at runtime (onlyconformance.tsdoes, type-only, esbuild strips). Worth a comment inconformance.tsso a future author who adds a runtime import knows to update the test job. -
detectCalmVersionreturns"1.1"for canonical nested docs missingadrs/decorators/timelines. Parses correctly, but the version label onParseSuccess.versionis technically wrong. No functional impact. -
Missing test scenario: a relationship with TWO variant keys (e.g. both
connectsandinteracts). ThesuperRefinerejects this, which is the strictest-vs-canonical-oneOfbehavior and worth pinning explicitly. -
Makefile is unrelated scope creep (PR body acknowledges, offers to split). The Makefile itself is fine. No strong opinion either way.
Will re-approve once the file: → "*" flip lands. Everything else can be a follow-up.
| "@ai-sdk/xai": "^3.0.57", | ||
| "@dagrejs/dagre": "^2.0.4", | ||
| "@finos/calm-cli": "^1.33.0", | ||
| "@finos/calm-models": "file:../../calm-models", |
There was a problem hiding this comment.
| "@finos/calm-models": "file:../../calm-models", | |
| "@finos/calm-models": "*", |
| "@ai-sdk/xai": "^3.0.57", | ||
| "@dagrejs/dagre": "^2.0.4", | ||
| "@finos/calm-cli": "^1.33.0", | ||
| "@finos/calm-models": "file:../../calm-models", |
There was a problem hiding this comment.
| "@finos/calm-models": "file:../../calm-models", | |
| "@finos/calm-models": "*", |
|
👍 Both suggestions are exactly the change requested — please apply. One heads-up on the rm -rf node_modules package-lock.json && npm install(or Once that lands I'll re-review and flip to APPROVE. Thanks for the quick turnaround! |
|
Looks like #2730 got merged ahead of this one, so I'll need to revisit the entire PR stack. |
Description
The journey this fixes: You model an architecture in CALM Studio, export it (or grab any CALM document the CLI / Hub produces), then open CALMGuard to run compliance analysis on it. Today that fails — the document is rejected the moment you upload it, before any analysis runs.
Concretely: take Studio's own
ecommercedemo export and drop it into CALMGuard's upload zone → it's refused at the CALM-schema step.The cause is a dialect mismatch. Canonical CALM 1.2 nests the relationship variant inside an object:
…but CALMGuard hand-rolled its own Zod schema using a flat string discriminant with a sibling key (
"relationship-type": "connects"+ a sibling"connects": {…}). So CALMGuard was the only tool in the suite speaking a private dialect: it couldn't read what Studio/CLI/Hub/Visualizer produce, and the remediation PRs it wrote back were schema-invalid to those same tools. It also enforced a closed 9-valuenode-typeenum, so Studio extension node-types (e.g.aws:lambda) were rejected too.After this PR, the round trip works:
calm validate, the Hub, and the Visualizer accept.node-typevalues from Studio packs are accepted.What changed
relationship-typeobject (exactly-one-variant enforced viasuperRefine) and relaxednode-typeto an open string, matching the CALM core spec.getRelationshipVariant()and routed every reader (CALM extractor, the React-Flow graph mapping, the learning extractor) through the nested shape.@finos/calm-modelstypes (gated bytsc, so drift fails the typecheck) plus interop tests that parse real canonical fixtures authored by other CALM tools and assert the legacy flat form is now rejected.calm-guard/AGENTS.md.This PR also includes a small unrelated convenience commit,
chore(calm-suite): add Makefile to launch suite apps on demand— happy to drop it if you'd prefer the PR scoped purely to the fix.Type of Change
Affected Components
cli/)calm/)calm-ai/)calm-hub/)calm-hub-ui/)calm-server/)calm-widgets/)docs/)shared/)calm-plugins/vscode/)Commit Message Format ✅
Conventional Commits, DCO signed-off:
fix(calm-suite): adopt canonical nested relationship-type in CALMGuard for cross-tool interopchore(calm-suite): add Makefile to launch suite apps on demandTesting
Verified on Node 22:
npm run typecheck,npm run lint,npm run test:run(117 tests, including new interop + conformance coverage), andnpm run buildforcalmguardall pass. Manually confirmed against a real Studio export: pre-fix CALMGuard rejects it at the upload zone; post-fix it parses and proceeds to analysis.Checklist