RegASK: create a new backend workload
The authoritative, step-by-step runbook lives in the dev handbook:
dev-handbook/docs/development/new-service.md (relative to the regask workspace root)
Read that file first, in full, and follow it. It is written to be executed literally. This skill
file only carries what to settle before opening it, and the mistakes that cost the most time.
Ask the app type BEFORE writing anything
This is the one question you cannot infer, and getting it wrong means rewriting the entry points and
deleting GitOps directories. Ask it explicitly:
|
service |
ai-service |
kservice |
| Entry points |
main.js + event-handler.js |
same |
knative.main.js only |
| Events arrive as |
Kafka messages |
same |
HTTP POST from a Knative Trigger |
| Idle |
1+ replica |
same |
scaled to zero |
| GitOps |
apps/services/<name> and apps/event-handlers/<name> |
apps/ai-services/<name> (+ event-handlers) |
apps/kservices/<name> — one app |
| Extra GitOps files |
— |
— |
broker.yaml, trigger.yaml |
service and ai-service are the same source shape; their infra bases differ by one env var
(SERVICES_WEB_SCRAPER). ai-services/ is an organisational grouping for AI/ML workloads.
kservice is the genuinely different one: one process, no Kafka consumer, non-2xx means retry.
- A repo can own several apps of different types from one image (
alert owns
services/alert + event-handlers/alert + two kservices/). So ask which apps this repo
deploys, not just "what type is it".
Also confirm, if the answer changes the work:
- Skeleton scope — Temporal or MCP on day one? Temporal needs a new
TaskQueueEnum value in
common-lib, a cross-repo PR. Leave both out unless asked.
- Which environments to scaffold: development only, or all four.
- Git —
git init / create the GitHub repo / push, or leave plain files. Creating an org repo is
outward-facing; get it explicitly.
Orientation
The deliverable is three things: the source repo, its .github/workflows/ (where the app type shows
up as the microservices array), and the platform-gitops manifests. <name> is one kebab-case
string reused verbatim as the GitHub repo, the ACR image, the GitOps app directory, APP_NAME, and
CONNECTION_NAME.
Shape references to read alongside the runbook:
- kservice —
alert/src/knative.main.ts + knative.module.ts + knative-handlers/, and
apps/kservices/update-chronology-count-system-alert for the GitOps app. Do not copy
alert-tenant-integration: it is kservice-only but names its entry point main.ts and patches the
GitOps command to compensate. Name it knative.main.ts and the infra default just works.
- Kafka v2 —
workflow-builder is the only service with no v1 consumer.
Traps, in the order they bite
Each is spelled out in the runbook:
- Pick the clone source by freshness, not domain similarity. Verify with the two commands in the
runbook's clone-source step. The service whose domain looks closest is often two baselines behind.
app.module.ts is test-only — it wires both entry points for CustomTestHelper. For a
single-entry-point service (kservice) don't create it at all; KnativeAppModule is the graph.
- Kafka v2 only. Drop
connectKafka and KafkaConsumerModule. For a kservice, drop the
consumer entirely and keep only the producer.
CONNECTION_NAME is not the database name. buildMongoUri() ignores it and builds from
MONGO_ATLAS_CONNECTION_STRING + ATLAS_DATABASE_NAME; the cluster shares one Atlas database per
environment, and services are separated by a <name>__ collection prefix. Don't copy
MONGO_DB_NAME — nothing reads it.
- Regenerate
pnpm-lock.yaml after editing package.json. The Dockerfile and CI use
--frozen-lockfile and fail on a stale lockfile.
AZURE_PUBSUB_CONNECTION_STRING must be commented out in .env.example. A placeholder value
crashes the boot (connection string missing access key); an absent var no-ops.
kustomize/scripts/scaffold.sh fails on stock macOS (bash 3.2, declare -A). Write the
GitOps files by hand or brew install bash.
- Knative bases need
includeSelectors: false plus a serving.knative.dev/Service label
fields entry, or you get permanent ArgoCD drift.
- kservice: the broker belongs to whoever owns the event. If it already exists, point the
Trigger at it and ship no
broker.yaml. And subscriber.uri must match a @Post('<EventName>')
route in knative-handlers/.
- The event-handler app's secret is
<name>-eh-secret, not <name>-secret. A wrong patch
target silently no-ops.
newTag: "unknown", never "latest". scaffold.sh emits latest; change it. latest
resolves, so a not-yet-deployed app can come up on an arbitrary image; unknown cannot resolve
and fails loudly in ImagePullBackOff. lint.sh flags latest and not unknown.
- The container command belongs in the repo's CI, not GitOps. Put it in the
microservices
array in cd.yml / release-on-merged.yaml / release-published.yml — the entry point is a
fact about the source tree, so renaming it shouldn't need a GitOps PR. Use exec so node is
PID 1 and SIGTERM reaches enableShutdownHooks(). Also check in a matching
base/command.yaml + its patch entry: when that file is absent the bot appends the patch
entry to base/kustomization.yaml itself, and when it's present it only rewrites the file.
Matching byte-for-byte makes the first CD run a no-op diff.
type drives the GitOps path. promote-to-env-new.yml resolves
kustomize/apps/<type>s/<name>/overlays/<env>/ from it — a typo silently promotes nothing.
Verification is not optional
Do not report completion on a green build. The runbook's verification steps are the deliverable:
- source repo:
pnpm install --frozen-lockfile, build, lint, format:check, test
- the
dist/ artefacts the GitOps commands name exist — and no stale one survives (after switching
a service to a kservice, dist/src/main.js must be gone)
- every entry point boots — a build proves types, only a boot proves the DI graph. Look for
Nest application successfully started and a mapped /api/healthcheck/liveness
- all GitOps overlays build with
kubectl kustomize, and one rendered overlay is read: image name,
entry point in the command, Sentry key, ATLAS_DATABASE_NAME, and for a kservice that the Trigger
still carries subscriber.ref → <name>-kservice after the patch
- grep for stale references to anything deleted, including inside doc comments
Then
Merge the source repo first so CD builds an image, then the GitOps PR (development auto-syncs, so the
reverse order parks it in ImagePullBackOff). Add the service-catalog page to the dev handbook.
External prerequisites someone else owns: NODE_AUTH_TOKEN locally, the GitHub repo, the
SENTRY-DSN-<NAME> Key Vault secret, and the SonarQube project. ArgoCD needs no registration — its
ApplicationSet auto-discovers kustomize/apps/*/*/overlays/<env>.
1---2name: regask-new-service3description: Scaffold a new RegASK backend workload end-to-end — source repo cloned from the fleet's newest baseline with the donor's domain stripped, CI/CD wired, and platform-gitops manifests for all four environments. Handles all three app types (service, ai-service, kservice). Use when the user asks to set up / create / scaffold a new service, clone an existing service as a boilerplate, or add a service to platform-gitops. Vietnamese triggers: setup source cho service mới, clone từ thằng X, dựng service mới, thêm service vào gitops.4---56# RegASK: create a new backend workload78The authoritative, step-by-step runbook lives in the dev handbook:910**`dev-handbook/docs/development/new-service.md`** (relative to the `regask` workspace root)1112**Read that file first, in full, and follow it.** It is written to be executed literally. This skill13file only carries what to settle before opening it, and the mistakes that cost the most time.1415## Ask the app type BEFORE writing anything1617This is the one question you cannot infer, and getting it wrong means rewriting the entry points and18deleting GitOps directories. Ask it explicitly:1920| | `service` | `ai-service` | `kservice` |21|---|---|---|---|22| Entry points | `main.js` + `event-handler.js` | same | **`knative.main.js` only** |23| Events arrive as | Kafka messages | same | **HTTP POST from a Knative Trigger** |24| Idle | 1+ replica | same | **scaled to zero** |25| GitOps | `apps/services/<name>` **and** `apps/event-handlers/<name>` | `apps/ai-services/<name>` (+ event-handlers) | `apps/kservices/<name>` — one app |26| Extra GitOps files | — | — | `broker.yaml`, `trigger.yaml` |2728- `service` and `ai-service` are the **same source shape**; their infra bases differ by one env var29 (`SERVICES_WEB_SCRAPER`). `ai-services/` is an organisational grouping for AI/ML workloads.30- `kservice` is the genuinely different one: one process, no Kafka consumer, non-2xx means retry.31- **A repo can own several apps of different types** from one image (`alert` owns32 `services/alert` + `event-handlers/alert` + two `kservices/`). So ask *which apps this repo33 deploys*, not just "what type is it".3435Also confirm, if the answer changes the work:3637- **Skeleton scope** — Temporal or MCP on day one? Temporal needs a new `TaskQueueEnum` value in38 `common-lib`, a cross-repo PR. Leave both out unless asked.39- **Which environments** to scaffold: development only, or all four.40- **Git** — `git init` / create the GitHub repo / push, or leave plain files. Creating an org repo is41 outward-facing; get it explicitly.4243## Orientation4445The deliverable is three things: the source repo, its `.github/workflows/` (where the app type shows46up as the `microservices` array), and the `platform-gitops` manifests. `<name>` is one kebab-case47string reused verbatim as the GitHub repo, the ACR image, the GitOps app directory, `APP_NAME`, and48`CONNECTION_NAME`.4950Shape references to read alongside the runbook:5152- **kservice** — `alert/src/knative.main.ts` + `knative.module.ts` + `knative-handlers/`, and53 `apps/kservices/update-chronology-count-system-alert` for the GitOps app. Do *not* copy54 `alert-tenant-integration`: it is kservice-only but names its entry point `main.ts` and patches the55 GitOps command to compensate. Name it `knative.main.ts` and the infra default just works.56- **Kafka v2** — `workflow-builder` is the only service with no v1 consumer.5758## Traps, in the order they bite5960Each is spelled out in the runbook:61621. **Pick the clone source by freshness, not domain similarity.** Verify with the two commands in the63 runbook's clone-source step. The service whose domain looks closest is often two baselines behind.642. **`app.module.ts` is test-only** — it wires both entry points for `CustomTestHelper`. For a65 single-entry-point service (kservice) don't create it at all; `KnativeAppModule` is the graph.663. **Kafka v2 only.** Drop `connectKafka` and `KafkaConsumerModule`. For a kservice, drop the67 *consumer* entirely and keep only the producer.684. **`CONNECTION_NAME` is not the database name.** `buildMongoUri()` ignores it and builds from69 `MONGO_ATLAS_CONNECTION_STRING` + `ATLAS_DATABASE_NAME`; the cluster shares one Atlas database per70 environment, and services are separated by a `<name>__` collection prefix. Don't copy71 `MONGO_DB_NAME` — nothing reads it.725. **Regenerate `pnpm-lock.yaml`** after editing `package.json`. The Dockerfile and CI use73 `--frozen-lockfile` and fail on a stale lockfile.746. **`AZURE_PUBSUB_CONNECTION_STRING` must be commented out** in `.env.example`. A placeholder value75 crashes the boot (`connection string missing access key`); an absent var no-ops.767. **`kustomize/scripts/scaffold.sh` fails on stock macOS** (bash 3.2, `declare -A`). Write the77 GitOps files by hand or `brew install bash`.788. **Knative bases need `includeSelectors: false`** plus a `serving.knative.dev/Service` label79 `fields` entry, or you get permanent ArgoCD drift.809. **kservice: the broker belongs to whoever owns the event.** If it already exists, point the81 Trigger at it and ship no `broker.yaml`. And `subscriber.uri` must match a `@Post('<EventName>')`82 route in `knative-handlers/`.8310. **The event-handler app's secret is `<name>-eh-secret`**, not `<name>-secret`. A wrong patch84 target silently no-ops.8511. **`newTag: "unknown"`, never `"latest"`.** `scaffold.sh` emits `latest`; change it. `latest`86 resolves, so a not-yet-deployed app can come up on an arbitrary image; `unknown` cannot resolve87 and fails loudly in `ImagePullBackOff`. `lint.sh` flags `latest` and not `unknown`.8812. **The container command belongs in the repo's CI, not GitOps.** Put it in the `microservices`89 array in `cd.yml` / `release-on-merged.yaml` / `release-published.yml` — the entry point is a90 fact about the source tree, so renaming it shouldn't need a GitOps PR. Use `exec` so node is91 PID 1 and `SIGTERM` reaches `enableShutdownHooks()`. Also check in a matching92 `base/command.yaml` + its patch entry: when that file is **absent** the bot appends the patch93 entry to `base/kustomization.yaml` itself, and when it's **present** it only rewrites the file.94 Matching byte-for-byte makes the first CD run a no-op diff.9513. **`type` drives the GitOps path.** `promote-to-env-new.yml` resolves96 `kustomize/apps/<type>s/<name>/overlays/<env>/` from it — a typo silently promotes nothing.9798## Verification is not optional99100Do not report completion on a green build. The runbook's verification steps are the deliverable:101102- source repo: `pnpm install --frozen-lockfile`, `build`, `lint`, `format:check`, `test`103- the `dist/` artefacts the GitOps commands name exist — and no stale one survives (after switching104 a service to a kservice, `dist/src/main.js` must be gone)105- **every entry point boots** — a build proves types, only a boot proves the DI graph. Look for106 `Nest application successfully started` and a mapped `/api/healthcheck/liveness`107- all GitOps overlays build with `kubectl kustomize`, and one rendered overlay is *read*: image name,108 entry point in the command, Sentry key, `ATLAS_DATABASE_NAME`, and for a kservice that the Trigger109 still carries `subscriber.ref` → `<name>-kservice` after the patch110- grep for stale references to anything deleted, including inside doc comments111112## Then113114Merge the source repo first so CD builds an image, then the GitOps PR (development auto-syncs, so the115reverse order parks it in `ImagePullBackOff`). Add the service-catalog page to the dev handbook.116117External prerequisites someone else owns: `NODE_AUTH_TOKEN` locally, the GitHub repo, the118`SENTRY-DSN-<NAME>` Key Vault secret, and the SonarQube project. ArgoCD needs no registration — its119ApplicationSet auto-discovers `kustomize/apps/*/*/overlays/<env>`.