@onlineapps/conn-orch-validator 7.0.0 → 8.0.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/CHANGELOG.md +2558 -2
- package/README.md +1038 -4
- package/docs/DESIGN.md +3 -1
- package/manifests/biz-service.manifest.json +658 -0
- package/manifests/library.manifest.json +324 -0
- package/package.json +12 -6
- package/src/CookbookTestRunner.js +408 -101
- package/src/CookbookTestUtils.js +7 -8
- package/src/ServiceReadinessValidator.js +10 -35
- package/src/ValidationOrchestrator.js +219 -71
- package/src/cli/biz-ci-gate.js +176 -33
- package/src/cli/oa-lint-scripts.js +221 -0
- package/src/cli/oa-sync-template.js +1020 -0
- package/src/cli/oa-validate.js +474 -0
- package/src/helpers/README.md +2 -1
- package/src/helpers/createServiceReadinessTests.js +60 -4
- package/src/index.js +33 -3
- package/src/lint/scripts/lintScripts.js +298 -0
- package/src/manifest/checks/composeRunnerBlock.js +222 -0
- package/src/manifest/checks/composeShape.js +165 -0
- package/src/manifest/checks/contractBridge.js +181 -0
- package/src/manifest/checks/discoveryOrphan.js +50 -0
- package/src/manifest/checks/docsLintBridge.js +553 -0
- package/src/manifest/checks/fileAbsent.js +35 -0
- package/src/manifest/checks/gitTracked.js +204 -0
- package/src/manifest/checks/index.js +111 -0
- package/src/manifest/checks/libraryContext.js +226 -0
- package/src/manifest/checks/libraryDocs.js +75 -0
- package/src/manifest/checks/libraryPackage.js +272 -0
- package/src/manifest/checks/librarySource.js +274 -0
- package/src/manifest/checks/libraryTests.js +121 -0
- package/src/manifest/checks/libraryWorkspace.js +293 -0
- package/src/manifest/checks/readmeRegion.js +135 -0
- package/src/manifest/checks/scriptHeaders.js +79 -0
- package/src/manifest/checks/serviceConfig.js +390 -0
- package/src/manifest/checks/serviceConnectors.js +81 -0
- package/src/manifest/checks/serviceDb.js +388 -0
- package/src/manifest/checks/serviceFiles.js +754 -0
- package/src/manifest/checks/serviceIdentityRows.js +351 -0
- package/src/manifest/checks/serviceRuntime.js +295 -0
- package/src/manifest/checks/serviceScripts.js +213 -0
- package/src/manifest/deployabilitySignal.js +121 -0
- package/src/manifest/discovery.js +386 -0
- package/src/manifest/loadManifest.js +62 -0
- package/src/manifest/manifestShape.js +446 -0
- package/src/manifest/report.js +245 -0
- package/src/manifest/runManifest.js +449 -0
- package/src/manifest/serviceIdentity.js +140 -0
- package/src/manifest/walk.js +74 -0
- package/src/manifest/workspaceRoot.js +242 -0
- package/src/mocks/MockMQClient.js +13 -30
- package/src/mocks/MockRegistry.js +4 -2
- package/src/mocks/MockStorage.js +4 -2
- package/src/sync/docsRegion.js +463 -0
- package/src/sync/generatedRegion.js +228 -0
- package/src/sync/readmeLocation.js +182 -0
- package/src/sync/readmePointer.js +477 -0
- package/src/sync/serviceTemplate.js +583 -0
- package/src/sync/sharedEnv.js +162 -0
- package/src/sync/uniformFiles.js +474 -0
- package/src/utils/bizCiGateContract.js +131 -7
- package/src/utils/connectorContract.js +97 -7
- package/src/utils/cookbookFormat.js +81 -40
- package/src/utils/deployContract.js +140 -9
- package/src/utils/envContract.js +57 -1
- package/src/utils/handlerRef.js +181 -0
- package/src/utils/installContract.js +287 -41
- package/src/utils/libCompat.js +29 -7
- package/src/utils/migrationOrder.js +163 -0
- package/src/utils/preValidation.js +20 -7
- package/src/utils/setupDatabase.js +194 -13
- package/src/utils/testCoverageContract.js +539 -0
- package/src/utils/testNamespace.js +247 -23
- package/src/utils/throwawaySchema.js +207 -0
- package/src/validators/ServiceStructureValidator.js +2 -1
- package/templates/business-service/.dockerignore +42 -0
- package/templates/business-service/.gitlab-ci.yml +290 -0
- package/templates/business-service/Dockerfile +27 -0
- package/templates/business-service/README.md +213 -0
- package/templates/business-service/config/biz-docs-lint.tree.json +10 -0
- package/templates/business-service/config/env-templates/__SERVICE_NAME__.env +4 -0
- package/templates/business-service/config/env-templates/shared.env +65 -0
- package/templates/business-service/config/service/config.json +14 -0
- package/templates/business-service/config/service/integration-contract.json +12 -0
- package/templates/business-service/config/service/operations.json +41 -0
- package/templates/business-service/docker-compose.production.yml +60 -0
- package/templates/business-service/docker-compose.yml +93 -0
- package/templates/business-service/docs/80-setup/INSTALL.md +101 -0
- package/templates/business-service/docs/80-setup/PLATFORM_MATRIX.md +65 -0
- package/templates/business-service/docs/80-setup/README.md +18 -0
- package/templates/business-service/docs/80-setup/VALIDATION.md +78 -0
- package/templates/business-service/docs/README.md +18 -0
- package/templates/business-service/gitignore +42 -0
- package/templates/business-service/index.js +10 -0
- package/templates/business-service/init.sh +54 -0
- package/templates/business-service/jest.config.js +6 -0
- package/templates/business-service/package.json.template +31 -0
- package/templates/business-service/scripts/verify-deploy-uniform.sh +180 -0
- package/templates/business-service/src/handlers/v3/echo.js +39 -0
- package/templates/business-service/tests/cookbooks/echo.json +36 -0
- package/templates/business-service/tests/unit/handler.test.js +78 -0
- package/src/WorkflowTestRunner.js +0 -402
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "3.0",
|
|
3
|
+
"operations": {
|
|
4
|
+
"echo": {
|
|
5
|
+
"description": "Return the submitted text unchanged. The example operation a new service replaces with its own.",
|
|
6
|
+
"handler": "handlers/v3/echo#echo",
|
|
7
|
+
"mutates": false,
|
|
8
|
+
"resource_type": null,
|
|
9
|
+
"bundle_scope": "platform",
|
|
10
|
+
"minRole": "EDITOR",
|
|
11
|
+
"input": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"additionalProperties": false,
|
|
14
|
+
"required": ["text"],
|
|
15
|
+
"properties": {
|
|
16
|
+
"text": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "Text to return unchanged",
|
|
19
|
+
"minLength": 1,
|
|
20
|
+
"maxLength": 1000,
|
|
21
|
+
"examples": ["cookbook"]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"output": {
|
|
26
|
+
"type": "object",
|
|
27
|
+
"required": ["text", "length"],
|
|
28
|
+
"properties": {
|
|
29
|
+
"text": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"description": "The submitted text, byte for byte"
|
|
32
|
+
},
|
|
33
|
+
"length": {
|
|
34
|
+
"type": "integer",
|
|
35
|
+
"description": "Number of UTF-16 code units in the submitted text"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Production compose for __SERVICE_NAME__
|
|
2
|
+
#
|
|
3
|
+
# The image is the one CI built and pushed, pinned by its DIGEST (requirement R1,
|
|
4
|
+
# api/docs/operations/biz-rework-deployment-requirements.md). The build job
|
|
5
|
+
# publishes BIZ_IMAGE_DIGEST as a dotenv artifact and the deploy job exports it;
|
|
6
|
+
# with the value missing, the ${VAR:?…} marker fails the run by name instead of
|
|
7
|
+
# resolving to a nameless image. A mutable tag is refused because two concurrent
|
|
8
|
+
# pipelines make it nondeterministic between the push and the pull, and a
|
|
9
|
+
# rollback then has no target to roll back to.
|
|
10
|
+
#
|
|
11
|
+
# HOST CONTRACT OF THE BIZ BOX — owner decision
|
|
12
|
+
# api/docs/governance/confirmations/server-topology.md 004:
|
|
13
|
+
# * ../shared.env is the ONE host-level env file, at
|
|
14
|
+
# /var/www/html/oadrive/api_biz/shared.env. Its values are the infra box's
|
|
15
|
+
# property, so it is RENDERED there from that box's own env-active by
|
|
16
|
+
# api/scripts/provision/render-biz-shared-env.sh and carried to the biz box by
|
|
17
|
+
# the operator (server-topology 004 and 005) — role-biz writes no env file.
|
|
18
|
+
# This compose REFERENCES it and never copies it: one file per box, not one
|
|
19
|
+
# copy per repository.
|
|
20
|
+
# * ./config/env-active/__SERVICE_NAME__.env carries only this service's own
|
|
21
|
+
# keys.
|
|
22
|
+
# * biz_network is external and created by role-biz. It is the ONLY network
|
|
23
|
+
# that box has: the infra box's networks do not exist there, so naming one
|
|
24
|
+
# of them here would make the service undeployable — which is why the gate
|
|
25
|
+
# and tests assert that no other network name appears in this file at all.
|
|
26
|
+
#
|
|
27
|
+
# Usage: BIZ_IMAGE_DIGEST=sha256:… docker compose -f docker-compose.production.yml up -d
|
|
28
|
+
|
|
29
|
+
services:
|
|
30
|
+
__CONTAINER_NAME__:
|
|
31
|
+
image: "registry.gitlab.com/onlineapps/oadrive/__REPO_NAME__@${BIZ_IMAGE_DIGEST:?BIZ_IMAGE_DIGEST is required - the CI deploy job must export the digest of the pushed image}"
|
|
32
|
+
# There is no build section on purpose: docker refuses a build tag that
|
|
33
|
+
# carries a digest, and an image built here would in any case not be the one
|
|
34
|
+
# CI proved. Build locally through docker-compose.yml instead.
|
|
35
|
+
container_name: __CONTAINER_NAME__
|
|
36
|
+
deploy:
|
|
37
|
+
resources:
|
|
38
|
+
limits:
|
|
39
|
+
# Same norm as the dev compose, and deliberately the same number —
|
|
40
|
+
# owner decision api/docs/governance/confirmations/biz-memory-limits.md
|
|
41
|
+
# 001 names dev and production together, so production is not the
|
|
42
|
+
# place to discover a different limit.
|
|
43
|
+
#
|
|
44
|
+
# WHAT IT COVERS: the resident service process. There is no test
|
|
45
|
+
# runner here — suites do not run in production — so nothing else
|
|
46
|
+
# shares this cgroup by construction.
|
|
47
|
+
#
|
|
48
|
+
# This file used to declare no limit at all, which meant a new service
|
|
49
|
+
# reached production unbounded while its dev container was capped.
|
|
50
|
+
memory: 512M
|
|
51
|
+
env_file:
|
|
52
|
+
- ../shared.env
|
|
53
|
+
- ./config/env-active/__SERVICE_NAME__.env
|
|
54
|
+
restart: unless-stopped
|
|
55
|
+
networks:
|
|
56
|
+
- biz_network
|
|
57
|
+
|
|
58
|
+
networks:
|
|
59
|
+
biz_network:
|
|
60
|
+
external: true
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
services:
|
|
2
|
+
__CONTAINER_NAME__:
|
|
3
|
+
build:
|
|
4
|
+
context: .
|
|
5
|
+
container_name: __CONTAINER_NAME__
|
|
6
|
+
user: "1000:1000"
|
|
7
|
+
# No `ports:`. A biz service binds nothing — work arrives over MQ and is
|
|
8
|
+
# dispatched in-process (ADR 0005), and
|
|
9
|
+
# docs/biz/00-model/service-shape.md § No-HTTP tests states the rule for the
|
|
10
|
+
# compose file directly. The scaffold used to publish a port here, so every
|
|
11
|
+
# new service broke the shape at the moment it was created.
|
|
12
|
+
deploy:
|
|
13
|
+
resources:
|
|
14
|
+
limits:
|
|
15
|
+
# The platform norm for a biz service in operation — owner decision
|
|
16
|
+
# api/docs/governance/confirmations/biz-memory-limits.md 001, which
|
|
17
|
+
# holds in dev AND in production. It is a prescribed value, not one
|
|
18
|
+
# measured on this service or on any other.
|
|
19
|
+
#
|
|
20
|
+
# WHAT IT COVERS: the resident service process, and nothing else. No
|
|
21
|
+
# test suite may share this cgroup — that is why the runner below has
|
|
22
|
+
# a budget of its own, and why raising this number is never the fix
|
|
23
|
+
# for a suite that runs out of memory.
|
|
24
|
+
#
|
|
25
|
+
# Revise it when this service exceeds 60% of the limit in operation,
|
|
26
|
+
# measured the way the norm was: cgroup memory.stat:anon, sampled
|
|
27
|
+
# inside the running container.
|
|
28
|
+
memory: 512M
|
|
29
|
+
volumes:
|
|
30
|
+
- .:/app
|
|
31
|
+
env_file:
|
|
32
|
+
- ./config/env-active/shared.env
|
|
33
|
+
- ./config/env-active/__SERVICE_NAME__.env
|
|
34
|
+
networks:
|
|
35
|
+
- api_network
|
|
36
|
+
restart: unless-stopped
|
|
37
|
+
entrypoint: ["/bin/sh", "init.sh"]
|
|
38
|
+
# PID 1 is node, never npm: init.sh execs what it is given, and npm does
|
|
39
|
+
# not forward SIGTERM - a stop would be ten seconds of waiting and then a
|
|
40
|
+
# SIGKILL through a service that never closed a channel. Row R-PID1,
|
|
41
|
+
# confirmation biz-compose-pid1 001/002.
|
|
42
|
+
command: ["node", "index.js"]
|
|
43
|
+
|
|
44
|
+
# --- oa-test-runner v1
|
|
45
|
+
# One-shot test runner — part of the unified biz service shape
|
|
46
|
+
# (owner decision 2026-09-07, docs/governance/confirmations/biz-test-container.md 001).
|
|
47
|
+
#
|
|
48
|
+
# Same build, same env_file, same networks as the service above, so
|
|
49
|
+
# "tests run in a container" stays true — but its OWN memory budget.
|
|
50
|
+
#
|
|
51
|
+
# WHY: two loads were sharing one budget, not one load being too big. Measured
|
|
52
|
+
# by the BIZ-META thread on 2026-09-06/07 inside the containers, from
|
|
53
|
+
# /sys/fs/cgroup/memory.peak (api_biz/meta/docker-compose.yml carries the full
|
|
54
|
+
# numbers): the unit suite peaked at 343 MiB and the service's own peak was
|
|
55
|
+
# 132 MiB, against a shared 384 MiB limit — so a `docker exec` test run was
|
|
56
|
+
# killed by the kernel AND took the service down with it, while the
|
|
57
|
+
# integration suite failed intermittently on a different file each time.
|
|
58
|
+
#
|
|
59
|
+
# The 1g below is the platform NORM for the runner — owner decision
|
|
60
|
+
# api/docs/governance/confirmations/biz-memory-limits.md 001, the same
|
|
61
|
+
# decision that sets the service's 512M above. It applies in dev and CI only,
|
|
62
|
+
# and it assumes one suite at a time (`--maxWorkers=1`). It is no longer
|
|
63
|
+
# "meta's number carried over": measurement is what produced the norm, the
|
|
64
|
+
# norm is what this file now states.
|
|
65
|
+
#
|
|
66
|
+
# `mem_limit`, not the `deploy.resources.limits` form: both were measured to
|
|
67
|
+
# apply on `docker compose run` with Compose v5.1.3, and this is the plain
|
|
68
|
+
# Compose one rather than the swarm section Compose honours in part.
|
|
69
|
+
#
|
|
70
|
+
# NOT started by a plain `docker compose up` — the `test` profile keeps it
|
|
71
|
+
# out. No `restart`, no healthcheck: it runs once and exits. The one
|
|
72
|
+
# documented way to run it is the npm scripts in package.json, which wrap:
|
|
73
|
+
# docker compose run --rm __CONTAINER_NAME___tests npm run test:unit
|
|
74
|
+
__CONTAINER_NAME___tests:
|
|
75
|
+
build:
|
|
76
|
+
context: .
|
|
77
|
+
profiles:
|
|
78
|
+
- test
|
|
79
|
+
user: "1000:1000"
|
|
80
|
+
mem_limit: 1g
|
|
81
|
+
volumes:
|
|
82
|
+
- .:/app
|
|
83
|
+
env_file:
|
|
84
|
+
- ./config/env-active/shared.env
|
|
85
|
+
- ./config/env-active/__SERVICE_NAME__.env
|
|
86
|
+
networks:
|
|
87
|
+
- api_network
|
|
88
|
+
entrypoint: ["/bin/sh", "init.sh"]
|
|
89
|
+
# --- end oa-test-runner v1
|
|
90
|
+
|
|
91
|
+
networks:
|
|
92
|
+
api_network:
|
|
93
|
+
external: true
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
Parent: [../../README.md](../../README.md)
|
|
2
|
+
Owns: the installation flow for a new __REPO_NAME__ instance — prerequisites, environment preparation, the bootstrap sequence, and what this service does NOT need.
|
|
3
|
+
|
|
4
|
+
# INSTALL
|
|
5
|
+
|
|
6
|
+
## Scope
|
|
7
|
+
|
|
8
|
+
Bootstraps `__REPO_NAME__` — a business service with no database and no object
|
|
9
|
+
storage. It binds no port: work arrives over MQ and is dispatched in-process
|
|
10
|
+
(ADR 0005, `api/docs/biz/80-decisions/0005-no-http-in-biz-containers.md`), so
|
|
11
|
+
there is no HTTP endpoint to install or to call afterwards.
|
|
12
|
+
|
|
13
|
+
What the service needs is declared once, in
|
|
14
|
+
`config/service/integration-contract.json` → `requiredConnectors`: `mq` only.
|
|
15
|
+
`db`, `redis` and `minio` are `false`, which is why this document has no SQL
|
|
16
|
+
step — the installation contract derives the SQL half from the contract's
|
|
17
|
+
`database` block, and this service declares none.
|
|
18
|
+
|
|
19
|
+
## Prerequisites
|
|
20
|
+
|
|
21
|
+
- Docker and Docker Compose v2.
|
|
22
|
+
- Node.js — the major this repository declares in `package.json` `engines.node`.
|
|
23
|
+
The same major is in the `Dockerfile` base image and in `.gitlab-ci.yml`;
|
|
24
|
+
requirement R3 of the deploy contract fails the gate if the three disagree, so
|
|
25
|
+
read the major there rather than from a number written here.
|
|
26
|
+
- A reachable RabbitMQ and service registry. Their URLs arrive as
|
|
27
|
+
`${RABBITMQ_URL}` and `${REGISTRY_URL}`, the two placeholders
|
|
28
|
+
`config/service/config.json` carries; ConfigLoader substitutes them at boot and
|
|
29
|
+
fails by name if either is unset.
|
|
30
|
+
- The external Docker network the compose file names (`api_network` in dev), and
|
|
31
|
+
on the production biz box `biz_network` — both are created outside this
|
|
32
|
+
repository.
|
|
33
|
+
|
|
34
|
+
## Environment preparation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
mkdir -p config/env-active
|
|
38
|
+
cp config/env-templates/shared.env config/env-active/shared.env
|
|
39
|
+
cp config/env-templates/__SERVICE_NAME__.env config/env-active/__SERVICE_NAME__.env
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Then edit both files: `shared.env` carries the infrastructure URLs and the
|
|
43
|
+
testing namespace (`TESTING_TENANT_ID`, `TESTING_WORKSPACE_ID`), the service file
|
|
44
|
+
carries what belongs to this service alone. `config/env-active/` is gitignored —
|
|
45
|
+
the templates are the versioned half, the active files are per-instance.
|
|
46
|
+
|
|
47
|
+
Every variable a handler reads must additionally be declared in
|
|
48
|
+
`config/service/integration-contract.json` → `env`, with a reason per name. The
|
|
49
|
+
scaffold declares no `env` block because it reads none: the only two names it
|
|
50
|
+
needs are the `${…}` placeholders above, and the contract must not repeat what a
|
|
51
|
+
config placeholder already covers. The first handler that reads an environment
|
|
52
|
+
variable adds the block.
|
|
53
|
+
|
|
54
|
+
On the production biz box the second env file is a host-level `../shared.env`,
|
|
55
|
+
written once per box and referenced rather than copied (owner decision
|
|
56
|
+
`api/docs/governance/confirmations/server-topology.md` 004).
|
|
57
|
+
|
|
58
|
+
## Bootstrap sequence
|
|
59
|
+
|
|
60
|
+
1. Prepare the env files (above).
|
|
61
|
+
2. Start the dependencies this service declares required — MQ and the registry.
|
|
62
|
+
They are not part of this repository.
|
|
63
|
+
3. Start the service:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
docker compose up -d --build
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`init.sh` installs dependencies on first start and skips the install when
|
|
70
|
+
neither `package.json` nor `package-lock.json` changed.
|
|
71
|
+
|
|
72
|
+
That command builds the SERVICE only. The one-shot test runner carries
|
|
73
|
+
`profiles: [test]`, so it is outside a plain build as well as outside a plain
|
|
74
|
+
`up` — measured 2026-09-14: `docker compose build --dry-run` builds one image,
|
|
75
|
+
`docker compose --profile test build --dry-run` builds two. Build it when the
|
|
76
|
+
suites are to run against this checkout:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
docker compose --profile test build
|
|
80
|
+
```
|
|
81
|
+
4. Verify the install — [VALIDATION.md](VALIDATION.md) states the checks and what
|
|
82
|
+
each one must print.
|
|
83
|
+
|
|
84
|
+
## Production
|
|
85
|
+
|
|
86
|
+
The production compose pins the image by digest, so it is pulled, never built:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
BIZ_IMAGE_DIGEST=sha256:… docker compose -f docker-compose.production.yml pull
|
|
90
|
+
BIZ_IMAGE_DIGEST=sha256:… docker compose -f docker-compose.production.yml up -d
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
CI publishes that digest from the build stage and the deploy job refuses to run
|
|
94
|
+
without it (requirement R1). A first deploy also clones the checkout on the box
|
|
95
|
+
if it is missing, so onboarding a new service needs no manual step there.
|
|
96
|
+
|
|
97
|
+
## Status
|
|
98
|
+
|
|
99
|
+
Current. One of the three files the installation contract requires by name —
|
|
100
|
+
`biz-ci-gate verify-install-contract` (and `verify-contract`, which folds it in)
|
|
101
|
+
fails when it is missing.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
Parent: [../../README.md](../../README.md)
|
|
2
|
+
Owns: the execution differences between Linux, macOS and a CI/cloud runner when installing or validating __REPO_NAME__.
|
|
3
|
+
|
|
4
|
+
# PLATFORM MATRIX
|
|
5
|
+
|
|
6
|
+
Every command in this document runs from the repository root
|
|
7
|
+
(`api_biz/__SERVICE_NAME__/`). Relative paths in prose and in links resolve from
|
|
8
|
+
this document's own directory (`docs/80-setup/`) instead — the two bases differ,
|
|
9
|
+
so a path copied from one into the other does not resolve.
|
|
10
|
+
|
|
11
|
+
The service itself runs inside the image the `Dockerfile` builds, so the host
|
|
12
|
+
platform decides how the containers are reached, never what runs in them. That
|
|
13
|
+
is why the differences below are about the shell around the container, not about
|
|
14
|
+
the service.
|
|
15
|
+
|
|
16
|
+
## Linux
|
|
17
|
+
|
|
18
|
+
- Reference environment.
|
|
19
|
+
- The compose file runs the service as `user: "1000:1000"`. On a host whose own
|
|
20
|
+
uid is not 1000, files the container writes into the bind mount (`node_modules`,
|
|
21
|
+
`conn-runtime/`) belong to that uid — run the container as your own uid, or
|
|
22
|
+
accept the ownership.
|
|
23
|
+
- Start and validate:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
docker compose up -d --build
|
|
27
|
+
npm run test:container
|
|
28
|
+
npm run test:cookbooks:container
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## macOS
|
|
32
|
+
|
|
33
|
+
- Same commands; Docker Desktop maps the bind mount, so the `user: "1000:1000"`
|
|
34
|
+
line above has no visible effect on file ownership.
|
|
35
|
+
- Run the suites through the container scripts above rather than on the host: the
|
|
36
|
+
runner has the env files and the same image the service uses. A bare `npm test`
|
|
37
|
+
on the host runs against whatever Node and environment the shell happens to
|
|
38
|
+
carry.
|
|
39
|
+
- The dependency guard in `init.sh` uses `md5sum`, which runs inside the Alpine
|
|
40
|
+
container, not on the host — macOS shipping `md5` instead is irrelevant here.
|
|
41
|
+
|
|
42
|
+
## CI / cloud runner
|
|
43
|
+
|
|
44
|
+
- Non-interactive: no prompts, and nothing may depend on a developer's shell.
|
|
45
|
+
`.gitlab-ci.yml` installs with `npm ci` and runs `npm run test:ci`.
|
|
46
|
+
- The contract gates run from the installed validator, not from a checkout of the
|
|
47
|
+
platform repository:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
node node_modules/@onlineapps/conn-orch-validator/src/cli/biz-ci-gate.js verify-contract
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`verify-contract` needs `LIBRARIES_SSOT_URL` (requirement R6 compares the
|
|
54
|
+
`@onlineapps/*` pins against the platform library set) and the service's
|
|
55
|
+
dependencies installed — the test-coverage half asks the service's own jest
|
|
56
|
+
which files it would run.
|
|
57
|
+
- The build stage publishes the image digest as a dotenv artifact and the deploy
|
|
58
|
+
stage refuses to run without it; both stages are declared in `.gitlab-ci.yml`.
|
|
59
|
+
- Windows is outside the supported matrix.
|
|
60
|
+
|
|
61
|
+
## Status
|
|
62
|
+
|
|
63
|
+
Current. One of the three files the installation contract requires by name —
|
|
64
|
+
`biz-ci-gate verify-install-contract` (and `verify-contract`, which folds it in)
|
|
65
|
+
fails when it is missing.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Parent: [../README.md](../README.md)
|
|
2
|
+
Owns: the map of the installation branch — which of the three installation documents answers which question
|
|
3
|
+
|
|
4
|
+
# Setup
|
|
5
|
+
|
|
6
|
+
An operator installs this repository from these three documents, in this order.
|
|
7
|
+
|
|
8
|
+
- [INSTALL](INSTALL.md) — prerequisites, environment preparation and the bootstrap sequence
|
|
9
|
+
- [PLATFORM_MATRIX](PLATFORM_MATRIX.md) — what differs between Linux, macOS and a CI runner
|
|
10
|
+
- [VALIDATION](VALIDATION.md) — the checks that prove the install, and what each failure means
|
|
11
|
+
|
|
12
|
+
The three are the platform installation contract (`SETUP_DOCS`), so the set is
|
|
13
|
+
fixed: a repository carries all three or it does not satisfy the contract.
|
|
14
|
+
|
|
15
|
+
## Status
|
|
16
|
+
|
|
17
|
+
Current. The map of this branch: `M001` of the documentation lint reports a leaf
|
|
18
|
+
this file does not link, and `S008` reports a branch that carries no map at all.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
Parent: [../../README.md](../../README.md)
|
|
2
|
+
Owns: the mandatory checks after installing __REPO_NAME__, the output each one must produce, and the failure signature that says which check did not hold.
|
|
3
|
+
|
|
4
|
+
# VALIDATION
|
|
5
|
+
|
|
6
|
+
## Required checks after install
|
|
7
|
+
|
|
8
|
+
1. The repository satisfies the platform contract — deploy, installation,
|
|
9
|
+
environment and test coverage.
|
|
10
|
+
2. The unit suite passes in the test runner container.
|
|
11
|
+
3. The cookbook run dispatches the example operation and asserts its output by
|
|
12
|
+
value.
|
|
13
|
+
4. The container reaches the end of boot and registers. **There is no health
|
|
14
|
+
endpoint to call** — a biz container serves no HTTP (ADR 0005), so the
|
|
15
|
+
container log and the registry are the only readiness evidence.
|
|
16
|
+
|
|
17
|
+
## Command sequence
|
|
18
|
+
|
|
19
|
+
Every command runs from the repository root (`api_biz/__SERVICE_NAME__/`).
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# 1 — contract gates (deploy R1-R5/R7-R8, installation docs, env, test coverage, R6)
|
|
23
|
+
node node_modules/@onlineapps/conn-orch-validator/src/cli/biz-ci-gate.js verify-contract
|
|
24
|
+
|
|
25
|
+
# 2 — unit suite, in the one-shot runner beside the service
|
|
26
|
+
npm run test:container
|
|
27
|
+
|
|
28
|
+
# 3 — cookbooks, in the same runner (it has the env files)
|
|
29
|
+
npm run test:cookbooks:container
|
|
30
|
+
|
|
31
|
+
# 4 — boot evidence
|
|
32
|
+
docker compose logs --tail=50 __CONTAINER_NAME__
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Expected outcome
|
|
36
|
+
|
|
37
|
+
| Check | Passes when |
|
|
38
|
+
|---|---|
|
|
39
|
+
| 1 contract gates | the run ends with `[BizCiGate] OK verify-contract` and exits `0`; each half prints its own `OK` line first |
|
|
40
|
+
| 2 unit suite | jest reports no failing test and the command exits `0` |
|
|
41
|
+
| 3 cookbooks | the run prints `Failed: 0`, ends with `=== Pre-Validation Completed Successfully ===` and writes `conn-runtime/validation-proof.json` |
|
|
42
|
+
| 4 boot | the log shows registration and handler validation, and the container stays up (`docker compose ps` reports it running, not restarting) |
|
|
43
|
+
|
|
44
|
+
The cookbook check asserts a VALUE, not that something came back: the shipped
|
|
45
|
+
cookbook requires the echoed text to equal what it sent and its length to equal
|
|
46
|
+
`8`. Keep that property when the example operation is replaced — an assertion of
|
|
47
|
+
truthiness proves the dispatch worked and nothing about the result
|
|
48
|
+
(`.claude/rules/architecture-principles.md` §10a).
|
|
49
|
+
|
|
50
|
+
## Failure signatures
|
|
51
|
+
|
|
52
|
+
- `[BizCiGate] Missing file - …/config/service/integration-contract.json` — the
|
|
53
|
+
contract is gone; every gate reads it first.
|
|
54
|
+
- `[BizCiGate] FAIL … SETUP_DOCS — Missing required file - docs/80-setup/…` — one
|
|
55
|
+
of these three documents was deleted or renamed.
|
|
56
|
+
- `[BizCiGate] FAIL … ENV_COMPLETENESS — "X" is read in … but is neither declared
|
|
57
|
+
… nor covered` — a handler started reading an environment variable; declare it
|
|
58
|
+
in the contract's `env` block with the reason, or delete the read.
|
|
59
|
+
- `[BizCiGate] FAIL … TEST_RUNS_NOWHERE — Test file runs nowhere - …` — a test
|
|
60
|
+
file exists that the `test:all` chain does not run; put it in the chain, or
|
|
61
|
+
declare its script in `stackTiers` with the reason it cannot be there.
|
|
62
|
+
- `[BizCiGate] Missing package script - test:all …` — the chain the coverage gate
|
|
63
|
+
measures against was removed.
|
|
64
|
+
- `[SetupDatabase] Missing database.collation - the schema is created with the
|
|
65
|
+
collation the service declares, and there is no default.` — `setup-db` was asked
|
|
66
|
+
to build a schema for a service whose contract carries a `database` block
|
|
67
|
+
without `collation`. Add the key with the collation this service's tables
|
|
68
|
+
already use; the same key the production runbook reads, and the one uniform row
|
|
69
|
+
`D-DB-COLLATION` asks for.
|
|
70
|
+
- `[ConfigLoader] Missing environment variable - X is required (config placeholder
|
|
71
|
+
${X})` at boot — the env-active files were not prepared, or that name is missing
|
|
72
|
+
from them.
|
|
73
|
+
|
|
74
|
+
## Status
|
|
75
|
+
|
|
76
|
+
Current. One of the three files the installation contract requires by name —
|
|
77
|
+
`biz-ci-gate verify-install-contract` (and `verify-contract`, which folds it in)
|
|
78
|
+
fails when it is missing.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Parent: [../README.md](../README.md)
|
|
2
|
+
Owns: the map of this repository's documentation tree — which branch answers which question
|
|
3
|
+
|
|
4
|
+
# __REPO_NAME__ — documentation
|
|
5
|
+
|
|
6
|
+
Every document in this tree is reached from here by clicking down; a node no map
|
|
7
|
+
links to is a node nobody finds (DOC-STANDARD rule 1).
|
|
8
|
+
|
|
9
|
+
- [Setup](80-setup/README.md) — installing this repository and proving the install
|
|
10
|
+
|
|
11
|
+
The platform-wide concepts this service is built on — the service shape, the
|
|
12
|
+
tenancy model, the contracts it speaks — live in the platform tree
|
|
13
|
+
`api/docs/biz/`, and this tree links to them rather than restating them.
|
|
14
|
+
|
|
15
|
+
## Status
|
|
16
|
+
|
|
17
|
+
Current. The map of this tree: `L006` of the documentation lint reports every node
|
|
18
|
+
this file does not reach by clicking down.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
|
|
4
|
+
# Runtime data
|
|
5
|
+
config/runtime/
|
|
6
|
+
config/env-active/
|
|
7
|
+
|
|
8
|
+
# Validation proof written by `npm run test:cookbooks` (the validator's own CLI)
|
|
9
|
+
# and by the wrapper on every boot — generated, never committed (same line
|
|
10
|
+
# api_biz/hello-service carries).
|
|
11
|
+
conn-runtime/
|
|
12
|
+
|
|
13
|
+
# Deployability signal written by the manifest step of the boot validation and
|
|
14
|
+
# by `npx oa-validate` (`ci/deployability.json`, the shape of
|
|
15
|
+
# `ci/integration-signal.json`) - generated on every start, never committed.
|
|
16
|
+
ci/
|
|
17
|
+
|
|
18
|
+
# Logs
|
|
19
|
+
logs/
|
|
20
|
+
*.log
|
|
21
|
+
npm-debug.log*
|
|
22
|
+
|
|
23
|
+
# Coverage
|
|
24
|
+
coverage/
|
|
25
|
+
|
|
26
|
+
# Environment (legacy)
|
|
27
|
+
.env
|
|
28
|
+
.env.local
|
|
29
|
+
|
|
30
|
+
# IDE
|
|
31
|
+
.vscode/
|
|
32
|
+
.idea/
|
|
33
|
+
*.swp
|
|
34
|
+
*.swo
|
|
35
|
+
*~
|
|
36
|
+
|
|
37
|
+
# OS
|
|
38
|
+
.DS_Store
|
|
39
|
+
Thumbs.db
|
|
40
|
+
|
|
41
|
+
# Build artifacts
|
|
42
|
+
.oa_drive_deps_hash
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* __SERVICE_NAME__ - Main Entry Point
|
|
3
|
+
* Uses unified bootstrap from @onlineapps/service-wrapper
|
|
4
|
+
*/
|
|
5
|
+
const { bootstrap } = require('@onlineapps/service-wrapper');
|
|
6
|
+
|
|
7
|
+
bootstrap(__dirname).catch(error => {
|
|
8
|
+
console.error('Failed to start service:', error);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
oa_npm_install() {
|
|
5
|
+
npm install --no-audit --no-fund
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
# --- oa-deps-guard v1 (identical in every OA Drive init.sh) ---
|
|
9
|
+
# The dev stack's only skip condition: the md5 of package.json + package-lock.json
|
|
10
|
+
# against the marker the last SUCCESSFUL install wrote. A package-directory probe
|
|
11
|
+
# cannot see a version bump and an mtime probe cannot see a checkout that rewinds
|
|
12
|
+
# package.json, so neither is used here or anywhere else in the file. Enforced byte
|
|
13
|
+
# for byte, within this repo, by tests/scripts/infra-init-scripts.bats — across the
|
|
14
|
+
# eight infra/*/init.sh, this template and the generator's output. A copy pasted
|
|
15
|
+
# into an api_biz/* repo is kept in sync by review only; the gate cannot reach
|
|
16
|
+
# another checkout. A service's own install steps belong in oa_npm_install() above,
|
|
17
|
+
# everything else it needs goes below.
|
|
18
|
+
MARKER_FILE=".oa_drive_deps_hash"
|
|
19
|
+
|
|
20
|
+
HASH_INPUT=""
|
|
21
|
+
if [ -f "package.json" ]; then
|
|
22
|
+
HASH_INPUT="${HASH_INPUT}$(md5sum package.json | awk '{print $1}')"
|
|
23
|
+
fi
|
|
24
|
+
if [ -f "package-lock.json" ]; then
|
|
25
|
+
HASH_INPUT="${HASH_INPUT}$(md5sum package-lock.json | awk '{print $1}')"
|
|
26
|
+
fi
|
|
27
|
+
CURRENT_HASH=$(echo "$HASH_INPUT" | md5sum | awk '{print $1}')
|
|
28
|
+
|
|
29
|
+
PREVIOUS_HASH=""
|
|
30
|
+
if [ -f "$MARKER_FILE" ]; then
|
|
31
|
+
PREVIOUS_HASH=$(cat "$MARKER_FILE" 2>/dev/null || true)
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
NEEDS_INSTALL=0
|
|
35
|
+
if [ ! -d "node_modules" ] || [ -z "$(ls -A node_modules 2>/dev/null)" ]; then
|
|
36
|
+
NEEDS_INSTALL=1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
if [ "$CURRENT_HASH" != "$PREVIOUS_HASH" ] || [ "$NEEDS_INSTALL" = "1" ]; then
|
|
40
|
+
echo "[init.sh] Installing dependencies (hash changed or node_modules missing)..."
|
|
41
|
+
if oa_npm_install; then
|
|
42
|
+
echo "$CURRENT_HASH" > "$MARKER_FILE"
|
|
43
|
+
echo "[init.sh] Dependencies installed, marker $MARKER_FILE updated."
|
|
44
|
+
else
|
|
45
|
+
echo "[init.sh] Dependency install failed - marker $MARKER_FILE left untouched, so the next start retries instead of trusting a half-installed tree. Fix: read the npm error above, then run 'npm install' in this service directory."
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
else
|
|
49
|
+
echo "[init.sh] Dependencies unchanged. Skipping npm install."
|
|
50
|
+
fi
|
|
51
|
+
# --- end oa-deps-guard v1 ---
|
|
52
|
+
|
|
53
|
+
# Run the passed command (e.g. npm run dev or npm run prod).
|
|
54
|
+
exec "$@"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__REGISTRY_NAME__",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=24.0.0 <25"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node index.js",
|
|
11
|
+
"dev": "nodemon index.js",
|
|
12
|
+
"test": "jest --config jest.config.js tests/unit --runInBand",
|
|
13
|
+
"test:ci": "jest",
|
|
14
|
+
"test:unit": "jest tests/unit",
|
|
15
|
+
"test:all": "npm run test:unit",
|
|
16
|
+
"test:container": "docker compose run --rm __CONTAINER_NAME___tests npm run test:unit",
|
|
17
|
+
"test:all:container": "docker compose run --rm __CONTAINER_NAME___tests npm run test:all",
|
|
18
|
+
"test:coverage": "jest --coverage",
|
|
19
|
+
"test:cookbooks": "node node_modules/@onlineapps/conn-orch-validator/src/cli/biz-ci-gate.js run-prevalidation",
|
|
20
|
+
"test:cookbooks:container": "docker compose run --rm __CONTAINER_NAME___tests npm run test:cookbooks"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@onlineapps/conn-orch-validator": "__SSOT_PIN__",
|
|
24
|
+
"@onlineapps/service-wrapper": "__SSOT_PIN__",
|
|
25
|
+
"@onlineapps/service-common": "__SSOT_PIN__"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"jest": "29.7.0",
|
|
29
|
+
"nodemon": "3.1.7"
|
|
30
|
+
}
|
|
31
|
+
}
|