@ossy/platform 3.0.8 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -53,6 +53,8 @@ The server reads configuration from:
53
53
  - `build/manifest.json` — produced by `@ossy/app build`.
54
54
  - `process.env` — used by integrations for their credentials and by startup hooks.
55
55
 
56
+ Platform integrations are **boot-time and process-global** via `IntegrationService`. Per-workspace credentials are a separate design — see [WORKSPACE-INTEGRATION-SECRETS.md](../../docs/concepts/WORKSPACE-INTEGRATION-SECRETS.md) (SPEC only; not implemented).
57
+
56
58
  Optional environment variables used by the platform itself:
57
59
 
58
60
  | Variable | Description |
@@ -61,6 +63,16 @@ Optional environment variables used by the platform itself:
61
63
  | `API_URL` + `OSSY_API_KEY` | SDK configuration. When set, tasks receive a pre-configured SDK instance. |
62
64
  | `PORT` | HTTP port. |
63
65
 
66
+ ## Health check
67
+
68
+ Both `startServer` (website / app images) and `startRuntime` (CMS multi-tenant image) expose an unauthenticated liveness probe:
69
+
70
+ | Method | Path | Response |
71
+ |---|---|---|
72
+ | `GET` / `HEAD` | `/health` | `200` with `{ ok: true, status: "ok", service }` |
73
+
74
+ The route is mounted before auth and before CMS site loading, so ALB target groups and ECS container health checks can use path `/health` without cookies, API tokens, or a resolvable hostname.
75
+
64
76
  ## Exported API
65
77
 
66
78
  ```js
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ossy/platform",
3
- "version": "3.0.8",
3
+ "version": "3.1.0",
4
4
  "description": "Ossy application server runtime",
5
5
  "repository": {
6
6
  "type": "git",
@@ -44,17 +44,17 @@
44
44
  "@aws-sdk/util-create-request": "^3.972.26",
45
45
  "@aws-sdk/util-format-url": "^3.972.17",
46
46
  "@modelcontextprotocol/sdk": "^1.12.1",
47
- "@ossy/config": "^3.0.8",
48
- "@ossy/event-store": "^3.0.8",
49
- "@ossy/locale": "^3.0.8",
50
- "@ossy/manifest": "^3.0.8",
51
- "@ossy/observability": "^3.0.8",
52
- "@ossy/policies": "^3.0.8",
53
- "@ossy/schema": "^3.0.8",
54
- "@ossy/sdk": "^3.0.8",
55
- "@ossy/tokens": "^3.0.8",
56
- "@ossy/users": "^3.0.8",
57
- "@ossy/workspaces": "^3.0.8",
47
+ "@ossy/config": "^3.0.9",
48
+ "@ossy/event-store": "^3.0.9",
49
+ "@ossy/locale": "^3.0.9",
50
+ "@ossy/manifest": "^3.0.9",
51
+ "@ossy/observability": "^3.0.9",
52
+ "@ossy/policies": "^3.0.9",
53
+ "@ossy/schema": "^3.0.9",
54
+ "@ossy/sdk": "^3.0.9",
55
+ "@ossy/tokens": "^3.0.9",
56
+ "@ossy/users": "^3.0.9",
57
+ "@ossy/workspaces": "^3.1.0",
58
58
  "cookie-parser": "^1.4.7",
59
59
  "dotenv": ">=16.0.0 <18.0.0",
60
60
  "express": ">=5.0.0 <6.0.0",
@@ -74,5 +74,5 @@
74
74
  "src",
75
75
  "Dockerfile"
76
76
  ],
77
- "gitHead": "980b380c9dee0de78ef3ebabe86efcec1c223a9a"
77
+ "gitHead": "9b146fb6fd9769f800863806c5bd16016508b218"
78
78
  }
package/src/Definition.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export const Definition = {
2
2
  id: 'platform',
3
3
  title: 'Platform',
4
- description: 'Deployment platform configuration aligned with deployment-tools S3 platform-config.json.',
4
+ description: 'Deployment platform configuration aligned with deployment-tools platforms.json / PlatformConfig.',
5
5
  icon: 'controller',
6
6
  status: ['beta'],
7
7
  entitlementRequired: false,
package/src/health.js ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Stable liveness probe for ALB / ECS health checks.
3
+ *
4
+ * Mounted before auth and site loading so probes succeed whenever the
5
+ * process can accept HTTP traffic. No authentication required.
6
+ */
7
+ export const HEALTH_PATH = '/health'
8
+
9
+ /**
10
+ * @param {import('express').Express} app
11
+ * @param {{ service?: string }} [options]
12
+ */
13
+ export function mountHealthEndpoint (app, { service = 'ossy' } = {}) {
14
+ const handler = (_req, res) => {
15
+ res.status(200).json({
16
+ ok: true,
17
+ status: 'ok',
18
+ service,
19
+ })
20
+ }
21
+
22
+ app.get(HEALTH_PATH, handler)
23
+ app.head(HEALTH_PATH, handler)
24
+ }
@@ -0,0 +1,53 @@
1
+ import { describe, expect, it, jest } from '@jest/globals'
2
+ import express from 'express'
3
+ import { HEALTH_PATH, mountHealthEndpoint } from './health.js'
4
+
5
+ function createMockRes () {
6
+ const res = {
7
+ statusCode: 200,
8
+ body: undefined,
9
+ status (code) {
10
+ this.statusCode = code
11
+ return this
12
+ },
13
+ json (body) {
14
+ this.body = body
15
+ return this
16
+ },
17
+ }
18
+ return res
19
+ }
20
+
21
+ describe('mountHealthEndpoint', () => {
22
+ it('registers GET /health that returns 200 without auth', () => {
23
+ const app = express()
24
+ const get = jest.spyOn(app, 'get')
25
+ const head = jest.spyOn(app, 'head')
26
+
27
+ mountHealthEndpoint(app, { service: 'platform-runtime' })
28
+
29
+ expect(get).toHaveBeenCalledWith(HEALTH_PATH, expect.any(Function))
30
+ expect(head).toHaveBeenCalledWith(HEALTH_PATH, expect.any(Function))
31
+
32
+ const handler = get.mock.calls[0][1]
33
+ const res = createMockRes()
34
+ handler({}, res)
35
+
36
+ expect(res.statusCode).toBe(200)
37
+ expect(res.body).toEqual({
38
+ ok: true,
39
+ status: 'ok',
40
+ service: 'platform-runtime',
41
+ })
42
+ })
43
+
44
+ it('defaults service name to ossy', () => {
45
+ const app = express()
46
+ const get = jest.spyOn(app, 'get')
47
+ mountHealthEndpoint(app)
48
+ const handler = get.mock.calls[0][1]
49
+ const res = createMockRes()
50
+ handler({}, res)
51
+ expect(res.body.service).toBe('ossy')
52
+ })
53
+ })
@@ -1,12 +1,10 @@
1
1
  /**
2
- * Matches the shape written to S3 as `platform-config.json` by deployment-tools
3
- * (`BucketDeployment` → `Source.jsonData('platform-config.json', { ...config, awsRoleToAssume: undefined })`).
2
+ * Matches the `platforms.json` / PlatformConfig shape used by `@ossy/deployment-tools`.
4
3
  *
5
4
  * @see ossy/packages/deployment-tools/src/config/platform-config.js (PlatformConfig typedef)
6
- * @see ossy/packages/deployment-tools/src/infrastructure/container-deployment-target/container-deployment-target.js
7
5
  *
8
- * Note: `awsRoleToAssume` is intentionally omitted from the uploaded file (secret); do not store it here.
9
- * Optional `sesDomains` / `dnsRecords` are JSON text fields for arrays/objects.
6
+ * Note: `env` secret values live in `platforms.json` and Secrets Manager; do not store them here.
7
+ * Optional `domains` / `services` / `sesDomains` / `dnsRecords` / `githubDeployRepos` are JSON text fields.
10
8
  */
11
9
  export default {
12
10
  name: 'Platform config',
@@ -34,29 +32,29 @@ export default {
34
32
  type: 'text',
35
33
  },
36
34
  {
37
- name: 'awsKeyPairName',
38
- label: 'awsKeyPairName',
39
- type: 'text',
35
+ name: 'domains',
36
+ label: 'domains (JSON array — Route53 → CloudFront + ACM)',
37
+ type: 'textarea',
40
38
  },
41
39
  {
42
- name: 'awsStaticBucketName',
43
- label: 'awsStaticBucketName',
44
- type: 'text',
40
+ name: 'services',
41
+ label: 'services (JSON array — HTTP → ECS/ALB; TCP ignored)',
42
+ type: 'textarea',
45
43
  },
46
44
  {
47
- name: 'awsDeploymentSqsName',
48
- label: 'awsDeploymentSqsName',
49
- type: 'text',
45
+ name: 'githubDeployRepos',
46
+ label: 'githubDeployRepos (JSON array of owner/repo for OIDC)',
47
+ type: 'textarea',
50
48
  },
51
49
  {
52
- name: 'awsDeploymentSqsArn',
53
- label: 'awsDeploymentSqsArn',
54
- type: 'text',
50
+ name: 'sesDomains',
51
+ label: 'sesDomains (JSON array of strings, optional)',
52
+ type: 'textarea',
55
53
  },
56
54
  {
57
- name: 'ciGithubActionsRepo',
58
- label: 'ciGithubActionsRepo (org/repo)',
59
- type: 'text',
55
+ name: 'dnsRecords',
56
+ label: 'dnsRecords (JSON object, optional)',
57
+ type: 'textarea',
60
58
  },
61
59
  {
62
60
  name: 'ciDockerNetworkName',
@@ -64,14 +62,9 @@ export default {
64
62
  type: 'text',
65
63
  },
66
64
  {
67
- name: 'sesDomains',
68
- label: 'sesDomains (JSON array of strings, optional)',
69
- type: 'textarea',
70
- },
71
- {
72
- name: 'dnsRecords',
73
- label: 'dnsRecords (JSON object, optional)',
74
- type: 'textarea',
65
+ name: 'awsKeyPairName',
66
+ label: 'awsKeyPairName (unused after EC2 decommission)',
67
+ type: 'text',
75
68
  },
76
69
  {
77
70
  name: 'Notes',
package/src/runtime.js CHANGED
@@ -10,6 +10,7 @@ import { buildManifestSummary } from '@ossy/manifest/build-manifest-summary'
10
10
  import { ProxyInternal } from './proxy-internal.js'
11
11
  import { loadSite, invalidateSite } from './site-loader.js'
12
12
  import { createLogger } from '@ossy/observability'
13
+ import { mountHealthEndpoint } from './health.js'
13
14
 
14
15
  const log = createLogger('platform')
15
16
 
@@ -76,6 +77,8 @@ export async function startRuntime ({ port } = {}) {
76
77
  const resolvedPort = port ?? resolvePort()
77
78
 
78
79
  const app = express()
80
+ // Liveness for ALB/ECS — before site loading so probes never depend on CMS/domain.
81
+ mountHealthEndpoint(app, { service: 'platform-runtime' })
79
82
  app.use(morgan('tiny'))
80
83
  app.use(express.json({ strict: false }))
81
84
  app.use(cookieParser(process.env.OSSY_COOKIE_SECRET || 'default_secret'))
package/src/server.js CHANGED
@@ -27,6 +27,7 @@ import {
27
27
  createWorkspaceLoader,
28
28
  } from './entitlements/action-entitlement.js'
29
29
  import { closePushSseConnections, mountPushSse } from './push/mount-push-sse.js'
30
+ import { mountHealthEndpoint } from './health.js'
30
31
  import {
31
32
  createSlowRequestLogger,
32
33
  createTimedOperation,
@@ -320,6 +321,8 @@ export async function startServer (options = {}) {
320
321
  }
321
322
 
322
323
  const app = express()
324
+ // Liveness for ALB/ECS — before auth so probes never depend on cookies/Mongo.
325
+ mountHealthEndpoint(app, { service: 'platform' })
323
326
  app.use(morgan('tiny'))
324
327
  app.use(express.json({ strict: false }))
325
328
  app.use(cookieParser(ConfigService.TokenSecret))