Skip to content

Commit 5ed2d3c

Browse files
committed
chore: dev env
Signed-off-by: Joana Maia <jmaia@contractor.linuxfoundation.org>
1 parent a965066 commit 5ed2d3c

8 files changed

Lines changed: 342 additions & 1 deletion

File tree

.claude/rules/skill-guidance.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ This project has guided skills for common workflows. **Proactively suggest the r
1616
| `/review-pr` | Review a PR, audit code changes, check PR quality, validate a PR against standards |
1717
| `/adr` | Record an architecture decision, choose between frameworks/libraries/patterns, query past decisions |
1818
| `/scaffold-snowflake-connector` | Add a new Snowflake-connector data source or integration |
19+
| `/packages-worker-setup` | First-time setup of packages-db and github-repos-enricher for a new engineer |
20+
| `/packages-worker-add-entrypoint` | Scaffold a new sibling worker inside packages_worker (npm, OSV, scorecard, etc.) |
1921

2022
## Trigger Phrases
2123

@@ -45,3 +47,13 @@ This project has guided skills for common workflows. **Proactively suggest the r
4547
**`/scaffold-snowflake-connector`** — match any of these intents:
4648
- "Add a new Snowflake connector", "New integration for [platform]"
4749
- "Scaffold a new data source", anything about adding a platform to `snowflake_connectors`
50+
51+
**`/packages-worker-setup`** — match any of these intents:
52+
- "Set up packages worker", "how do I run the enricher", "first time on this branch"
53+
- "Get packages-db running", "packages-db won't start", "ENRICHER_GITHUB_TOKENS"
54+
- Any first-time setup question specific to `packages_worker` or `packages-db`
55+
56+
**`/packages-worker-add-entrypoint`** — match any of these intents:
57+
- "Add a new packages worker", "scaffold a sibling worker", "new entry point in packages_worker"
58+
- "Add npm ingestion", "add OSV worker", "add scorecard runner"
59+
- Any request to create a new `src/bin/*.ts` worker inside `packages_worker`
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
---
2+
name: packages-worker-add-entrypoint
3+
description: >
4+
Scaffold a new sub-worker inside packages_worker (npm, deps.dev, osv, scorecard,
5+
etc.) following the single-service multi-entry-point structure. Use when: "add a
6+
new packages worker", "scaffold a sub-worker in packages_worker", "new worker for
7+
packages-db", "add npm worker", "add OSV worker", "add deps.dev worker".
8+
allowed-tools: Read, Write, Edit, Bash, AskUserQuestion, Glob
9+
---
10+
11+
# packages-worker — Add a New Sub-worker
12+
13+
You are adding a new data-ingestion worker to `services/apps/packages_worker/`.
14+
The structure follows the same pattern as `backend/` (where `api.ts` and
15+
`job-generator.ts` share one Dockerfile): one npm package, one Docker image,
16+
each worker in its own `src/{worker}/` directory with its own entry point.
17+
18+
```
19+
services/apps/packages_worker/
20+
src/
21+
bin/
22+
packages-worker.ts ← parent stub
23+
github-repos-enricher.ts ← existing worker
24+
<name>.ts ← entry point you will create
25+
github/ ← existing worker logic
26+
<worker>/ ← directory you will create
27+
index.ts ← main logic for this worker
28+
types.ts
29+
config.ts ← shared — add your config getter here
30+
db.ts ← shared — do not modify
31+
```
32+
33+
## Step 1 — Gather requirements
34+
35+
Ask the engineer for:
36+
37+
1. **Worker name** (kebab-case) — e.g. `npm-sync`, `osv-sync`, `scorecard-runner`. Used as the entry point filename (`src/bin/<name>.ts`) and docker-compose service name.
38+
2. **Worker directory name** (short, lowercase) — e.g. `npm`, `osv`, `scorecard`. Becomes `src/<worker>/`.
39+
3. **What it does** — what data it fetches/writes, what table(s) in packages-db it reads from and writes to.
40+
4. **External API or data source** (if any) — URL, auth method, rate-limit characteristics.
41+
5. **Required env vars** beyond the shared DB vars — e.g. `NPM_API_URL`, `OSV_API_KEY`.
42+
43+
Do not proceed until you have answers to 1–3.
44+
45+
## Step 2 — Read existing files first
46+
47+
```bash
48+
cat services/apps/packages_worker/src/bin/github-repos-enricher.ts
49+
cat services/apps/packages_worker/src/config.ts
50+
cat services/apps/packages_worker/package.json
51+
cat scripts/services/github-repos-enricher.yaml
52+
```
53+
54+
These are the canonical references. Do not deviate from the patterns you see there.
55+
56+
## Step 3 — Scaffold the files
57+
58+
### 3a. Worker directory — `services/apps/packages_worker/src/<worker>/`
59+
60+
Create the directory with at minimum:
61+
62+
**`types.ts`** — types specific to this worker (input/output shapes, error kinds if calling an external API).
63+
64+
**`index.ts`** — the main logic function(s) this worker runs. What goes here depends entirely on what the worker does — do not force a loop shape if it does not fit. Discuss with the engineer what the execution model should be (continuous loop, one-shot batch, event-driven, etc.) and implement accordingly.
65+
66+
Add any additional files the worker needs (e.g. an API client, a DB query helper). All DB access uses inline pg-promise SQL via `qx.select` / `qx.result` / `qx.none` — do not add files to `services/libs/data-access-layer`.
67+
68+
### 3b. Entry point — `services/apps/packages_worker/src/bin/<name>.ts`
69+
70+
Follow the structure of `github-repos-enricher.ts`:
71+
- Import `getServiceLogger` from `@crowd/logging`
72+
- Import your worker's config getter from `../config` and `getPackagesDb` from `../db`
73+
- Import your worker's main function from `../<worker>/index`
74+
- Set `liveFilePath` / `readyFilePath` to `../tmp/<name>-live.tmp` / `../tmp/<name>-ready.tmp`
75+
- Handle SIGINT / SIGTERM with a `shuttingDown` flag
76+
- In `main()`: call config getter → validate any required tokens/keys → `await getPackagesDb()``await qx.selectOne('SELECT 1')``fs.mkdirSync` for the tmp dir → `setInterval` writing probe files every 5000ms → call your worker's main function → `clearInterval``process.exit(0)`
77+
- Fatal handler: `main().catch(err => { log.error({ err }, '<name> fatal error'); process.exit(1) })`
78+
79+
### 3c. Config additions — `services/apps/packages_worker/src/config.ts`
80+
81+
Read the file first, then add a `get<Worker>Config()` function:
82+
- Use `requireEnv(name)` for string vars, `requireEnvInt(name)` for integers
83+
- No defaults, no `?? undefined` — the process must refuse to start on missing config
84+
85+
### 3d. Docker-compose service — `scripts/services/<name>.yaml`
86+
87+
Copy `scripts/services/github-repos-enricher.yaml` and adapt:
88+
- Service names: `<name>` (prod) and `<name>-dev` (dev)
89+
- `command` (prod): `pnpm run start:<name>`
90+
- `command` (dev): `pnpm run dev:<name>`
91+
- `env_file`: keep the same four files (`backend/.env.dist.local`, `backend/.env.dist.composed`, `backend/.env.override.local`, `backend/.env.override.composed`)
92+
- `environment`: set any tuning var defaults inline (avoids requiring them in `.env.override.local` for local dev)
93+
- `volumes` (dev only): bind-mount `./services/apps/packages_worker/src` plus every `services/libs/*/src` directory (copy the full list from the enricher yaml for hot reload)
94+
95+
### 3e. package.json scripts — `services/apps/packages_worker/package.json`
96+
97+
Read the file first, then add:
98+
```json
99+
"start:<name>": "tsx src/bin/<name>.ts",
100+
"dev:<name>": "tsx watch src/bin/<name>.ts"
101+
```
102+
103+
### 3f. Env var files — `backend/.env.dist.local` and `backend/.env.dist.composed`
104+
105+
Append new required vars with empty-string defaults (or sensible local values for non-secrets):
106+
```
107+
NEW_WORKER_API_KEY=
108+
```
109+
110+
## Step 4 — TypeScript check
111+
112+
```bash
113+
cd services/apps/packages_worker && pnpm tsc --noEmit
114+
```
115+
116+
Fix any errors before proceeding.
117+
118+
## Checklist before committing
119+
120+
- [ ] `src/<worker>/` directory created with `types.ts` and `index.ts`
121+
- [ ] `src/bin/<name>.ts` — probe files, SIGINT/SIGTERM handler, fail-fast config check, `SELECT 1` on startup
122+
- [ ] `config.ts` — new `get<Worker>Config()` using `requireEnv`/`requireEnvInt`, no defaults
123+
- [ ] `scripts/services/<name>.yaml` — prod + dev services with bind mounts
124+
- [ ] `package.json``start:<name>` and `dev:<name>` scripts added
125+
- [ ] `backend/.env.dist.local` and `.env.dist.composed` — new vars documented
126+
- [ ] No new files in `services/libs/data-access-layer` (packages-db uses inline SQL)
127+
- [ ] `pnpm tsc --noEmit` passes
128+
129+
Use `/preflight` before opening a PR and `/commit` to sign off.
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
---
2+
name: packages-worker-setup
3+
description: >
4+
Get packages_worker running locally — first time or resuming after a break.
5+
Spins up packages-db if not running, applies any pending migrations, and starts
6+
the worker. All steps are safe to re-run.
7+
Use when: "set up packages worker", "start packages worker", "resume packages worker",
8+
"get packages-db running", "packages-db stopped", "restart the worker".
9+
allowed-tools: Read, Bash, Edit, AskUserQuestion
10+
---
11+
12+
# packages-worker
13+
14+
Get `packages_worker` running locally. All steps are idempotent — safe to run
15+
whether this is your first time or you're resuming after a break.
16+
17+
## Prerequisites check
18+
19+
```bash
20+
git branch --show-current # should be feat/track-packages
21+
docker info --format '{{.ServerVersion}}'
22+
pnpm --version
23+
```
24+
25+
If the branch is wrong: `git checkout feat/track-packages && pnpm i`.
26+
27+
## Step 1 — Start packages-db
28+
29+
No-op if already running.
30+
31+
```bash
32+
docker compose -f scripts/scaffold.yaml up -d packages
33+
until docker compose -f scripts/scaffold.yaml exec packages pg_isready -U postgres; do sleep 1; done
34+
echo "packages-db is ready"
35+
```
36+
37+
## Step 2 — Apply pending migrations
38+
39+
Flyway skips already-applied migrations, so this is safe to re-run.
40+
41+
```bash
42+
arch=$(uname -m)
43+
[ "$arch" = "arm64" ] && PLATFORM="--platform=linux/arm64/v8" || PLATFORM="--platform=linux/amd64"
44+
docker build $PLATFORM -t packages_flyway \
45+
-f backend/src/osspckgs/Dockerfile.flyway backend/src/osspckgs --load
46+
47+
docker run --rm --network crowd-bridge \
48+
-e PGHOST=packages \
49+
-e PGPORT=5432 \
50+
-e PGUSER=postgres \
51+
-e PGPASSWORD=example \
52+
-e PGDATABASE=packages-db \
53+
packages_flyway
54+
```
55+
56+
To create a new migration:
57+
58+
```bash
59+
./scripts/cli scaffold create-packages-migration <descriptive_name>
60+
```
61+
62+
## Step 3 — Start the worker
63+
64+
```bash
65+
DEV=1 ./scripts/cli service packages-worker up
66+
```
67+
68+
Dev mode uses hot reload — edits to `services/apps/packages_worker/src/` and
69+
`services/libs/*/src/` are picked up immediately without restarting.
70+
71+
## Day-to-day commands
72+
73+
```bash
74+
# Follow logs
75+
./scripts/cli service packages-worker logs
76+
77+
# Stop
78+
./scripts/cli service packages-worker down
79+
80+
# Restart
81+
./scripts/cli service packages-worker restart
82+
83+
# Check status
84+
./scripts/cli service packages-worker status
85+
```
86+
87+
## Going further
88+
89+
- Add a new sub-worker (npm-sync, osv-sync, etc.): `/packages-worker-add-entrypoint`
90+
- Record an architecture decision: `/adr`
91+
- Before opening a PR: `/preflight`
92+
- Commit with DCO sign-off: `/commit`
93+
94+
## Troubleshooting
95+
96+
| Symptom | Likely cause | Fix |
97+
|---|---|---|
98+
| `Connection refused` on packages-db | Docker not running | `docker compose -f scripts/scaffold.yaml up -d packages` |
99+
| `permission denied: scripts/cli` | CLI not executable | `chmod +x scripts/cli` |

docs/adr/0001-packages-database.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# ADR-0001: Separate physical database for the packages domain
2+
3+
**Date**: 2026-05-25
4+
**Status**: accepted
5+
**Deciders**: CDP/Insights team
6+
7+
## Context
8+
9+
The packages domain (tracking open-source packages, dependency graphs, repositories, security advisories, and maintainers) is being built as a new capability inside the CDP platform. The main CDP database (`crowd-web`) and the existing `product-db` already exist as separate physical Postgres instances, each owned by a distinct domain. The packages schema has no foreign-key relationships into either existing database, and requires partitioned tables sized for 90M+ versions and 1.15B+ dependency rows — a scale profile that would create resource contention if mixed with CDP's community-activity tables.
10+
11+
## Decision
12+
13+
We store all packages-domain data in a dedicated physical Postgres instance (`packages-db`, port 5434) with its own Flyway migration path (`backend/src/osspckgs/migrations/`), following the same Dockerfile and migration-script pattern used by `product-db`. Schema and connection code live entirely within the `packages_worker` service.
14+
15+
## Alternatives Considered
16+
17+
### Alternative 1: Add tables to the main CDP db (`crowd-web`)
18+
19+
- **Pros**: No new database to manage; existing Flyway setup and pg-promise helpers already target it.
20+
- **Cons**: Packages tables have a completely different shape and scale from community-activity tables. Resource contention at scale is a real risk, and schema coupling makes independent evolution harder.
21+
- **Why not**: The packages schema has zero FK dependencies on CDP tables. Co-locating independent domains in one database couples their lifecycle, backup strategy, and performance headroom for no benefit.
22+
23+
## Consequences
24+
25+
### Positive
26+
27+
- Clear domain boundary: packages-db has no FK relationships outside its own schema.
28+
- Independent scaling, backup, and maintenance.
29+
- Follows existing precedent; `product-db` demonstrates the pattern works.
30+
- Schema decisions (partitioning, GDPR) are isolated to one database and one migration path.
31+
32+
### Negative
33+
34+
- A third Postgres instance to operate, monitor, and back up.
35+
- Read/write host split is prepared in env vars (`CROWD_PACKAGES_DB_READ_HOST` / `CROWD_PACKAGES_DB_WRITE_HOST`) but only the write host is wired in `config.ts` today — read routing is deferred.
36+
37+
---
38+
39+
**Partitioning rationale (captured here to avoid re-litigating per-table):**
40+
41+
| Table | Strategy | Buckets | Hot query shape |
42+
|---|---|---|---|
43+
| `versions` | HASH(`package_id`) | 32 | Lookup by package — lands in one partition; ~2.8M rows each at 90M total |
44+
| `package_dependencies` | HASH(`depends_on_id`) | 64 | "Who depends on vulnerable package X?" — lands in one partition; ~18M rows each at 1.15B total |
45+
| `downloads_daily` | RANGE(`date`) via `pg_partman` | automatic | Time-series; pruning old partitions is straightforward |
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# ADR-0002: Single-service, multi-entry-point architecture for packages_worker
2+
3+
**Date**: 2026-05-25
4+
**Status**: accepted
5+
**Deciders**: CDP/Insights team
6+
7+
## Context
8+
9+
The packages domain requires several independent data-ingestion workers (GitHub repository enrichment, npm package sync, deps.dev dependency data, OSV advisories, OpenSSF Scorecard, and others). Each worker has distinct external API dependencies, rate-limit profiles, and scheduling needs. The platform already demonstrates this pattern in `backend/`, where `api.ts` and `job-generator.ts` are two separate processes built from the same Dockerfile and npm package.
10+
11+
## Decision
12+
13+
All packages_worker sub-workers live in a single npm package (`services/apps/packages_worker`) and are built from one Dockerfile (`scripts/services/docker/Dockerfile.packages-worker`). Each sub-worker is a self-contained directory under `services/apps/packages_worker/src/{worker}/` with its own logic, types, and database access. Each is launched as a separate container using a different entry point command, sharing the same image. Config helpers (`requireEnv`, `requireEnvInt`) and the packages-db connection are shared across all entry points.
14+
15+
```
16+
services/apps/packages_worker/
17+
src/
18+
bin/
19+
packages-worker.ts ← parent / health-check stub
20+
github-repos-enricher.ts
21+
github/ ← github-repos-enricher logic
22+
npm/ ← npm worker (future)
23+
deps-dev/ ← deps.dev worker (future)
24+
osv/ ← OSV worker (future)
25+
config.ts ← shared requireEnv / requireEnvInt
26+
db.ts ← shared packages-db connection
27+
```
28+
29+
## Alternatives Considered
30+
31+
### Alternative 1: One npm package per worker (matching the pattern of other workers in `services/apps/`)
32+
33+
- **Pros**: Full isolation; each worker has its own `package.json`, Dockerfile, and deploy lifecycle.
34+
- **Cons**: Most packages-domain workers share the same DB connection, config shape, and type definitions. Duplicating these across N packages creates maintenance overhead that grows with each new data source.
35+
- **Why not**: The packages domain has a clear shared foundation (one DB, one config pattern, one set of domain types). A monorepo sub-package per worker is the right split when workers diverge significantly in dependencies or deploy cadence — that is not the case here.
36+
37+
### Alternative 2: One monolithic process running all workers
38+
39+
- **Pros**: Simpler deployment — one container.
40+
- **Cons**: Workers have different rate-limit profiles and external API dependencies. A failure or resource spike in one worker affects all others. Independent scaling is impossible.
41+
- **Why not**: Workers must be deployed and scaled independently. A single-process monolith would require internal concurrency management that replicates what separate processes give for free.
42+
43+
## Consequences
44+
45+
### Positive
46+
47+
- One Dockerfile and one build to maintain regardless of how many sub-workers are added.
48+
- Shared `config.ts` enforces fail-fast env-var validation (`requireEnv`/`requireEnvInt`) across all workers — no silent `undefined`/`NaN` tuning values.
49+
- Each worker can be deployed, scaled, and restarted independently.
50+
- Adding a new data source means adding `src/{worker}/` and a new compose service entry — no new npm package, no new Dockerfile.
51+
52+
### Negative
53+
54+
- All workers in the service share the same npm dependency tree. A dependency needed by only one worker adds to the image size for all.
55+
- A breaking change to shared code (`config.ts`, `db.ts`) affects all entry points simultaneously.

docs/adr/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ Use the `/adr` skill in Claude Code to record new ADRs or query past decisions.
88

99
| ADR | Title | Status | Date |
1010
| --- | ----- | ------ | ---- |
11-
| _none yet_ | | | |
11+
| [ADR-0001](./0001-packages-database.md) | Separate physical database for the packages domain | accepted | 2026-05-26 |
12+
| [ADR-0002](./0002-packages-worker-architecture.md) | Single-service, multi-entry-point architecture for packages_worker | accepted | 2026-05-25 |
1213

1314
## Why ADRs?
1415

services/apps/packages_worker/src/tmp/packages-worker-live.tmp

Whitespace-only changes.

services/apps/packages_worker/src/tmp/packages-worker-ready.tmp

Whitespace-only changes.

0 commit comments

Comments
 (0)