# Regask New Service

> 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.

- Skill: `tiennguyen1203/regask-new-service` (Agent Skill)
- Install (CLI): `npx skillmds@latest add tiennguyen1203/regask-new-service`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tiennguyen1203/regask-new-service/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: tiennguyen1203 (https://skillmd.com/u/tiennguyen1203)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/tiennguyen1203/regask-new-service

---


# 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:

1. **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.
2. **`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.
3. **Kafka v2 only.** Drop `connectKafka` and `KafkaConsumerModule`. For a kservice, drop the
   *consumer* entirely and keep only the producer.
4. **`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.
5. **Regenerate `pnpm-lock.yaml`** after editing `package.json`. The Dockerfile and CI use
   `--frozen-lockfile` and fail on a stale lockfile.
6. **`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.
7. **`kustomize/scripts/scaffold.sh` fails on stock macOS** (bash 3.2, `declare -A`). Write the
   GitOps files by hand or `brew install bash`.
8. **Knative bases need `includeSelectors: false`** plus a `serving.knative.dev/Service` label
   `fields` entry, or you get permanent ArgoCD drift.
9. **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/`.
10. **The event-handler app's secret is `<name>-eh-secret`**, not `<name>-secret`. A wrong patch
    target silently no-ops.
11. **`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`.
12. **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.
13. **`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>`.

