# Developing a split product repo

A product repo (funke-pegasus, playout, probe, commentary, studio) is
self-contained: it consumes ctl and the Norsk runtime as **published
packages/images**, and needs no norsk-ctl source checkout to build, launch, or
test. This is the whole point of the split — you clone the product and go.

## Running ctl — the daemon

`norsk-ctl` (the daemon + CLI that launches product instances) is **not** an npm
dependency; it is the released binary, pinned by version+hash in the repo's
`flake.nix` (the shared shell pieces come from `nix/ctl-shell.nix`, single-sourced
from `@norskvideo/ctl-dev-kit`). The dev shell puts
it on `PATH`:

```sh
nix develop              # build tools (bun, cargo/rustc, git) + the pinned norsk-ctl
#   nix develop .#dev    # equivalent alias
#   nix develop .#build  # lean shell, no ctl — what CI (build-image/integration) uses

norsk-ctl serve          # daemon on :8333, oauth2 proxy on :9443
norsk-ctl --version      # confirm the pinned build
```

The binary is fetched from the S3 channel and patched for the local system
(`autoPatchelfHook` on Linux/NixOS, plain install on macOS). To move the pin,
bump `ctlVersion` + the four platform hashes in `flake.nix` (or run
`packages/dev-kit/build/refresh-ctl-pin.sh` in the monorepo).

## The two dev loops

**Inner loop (fast, no image build)** — run the backend from source and register
it as a `kind: dev` product, so edits to the backend / workflow / components land
without building a container. `bun run demo -- up` does the whole thing on a
private daemon — `tests/demo.spec.ts` run by the test-harness demo driver: dev
backend, registration, launch, the sample sources, the ready gates, the URLs;
Ctrl-C tears down (`spec` prints the plan, `down` tears down from another
shell). `scripts/demo` is the same entry, re-exec'd under `nix develop .#dev`
when the pinned ctl is not on PATH. By hand, against your own daemon:

```sh
bun install
bun run dev                                             # backend + frontend (Vite)
norsk-ctl product add --dev-url http://localhost:<backendPort>
norsk-ctl instance launch-template <id> --template <t> --hardware none
```

**Outer loop (real container)** — rebuild the product image and reload it into
the live daemon. This is `bun run iterate` (`deployment/iterate.sh`):
`build:image`, then `bun run demo -- up --mode image --daemon reuse`, where the
driver deletes the instance, removes the template + product, re-adds the fresh
image, checks the pins against `manifest.seed.json` and relaunches:

```sh
NORSK_LICENSE_FILE=/path/to/license.json bun run iterate       # needs a running daemon (+ GPU where the spec says nvidia)
HARDWARE=none NORSK_LICENSE_FILE=... bun run iterate           # no GPU reservation (funke)
```

`bun run demo -- check` is the headless form CI runs after `test:integration`,
both against the **released** ctl (via `NORSK_CTL_BINARY`), exactly as a
customer would — see `.github/workflows/integration.yml`.

---

# Advanced overrides

To develop a product against **un-published** ctl code or a **locally-built**
media server, there are three independent override layers. Each is opt-in and
leaves the default (registry/pinned) path untouched when unset. They compose:
changing the daemon _and_ a ctl library it exposes means layers 1 + 3 together.

## 1. The npm layer — local `@norskvideo/ctl-*` checkouts

The product depends on `@norskvideo/ctl-{sdk,foundation,product-template-schema,
test-harness,commands,dev-kit}` at a caret range. To point one (or all)
at a local checkout of the ctl monorepo instead of the registry, use
`dev-link.sh`:

```sh
# link every @norskvideo/ctl-* dep to a sibling monorepo checkout
node_modules/@norskvideo/ctl-dev-kit/local-dev/dev-link.sh ../norsk-ctl

# undo — restore the registry versions
node_modules/@norskvideo/ctl-dev-kit/local-dev/dev-link.sh --unlink
```

It `bun link`s the packages the monorepo exposes and re-installs; `--unlink`
drops the overrides and reinstalls from the lockfile. Nothing is committed —
the links live only in your local `node_modules`.

## 2. The image layer — local media / studio image

Every product's `shared/src/version.ts` already reads the runtime image tag from
the environment, so this layer needs no script — just export the hook before you
launch:

- `NORSK_MEDIA_TAG` — override the Norsk media-server image tag.
- `NORSK_STUDIO_TAG` — override the Norsk Studio image tag.

Since Workstream C unified the tag convention, both are **bare tags** across every
product (no full-ref/bare-tag divergence), so the same exported value means the
same thing everywhere:

```sh
export NORSK_STUDIO_TAG=my-local-studio:dev
bun run --cwd . iterate      # or the product's launch/iterate entry
```

Leave the hooks unset to launch the pinned tags the product declares.

## 3. The daemon layer — run ctl from a source checkout (`../norsk-ctl`)

Use this when you are changing the **daemon itself** (packages/norsk-ctl) and
want the product to launch through your build instead of the pinned binary. The
daemon is a single process on `:8333` — where you start it from is irrelevant, so
just run it out of your checkout and drive it with the same checkout's CLI. Use
`nix develop .#build` on the product side so the flake's pinned `norsk-ctl`
doesn't shadow the one you're running from source.

```sh
# 1. daemon, from your ctl checkout (root `cli` script -> packages/norsk-ctl cli)
( cd ../norsk-ctl && bun run cli serve )

# 2. product backend, from the product repo
bun run dev

# 3. register + launch, using the SOURCE cli against the SOURCE daemon
bun run --cwd ../norsk-ctl cli product add --dev-url http://localhost:<backendPort>
bun run --cwd ../norsk-ctl cli instance launch-template demo --template <t> --hardware none
```

No linking is needed for this — you are running the daemon _code_ directly. Pair
with layer 1 (`dev-link.sh`) only if you also changed a `@norskvideo/ctl-*`
library the product _compiles_ against.

### Integration tests against source ctl

The harness spawns the daemon from `NORSK_CTL_BINARY` as `<that> serve …`. A split
repo has no in-workspace ctl source for it to auto-discover, so point that env at
a one-line wrapper that forwards to your checkout's CLI:

```sh
printf '#!/usr/bin/env bash\nexec bun run --cwd %s cli "$@"\n' "$(cd ../norsk-ctl && pwd)" > /tmp/norsk-ctl-src
chmod +x /tmp/norsk-ctl-src
NORSK_CTL_BINARY=/tmp/norsk-ctl-src NORSK_LICENSE_FILE=/path/to/license.json bun run test:integration
```

The wrapper forwards `serve` / `product add` / everything, so the whole slow tier
runs against `../norsk-ctl`. (`NORSK_CTL_BINARY` resolution lives in
`@norskvideo/ctl-test-harness`'s `cli-command.ts`: explicit path → in-workspace
source → `norsk-ctl` on PATH.)
