# Portfolio, Application & Feature Flag Provisioning

> portfolio.js: getPortfolio() (selects the first portfolio from GET /mr-api/portfolio), createApplication() (POST to portfolio endpoint), getEnvironment() (resolves default environment). features.js: the createAllFeatures() orchestration loop — createFeature() (idempotent, 409 treated as skipped), unlockBooleanFlag() (BOOLEAN-only unlock step via PUT feature-environments), setFeatureValue() (typed value dispatch: valueBoolean / valueString / valueNumber / valueJson), and the created/skipped/errors summary. flags.js: loadFlags() 4-path search strategy.

- Repository: vtex/dk-flags
- GitHub: https://github.com/vtex/dk-flags
- Human wiki: https://grok-wiki.com/public/wiki/vtex-dk-flags-0a8c140c3cfa
- Complete Markdown: https://grok-wiki.com/public/wiki/vtex-dk-flags-0a8c140c3cfa/llms-full.txt

## Source Files

- `docker/portfolio.js`
- `docker/features.js`
- `docker/flags.js`
- `docker/config.js`
- `docker/setup.js`
- `docker/exampleFlags.json`

---

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [docker/setup.js](docker/setup.js)
- [docker/portfolio.js](docker/portfolio.js)
- [docker/features.js](docker/features.js)
- [docker/flags.js](docker/flags.js)
- [docker/config.js](docker/config.js)
- [docker/api.js](docker/api.js)
- [docker/exampleFlags.json](docker/exampleFlags.json)
</details>

# Portfolio, Application & Feature Flag Provisioning

This page covers the end-to-end provisioning pipeline that `setup.js` drives against a running FeatureHub instance: selecting a portfolio, creating an application inside it, resolving the default environment, loading a flag definition file, and idempotently seeding every feature flag with its typed default value. Understanding this flow is essential for anyone bootstrapping a local or CI FeatureHub instance, writing integration tests, or extending the flag catalog.

The pipeline is intentionally sequential — each step produces an identifier that the next step consumes — so any failure short-circuits the run and prints a diagnostic without crashing the container process.

---

## Overall Orchestration

`setup.js` is the entry point. It runs as a one-shot Node script and exits with code `0` on success or `1` on unrecoverable error.

```
setup()
  1. waitForApp()           — poll APP_URL until HTTP 200 (up to 60 s)
  2. authenticate()         — obtain Bearer token
  3. getPortfolio()         — resolve portfolioId
  4. createApplication()    — create app inside portfolio → applicationId
  5. getEnvironment()       — resolve environmentId
  6. loadFlags()            — read exampleFlags.json → flags[]
  7. createAllFeatures()    — seed every flag
```

Sources: [docker/setup.js:8-73](docker/setup.js)

If `getPortfolio`, `createApplication`, `getEnvironment`, or `loadFlags` returns a falsy value, the script logs a warning and returns early without throwing — this keeps the Docker entrypoint from restarting indefinitely when FeatureHub is intentionally empty.

---

## Configuration (`config.js`)

All runtime knobs are resolved once from environment variables with safe defaults:

| Key | Env variable | Default |
|---|---|---|
| `APP_URL` | `APP_URL` | `http://localhost:8085` |
| `ADMIN_USER` | `ADMIN_USER` | `admin` |
| `ADMIN_PASSWORD` | `ADMIN_PASSWORD` | `admin` |
| `ADMIN_EMAIL` | `ADMIN_EMAIL` | `admin@example.com` |
| `MAX_WAIT_TIME` | _(hardcoded)_ | `60000` ms |
| `PORTFOLIO_NAME` | _(hardcoded)_ | `integrationTest` |
| `APPLICATION_NAME` | _(hardcoded)_ | `integrationTest` |

Sources: [docker/config.js:1-10](docker/config.js)

`PORTFOLIO_NAME` and `APPLICATION_NAME` are not currently wired to env variables; they are fixed constants used as the canonical test portfolio and application names.

---

## Portfolio Module (`portfolio.js`)

### `getPortfolio(token)`

Issues `GET /mr-api/portfolio` and selects the **first** portfolio from the response. The function is defensive about the response envelope — it checks `response.data` as a plain array first, then falls back to `.items`, `.portfolios`, or `.data` sub-keys before giving up. Returns the first resolved `id` / `portfolioId` / `portfolio.id`, or `null` if none are found.

```js
// docker/portfolio.js:8-21
const response = await makeRequest('GET', '/mr-api/portfolio', null, token);
const portfolios = Array.isArray(response.data) ? response.data :
    (response.data.items || response.data.portfolios || response.data.data || []);
if (portfolios.length > 0) {
    const portfolioId = portfolios[0].id || portfolios[0].portfolioId || portfolios[0].portfolio?.id;
    return portfolioId;
}
```

This "first wins" strategy means the script does not create a portfolio — it expects one to exist already (created manually or by a prior run).

### `createApplication(portfolioId, token)`

Posts to `POST /mr-api/portfolio/{portfolioId}/application` with the body `{ name: config.APPLICATION_NAME, description: 'Example application' }`. Accepts HTTP 200 or 201 as success and returns `response.data.id`. Any other status is logged and returns `null`.

Sources: [docker/portfolio.js:29-52](docker/portfolio.js)

### `getEnvironment(applicationId, token)`

Issues `GET /mr-api/application/{applicationId}/environment` and picks the first item from the array response, returning `data[0].id`. Like `getPortfolio`, it does not create an environment — it relies on FeatureHub auto-creating a default environment when the application is provisioned.

Sources: [docker/portfolio.js:54-74](docker/portfolio.js)

---

## Feature Flag Module (`features.js`)

### Flag Definition Shape

Each flag in `exampleFlags.json` follows this schema:

| Field | Required | Description |
|---|---|---|
| `key` | yes | Stable identifier used as the flag key in all API calls |
| `type` | yes | `BOOLEAN`, `STRING`, `NUMBER`, or `JSON` |
| `defaultValue` | yes | Value to set on the default environment |
| `name` | no | Human-readable name (falls back to `key`) |
| `description` | no | Falls back to `"Feature flag: {key}"` |

Sources: [docker/exampleFlags.json:1-55](docker/exampleFlags.json)

### `createFeature(applicationId, flag, token)`

Posts `{ name, key, description, valueType }` to `POST /mr-api/application/{applicationId}/features`.

| Response | Outcome |
|---|---|
| 200 / 201 | `{ success: true, skipped: false }` — flag created |
| 409 | `{ success: true, skipped: true }` — flag already exists, treat as no-op |
| Other | `{ success: false, skipped: false }` — creation failure |

The 409-as-skip pattern makes the entire provisioning run **idempotent** — re-running against an already-seeded FeatureHub instance does not fail; it counts existing flags as "skipped."

Sources: [docker/features.js:3-37](docker/features.js)

### `unlockBooleanFlag(applicationId, flagKey, environmentId, defaultValue, version, token)`

Called **only for `BOOLEAN` flags**, immediately after `createFeature` succeeds. Sends a `PUT` to `/mr-api/application/{applicationId}/feature-environments/{flagKey}` with the payload:

```js
[{
    key: flagKey,
    locked: false,
    environmentId: environmentId,
    valueBoolean: defaultValue,
    version: version
}]
```

This step exists because FeatureHub creates new BOOLEAN features in a locked state. The unlock must precede `setFeatureValue` for the value write to succeed. Non-boolean types do not have this locking behavior, so the unlock step is skipped for them.

Sources: [docker/features.js:39-64](docker/features.js)

### `setFeatureValue(environmentId, flag, version, token)`

Dispatches to `PUT /mr-api/features/{environmentId}/feature/{flagKey}` with exactly one of four typed value fields populated, all others set to `null`:

```js
{
    key: flagKey,
    locked: false,
    valueString:  flagType === 'STRING'  ? flag.defaultValue : null,
    valueNumber:  flagType === 'NUMBER'  ? flag.defaultValue : null,
    valueBoolean: flagType === 'BOOLEAN' ? flag.defaultValue : null,
    valueJson:    flagType === 'JSON'    ? JSON.stringify(flag.defaultValue) : null,
    version: version
}
```

`valueJson` serializes the JavaScript object/array to a JSON string before sending, matching the FeatureHub API expectation. On success, returns `{ success: true, newVersion: version + 1 }`, advancing the version counter for the next write in the loop.

Sources: [docker/features.js:66-98](docker/features.js)

### `createAllFeatures(applicationId, environmentId, flags, token)` — Orchestration Loop

Iterates over every flag definition in order, applying a three-step sub-sequence:

```
for each flag:
  1. createFeature()          → success / skipped / error
  2. if BOOLEAN: unlockBooleanFlag()
  3. setFeatureValue()        → success / error; version++
```

A shared `version` counter starts at `0` and increments after each successful `setFeatureValue` call (and also after each `unlockBooleanFlag` call, since the unlock itself advances the version). Running totals for `created`, `skipped`, and `errors` are accumulated and printed as a summary at the end:

```
Feature flags creation summary:
  ✓ Created: N
  ⊘ Skipped (already exists): N
  ✗ Errors: N
```

The function returns the summary object `{ created, skipped, errors }` to the caller in `setup.js`, which uses it only to emit an additional warning when `errors > 0 && created === 0`.

Sources: [docker/features.js:100-147](docker/features.js)

---

## Flags Loader (`flags.js`)

`loadFlags()` resolves `exampleFlags.json` via a four-path search, stopping at the first readable file:

| Priority | Path |
|---|---|
| 1 | `__dirname/exampleFlags.json` (same directory as `flags.js`) |
| 2 | `process.cwd()/exampleFlags.json` (working directory at runtime) |
| 3 | `/app/exampleFlags.json` (Docker container absolute path) |
| 4 | `./exampleFlags.json` (relative CWD shorthand) |

Each candidate is tested with `fs.existsSync` before reading, and parse errors skip silently to the next path. If no path yields a valid file, `loadFlags` returns `null` and logs a warning — `setup.js` then exits early without throwing.

Sources: [docker/flags.js:4-29](docker/flags.js)

The priority ordering places the script-adjacent path first (correct for local runs) and the Docker absolute path third (correct for container deployments where the working directory may differ from the script location).

---

## End-to-End Sequence

```mermaid
sequenceDiagram
    participant S as setup.js
    participant P as portfolio.js
    participant F as features.js
    participant FL as flags.js
    participant API as FeatureHub REST API

    S->>API: GET /mr-api/portfolio
    API-->>S: portfolioId
    S->>API: POST /mr-api/portfolio/{id}/application
    API-->>S: applicationId
    S->>API: GET /mr-api/application/{id}/environment
    API-->>S: environmentId
    S->>FL: loadFlags()
    FL-->>S: flags[]
    loop for each flag
        S->>API: POST /mr-api/application/{appId}/features
        API-->>S: 201 (created) or 409 (exists)
        opt BOOLEAN only
            S->>API: PUT /mr-api/application/{appId}/feature-environments/{key}
        end
        S->>API: PUT /mr-api/features/{envId}/feature/{key}
    end
    S-->>S: print summary
```

---

## Example Flag Catalog (`exampleFlags.json`)

The bundled catalog covers all four supported value types:

| Key | Type | Default Value |
|---|---|---|
| `booleanConf` | `BOOLEAN` | `true` |
| `stringConf` | `STRING` | `"myStringConf"` |
| `intConf` | `NUMBER` | `10` |
| `floatConf` | `NUMBER` | `1.5` |
| `jsonConf` | `JSON` | `{ "data": "test", "evaluation": true }` |
| `objectConf` | `JSON` | Nested object with string, int, bool, and array fields |
| `stringArrayConf` | `JSON` | `["stringValue1", "stringValue2", "stringValue3"]` |

Sources: [docker/exampleFlags.json:1-55](docker/exampleFlags.json)

The `JSON` type is used for both object and array payloads; there is no separate `ARRAY` type. Arrays are expressed as top-level JSON values and serialized with `JSON.stringify` in `setFeatureValue`.

---

## HTTP Transport (`api.js`)

All modules share `makeRequest(method, path, data, token)` from `api.js`. It resolves the full URL against `config.APP_URL`, sets `Content-Type: application/json`, attaches a `Bearer` token when provided, and returns `{ status, data, headers }`. JSON responses are parsed automatically; non-JSON responses are returned as raw text. Connection errors bubble up as thrown `Error` instances.

Sources: [docker/api.js:3-40](docker/api.js)

---

## Summary

The provisioning pipeline is a linear, fail-fast chain: portfolio → application → environment → flags. The modular design keeps each responsibility in its own file (`portfolio.js`, `features.js`, `flags.js`) with `setup.js` as the thin orchestrator. Idempotency is achieved via 409-as-skip at the feature creation layer, and the BOOLEAN-specific unlock step is the only branching logic inside the per-flag loop. The four-path file resolution in `loadFlags` handles both local development and containerized deployment without requiring runtime configuration.
