Service Management Collaboration and Packaging
This is the E8 document of the Service Management E1–E8 documentation chain
(JUM-494 )
— the last link of the chain and the epic’s terminal documentation gate
under
Requirement 094:
the Linear Project cannot be set to Completed while this Issue is in any
state other than completed.
It documents the collaboration and packaging lane exactly as it shipped:
- Collaboration — the multi-user shared catalog
(JUM-491 ):
a contract-first
Catalogsbackend module, optimistic concurrency per catalog record, tombstone deletion with restore, and a designer-side sync client that converges by document read-back. - Domain-package versioning (JUM-492 ): exported domain packages are versioned data with a dependency graph and deterministic, explainable conflict resolution on import.
- Packaging
(JUM-493 ):
the framework-free designer core is the versioned
@jumentix/designer-corepackage underpackages/designer-core/— manifest, deterministic build, type declarations, DOM-free proof and a consumer smoke test against the built artifact. Publishing is dry-run only (Requirement 070): the publish path is verified, never fired.
Because this is the terminal gate, its content reflects what was delivered, including everything carried over — each descope below is named, not silently dropped. The chain-closure section at the end is the Req 094 evidence: it names the E1–E8 chain completely and records its gate state.
This document is the English reference. A versão em português está em SERVICE-MANAGEMENT-COLLABORATION-PACKAGING.pt-BR.md.
Collaboration: the shared catalog (JUM-491)
Everything before JUM-491 treats the designer as a single-browser tool: Cana persists the designer document in the browser’s IndexedDB, and JUM-485 synchronizes it across that browser’s tabs. JUM-491 turns the designer into a multi-user system: a team shares one catalog of domain designs through the backend — also the only continuous second copy of a user’s work on different hardware (Cana has no fallback; export is manual).
The full mechanism is owned by the dedicated Shared Catalog Sync document (architecture, OAS contract, authorization model, convergence proof, verification commands). This document states what the delivery means for the epic — the concurrency unit, the rejection path, the deletion semantics and the convergence rule — once, and defers to it rather than maintaining a second, drifting explanation.
What shipped
- A contract-first
Catalogsbackend module (apps/service-management-api/src/modules/Catalogs/domain/Model/Catalog.ts), hexagonal like the Users reference module: domain aggregate (version bump, tombstone, restore), pureCatalogAuthorizationPolicy, use cases (CatalogUseCases.ts), the optimistic-concurrency enforcement point (CatalogDataRepository.ts), the OAS-validatedCatalogControllerand composition (composeCatalogsServices.ts). Six operations on/catalogsin the canonicalspec/1.0.0.yml— list, create, get, update, delete, restore — enforced bybun run oas:check-routes. - Optimistic concurrency per catalog record. The record (one shared
domain design) is the declared concurrency unit: coarse enough that a
relationship spanning two entities always has a consistent version to
check, fine enough that teammates never block each other across domains.
The server-managed
versiontoken starts at 1 on create and bumps on every write; a stale write is rejected with a reviewable 409 whose metadata carriescatalogId,expectedVersion,currentVersionand the current record — the loser’s edit is never discarded. - Deletion is a tombstone, recoverable. Delete sets
deletedAtand bumps the version so the deletion propagates on read-back; a locally-dirty copy raises adeleted-remotelyconflict instead of vanishing;POST /catalogs/{id}/restorerecovers the record. - Server-side authorization, TENANT-RBAC aligned. The role matrix gains
catalog scopes (
admin: read/create/update/delete;user: read/create/update — team members edit, only admins delete), and the tenant policy binds the catalog to exactly one organization. A client cannot grant itself access — proven by positive/negative integration tests. See Tenant and RBAC Authorization Contract. - Mediator events. Every successful write publishes
catalogs.catalog.created | updated | deleted | restoredwith{ id, organization, version, actor }— the same version token the API enforces — on the message mediator (CatalogService.ts); publication never breaks the primary write. - A DOM-free designer sync client
(
apps/service-management/src/state/catalogSyncClient.js): a sibling consumer of the same Cana committed-event stream that JUM-485’s tab sync subscribes to. Local commits schedule a debounced push of dirty shared domains (dirtiness decided by the durable markerdomain.context.catalog = { id, version, contentHash }, carried additively per JUM-492’s pattern); convergence is a polled document read-back diffed by(id, version)— Cana’s resync rule applied across the network: a gap is a reload signal, never an event replay, because Cana cursors are per client instance and mean nothing across machines. Remote changes cross the sameapplyRemoteDocumentone-path as tab sync, so undo isolation, selection reconciliation and redo truncation are identical. - A real convergence proof.
catalogSync.integration.test.tsboots the real Express backend (real JWT auth, real mediator) and runs two real designer clients over realfetch; one is partitioned behind a realECONNREFUSED, both keep editing, and on heal the read-back converges them — the partitioned edit survives as a reviewable conflict, resolved explicitly (take-server/take-local, wheretake-localis a deliberate new write against the server’s current version, never a blind overwrite).
What JUM-491 deliberately descoped (12-01 carry-over candidates)
Named in Shared Catalog Sync and restated here because the gate must see them:
- WebSocket fan-out to browsers (push instead of poll): the broker
adapters exist in
packages/message-mediator, but no fan-out to designer clients is wired; poll-based read-back is correct under any broker choice. - Designer share/conflict UI chrome: the client module is DOM-free and composable; the share/unshare/conflict surfaces (and the token-provider UX) are a follow-up.
- Driver-level native conditional writes: the repository’s
read-check-write is the reference behavior; production drivers should later
map the same check into
IStoreMutationOptions.expectedVersionnative conditionals. - Offline deletion-intent queue: an offline local deletion of a shared domain cannot be pushed; the surviving server record is re-admitted on read-back — the “committed document wins” boundary, session-scoped by design in this slice.
What this changes for the user — and what it does not
Before JUM-491, the designer was per-browser and export was the only way work left the machine — the E6 document, Service Management Cana Adoption, Migration and Offline Behaviour, owns that data story and this document does not restate it. The shared catalog adds the missing lane: work a user shares now lives on the server and converges across machines. Two honest boundaries remain, stated up front:
- A sync target is not a backup. It propagates deletions; it does not replace the storage-quota/eviction policy or the offline durability contract.
- Cana still has no fallback. When the catalog is unreachable the client
declares it (
degradedon the status surface) and keeps saving locally to Cana — it never silently degrades into a hidden single-user mode.
Domain-package versioning (JUM-492)
A domain package (the <domain>-package.json export) is versioned data,
not code, and import is a deterministic policy instead of an unconditional
append. The public contract is pinned in
Requirement 126, Contract 3;
the implementation lives in
src/packages/packageVersioning.js
(DOM-free), wired through the exporter
(buildDomainPackageDocument)
and the importer
(designerImporters.js).
The versioned package document
Export emits the v2 shape { kind: "domain-package", version: "2.0.0", exportedAt, package: { name, version, dependencies: [{ name, range }] }, domain }. The package block declares identity from the domain’s context:
packageName (falling back to the domain name), packageVersion (falling
back to 1.0.0) and packageDependencies entries (name@range; a bare
name is a presence-only dependency). Compatibility is explicit in both
directions: legacy v1 documents keep importing with a synthesized 1.0.0
identity; a document whose major is newer than the importer’s, or a kind
other than domain-package, is refused clearly.
Version semantics, redefined for a data model
The usual semver meanings do not map onto a data model, so Requirement 126 redefines them:
- patch — documentation/metadata only (field descriptions, formats, constraints, domain context text, OAS composition hints);
- minor — additive structure (a new entity, field or message contract; a required flag loosened);
- major — removal or narrowing (a removed entity/field/contract, a field type or PK/FK/unique change, a required flag tightened, an RBAC or invariant change, an aggregate declaration change).
Provenance and the installed-package registry
Imported content is stamped: the domain carries context.provenance = { package, version } plus context.packageName/context.packageVersion,
and every imported entity carries meta.provenance. The normalizers carry
these fields additively — only when the source declares them — so
pre-JUM-492 payloads are unchanged and provenance crosses storage, loads and
the full-suite export intact. The installed-package registry derives from
provenance only: a hand-built domain is never an installation, so
importing a package named like a locally-built domain appends (with the
JUM-617 id recomputation) instead of merging into unrelated content.
The dependency graph
Dependencies resolve transitively over the registry with the incoming
package overlaid. Ranges follow the npm convention: */empty (any), exact
1.2.3, caret ^1.2.3 (same major; for 0.x, same minor), tilde ~1.2.3
(same major.minor); anything else is invalid and satisfies nothing, so it is
reported rather than silently accepted. A missing or range-incompatible
dependency is reported through the status region and the import proceeds
— the designer reports, it is not the resolver. A cycle the incoming package
would close is reported by name chain and the import is refused — cycles
are detected, never entered.
Semantic conflict resolution
Re-importing an installed package resolves deterministically and explainably:
- same version + equal content → no-op — idempotent re-import, proven by test;
- same version + different content → refused (
same-version-conflict, divergences listed — version immutability); - older version → refused (
downgrade-rejected); - newer version → merge. Additive and metadata changes (patch/minor
semantics —
AUTO_MERGE_CLASSES) apply automatically. Removals, narrowings and always RBAC and invariants (REQUIRES_DECISION_CLASSES) keep the existing designer content and are listed in the merge preview, rendered on the schema-diff surface before anything changes; the merge applies only after the user explicitly accepts (a gatedwindow.confirm— one of the destructive-action gates JUM-543 deliberately keeps). RBAC and invariants are always in the decision class: automatically resolving a security policy or a domain invariant is a decision a merge algorithm must not make. All outcomes surface throughshowStatus, neveralert().
Entity matching inside a merge is by name, never by id: colliding ids are
recomputed at import on the
JUM-617
rule, so ids can never be the match key; new incoming entities receive
collision-free ids through the importer’s uniqueId callback.
Proven by:
designerPackageVersioning.test.ts
(version parsing/ordering, range satisfaction, dependency parsing and
transitive resolution, cycle detection, every conflict class),
designerRoundTrip.test.ts
(versioned export→import deep-equal with provenance, re-export fixed point,
idempotent re-import, conflicting re-import refusal, deterministic merge
with RBAC kept, compatible/incompatible dependency pairs, JUM-617 preserved
on the append path) and
designerExporters.test.ts
(the v2 package document shape, pinned).
Packaging: the @jumentix designer core (JUM-493)
This lane shipped as a package with a verified dry run — never an automatic
publish. This section previously recorded JUM-493 as descoped (the 12-01
decision); the package has since landed, and this document records the landed
state. The framework-free designer core is now the versioned
packages/designer-core/ package under the
xpertminds organization — browser-safe ESM, zero runtime dependencies, MIT
licensed, with provenance metadata pointing at its monorepo location.
- The package is the canonical home, not a copy. The core modules moved
from
apps/service-management/src/intopackages/designer-core/src/— the workspace boundary gate forbids a package importing from an app, so the dependency direction was inverted: the zero-build SPA now consumes the package through@jumentix/designer-core/…bare specifiers, resolved by the import map to a vendored tree (apps/service-management/vendor/designer-core/, containment-safe, synced byci-cd/sync-service-management-designer-core.js— the same vendoring model as the Cana bundle) in the browser, and by the repo’s path mappings (tsconfigpaths, JestmoduleNameMapper) straight to the canonical sources in tests. The shipped surface — the domain model and its normalizers, the validation/model-check engine, the exporters (JSON, Markdown, JSON Schema, AsyncAPI, boilerplate bundle, package, OAS), the importers, the schema-diff/merge-preview engine, the hexagonal codegen, andIDesignerStoreas a type/contract only — is exactly the package’ssrc/tree; the build (scripts/build.js) copies it verbatim intodist/and generates type declarations from the JSDoc-annotated sources with the repo-pinned TypeScript compiler. Out, enforced by test: every DOM module (script.js,ui/,pwa/), the sync clients (state/designerSync.js,state/catalogSyncClient.js) and the storage adapters — neitherLocalStorageDesignerStorenorCanaDesignerStoreships, and the package has no dependency on Cana. - The acceptance bar is met by proof, not by construction. Three suites
under
packages/designer-core/test/pin the package:packaging.test.tsasserts the manifest (entry points at built output, types-first exports map,files, licence,sideEffects, dry-run-only scripts, in the style of the cana packaging suite), asserts the built file set is exactly the declared closure, and asserts the packed tarball contents vianpm pack --dry-run --json;dom-free.test.tsscans the built artifact’s AST for anywindow,document,localStorage,indexedDB,alert()or FileReader/DOMParser reference and for any import crossing the package boundary;consumer-smoke.test.tsexecutes the issue’s acceptance test — it imports the built artifact in a separate non-DOM process (nodocument, nowindow, nolocalStorage) and runs a validate → export → re-import round trip on the sample model, deep-equal with a re-export fixed point. - The publish policy stands: dry-run only. Per
Requirement 070
no automatic publish exists. The repository’s dry-run surface
(
bun run npm:publish:dry-run:packages, withnpm:org:check:xpertmindsfor the org side) picks the package up like every other non-private workspace package and runsbun publish --dry-run --access public;prepublishOnlyforces a clean rebuild first, so the dry run verifies a deterministic artifact whose contents the packaging suite has asserted. - Versioning policy. The package follows semver over its public barrel: patch for internal fixes, minor for additive exports, major for removed or narrowed surface. The data contracts it reads and writes (full-suite export, domain-package document) stay versioned in-payload under JUM-492’s policy (Requirement 126, Contract 3) — the package version does not restate them. JUM-492’s domain-package versioning builds on exactly this split.
The practical consequence for the user is unchanged from the E6 document: export is how work leaves the machine — as the full-suite document or as a versioned domain package — and the shared catalog (above) is the only continuous second copy. The package changes who can depend on the core, not how a designer user’s work is stored.
The full-suite export (JUM-547), the portable bundle
The JSON export is the versioned full-suite document
({ kind: "service-management-suite", version: "2.0.0", domains, relationships, interfaces, serviceConfiguration, runtimeEnvironment, codeWorkspace, deployments, view }) carrying all five tabs in a re-importable shape
(JUM-547 ).
One recorded security decision matters for packaging: the bundle carries the
runtime environment selection only ({ environment, fileName }) —
never values, because the values mirror real .env contents of the
machine the designer runs on. No secret can leave in a bundle; on import the
selection is restored and the local machine’s values are preserved. The
contract is pinned in Requirement 126, Contract 3; the E5 document,
Service Management Operations Console,
owns the operations-console side of the runtime-environment story.
Chain closure: the E1–E8 gate evidence (Req 094)
This section is the epic’s documentation-gate evidence: the chain named completely, each link’s state, and the ownership boundaries that keep two documents from drifting over the same behaviour.
The published chain. The chain is named E1–E8; the project published seven dedicated documentation issues — E1 and E3 through E8. No E2 documentation issue exists in the project (an audit of the project’s issue list confirms none was ever created), so the gate closes on the seven published documents:
| Link | Issue | Document | State |
|---|---|---|---|
| E1 | JUM-464 | Runtime Environment Contracts | Done |
| E3 | JUM-473 | Service Management Module Architecture | Done |
| E4 | JUM-479 | Service Management Contract Parity Guarantees | Done |
| E5 | JUM-482 | Service Management Operations Console | Done |
| E6 | JUM-487 | Service Management Cana Adoption, Migration and Offline Behaviour | Done |
| E7 | JUM-490 | Service Management Design System and PWA Shell | Done |
| E8 | JUM-494 | this document | this PR |
Every link is published in EN and PT-BR, synchronized per Requirement 076 — neither artifact is a stub.
Consistency: one owner per behaviour, the others link. The chain was audited for duplicated, drifting description; where two documents touch the same behaviour, the ownership is:
- The data story (where work lives, the one-way migration, offline data behaviour, export as the only recovery path) — owned by E6; E7 states the shell↔data boundary once and links, and this document links for the per-browser baseline the shared catalog extends.
- The storage port contract and the DOM-free module boundary — owned by E3; this document links for the packaging boundary instead of re-deriving it.
- The export contract and the domain-package contract — pinned by Requirement 126 (Contract 3), with the parity guarantees owned by E4; this document teaches the JUM-492 behaviour and cites the contract rather than restating it.
- The env-file/enum semantics and the runtime-env API — owned by E1; E5 owns the operations-console surfaces that consume them, including the status-surface contract every document above references.
- The shared-catalog mechanism — owned by Shared Catalog Sync (JUM-491’s dedicated document); this document states the delivery’s meaning for the epic and links.
Gate state, exactly. Under Req 094 the Project cannot be set to
Completed until this Issue is completed. At this PR: E1, E3–E7 are Done;
E8 is delivered by this PR and transitions only after it merges. No pending
check is described as passing here: the JUM-491 artifacts this document
links (SHARED-CATALOG-SYNC.md, catalogSyncClient.js, the Catalogs
module, the convergence test) land with their own PR, and the JUM-493
packaging lane has since shipped as @jumentix/designer-core — dry-run
only, per the section above — both stated, not smoothed
over. Project-completion evidence per Req 094 (linking this Issue, its PR
and commit evidence, and the documentation-integrity validation results) is
recorded in the epic’s Project Updates feed per
Requirement 102
when the gate closes.
What this document deliberately does not cover
- The shared-catalog mechanism in full — owned by Shared Catalog Sync: the OAS contract table, the authorization decision matrix, the sync client API and the verification commands.
- The per-browser data story — owned by the E6 document, Service Management Cana Adoption, Migration and Offline Behaviour; this document references it for the baseline the shared catalog extends.
- The storage port and module boundary — owned by the E3 document, Service Management Module Architecture.
- The export parity guarantees and the full contract text — owned by the E4 document, Service Management Contract Parity Guarantees, and pinned by Requirement 126, Contract 3.
- The operations console (Service Configuration, the runtime-environment editor, PM2 preview, Deploy Management) — owned by the E5 document, Service Management Operations Console.
References
- Collaboration (JUM-491):
apps/service-management-api/src/modules/Catalogs/domain/Model/Catalog.ts,CatalogAuthorizationPolicy,CatalogUseCases.ts,CatalogService.ts,CatalogDataRepository.ts,CatalogController,composeCatalogsServices.ts,spec/1.0.0.yml,apps/service-management/src/state/catalogSyncClient.js, Shared Catalog Sync - Domain-package versioning (JUM-492):
src/packages/packageVersioning.js,buildDomainPackageDocument,designerImporters.js - Packaging (JUM-493):
packages/designer-core/(manifest, barrel,scripts/build.js, README), with suitespackaging.test.ts,dom-free.test.tsandconsumer-smoke.test.ts; the source of truth remains the DOM-free boundary underapps/service-management/src/ - Suites:
catalogSyncClient.test.ts,catalogSync.integration.test.ts,designerPackageVersioning.test.ts,designerRoundTrip.test.ts,designerExporters.test.ts - Requirements: Requirement 094 (epic documentation completion gate), Requirement 076 (EN/PT parity), Requirement 070 (dry-run-only publish), Requirement 102 (project updates), Requirement 126, Contract 3 (ownership and public contracts, Contract 3)
- Sibling E-chain documents: Runtime Environment Contracts (E1), Service Management Module Architecture (E3), Service Management Contract Parity Guarantees (E4), Service Management Operations Console (E5), Service Management Cana Adoption, Migration and Offline Behaviour (E6), Service Management Design System and PWA Shell (E7), Service Management Application, Tenant and RBAC Authorization Contract
- Linear: JUM-491 , JUM-492 , JUM-493 , JUM-494 , JUM-547 , JUM-617