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 wire contracts are linked, not duplicated. The runtime-env API and the PM2 ecosystem endpoint are pinned by Requirement 126, Contracts 1 and 1b, and the env-file/enum semantics by the E1 document, Runtime Environment Contracts (JUM-464 ). This document references them; it does not restate them.
- Persistence and boot behavior are out of scope. The storage port and the storage migration belong to the E3 document, Service Management Module Architecture; their user-facing promises — where the data lives, the loss modes and the export recourse — belong to the E6 document, Service Management Cana Adoption, Migration and Offline Behaviour.
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-serverruns onself-hosted;virtual-machineonaws/google/azure;containerondockerorself-hosted;functionsonaws/vercel/cloudflare. Anything outside these rows (e.g.functions+self-hosted, or a PM2-based run mode againstvercel) 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) acceptrestapi,websocket+restapiandgrpc+restapiservices; the function rows (lambda,vercel-functions,cloudflare-workers) acceptfunctionsonly. Afunctionsservice 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:
restapiserves HTTP,websocket+restapiadds WebSocket,grpc+restapiadds gRPC, and function APIs are HTTP entrypoints. Asking arestapiservice 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-apiservice 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/keyValueDriverare defined by the Requirement 059 matrix as “the selectedJUMENTIX_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:
- Vocabulary —
serviceKind,runModeandcloudProvidermust be values the storage schema (Requirement 126, Contract 2) and the UI selects know. - 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.
- 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 throughGET /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 bypm2EcosystemUi.contract.test.ts. - An unaccepted environment is rejected, never coerced. The server
resolves the
environmentparameter against an explicit accepted set (dev/development→.env.dev,staging→.env.staging,ci/test→.env.ci) and answers an unknown value with400naming the accepted list — it never silently falls back todev. - 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.*.cjsfiles throughGET /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) orpm2: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/testmap toecosystem.ci.cjs, which the repository does not define) is an explicitexists: falsestate 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 withcodeandpath— parallel to the env-file filesystem class (JUM-543) — so a brokenpm2/ecosystem.*.cjsis 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 (
restapifor REST-only, pluswebsocketapiorgrpcapifor 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.v1payload (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 undermonitoringHistoryinservice-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 optionalasync-context-metricsscrapes (counters + redacted ALS MaprecentStores). The scrape targetsGET http://127.0.0.1:<JUMENTIX_HTTP_PORT>/async-context-metricson each process that exposes that port — restart RestAPI from a checkout that includes the route (JUM-767+) if Monitoring reportsASYNC_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 severityerror. - 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:typebecomesdeployTargetthrough 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 thedevPM2 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.normalizeStatePayloadrestoring thedeploymentssection 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.
Editloads the entry into the form; the add button becomesSave Target(with aCancel Editaffordance) 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.
Duplicatestores an independent deep copy — never a shared reference — renamed by the(copy)rule (name (copy), thenname (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 —
collectDeployTargetFieldIssuesindeployTargetLifecycleValidation.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 likelatestis 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_TARGETSinpackages/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 theirwindow.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, pluscodeandpathon 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
IDesignerStoreport 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,serviceConfigurationanddeploymentssections cross the full-suite JSON export, andruntimeEnvironmentcrosses 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