Skip to Content
Jumentix DocsReferenceService Management Operations Console

Service Management Operations Console

This is the E5 document of the Service Management E1–E8 documentation chain (JUM-482 ). It documents the operations console — the surfaces that turn the designer from a modelling tool into something that describes and controls a running service — exactly as the code behaves today, after the operations-console lane (JUM-480 , JUM-481 , JUM-543 , JUM-544 , JUM-546 ) landed.

The console spans the Service Configuration tab (runtime profile, the PM2 ecosystem preview, and the runtime-environment editor), the Deploy Management tab, and — for its lifecycle rules — the Communication Interface Designer. These tabs carry rules a user cannot discover by clicking: which run-mode × cloud-provider and service-type × deploy-target combinations are valid and why an invalid one is rejected, which environment file a save writes, and where the PM2 preview gets its process list. This document makes those rules legible, names the check that proves each one, and states the shared-source rule that keeps them from diverging.

Two deliberate boundaries:

The shared capabilities matrix (JUM-544, JUM-481)

Both validating surfaces of the console — Service Configuration and Deploy Management — read the Requirement 059 matrices from one machine-readable source: packages/designer-core/src/model/deployCapabilityMatrix.js, the reader of the two matrix documents (JUMENTIX-DEPLOY-TARGET-AND-PACKAGING-MATRIX and JUMENTIX-SERVICE-FACTORY-CAPABILITIES-MATRIX).

The shared-source rule: the matrix is transcribed exactly once, into this module. A future contributor who extends or corrects the matrix edits the source module — and the matrix documents in the same PR, as the module header itself requires — never a second copy inside a tab’s validation. The console’s two validators already demonstrate why:

  • collectServiceConfigurationIssues (serviceConfigurationValidation.js, JUM-544) consumes the run-mode × cloud-provider support map and the active-port map.
  • collectDeployTargetIssues (deployTargetValidation.js, JUM-481) consumes the service-type × deploy-target support map, the per-service-type protocol map, the PM2-managed target set, and the driver vocabularies.

What the matrix encodes, and why an invalid combination is rejected:

  • Run mode × cloud provider. dedicated-server runs on self-hosted; virtual-machine on aws/google/azure; container on docker or self-hosted; functions on aws/vercel/cloudflare. Anything outside these rows (e.g. functions + self-hosted, or a PM2-based run mode against vercel) has no deploy target in the Requirement 059 matrix — the design could not be built by any packaging row the factory ships, so it is rejected rather than recorded.
  • Service type × deploy target. The PM2-managed rows (dedicated-server, vm, ec2) accept restapi, websocket+restapi and grpc+restapi services; the function rows (lambda, vercel-functions, cloudflare-workers) accept functions only. A functions service on a PM2 target — or a REST/realtime service on a function target — has no matrix row.
  • Protocol exposure. A service type exposes only the protocols it actually binds: restapi serves HTTP, websocket+restapi adds WebSocket, grpc+restapi adds gRPC, and function APIs are HTTP entrypoints. Asking a restapi service to bind WebSocket is rejected because the runtime would never start such a listener.
  • PM2 profile applicability. Only PM2-managed targets carry a pm2Profile (dev/staging/production); a profile on a provider-managed (serverless) target is a design the matrix cannot build, and a missing profile on a PM2-managed target leaves the ecosystem profile unchosen.
  • Active ports per service kind. A rest-api service binds no realtime listener, so its unused WebSocket/gRPC ports are not validated — only the ports the selected service kind actually binds must be integers in 1–65535 and mutually distinct.

Two vocabulary facts worth knowing before extending the matrix:

  • Two spellings coexist by design. Service Configuration uses the Requirement 126 storage vocabulary (serviceKind: rest-api, websocket-rest-api, grpc-rest-api); Deploy Management uses the matrix’s own spellings (serviceType: restapi, websocket+restapi, grpc+restapi, functions), which predate it. The module documents the divergence rather than papering over it.
  • The driver vocabularies mirror the runtime-env contract by reference. databaseDriver/keyValueDriver are defined by the Requirement 059 matrix as “the selected JUMENTIX_DATABASE_DRIVER”, so the module mirrors the Contract 1 enum sets — and the parity is asserted, not assumed (below).

Proven by: serviceConfigurationValidation.test.ts and deployTargetValidation.test.ts — the latter also reads script.js and the server.js allowlist enums to assert the driver vocabularies cannot drift apart from the runtime-env contract.

Service Configuration: validate before state (JUM-544)

The tab’s save gate validates the candidate profile before it touches state (script.js): collectServiceConfigurationIssues runs over the form values, and every issue is severity error, so an invalid profile is refused and reported on the tab’s status surface — the previous behaviour silently coerced bad ports back to the defaults. The same collector re-validates the persisted profile whenever the tab renders (renderServiceConfigStatus in inspectors.js), so an invalid state that arrives from storage is flagged instead of displayed as valid. The rules:

  1. Vocabulary — serviceKind, runMode and cloudProvider must be values the storage schema (Requirement 126, Contract 2) and the UI selects know.
  2. Ports — every port the selected service kind actually binds must be an integer in 1–65535, and no two active ports may collide; inactive ports are ignored.
  3. Run mode × cloud provider — the combination must exist in the shared matrix (above), and the rejection names the providers the matrix does support for the chosen run mode.

Multi-environment editing (JUM-480) — by cross-reference

The runtime-environment editor’s contract — accepted environments and their file mapping, the three-tier key classification (editable / read-only / never exposed), per-key enum sets, write semantics, and the error envelope — is pinned by Requirement 126, Contracts 1 and 1b and the E1 document, Runtime Environment Contracts. What this document adds is only the console-side behaviour of that contract:

  • Editing is sequential and per-file. The Environment selector (dev, staging, ci) loads exactly one environment at a time through GET /api/runtime/env; the panel always names the exact file the next save writes (Editing target: .env.dev (environment "dev"), straight from the API payload); and a save writes only that file — there is no cross-file batch edit. The per-file targeting line is pinned by pm2EcosystemUi.contract.test.ts.
  • An unaccepted environment is rejected, never coerced. The server resolves the environment parameter against an explicit accepted set (dev/development → .env.dev, staging → .env.staging, ci/test → .env.ci) and answers an unknown value with 400 naming the accepted list — it never silently falls back to dev.
  • The save is validated and confirmed. Only write-allowlisted keys are accepted, values are checked against the Contract 1 enum sets (out-of-enum values are rejected with the accepted list and nothing is written), the write is atomic (temp file, fsync, rename), and the response returns the post-write state the panel confirms (Environment "staging" saved to .env.staging).

The PM2 ecosystem preview (JUM-480) — by source, not by command string

The runtime profile pane previews the PM2 processes the designed service kind would run as. The one property that matters most about this preview — and the one most likely to be undone by a future shortcut — is where it reads from:

  • The preview reads the real pm2/ecosystem.*.cjs files through GET /api/runtime/pm2-ecosystem (Requirement 126, Contract 1b), never a hardcoded process list. Adding an app to an ecosystem file changes the preview with no code change and no server restart — the endpoint loads the ecosystem module cache-busted on every read. The rationale belongs in writing: the day a contributor embeds a literal process list or a package-manager invocation in the designer, the preview starts lying about reality, and the Bun cutover (JUM-33 , JUM-40 ) will change the invocation format underneath it. That is why Contract 1b forbids any package-manager string (pnpm run, bun run, npm run) or pm2:start:* script name in the server and the designer: the reported command is derived from the ecosystem definition (its path and the app name), so it stays true whatever package manager invokes PM2. This document therefore describes the preview by its source, not by the literal command strings it currently produces.
  • Honest edge states, never a silently blank pane. An environment without an ecosystem file (ci/test map to ecosystem.ci.cjs, which the repository does not define) is an explicit exists: false state rendered as “No PM2 ecosystem file for environment …”, not an error and not an empty list presented as real. An unreadable or syntactically broken ecosystem file surfaces as the 500-class envelope with code and path — parallel to the env-file filesystem class (JUM-543) — so a broken pm2/ecosystem.*.cjs is identifiable as an installation problem, never mistaken for a malformed request.
  • The preview environment is independent of the editing environment. The preview selector offers the environments the repository defines ecosystems for — dev, staging, production (pinned by the UI contract suite) — while the env editor targets the editable env files. Production has an ecosystem but no editable env file; the console keeps those axes separate instead of conflating them.
  • Filtering is by service kind, names come from the file. The pane selects the ecosystem apps whose names end with the suffixes the designed service kind implies (restapi for REST-only, plus websocketapi or grpcapi for the realtime kinds) and suggests a single derived command covering exactly those apps. The suffix match works for every environment prefix because no app name is enumerated in the designer.
  • The preview is transient. It lives in module-level UI state, never in the persisted service-management.v1 payload (Requirement 126, Contract 2) — a server-derived snapshot is not design state.

The PM2 monitoring dashboard — WebSocket live stream (Contract 1e) + HTTP one-shot (1c)

The Monitoring tab is runtime telemetry, not another static preview.

  • Primary UI path: WS /api/runtime/pm2-ws (Requirement 126, Contract 1e). The tab opens a WebSocket when active, subscribes with environment + refresh interval (500–2000 ms, default 1000), and renders host CPU/memory/disk charts (D3, vendored — no CDN), process stacks, filters, and start/stop/restart actions. Aggregate + per-process spark history persists in Cana under monitoringHistory in service-management.v1 (Contract 2). Leaving the tab closes the socket.
  • HTTP one-shot: GET /api/runtime/pm2-metrics (Contract 1c) remains for tests and tooling. Both transports collect through the PM2 Node API, compare live names with the selected ecosystem, include host metrics, per-process disk I/O (Linux; Darwin via in-process Bun FFI / proc_pid_rusage; Windows), and optional async-context-metrics scrapes (counters + redacted ALS Map recentStores). The scrape targets GET http://127.0.0.1:<JUMENTIX_HTTP_PORT>/async-context-metrics on each process that exposes that port — restart RestAPI from a checkout that includes the route (JUM-767+) if Monitoring reports ASYNC_CONTEXT_ROUTE_MISSING. Each process row exposes a help control describing the app’s role.

If PM2 cannot be loaded, connected or listed, the HTTP endpoint fails with the explicit PM2 metrics envelope and the WebSocket emits an error / failed action-result frame rather than inventing healthy process data.

Proven by: pm2Ecosystem.integration.test.ts (real ecosystem reads, metrics + host shape, WebSocket subscribe/actions, edit-reflected-without-restart, explicit missing-file state, honest 500 envelope, explicit rejection of unknown environments) and pm2EcosystemUi.contract.test.ts (the structural no-hardcoded-commands guarantee and WebSocket-backed Monitoring UI).

Deploy Management: the Requirement 059 metadata contract (JUM-481)

Every deploy target carries the Requirement 059 Service Management metadata contract — { name, region, runtime, serviceType, deployTarget, runtimeProtocol, databaseDriver, keyValueDriver, pm2Profile } — pinned as a backward-compatible extension of the storage schema (Requirement 126, Contract 2: the versioned key is unchanged). The tab’s rules:

  • What a target may contain is owned by collectDeployTargetIssues (deployTargetValidation.js): the six vocabularies, the service-type × deploy-target matrix row, protocol exposure, and PM2-profile applicability — every rejection names the violated constraint (the shared matrix section above gives the reasons). Every issue is severity error.
  • The list re-validates every persisted entry. renderDeployments (inspectors.js) runs the collector over each stored target and flags a rejected entry inline with its issues, instead of rendering it as a buildable design. Validation is therefore enforced on the surface where targets are consumed, regardless of how the entry arrived.
  • Legacy entries migrate forward on load, losslessly. normalizeDeploymentInput (designerState.js) migrates the pre-JUM-481 { name, type, region, runtime } shape: type becomes deployTarget through an alias map (dedicated → dedicated-server), and missing metadata takes matrix-derived defaults — the first service type the target supports, that type’s first protocol, the runtime-env contract’s default drivers, and the dev PM2 profile on PM2-managed targets only. Legacy values with no matrix counterpart (e.g. azure-functions) are kept verbatim — the migration never silently drops information; the vocabulary rule flags the entry instead, and the operator decides. normalizeStatePayload restoring the deployments section at all is the JUM-481 exception to the pinned load slice.

Proven by: deployTargetValidation.test.ts and the load-migration coverage in designerState.test.ts.

Deploy target lifecycle: edit, duplicate and field validation (JUM-546)

JUM-481 owns what a target may contain; JUM-546 owns how targets are managed. The tab’s full lifecycle is add, edit-in-place, duplicate and delete:

  • Edit-in-place. Edit loads the entry into the form; the add button becomes Save Target (with a Cancel Edit affordance) and the same validation gate applies to the replacement. An edit may keep its own name — the uniqueness check excludes the entry being replaced — and deleting an entry mid-edit cancels the edit (or re-points the index) instead of writing over another target.
  • Duplicate. Duplicate stores an independent deep copy — never a shared reference — renamed by the (copy) rule (name (copy), then name (copy 2), … until unique, compared case-insensitively). Deploy targets are the one thing operators create in near-identical sets (the same service across staging and production, the same configuration across regions); duplication is the primary defence against the retyping inconsistencies the field validation then has to catch.
  • Field validation — collectDeployTargetFieldIssues in deployTargetLifecycleValidation.js, run at the add/edit gate next to the JUM-481 matrix rules: the name is required and unique; the runtime/version is required and must match a name-plus-version pattern (nodejs22.x, python3.12 — free text like latest is rejected); the region is required on every cloud target and optional on the self-hosted Dedicated Server (SSH) row, where the field may carry host information instead. The self-hosted set is read from the shared matrix reader (SELF_HOSTED_DEPLOY_TARGETS in packages/designer-core/src/model/deployCapabilityMatrix.js), never transcribed. Every rejection names the reason on the JUM-543 status surface, and the candidate never touches state.
  • Target-type-aware field hints. The hint line under the form (deployTargetFieldHint) follows the selected matrix row: PM2-managed targets (VM/dedicated rows) are pointed at host information and the PM2 profile; function providers at the runtime/version, with the PM2-profile select disabled and cleared — a PM2 profile does not apply.

Proven by: deployTargetLifecycle.test.ts (the rules as pure functions) and deployTargetLifecycle.browser.integration.test.ts (the real UI in WebKit: validated add, rejection reasons on the status region, the (copy) renaming rule, edit-in-place independence, the per-type region rule, and the hint/PM2-select behaviour).

Lifecycle rules — what exists today, and what is open

The issue chain reserves full lifecycle (edit-in-place, duplicate, field-level validation, uniqueness) for both console lists to JUM-545  (interface adapters) and JUM-546  (deploy targets). JUM-545 and JUM-546 have both landed — this section records the lifecycle the code actually implements today.

Interface adapters (Communication Interface Designer). An adapter is added, edited in place and deleted (JUM-545). The add and edit gates share one validation path (upsertInterfaceAdapter in src/validation/interfaceAdapterValidation.js): the framework must belong to the per-interface-type subset of the canonical runtime matrix (src/model/interfaceFrameworkMatrix.js — the JUM-461 canonical spellings, with no derby/sails alias duplicates), the entrypoint must be a TypeScript/JavaScript path under src/interface/, the controller mapping must match the XController.action shape, and duplicates — same type + entrypoint, or same controller mapping — are rejected with the reason on the status surface. Persisted entries that predate the gate are flagged inline instead of passing as valid designs.

Deploy targets (Deploy Management). The full lifecycle has landed with JUM-546 — add, edit-in-place, duplicate and delete, with field-level validation (unique name, runtime/version pattern, region per target type) at the add/edit gate and the (copy) renaming rule on duplicates. See the JUM-546 section above.

The reason this honest-gap section exists at all: the console’s lists are the surfaces where a design becomes an operational intent, and an entry that only becomes valid after a reload — or a duplicated entry that nothing rejects — is a rule the user cannot see. Naming the owning issues keeps the rule visible until the code catches up.

The status-surface contract every console surface follows (JUM-543)

The console never blocks on feedback. JUM-543 replaced every window.alert with non-blocking status surfaces, and every console panel follows the same model:

  • A single aria-live toast region (#status-region, role="status", aria-live="polite") announces validation messages and API failures; info-severity notices auto-hide, errors persist. Destructive-action gates deliberately keep their window.confirm — a toast is not a substitute for a gate.
  • Inline status lines per panel — the PM2 preview status, the Service Configuration status, the runtime-env status and the per-file targeting line — carry failures with environment, file and cause where the user is looking, instead of a silent console error.
  • The client renders the API’s error envelope verbatim. error / details, plus code and path on the 500 filesystem classes, are surfaced exactly as returned — there is no client-side error remapping, so the parse/validation/filesystem split documented in Requirement 126 reaches the user intact.

What this document deliberately does not cover

  • Persistence and boot behaviour — the IDesignerStore port and the landed Cana migration (JUM-484 ) belong to the E3 document, and their user-facing promises to the E6 document.
  • Export/import of the console tabs — since JUM-547 the interfaces, serviceConfiguration and deployments sections cross the full-suite JSON export, and runtimeEnvironment crosses as the environment selection only; the E4 document owns that scope and its proof in Service Management Contract Parity Guarantees.
  • The literal PM2 invocation format — pinned by Requirement 126 Contract 1b and due to change with the Bun cutover (JUM-33 , JUM-40 ); the preview is documented by its source precisely so this document survives that cutover.

References

  • Shared matrix reader: packages/designer-core/src/model/deployCapabilityMatrix.js; matrix documents: JUMENTIX-DEPLOY-TARGET-AND-PACKAGING-MATRIX, JUMENTIX-SERVICE-FACTORY-CAPABILITIES-MATRIX
  • Validators: serviceConfigurationValidation.js, deployTargetValidation.js, deployTargetLifecycleValidation.js
  • Server endpoints: server.js; UI glue: script.js, inspectors.js, state/migration: designerState.js
  • Ecosystem sources: pm2/ecosystem.dev.config.cjs, pm2/ecosystem.staging.config.cjs, pm2/ecosystem.production.config.cjs
  • Suites: serviceConfigurationValidation.test.ts, deployTargetValidation.test.ts, deployTargetLifecycle.test.ts, designerState.test.ts, pm2EcosystemUi.contract.test.ts, runtimeEnvUi.contract.test.ts, pm2Ecosystem.integration.test.ts, runtimeEnv.integration.test.ts, runtimeEnvContract.integration.test.ts, deployTargetLifecycle.browser.integration.test.ts
  • Requirements: Requirement 126, Contracts 1 and 1b (Contracts 1, 1b and 2), 059 (the deploy and factory matrices), 076 (EN/PT parity)
  • Sibling E-chain documents: Runtime Environment Contracts (E1), Service Management Module Architecture (E3), Service Management Contract Parity Guarantees (E4), Service Management Application, Domain Designer Features and Usage
  • Linear: JUM-480 , JUM-481 , JUM-543 , JUM-544 , JUM-545 , JUM-546 , JUM-547 , JUM-464 , JUM-33 , JUM-40