Skip to content

fix(drs-prompt-generation): enforce v1-write ban under brandalf / brandalf_migration (LLMO-4743)#2479

Open
rainer-friederich wants to merge 4 commits into
mainfrom
fix/LLMO-4743
Open

fix(drs-prompt-generation): enforce v1-write ban under brandalf / brandalf_migration (LLMO-4743)#2479
rainer-friederich wants to merge 4 commits into
mainfrom
fix/LLMO-4743

Conversation

@rainer-friederich

@rainer-friederich rainer-friederich commented May 6, 2026

Copy link
Copy Markdown
Contributor

Related Issues

Summary

Pre-flip must-have for the Adobe brandalf_migration cutover. Under the new
policy v1 writes are forbidden and charUpdater does not run, so the S3
mirror at config/llmo/<siteId>/llmo-config.json must be treated as
read-only the moment an org has either brandalf=true or
brandalf_migration=true.

Today src/drs-prompt-generation/handler.js:159 only gates the v1 write on
source !== "onboarding" || onboardingMode !== "v2". Any non-onboarding job
(periodic regeneration, manual recovery, dashboard retriggers) for a
brandalf_migration org currently writes v1 unconditionally.

What changed

  1. New shared util src/utils/feature-flags.js exporting:
    • isBrandalfEnabled(orgId, env, log) — existing semantics (brandalf only),
      used by llmo-customer-analysis for v2 onboarding.
    • isBrandalfOrMigrationEnabled(orgId, env, log) — new helper,
      brandalf OR brandalf_migration. Used by drs-prompt-generation for
      the v1-write ban. Aligns with the DRS-side is_brandalf_or_migration_enabled
      helper from LLMO-4721.
    • Both helpers share one private fetcher; fail-open (return false) on
      missing env, non-ok response, or thrown error.
  2. src/llmo-customer-analysis/handler.js — replaces its private
    isBrandalfEnabled with the imported one. Same call site, same semantics.
  3. src/drs-prompt-generation/handler.js:
    • Resolves siteIdorgId via Site.findById and calls
      isBrandalfOrMigrationEnabled once per invocation.
    • Skips the v1 writeConfig when the org is brandalf-gated, AND
    • Skips the llmo-customer-analysis fan-out for the same orgs.
    • Fail-open: missing site, missing org, or any error in the resolver
      warns and writes v1 (preserves current behavior for non-brandalf orgs).
  4. Tests:
    • New test/utils/feature-flags.test.js (14 cases) — full unit coverage.
    • test/drs-prompt-generation/handler.test.js — 11 new gate tests
      covering both flags, both source values, both onboardingMode values,
      plus all fail-open edge cases.
    • test/audits/llmo-customer-analysis.test.js — refactored to mock
      feature-flags.js directly (cleaner than the prior fetch-call-ordering
      pattern). All 48 existing cases preserved.
  5. Coverage: 100% on all touched files (statements, branches, functions, lines).

Out of scope (covered elsewhere)

  • Reader 1 (getConfigCdnProvider) and Reader 2 (cdn-logs-report) —
    deferred to LLMO-4715b post-flip.
  • Reader 3 (llmo-customer-analysis change-detection diff) — split into a
    sibling ticket.
  • Server-side enforcement of the v1-write ban (api-service / S3 bucket policy).

Test plan

  • npx mocha 'test/audits/llmo-customer-analysis.test.js' 'test/utils/feature-flags.test.js' 'test/drs-prompt-generation/**/*.test.js' — 111 passing
  • npx eslint on touched files — clean
  • c8 coverage for all touched files — 100% statements / branches / functions / lines
  • CI green on this PR
  • Verify on a brandalf_migration test org that no writeConfig call lands and no fan-out fires
  • Verify on a non-brandalf v1 org that current behavior is preserved

🤖 Generated with Claude Code

rainer-friederich and others added 2 commits May 5, 2026 22:49
…ndalf_migration (LLMO-4743)

Resolves the site -> org for incoming DRS prompt-generation jobs and skips
the v1 LLMO config write (and the llmo-customer-analysis fan-out) whenever
the org has brandalf=true OR brandalf_migration=true. Pre-flip must-have
for the Adobe brandalf_migration cutover (LLMO-4587).

The shared brandalf flag check is extracted to src/utils/feature-flags.js
so the existing isBrandalfEnabled (used by llmo-customer-analysis for v2
onboarding) and the new isBrandalfOrMigrationEnabled (used here for the
v1 write ban) share one fetcher. Fail-open on lookup or API errors so
non-brandalf orgs keep working.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Collapse redundant brandalf gate: single early-return when the org is
  brandalf-blocked instead of duplicate `if (brandalfBlocksV1)` checks.
- Include `drsJobId` in the new skip-path log messages, matching the
  existing log style elsewhere in the file.
- Fix the `processes result for all sources` loop test which previously
  re-mocked the handler without stubbing feature-flags.js — the gate was
  hitting the real isBrandalfOrMigrationEnabled and only passing by
  accident via fail-open. Now stubs both feature-flags.js and Site.findById
  for the rebuilt context, and asserts the brandalf check actually runs.
- Add the missing test case for `source=onboarding, no onboarding_mode,
  brandalf=true` per the PR's own DoD ("Tests cover both flags, both source
  values, and both onboardingMode values").

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@codecov

codecov Bot commented May 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions

github-actions Bot commented May 6, 2026

Copy link
Copy Markdown
Contributor

This PR will trigger a patch release when merged.

@rainer-friederich

Copy link
Copy Markdown
Contributor Author

Self-review pass via three parallel code-reviewer agents (correctness, conventions, simplicity). Addressed in 8b11cba:

  • Broken loop-test mock (test/drs-prompt-generation/handler.test.js processes result for all sources): the loop's local esmock did not stub feature-flags.js or wire Site.findById, so isV1WriteBlockedByBrandalf was hitting the real module and passing only by fail-open accident. Now stubs both, plus asserts the brandalf check actually runs (calledOnceWith('org-uuid-loop')) on every iteration.
  • Missing DoD test case: added source=onboarding, no onboarding_mode, brandalf=true (periodic regen / dashboard retrigger path).
  • Redundant if-cascade (src/drs-prompt-generation/handler.js): collapsed else if (brandalfBlocksV1) ... else { ... } if (brandalfBlocksV1) return ok(); into a single early-return.
  • Log-message consistency: included drsJobId in the new skip-path messages to match the existing log style in this handler.

Skipped (with rationale):

  • Move isV1WriteBlockedByBrandalf to src/utils/feature-flags.js — adds API surface that no other caller needs today; the helper's "no organization" warn is handler-specific. Worth revisiting when a second drs-side caller appears.
  • Strict === true for flagValue is intentional — the API contract returns booleans and we want to fail-closed on malformed shapes (a "true" string or 1 should not silently enable brandalf).
  • The theoretical double-fetch of fetchEnabledLlmoFlags only matters if a single invocation calls both isBrandalfEnabled and isBrandalfOrMigrationEnabled for the same org — no such caller exists today.

After fixes: 112 tests passing, 100% coverage on src/utils/feature-flags.js, src/drs-prompt-generation/handler.js, src/llmo-customer-analysis/handler.js (statements / branches / functions / lines).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants