Skip to content

feat(#66): test environment layer — provisioning, config discovery, test data, config apply - #144

Open
Hareet wants to merge 14 commits into
mainfrom
66-test-environment-layer-implementation
Open

feat(#66): test environment layer — provisioning, config discovery, test data, config apply#144
Hareet wants to merge 14 commits into
mainfrom
66-test-environment-layer-implementation

Conversation

@Hareet

@Hareet Hareet commented Jul 18, 2026

Copy link
Copy Markdown
Member

feat(#66): Test Environment Layer — provisioning, config discovery, test data, config apply

Closes #66. Enables #64 (QA Supervisor pipeline invocation).

1. What this PR delivers

The Test Environment Layer for the cht-agent QA Supervisor: a deterministic
(no-LLM) orchestrator that stands up a live CHT instance, applies a cht-conf
config, reads the deployed config back, seeds conforming test data, and resets
between runs — all human-gated for Docker (the agent runs no Docker; a human
brings the environment up/down) and credential-safe (creds ride the cht-conf
--url arg / the HTTP Authorization header, never a log line).

It is the layer that drove the closed-loop demo's QA phase live; this PR ships it
at workbench parity minus the cht-conf-extension pieces (which land in their
own PR — see the deferred map in §7). The pipeline/LangGraph wiring that invokes
this layer arrives with #64; this PR ships the layer + its direct-use surface.

Real paths implemented

  • provision — human-gated bring-up (scripts/test-env-up.sh <cht-core>) +
    a bounded, backing-off readiness poll of /api/v2/monitoring.
  • applyConfig — one cht invocation per upload bucket (app-settings /
    app-forms / contact-forms / resources), status parsed from stdout
    (uploaded / skipped / failed), buckets independent so one failure does
    not abort the rest.
  • discoverConfigGET /api/v1/settings + the form: _all_docs range,
    parsed into DiscoveredConfig (contact types, roles, permissions, transitions,
    forms, and formVersions — each installed form's rev, the change-detection
    hash for the apply → verify loop).
  • prepareTestData — cht-conf csv-to-docs + upload-docs seed the
    instance, then create-users when users.csv exists; seeded doc ids are
    tracked per environment.
  • reset('couchdb') — the one reset the agent performs itself over the
    CouchDB HTTP API: wipe the tracked docs at their current revs, then reseed
    pristine copies (reseed source pre-flighted before the destructive wipe, so a
    vanished data project fails closed). restart/full stay human-gated.
  • teardown — prints the human docker compose down -v gate and clears the
    per-env tracking.

2. Implementation map

File Role
src/utils/cht-conf-runner.ts child_process isolation for cht-conf: runChtConf (generic, ordered verb list) + runBucket (config-upload buckets), classifyChtConfOutput, minimalEnv (secret-free child env), resolveChtConfBin (CHT_CONF_BIN seam).
src/utils/cht-api.ts fetch isolation for CHT/CouchDB: fetchSettings, fetchFormRevs, fetchDocRevs, bulkDocs (bounded, authed, cred-safe).
src/utils/test-data.ts fs isolation + cht-conf stdout parsers: readSeededDocs, cleanSeededDocs, classifySeededDocs, parseUploadDocsSummary, countCreatedUsers, hasUsersCsv.
src/agents/test-environment-agent.ts The orchestrator (mock + real paths). Docker-free; delegates every side effect to the isolation modules above.
src/agents/test-environment-agent.mock-data.ts Deterministic mock fixtures (CI-safe, no instance).
src/types/index.ts Layer types (Config/Discovery/Provision/Runner/apply sets; app-settings-only; instanceUrl REQUIRED).
src/utils/cht-readiness.ts Readiness poll (Phase 1).
scripts/test-env-up.sh, scripts/test-env-down.sh, docker/cht-agent-net.override.yml Human-gated bring-up/teardown + compose override (Phase 1).

3. Test surface

build + test + lint are green after every commit (Node 22). Full suite:
1112 passing. Real-path specs mock fetch / child_process / fs, so the
suite is green with no instance and no Docker.

Layer spec its
test/utils/cht-conf-runner.spec.ts 26
test/utils/cht-api.spec.ts 13
test/utils/test-data.spec.ts 19
test/agents/test-environment-agent.spec.ts 72
test/utils/cht-readiness.spec.ts 4
Layer total 134

4. Environment seams

  • CHT_CONF_BIN — override the cht-conf binary (default cht); lets the
    agent run a deployment-pinned cht-conf and lets specs stub a fake script.
  • CHT_URLprovision() falls back to it for the instance URL
    (options.urlCHT_URL (trimmed; blank ignored) → the on-network default
    https://nginx). The resolved handle.url is canonicalized (trailing slash
    stripped) and stripped of any embedded basic-auth creds (which survive only as
    an auth fallback, decodeUserinfo-decoded, tolerating a raw %).
  • COUCHDB_USER / COUCHDB_PASSWORD — the instance-auth seam, the same one
    scripts/test-env-up.sh uses for the bring-up, so a non-default password needs
    no code change. Auth precedence: options.auth → URL-embedded creds →
    COUCHDB_* env → the default medic/password.
  • ProvisionOptions.url / ProvisionOptions.auth — the highest-precedence
    target + creds the handle carries; every downstream call reads
    handle.url/handle.auth.
  • useMockDocker (constructor) — mock mode is the default; false selects
    the real paths. This is what keeps CI Docker-free.
  • scripts/test-env-up.sh <cht-core> — the human-gated bring-up seam.

5. Standalone guarantee + independent cht-core demo

Zero imports from workflow / cht-conf-extension code. Real-path specs mock
fetch/child_process → the suite is green with no instance and no Docker.

The demo below exercises the whole layer in real mode
(useMockDocker: false) against a real dockerized CHT, entirely in Docker:
the CHT stack comes up human-gated on cht-agent-net, and the layer runs in
a disposable container on the same network, reaching the instance at the
layer's native https://nginx default through the CHT_URL seam (§4).

1. Bring CHT up (human-gated — the layer itself never runs Docker):

scripts/test-env-up.sh ~/src/cht-core    # local images + stack, joined to cht-agent-net

2. Start a disposable runner on the same network (from this repo's root):

docker run --rm -it --network cht-agent-net \
  -v "$PWD":/app -w /app \
  -v "$HOME/src/cht-core/config/default":/config/default:ro \
  -e CHT_URL=https://nginx \
  -e NODE_TLS_REJECT_UNAUTHORIZED=0 \
  node:22 bash

(NODE_TLS_REJECT_UNAUTHORIZED=0 covers the dev stack's self-signed cert for
the layer's own fetch; the cht-conf child already runs with
--accept-self-signed-certs.)

3. Inside the container — build, then drive every layer method:

npm ci && npm run build && npm install -g cht-conf
mkdir -p /tmp/demo-data/csv && printf 'name\nDemo CHW\n' > /tmp/demo-data/csv/person.csv

node -e "
const { TestEnvironmentAgent } = require('./dist/agents/test-environment-agent');
(async () => {
  const agent = new TestEnvironmentAgent({ useMockDocker: false });
  const handle = await agent.provision({ chtCorePath: '~/src/cht-core' });
  const config = await agent.discoverConfig(handle);
  console.log('forms deployed:', Object.keys(config.formVersions ?? {}).length);
  const apply = await agent.applyConfig(handle, '/config/default');
  console.log('apply:', apply.actions.map(a => a.action + '=' + a.status).join(', '));
  const seeded = await agent.prepareTestData(handle, config, { dataPath: '/tmp/demo-data' });
  console.log('seeded:', JSON.stringify(seeded));
  await agent.reset(handle, 'couchdb');                 // wipe + reseed the tracked docs
  await agent.teardown(handle);                          // prints the human down-gate
})();"

No URL or credentials are passed to any call: provision() resolves the
instance from the CHT_URL env seam and the default auth, strips/canonicalizes
it onto the handle, and every downstream method reads the handle — the §4
seams doing their job. Expected: the readiness poll returns, discovery reports
the deployed forms, all four upload buckets run, the demo person seeds, the
couchdb-tier reset wipes and reseeds it, and teardown prints the human
down-gate.

Mock mode (useMockDocker, the default) mirrors the same call sequence
CI-safe — it is what the spec suite drives. Ticket-driven invocation of this
sequence is #64's scope; this PR ships the layer and the direct-use surface.

6. Excision proof (no cht-conf-extension code)

$ grep -rn "fetchFormXml\|verifyArtifact\|fetchDeployedFormXml\|runOfflineConvert\
\|createConvertSandbox\|skipValidate\|xform-inspect\|config-type\|cht-conf-tier2\
\|qa-workflow\|XlsformBindDiff" src/ test/
$ echo $?
1        # no matches → empty → clean

7. Deferred cht-conf-extension map (verbatim boundary)

Ported with excisions (the stripped items are the cht-conf extension):

Workbench source Stripped in this PR
src/agents/test-environment-agent.ts verifyArtifact, fetchDeployedFormXml, and their imports (fetchFormXml, verifyFormBinds)
src/utils/cht-api.ts fetchFormXml
src/utils/cht-conf-runner.ts offline-convert block (runOfflineConvert, createConvertSandbox, CONVERT_VERBS, SANDBOX_EXCLUDES, OfflineConvertOptions), skipValidate; instanceUrl restored to REQUIRED
src/types/index.ts VerifyArtifact*, QaInput/QaResult/QaTier2Result, XlsformBindDiff, offline-convert optionals
spec files describes for the stripped exports (fetchFormXml, verifyArtifact, runOfflineConvert, createConvertSandbox, F2/F5 xform describes)

Not ported at all (whole files → cht-conf-extension PR):
xform-inspect.ts, config-type.ts, cht-conf-tier2.ts, cht-conf-test-spec.ts,
qa-workflow.ts, orchestrator.ts wiring, CLI flags.

8. Commits (on 66-test-environment-layer-implementation, rebased onto origin/main @ fdf4af2)

  • feat(#66): phase-2 parity uplift — cht-conf runner to workbench parity minus excisions
  • feat(#66): phase 3 — discoverConfig + cht-api, prepareTestData + test-data, couchdb-tier reset
  • test(#66): layer spec suite ported (agent real paths, cht-api, test-data)
  • docs(#66): handoff status, PR description, deferred cht-conf-extension map
  • feat(#66): provision env-seam parity — CHT_URL fallback, cred stripping, COUCHDB_* auth seam
  • chore(#66): review hygiene — dead doc refs, stale roadmap note, should-style agent spec titles

(Phases 1–2 groundwork — provision/readiness/scripts/apply shape — is the branch's
pre-existing history, replayed unchanged by the rebase.)

Hareet and others added 12 commits July 18, 2026 01:17
…elector + result)

Bake the Phase-2 API shape into applyConfig so #134 PR5's cht-conf
diagnose→fix→validate loop plugs in without reshaping the type later.
Real Docker path still throws NOT_IMPLEMENTED (Phase 2); only the type
shape and mock change here.

- ConfigUploadAction buckets (app-settings/app-forms/contact-forms/
  resources) map to their cht-conf verbs; `actions` narrows which run so
  the loop re-uploads only the artifact it changed.
- `artifact` targets a single form (cht-conf --forms=<name>).
- ConfigActionStatus uploaded|skipped|failed — skipped is cht-conf's
  hash-check no-op, which a boolean couldn't express and verify needs.
- applyConfig returns ConfigApplyResult (was void); accepts a bare
  configPath string for back-compat.

573 tests pass, build + lint clean (Node 22).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t spawn, status parsing)

Implement applyConfig's !useMockDocker path. A new cht-conf-runner isolates
child_process: it spawns `cht` per upload bucket (no shell, explicit argv),
classifies the result as uploaded/skipped/failed, and never logs credentials.

- runBucket() spawns cht-conf with the autonomous-safe flags (--force,
  --skip-*-check, --accept-self-signed-certs); creds ride the --url arg.
- classifyChtConfOutput() parses status per line, skip-detected first — real
  cht-conf "no changes" lines contain the word "uploaded", so naive matching
  misclassifies them; verified against the cht-conf source strings.
- minimalEnv() passes a PATH/HOME allow-list, not the agent's full env, so the
  child (which runs config-project code) can't read LLM provider keys.
- agent loops buckets sequentially; one failure flips succeeded:false without
  aborting the rest. Mock path unchanged; both paths share toApplyResult().

591 tests pass, build + lint clean, coverage 96.66%/86.1% (Node 22).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…y minus excisions

Bring the Phase-2 cht-conf runner up to the workbench lineage's shape (the
port source of truth), excising the cht-conf-extension-only pieces so this
PR stays test-environment-layer scoped.

Parity uplift (from the workbench src/utils/cht-conf-runner.ts):
- Split the spawn wrapper into two layers: runChtConf() runs one `cht`
  process for an ordered verb list and returns the raw ChtConfExecResult;
  runBucket() wraps it for the config-upload buckets and classifies the
  status. The generic layer is what the Phase-3 test-data verbs reuse.
- resolveChtConfBin() adds the CHT_CONF_BIN seam so the agent can run a
  deployment-pinned cht-conf instead of the image's global `cht`.
- Add the `app-settings-only` bucket (upload-app-settings, no compile) for
  a pre-compiled, deployment-recovered app_settings.json.
- Artifact form-filter now rides after a literal `--` separator (cht-conf
  reads only cmdArgs['--'] into args-form-filter; a bare positional throws
  "Unsupported action(s)").
- minimalEnv() env allow-list and per-line skip-first classification
  preserved.

Excisions (cht-conf-extension, deferred to that PR):
- No offline-convert block (runOfflineConvert, createConvertSandbox,
  CONVERT_VERBS, SANDBOX_EXCLUDES, OfflineConvertOptions).
- No skipValidate handling; ChtConfExecOptions.instanceUrl is REQUIRED
  (offline URL-less convert was the only caller that omitted it).

Also fix the stale agent header comment (applyConfig is implemented now).
Types add ChtConfExecOptions/ChtConfExecResult and the app-settings-only
union member. Runner spec ported to parity minus the offline-convert
describes.

1045 tests pass, build + lint clean (Node 22).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-data, couchdb-tier reset

Land the Phase-3 real paths of the Test Environment Agent, ported from the
workbench lineage (the port source of truth), minus the cht-conf-extension
pieces that belong to that PR.

New HTTP/fs isolation modules (mirroring how cht-readiness.ts isolates fetch):
- src/utils/cht-api.ts — bounded, authed CouchDB/CHT helpers: fetchSettings,
  fetchFormRevs, fetchDocRevs, bulkDocs. Credentials ride the Authorization
  header only, never a URL or log line.
- src/utils/test-data.ts — json_docs reading, seeded-doc classification against
  the discovered config, and cht-conf stdout parsers (upload-docs summary,
  create-users progress; ANSI-stripped).

Agent real paths (mock paths unchanged):
- discoverConfig: GET /api/v1/settings + the form-rev _all_docs range, parsed
  into DiscoveredConfig. formVersions carries each installed form's rev — the
  change-detection hash for the apply -> verify loop.
- prepareTestData: cht-conf csv-to-docs + upload-docs seed the instance, then
  create-users when users.csv exists. Seeded doc ids are tracked per env for
  the reset. Takes a PrepareTestDataOptions (dataPath/bin/timeoutMs).
- reset('couchdb'): the one reset the agent does itself — wipe the tracked docs
  at their CURRENT revs, then reseed pristine copies. The reseed source is
  pre-flighted before the destructive wipe so a vanished data project fails
  closed. restart/full stay human-gated. teardown clears the tracking.

Excisions (cht-conf-extension, deferred): verifyArtifact + fetchDeployedFormXml
and their imports (fetchFormXml from cht-api, verifyFormBinds from xform-inspect
which is not ported); mockVerifyArtifactResult.

Types: DiscoveredConfig.formVersions, TestDataResult.succeeded/seededDocIds,
PrepareTestDataOptions. Mock fixture gains formVersions + seededDocIds. The
three obsolete "throws not-implemented in real mode" agent assertions are
removed here (real-path specs land in the following test commit).

1042 tests pass, build + lint clean (Node 22).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ata)

Complete the Test Environment Layer's real-path spec coverage, ported from
the workbench lineage minus the excised cht-conf-extension describes. All
real-path specs mock fetch / child_process, so the suite stays green with no
running instance and no Docker.

New unit specs:
- test/utils/cht-api.spec.ts — fetchSettings / fetchFormRevs / fetchDocRevs /
  bulkDocs against a stubbed global fetch (auth header, abort signal, path+status
  error text with no password, non-object/non-array normalisation). The
  fetchFormXml describe is excised with its import.
- test/utils/test-data.spec.ts — the upload-docs/create-users stdout parsers
  (ANSI-stripped), json_docs fixtures on a real tmp dir, and classifySeededDocs
  against a discovered config.

Agent real-mode describes (stubbing cht-api / cht-conf-runner / test-data):
- discoverConfig: fetches with the handle url+auth, parses contact types / roles /
  permissions / transitions dropping junk, lists forms with their revs, propagates
  a fetch failure, and warns on an instance with no contact_types.
- prepareTestData: runs csv-to-docs+upload-docs then create-users, classifies the
  seeded docs, counts users, skips create-users without users.csv, warns on
  partial/empty uploads, and reports succeeded:false on a failed run.
- reset('couchdb'): no-op when nothing tracked; wipes tracked docs at CURRENT revs
  and reseeds; skips tombstones; throws on rejected deletion / failed / partial
  reseed; fails closed before the wipe when the reseed source is gone; refreshes
  tracking; teardown clears tracking. Plus mock formVersions + succeeded/seededDocIds
  parity assertions.

The runner spec landed with the C1 parity uplift; the excised describes
(verifyArtifact, fetchDeployedFormXml, fetchFormXml, offline-convert) are not
ported. Excision grep over src/ and test/ is empty.

1104 tests pass, build + lint clean (Node 22).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n map

Add PR_66_DESCRIPTION.md (the reviewer-facing writeup) and mark the phase-2
handoff DONE now that phases 2 + 3 have landed.

PR_66_DESCRIPTION.md covers, per the completion plan §7 / §6b:
- what the layer delivers + the implementation map;
- the test surface (1104 passing; 126 in the layer) and the standalone,
  Docker-free / instance-free guarantee + independent cht-core demo recipe;
- the env seams (CHT_CONF_BIN, ProvisionOptions url/auth, useMockDocker,
  human-gated test-env-up.sh) with an explicit scope note on provision;
- the deferred cht-conf-extension boundary map (verbatim);
- the excision-grep proof (empty over src/ + test/);
- per-function parity proofs vs the workbench port source (identical modulo
  the excisions; test-data byte-identical; the one provision divergence called
  out);
- the sonar sweep result (four house rules zero; cognitive-complexity note).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ng, COUCHDB_* auth seam

Port the workbench provision() env-seam resolver (and the decodeUserinfo helper)
into the real-mode path verbatim. This is test-environment-layer code — nothing
cht-conf about it — and closes a handle-contract gap: the ported phase-2/3
consumers (credentialedUrl in applyConfig/prepareTestData/resetCouchdbTier,
discoverConfig's auth) assume provision's handle invariants — creds stripped off
handle.url, auth resolved into the handle, url canonical (it keys the seeded-doc
tracking map). Leaving provision at the Phase-1 shape risked that drift.

Six behaviors, exactly as the workbench:
- URL fallback order: options.url -> process.env.CHT_URL (trimmed; blank ignored)
  -> the on-network default (https://nginx).
- Resolved URL canonicalized: trailing slashes stripped.
- Embedded basic-auth creds stripped OUT of handle.url (it is logged and passed
  to undici fetch(), which rejects credentialed URLs); they survive only as an
  auth fallback, decoded via decodeUserinfo (tolerates a raw '%' without throwing).
- COUCHDB_USER/COUCHDB_PASSWORD auth seam (the same seam scripts/test-env-up.sh
  uses); COUCHDB_USER absent -> default user; gated on COUCHDB_PASSWORD presence.
- Auth precedence: options.auth -> embedded-URL creds -> COUCHDB_* env -> default.
- Handle shape otherwise unchanged (url, auth copy, network, chtCorePath, source).

Specs: port the workbench describe('CHT_URL fallback') block (8 its) and add the
CHT_URL/COUCHDB_* save/clear/restore to the provision real-mode describe so the
suite stays order-independent. The provision describe is now byte-identical to
the workbench.

Docs: PR_66_DESCRIPTION.md — provision is now at full workbench parity (the only
agent divergence is the excised verifyArtifact/fetchDeployedFormXml); updated the
env-seam list (CHT_URL/COUCHDB_*), the parity table, and the test counts.

1112 tests pass (+8), build + lint clean, excision grep still empty (Node 22).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d-style agent spec titles

Pre-push code-review cleanup. Comment / spec-title changes only — zero behavior
change (build clean, 1112 tests unchanged, lint clean, excision grep still empty).

- Drop See:-block refs to files that exist in no repo (designs/cht-conf-agent-
  extension.md §7.2) and the transient handoff citation, from the cht-conf-runner
  and cht-api headers and the ConfigUploadAction docblock — keep the durable
  designs/layer_recommendations/test-environment-layer.md reference.
- Agent header: replace the stale "LangGraph node + CLI" roadmap line with
  "The pipeline wiring that invokes this layer lands with #64."
- Compress the provision cred-stripping narration to two lines.
- Spec comment: "workbench env" -> "ambient env".
- docker/cht-agent-net.override.yml: drop the non-ascii warning glyph.
- Agent spec: rename the real-mode it() titles to should-style so the file is
  uniformly should-style (matching the repo's other agent specs / CHT convention);
  the four utils specs stay imperative per the repo's utils convention.
- Agent spec: hoist the shared discoverConfig / reset realAgent into a
  let + beforeEach, matching the couchdb-reset suite's pattern.

The workbench parity source receives these identical edits operator-side, so the
per-function parity claim in PR_66_DESCRIPTION.md §8 stays true (left unreworded).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Hareet Hareet changed the title # feat(#66): test environment layer — provisioning, config discovery, test data, config apply feat(#66): test environment layer — provisioning, config discovery, test data, config apply Jul 18, 2026
CHT Agent and others added 2 commits July 18, 2026 04:00
…chers, api polish

Behavior-preserving SonarCloud round for PR #144. No public API changes, no
spec-title changes, no NOSONAR — only helper extraction and assertion polish.

A. S3776 cognitive complexity — every function in the five touched src files now
   lands at <=5 (the repo threshold), via top-level helpers / private methods
   (never inner closures, which count toward the parent's nesting):
   - prepareTestData -> prepareTestDataReal + prepareDocs / seedUsers /
     noteUploadShortfall (the phase steps); warning + log order preserved.
   - classifySeededDocs -> classifyOneDoc / resolveContactType / noteUnknownType.
   - resetCouchdbTier -> wipeTrackedDocs / reseedTrackedDocs / buildTombstones.
   - waitForReady -> probeOnce (single-attempt probe).
   - provision -> resolveRealTarget / resolveRealAuth / buildMockHandle; the six
     documented env-seam behaviors are kept bit-for-bit.
   - parseRoles -> parseRole; runBucket -> deriveBucketStatus; applyConfig ->
     applyConfigReal; reset flattened to a mock-first / tier dispatch.

B. Warnings:
   - FORM_BUCKETS is a Set<ConfigUploadAction>, .has() at both call sites.
   - cht-api request(): 5 params -> 4 (the {method, body} init folded into the
     one options object; request is module-private, callers are in-file).
   - provision trailing-slash strip: the /\/+$/ regex replaced with a
     non-backtracking while (endsWith('/')) slice.
   - Chai dedicated matchers (.to.be.undefined / .to.be.null) at the flagged
     spec sites; test-data.spec asserts the concrete SyntaxError on a malformed
     doc instead of a bare throw.

Verified locally with a one-off eslint-plugin-sonarjs cognitive-complexity
["error", 5] pass over the five src files (all functions pass); the package.json
/ package-lock.json change from that install was reverted.

1112 tests pass (unchanged), build + lint clean, excision grep still empty (Node 22).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The complexity round introduced a 5-parameter helper (noteUnknownType in
src/utils/test-data.ts), which SonarCloud's S107 (max 4 parameters) flagged.
Bundle the two related sets — the config-known types and the already-warned
types — into one UnknownTypeTracker object, dropping the signature to 4 params.
Behavior-identical: the once-per-type unknown-contact-type warning is unchanged,
and classifySeededDocs stays at cognitive complexity 3.

Re-verified locally with a one-off eslint pass — sonarjs/cognitive-complexity
["error", 5] AND max-params ["error", 4] over the five src files all pass; the
package.json / package-lock.json change from that install was reverted.

1112 tests pass (unchanged), build + lint clean, excision grep still empty (Node 22).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

Implement Test Environment Layer

1 participant