[E00-S04-T02] Missing required setting gives field-specific startup error #397

Merged
kpcto merged 3 commits from feature/183 into main 2026-08-30 03:32:24 +00:00
Member

What changed

Implements [E00-S04-T02] Missing required setting gives field-specific startup error (#183): a missing required setting now fails startup with an error that names the missing field, built on the E00-S04-T01 TypeBox/Ajv schema.

  • packages/config — field-specific startup error (@personal-blog/config): new src/startup.ts compiles the committed configSchema with Ajv (same golden-tuple validator as validate.ts) and exports assertValidConfig(value): Config — the startup entry point the application calls before it binds — plus the field-specific errors: MissingRequiredSettingError (message missing required setting: <field> and .missingField name the missing required setting — the schema's required field sessionSecret, EPPP_SESSION_SECRET per Security-and-Operations §32/§26) and ConfigStartupError (any other schema violation, message names the violating field, e.g. sessionSecret: must NOT have fewer than 32 characters, extra: must NOT have additional properties). The package boundary re-exports both plus the entry point. Nothing reads process.env here — the environment adapter (E00-S04-T04) remains out of scope and later maps the environment onto this validated shape.
  • apps/server — startup wiring: src/index.ts imports assertValidConfig from @personal-blog/config (new workspace dependency) and validates the startup configuration (host/port/databaseUrl keep their committed defaults; sessionSecret from EPPP_SESSION_SECRET) before the server binds, so a deployment missing the required secret crashes at startup with missing required setting: sessionSecret instead of booting with an invalid configuration. The readiness gate, the migration run and the health endpoint are unchanged.
  • compose.yaml: the app service now provides EPPP_SESSION_SECRET with a dev-only ≥ 32-char default (${EPPP_SESSION_SECRET:-…}, override via .env/shell — same pattern as POSTGRES_PASSWORD:-eppp), so docker compose up -d keeps working from a clean clone while the app's startup validation has its required secret.
  • apps/server/Dockerfile: the build stage copies packages/config source and the runtime stage ships the compiled packages/config/dist + manifest (the server now imports @personal-blog/config); build/typecheck scripts build the config package first.
  • tests/config-startup-error.test.mjs — new suite locking in both acceptance criteria (details in the criterion → test table): static assertions on the committed startup-error module, the package boundary, the server wiring (validation before server.listen), the compose secret and the Dockerfile shipping — each backed by mutation probes proving non-vacuity — plus two deterministic probes: the compiled @personal-blog/config boundary throws MissingRequiredSettingError naming sessionSecret for a missing secret (and names the field for a short secret / unknown property), and the issue's test plan is executed against the real committed startup — booting the server without EPPP_SESSION_SECRET exits non-zero with the error naming the missing field, while a valid secret boots to GET /health 200.
  • Existing fixtures updated: health-endpoint/app-readiness boot probes provide a valid EPPP_SESSION_SECRET (the required setting is now validated at startup); .gitea/workflows/ci.yml gains a config-startup-error job (builds config + database-postgres, runs the suite) and the app-readiness job builds the config package too.
  • docs/development/non-container.md: the local run path now documents the required EPPP_SESSION_SECRET and the missing required setting: sessionSecret startup error.
  • pnpm-lock.yaml: apps/server importer gains @personal-blog/config (workspace link); pnpm install --frozen-lockfile passes.

Explicitly out of scope per the brief, not touched: TypeBox/Ajv schema (E00-S04-T01, already merged), secret redaction (E00-S04-T03), the process.env access rule / environment adapter (E00-S04-T04 — the server still reads the env vars it needs directly, as it did before this task; the adapter that centralizes these reads lands with T04).

Criterion → test table

Acceptance criterion Test (fails without the committed state)
missing required setting gives a field-specific startup error tests/config-startup-error.test.mjs — "the config package exposes the field-specific startup error (MissingRequiredSettingError + assertValidConfig)" (startup.ts compiles configSchema with Ajv and exports assertValidConfig + ConfigStartupError + MissingRequiredSettingError); "the package boundary re-exports the startup validation entry point and its errors"; "the server validates the required settings at startup, before it binds" (import { assertValidConfig } from '@personal-blog/config' + call with sessionSecret: process.env.EPPP_SESSION_SECRET at a source index before server.listen(); mutation probes "dropping the startup validation call …", "moving the startup validation after the server binds …", "dropping the missing-field detection …"; deterministic probes — the compiled boundary throws for a missing required setting and the server boot probe (the issue's test plan: "start with a missing required field and confirm the error names it") boots the committed server without EPPP_SESSION_SECRET → exits non-zero; a too-short secret also exits non-zero; a valid secret boots to GET /health 200
the error names the missing field tests/config-startup-error.test.mjs — "the config package exposes …" (/missing required setting: \$\{missingField\}/, /readonly missingField: string/, error.keyword === 'required' → MissingRequiredSettingError); mutation probe "an error message that does not name the missing field fails the naming assertion"; deterministic probes — the compiled boundary reports missingField === 'sessionSecret' and message /missing required setting: sessionSecret/ for a missing secret, and names the violating field for a short secret (/sessionSecret/) and an unknown property (/extra/); the server boot probe asserts stderr matches /missing required setting: sessionSecret/
the compose stack and image stay runnable with the required secret tests/config-startup-error.test.mjs — "the compose app service provides the required admin-session secret (EPPP_SESSION_SECRET)" (≥ 32-char dev default ${EPPP_SESSION_SECRET:-…}) and "the server image ships the config package (build source + runtime dist)"; mutation probes "removing EPPP_SESSION_SECRET from the compose app service …" and "dropping the config package from the image …"; the deterministic server boot probe with a valid secret confirms GET /health 200 (a valid startup still works)
the startup-error criterion gates merges via the config-startup-error job tests/config-startup-error.test.mjs — "the config-startup-error criterion is enforced in CI" (root test glob covers the suite; .gitea/workflows/ci.yml runs node --test tests/config-startup-error.test.mjs and builds @personal-blog/config + @personal-blog/database-postgres first); .gitea/workflows/ci.yml — config-startup-error job (additive, matching the security-reviewed #396 precedent)

Test plan executed

  • node --test tests/config-startup-error.test.mjs → 17 tests, 17 pass / 0 fail / 0 skip. The deterministic probes ran for real: the compiled boundary throws MissingRequiredSettingError naming sessionSecret for a missing secret and names the field for a short secret / unknown property; booting the committed server without EPPP_SESSION_SECRET exits non-zero with missing required setting: sessionSecret on stderr; with a too-short secret it exits non-zero naming sessionSecret; with a valid secret it boots to GET /health 200 {"status":"ok"}.
  • Affected existing suites: config-schema (12), health-endpoint (7), app-readiness (18 pass + 1 docker-gated skip) — all green; workspace-layout (8), workspace-config (6), strict-tsconfig (5), typescript-pin (3), architecture-import (10), no-core-extension-imports (2), compose-config, secrets-not-embedded, build-targets, non-root-user, readonly-rootfs — green (docker-gated probes skip without a daemon).
  • Full suite (node --test "tests/**/*.test.mjs"): 227 tests — 203 pass / 9 fail / 15 skip; the 9 failures are the pre-existing Node-22 environment artifacts identical to the base-commit baseline documented in #396 (this sandbox has Node 22 — the workspace engines gate requires Node ≥ 24): tests/frozen-install.test.mjs ×5 and tests/root-commands.test.mjs ×3 fail on ERR_PNPM_UNSUPPORTED_ENGINE, tests/node-engine.test.mjs ×1 asserts the runtime is Node 24.x. CI runs Node 24 where these pass.
  • Frozen install + lockfile: pnpm install --frozen-lockfile passes against the regenerated pnpm-lock.yaml (apps/server importer + @personal-blog/config workspace link).
  • Build/typecheck: packages/config, packages/database-postgres, apps/server and every other workspace package compile with tsc --noEmit exit 0 (strict base config, NodeNext, verbatimModuleSyntax, TypeScript 6.0.3).

Risks / notes

  • Server now requires EPPP_SESSION_SECRET at startup — this is the intended E00-S04-T02 behavior (the schema's only required field), and the compose dev default keeps the clean-clone docker compose up -d path working (same pattern as the POSTGRES_PASSWORD:-eppp dev default; runtime injection via Compose env, never baked into the image — T08's image-layer scan is unaffected, verified by secrets-not-embedded). T05 (.env.example) will document overriding it.
  • validateConfig (T01) is untouched: the startup module compiles the schema itself to read Ajv's structured params (e.g. missingProperty, additionalProperty) for field names; validateConfig keeps returning the raw message-only outcome, so the T01 boundary and its tests are unchanged.
  • Compose env default value is dev-only and never enters the image (Compose runtime injection, same as DATABASE_URL); the secrets-not-embedded suite still passes.
  • Rollback note from the issue: revert the validation error handling — remove packages/config/src/startup.ts (+ boundary re-exports), the server's assertValidConfig call and @personal-blog/config dependency, the compose EPPP_SESSION_SECRET entry, the Dockerfile config copies, the config-startup-error CI job and the fixture/probe updates (no data migration involved).
  • CI workflow change is strictly additive (new job; the app-readiness job's build step was extended, no existing job removed/modified otherwise) and matches the security-reviewed #396 precedent; per the review-checklist pipeline tripwire a human decision on the new merge gate may be requested.

Refs #183

## What changed Implements [E00-S04-T02] Missing required setting gives field-specific startup error (#183): a missing required setting now fails startup with an error that names the missing field, built on the E00-S04-T01 TypeBox/Ajv schema. - **`packages/config` — field-specific startup error** (`@personal-blog/config`): new `src/startup.ts` compiles the committed `configSchema` with Ajv (same golden-tuple validator as `validate.ts`) and exports `assertValidConfig(value): Config` — the startup entry point the application calls before it binds — plus the field-specific errors: `MissingRequiredSettingError` (message `missing required setting: <field>` and `.missingField` name the missing required setting — the schema's required field `sessionSecret`, `EPPP_SESSION_SECRET` per Security-and-Operations §32/§26) and `ConfigStartupError` (any other schema violation, message names the violating field, e.g. `sessionSecret: must NOT have fewer than 32 characters`, `extra: must NOT have additional properties`). The package boundary re-exports both plus the entry point. **Nothing reads `process.env` here** — the environment adapter (E00-S04-T04) remains out of scope and later maps the environment onto this validated shape. - **`apps/server` — startup wiring**: `src/index.ts` imports `assertValidConfig` from `@personal-blog/config` (new workspace dependency) and validates the startup configuration (`host`/`port`/`databaseUrl` keep their committed defaults; `sessionSecret` from `EPPP_SESSION_SECRET`) **before the server binds**, so a deployment missing the required secret crashes at startup with `missing required setting: sessionSecret` instead of booting with an invalid configuration. The readiness gate, the migration run and the health endpoint are unchanged. - **`compose.yaml`**: the `app` service now provides `EPPP_SESSION_SECRET` with a dev-only ≥ 32-char default (`${EPPP_SESSION_SECRET:-…}`, override via `.env`/shell — same pattern as `POSTGRES_PASSWORD:-eppp`), so `docker compose up -d` keeps working from a clean clone while the app's startup validation has its required secret. - **`apps/server/Dockerfile`**: the build stage copies `packages/config` source and the runtime stage ships the compiled `packages/config/dist` + manifest (the server now imports `@personal-blog/config`); build/typecheck scripts build the config package first. - **`tests/config-startup-error.test.mjs` — new suite** locking in both acceptance criteria (details in the criterion → test table): static assertions on the committed startup-error module, the package boundary, the server wiring (validation before `server.listen`), the compose secret and the Dockerfile shipping — each backed by **mutation probes** proving non-vacuity — plus two deterministic probes: the compiled `@personal-blog/config` boundary throws `MissingRequiredSettingError` naming `sessionSecret` for a missing secret (and names the field for a short secret / unknown property), and the **issue's test plan** is executed against the real committed startup — booting the server without `EPPP_SESSION_SECRET` exits non-zero with the error naming the missing field, while a valid secret boots to `GET /health` 200. - **Existing fixtures updated**: `health-endpoint`/`app-readiness` boot probes provide a valid `EPPP_SESSION_SECRET` (the required setting is now validated at startup); `.gitea/workflows/ci.yml` gains a `config-startup-error` job (builds config + database-postgres, runs the suite) and the `app-readiness` job builds the config package too. - **`docs/development/non-container.md`**: the local run path now documents the required `EPPP_SESSION_SECRET` and the `missing required setting: sessionSecret` startup error. - **`pnpm-lock.yaml`**: `apps/server` importer gains `@personal-blog/config` (workspace link); `pnpm install --frozen-lockfile` passes. Explicitly out of scope per the brief, **not touched**: TypeBox/Ajv schema (E00-S04-T01, already merged), secret redaction (E00-S04-T03), the `process.env` access rule / environment adapter (E00-S04-T04 — the server still reads the env vars it needs directly, as it did before this task; the adapter that centralizes these reads lands with T04). ## Criterion → test table | Acceptance criterion | Test (fails without the committed state) | | --- | --- | | missing required setting gives a field-specific startup error | `tests/config-startup-error.test.mjs` — **"the config package exposes the field-specific startup error (MissingRequiredSettingError + assertValidConfig)"** (startup.ts compiles `configSchema` with Ajv and exports `assertValidConfig` + `ConfigStartupError` + `MissingRequiredSettingError`); **"the package boundary re-exports the startup validation entry point and its errors"**; **"the server validates the required settings at startup, before it binds"** (`import { assertValidConfig } from '@personal-blog/config'` + call with `sessionSecret: process.env.EPPP_SESSION_SECRET` at a source index before `server.listen(`); mutation probes **"dropping the startup validation call …"**, **"moving the startup validation after the server binds …"**, **"dropping the missing-field detection …"**; **deterministic probes** — the compiled boundary throws for a missing required setting and the **server boot probe** (the issue's test plan: "start with a missing required field and confirm the error names it") boots the committed server without `EPPP_SESSION_SECRET` → exits non-zero; a too-short secret also exits non-zero; a valid secret boots to `GET /health` 200 | | the error names the missing field | `tests/config-startup-error.test.mjs` — **"the config package exposes …"** (`/missing required setting: \$\{missingField\}/`, `/readonly missingField: string/`, `error.keyword === 'required'` → `MissingRequiredSettingError`); mutation probe **"an error message that does not name the missing field fails the naming assertion"**; **deterministic probes** — the compiled boundary reports `missingField === 'sessionSecret'` and message `/missing required setting: sessionSecret/` for a missing secret, and names the violating field for a short secret (`/sessionSecret/`) and an unknown property (`/extra/`); the **server boot probe** asserts stderr matches `/missing required setting: sessionSecret/` | | the compose stack and image stay runnable with the required secret | `tests/config-startup-error.test.mjs` — **"the compose app service provides the required admin-session secret (EPPP_SESSION_SECRET)"** (≥ 32-char dev default `${EPPP_SESSION_SECRET:-…}`) and **"the server image ships the config package (build source + runtime dist)"**; mutation probes **"removing EPPP_SESSION_SECRET from the compose app service …"** and **"dropping the config package from the image …"**; the deterministic **server boot probe** with a valid secret confirms `GET /health` 200 (a valid startup still works) | | the startup-error criterion gates merges via the `config-startup-error` job | `tests/config-startup-error.test.mjs` — **"the config-startup-error criterion is enforced in CI"** (root test glob covers the suite; `.gitea/workflows/ci.yml` runs `node --test tests/config-startup-error.test.mjs` and builds `@personal-blog/config` + `@personal-blog/database-postgres` first); `.gitea/workflows/ci.yml` — **`config-startup-error` job** (additive, matching the security-reviewed #396 precedent) | ## Test plan executed - `node --test tests/config-startup-error.test.mjs` → **17 tests, 17 pass / 0 fail / 0 skip**. The deterministic probes ran for real: the compiled boundary throws `MissingRequiredSettingError` naming `sessionSecret` for a missing secret and names the field for a short secret / unknown property; booting the committed server **without** `EPPP_SESSION_SECRET` exits non-zero with `missing required setting: sessionSecret` on stderr; with a too-short secret it exits non-zero naming `sessionSecret`; with a valid secret it boots to `GET /health` 200 `{"status":"ok"}`. - **Affected existing suites**: `config-schema` (12), `health-endpoint` (7), `app-readiness` (18 pass + 1 docker-gated skip) — all green; `workspace-layout` (8), `workspace-config` (6), `strict-tsconfig` (5), `typescript-pin` (3), `architecture-import` (10), `no-core-extension-imports` (2), `compose-config`, `secrets-not-embedded`, `build-targets`, `non-root-user`, `readonly-rootfs` — green (docker-gated probes skip without a daemon). - **Full suite** (`node --test "tests/**/*.test.mjs"`): **227 tests — 203 pass / 9 fail / 15 skip**; the 9 failures are the pre-existing Node-22 environment artifacts identical to the base-commit baseline documented in #396 (this sandbox has Node 22 — the workspace engines gate requires Node ≥ 24): `tests/frozen-install.test.mjs` ×5 and `tests/root-commands.test.mjs` ×3 fail on `ERR_PNPM_UNSUPPORTED_ENGINE`, `tests/node-engine.test.mjs` ×1 asserts the runtime is Node 24.x. CI runs Node 24 where these pass. - **Frozen install + lockfile**: `pnpm install --frozen-lockfile` passes against the regenerated `pnpm-lock.yaml` (apps/server importer + `@personal-blog/config` workspace link). - **Build/typecheck**: `packages/config`, `packages/database-postgres`, `apps/server` and every other workspace package compile with `tsc --noEmit` exit 0 (strict base config, NodeNext, verbatimModuleSyntax, TypeScript 6.0.3). ## Risks / notes - **Server now requires `EPPP_SESSION_SECRET` at startup** — this is the intended E00-S04-T02 behavior (the schema's only required field), and the compose dev default keeps the clean-clone `docker compose up -d` path working (same pattern as the `POSTGRES_PASSWORD:-eppp` dev default; runtime injection via Compose env, never baked into the image — T08's image-layer scan is unaffected, verified by `secrets-not-embedded`). T05 (`.env.example`) will document overriding it. - **`validateConfig` (T01) is untouched**: the startup module compiles the schema itself to read Ajv's structured `params` (e.g. `missingProperty`, `additionalProperty`) for field names; `validateConfig` keeps returning the raw message-only outcome, so the T01 boundary and its tests are unchanged. - **Compose env default value** is dev-only and never enters the image (Compose runtime injection, same as `DATABASE_URL`); the secrets-not-embedded suite still passes. - **Rollback note from the issue**: revert the validation error handling — remove `packages/config/src/startup.ts` (+ boundary re-exports), the server's `assertValidConfig` call and `@personal-blog/config` dependency, the compose `EPPP_SESSION_SECRET` entry, the Dockerfile config copies, the `config-startup-error` CI job and the fixture/probe updates (no data migration involved). - CI workflow change is strictly additive (new job; the `app-readiness` job's build step was extended, no existing job removed/modified otherwise) and matches the security-reviewed #396 precedent; per the review-checklist pipeline tripwire a human decision on the new merge gate may be requested. Refs #183
bot-implementer added 3 commits 2026-08-30 03:01:14 +00:00
- packages/config: add src/startup.ts exposing assertValidConfig (builds on
  the T01 TypeBox/Ajv schema) and the field-specific startup errors
  (MissingRequiredSettingError names the missing field; ConfigStartupError
  names each violating field); re-export from the package boundary
- apps/server: validate the startup configuration (including the required
  EPPP_SESSION_SECRET) before the server binds, so a missing required
  setting crashes the process at startup naming the field; depends on
  @personal-blog/config
- compose.yaml: provide EPPP_SESSION_SECRET for the app service (dev-only
  >= 32 char default; override via .env / shell)
- Dockerfile: ship the compiled packages/config in the image (build source +
  runtime dist), matching the server's new workspace dependency
- pnpm-lock.yaml: apps/server importer gains @personal-blog/config
- tests/config-startup-error.test.mjs: static assertions on the committed
  startup-error module, the package boundary, the server wiring (validation
  before bind), the compose secret and the Dockerfile shipping, each backed
  by mutation probes; the deterministic probes execute the issue's test plan
  ("start with a missing required field and confirm the error names it") —
  the compiled boundary throws MissingRequiredSettingError naming
  sessionSecret, and booting the committed server without EPPP_SESSION_SECRET
  exits non-zero naming the field while a valid secret boots to /health 200
- health-endpoint/app-readiness boot probes: provide a valid
  EPPP_SESSION_SECRET (the required setting is validated at startup)
- ci.yml: new config-startup-error job (builds config + database-postgres,
  runs the suite); app-readiness job now builds the config package too
- .gitignore: transient .config-startup-probe-*.mjs files
docs: document the required EPPP_SESSION_SECRET and the startup error in the non-container guide (E00-S04-T02)
CI / Frozen lockfile install (pull_request) Successful in 45s
CI / Secrets not embedded (E00-S02-T08) (pull_request) Successful in 26s
CI / Database-postgres import isolation (E00-S03-T02) (pull_request) Successful in 26s
CI / Migration advisory lock (E00-S03-T04) (pull_request) Successful in 43s
CI / Migration failure diagnostic (E00-S03-T05) (pull_request) Successful in 42s
CI / App readiness after migrations (E00-S03-T06) (pull_request) Successful in 1m1s
CI / Migration ledger (E00-S03-T03) (pull_request) Successful in 46s
CI / Field-specific startup errors (E00-S04-T02) (pull_request) Successful in 1m6s
CI / TypeBox/Ajv config schema (E00-S04-T01) (pull_request) Successful in 48s
CI / Compose config (E00-S03-T01) (pull_request) Successful in 27s
873264004a
Member
agent: tester
role: tester
issue: 183
pr: 397
phase: start
objective: independently probe the PR for #183
constraints:
  - never edit the implementer's tests in the same PR
acceptance:
  - missing required setting gives a field-specific startup error
  - the error names the missing field
plan:
  - read committed config package sources (schema.ts / validate.ts / startup.ts) and the server wiring (apps/server/src/index.ts)
  - independently execute the committed assertValidConfig boundary (compiled from committed source) against missing / short / valid configs
  - independently boot the committed server without EPPP_SESSION_SECRET to confirm non-zero exit + the field name in the error
  - cross-check CI run #98 (all jobs green) and post evidence
status: in-progress
```yaml agent: tester role: tester issue: 183 pr: 397 phase: start objective: independently probe the PR for #183 constraints: - never edit the implementer's tests in the same PR acceptance: - missing required setting gives a field-specific startup error - the error names the missing field plan: - read committed config package sources (schema.ts / validate.ts / startup.ts) and the server wiring (apps/server/src/index.ts) - independently execute the committed assertValidConfig boundary (compiled from committed source) against missing / short / valid configs - independently boot the committed server without EPPP_SESSION_SECRET to confirm non-zero exit + the field name in the error - cross-check CI run #98 (all jobs green) and post evidence status: in-progress ```
Member
agent: tester
role: tester
issue: 183
pr: 397
phase: end
verdict: pass
acceptance:
  - criterion: missing required setting gives a field-specific startup error
    result: pass
    evidence:
      - "assertValidConfig is invoked at the top of apps/server/src/index.ts, before server.listen(...), feeding sessionSecret: process.env.EPPP_SESSION_SECRET"
      - "independent server boot without EPPP_SESSION_SECRET -> exit code 1, fails fast before the server binds (no [migrate]/listening output); stderr: `MissingRequiredSettingError: missing required setting: sessionSecret`"
  - criterion: the error names the missing field
    result: pass
    evidence:
      - "error.message === 'missing required setting: sessionSecret' and error.missingField === 'sessionSecret'"
      - "too-short secret -> ConfigStartupError 'invalid configuration: sessionSecret: must NOT have fewer than 32 characters'"
independent_probes:
  - kind: config boundary (compiled from byte-identical committed schema.ts / validate.ts / startup.ts with tsc, strict NodeNext)
    result: 7/7 checks passed
    details:
      - "configSchema requires sessionSecret with minLength 32"
      - "missing required setting -> MissingRequiredSettingError naming sessionSecret (message + .missingField)"
      - "too-short secret -> ConfigStartupError naming sessionSecret"
      - "unknown property -> ConfigStartupError naming the extra field"
      - "valid secret-only config and full valid config pass and are returned as Config"
  - kind: server boot (compiled committed apps/server/src/index.ts + @personal-blog/config + @personal-blog/database-postgres, Node 22.23.2)
    result: 3/3 cases pass
    details:
      - "missing secret -> exit 1, `missing required setting: sessionSecret`"
      - "short secret -> exit 1, names sessionSecret"
      - "valid secret (32 chars) -> GET /health 200 {\"status\":\"ok\"}"
ci:
  run: http://gitea:3000/Fabrika/PersonalBlog/actions/runs/98
  conclusion: success
  jobs: 10/10 success
  key_job: Field-specific startup errors (E00-S04-T02)
  key_job_url: http://gitea:3000/Fabrika/PersonalBlog/actions/runs/98/jobs/209
constraint_compliance:
  - "did not edit the implementer's tests in the PR; all independent probes ran against byte-identical committed sources compiled under /tmp"
status: done
```yaml agent: tester role: tester issue: 183 pr: 397 phase: end verdict: pass acceptance: - criterion: missing required setting gives a field-specific startup error result: pass evidence: - "assertValidConfig is invoked at the top of apps/server/src/index.ts, before server.listen(...), feeding sessionSecret: process.env.EPPP_SESSION_SECRET" - "independent server boot without EPPP_SESSION_SECRET -> exit code 1, fails fast before the server binds (no [migrate]/listening output); stderr: `MissingRequiredSettingError: missing required setting: sessionSecret`" - criterion: the error names the missing field result: pass evidence: - "error.message === 'missing required setting: sessionSecret' and error.missingField === 'sessionSecret'" - "too-short secret -> ConfigStartupError 'invalid configuration: sessionSecret: must NOT have fewer than 32 characters'" independent_probes: - kind: config boundary (compiled from byte-identical committed schema.ts / validate.ts / startup.ts with tsc, strict NodeNext) result: 7/7 checks passed details: - "configSchema requires sessionSecret with minLength 32" - "missing required setting -> MissingRequiredSettingError naming sessionSecret (message + .missingField)" - "too-short secret -> ConfigStartupError naming sessionSecret" - "unknown property -> ConfigStartupError naming the extra field" - "valid secret-only config and full valid config pass and are returned as Config" - kind: server boot (compiled committed apps/server/src/index.ts + @personal-blog/config + @personal-blog/database-postgres, Node 22.23.2) result: 3/3 cases pass details: - "missing secret -> exit 1, `missing required setting: sessionSecret`" - "short secret -> exit 1, names sessionSecret" - "valid secret (32 chars) -> GET /health 200 {\"status\":\"ok\"}" ci: run: http://gitea:3000/Fabrika/PersonalBlog/actions/runs/98 conclusion: success jobs: 10/10 success key_job: Field-specific startup errors (E00-S04-T02) key_job_url: http://gitea:3000/Fabrika/PersonalBlog/actions/runs/98/jobs/209 constraint_compliance: - "did not edit the implementer's tests in the PR; all independent probes ran against byte-identical committed sources compiled under /tmp" status: done ```
kpcto merged commit ebb9d4f421 into main 2026-08-30 03:32:24 +00:00
kpcto deleted branch feature/183 2026-08-30 03:32:25 +00:00
Sign in to join this conversation.