Skip to content

Release 1.31.3 — Semaphore taxonomy auto-sync (DEV→main promotion) - #2492

Merged
jung-thomas merged 17 commits into
mainfrom
DEV
Sep 23, 2026
Merged

jung-thomas merged 17 commits into
mainfrom
DEV

Conversation

@jung-thomas

Copy link
Copy Markdown
Contributor

Release 1.31.3 — DEV → main promotion

Promotes today's Semaphore taxonomy-sync work from DEV to main for a PROD deploy. Version bumped to 1.31.3 in .deploy/mta.yaml (#2491).

Shipped

Semaphore taxonomy auto-sync (#2184 / #2477 / #2478 / #2479)

Docs

  • author branch-protection & PR guide
  • challenge-widget cost-sizing sidebar entry

Verified on DEV

  • Sync ran live: 119 new inert tags inserted, 148 existing adopted (editorial flags intact), 20,712 SES terms correctly rejected. Tags 160→279, all with valid ID + legacyId.
  • Backend deployed (mtar 1.31.2 at time of DEV deploy); ChatSettings.enabled=true confirmed.

PROD rollout notes (post-merge, NOT in this PR)

  • Semaphore stays OFF in PROD until the PROD DB config is set — code deploy alone does not enable it. Set flag.semaphore.sync, semaphore.sync.intakeClasses, semaphore.sync.dryRun on the PROD instance after deploy; validate with a dry-run before flipping live.
  • Verify PROD semaphore.sync.* config is clean before enabling (a stray intakeClasses value existed on PROD earlier).
  • Rotate the compromised Semaphore API key (Finish semaphore-tag-sync two-tier rollout (prod config, dry-run validate, flip live) #2479) — independent of this release.
  • Known unrelated item: /topics/ leaf-slug smoke check flags on DEV (pre-existing content-publish state, not caused by this release).

…e) + fix mislog

Rework the semaphore-tag-sync applier so a weekly run adopts the existing
tag corpus without importing all ~20k SES terms, and picks up genuinely
new tags only under an explicit class allowlist.

Tier 1 (adopt-only): match each SES term by semaphoreId, then by name.
Backfill taxonomy fields (label/name/titlePath/semaphoreId) in place when
they differ; NEVER overwrite editorial flags (isActualTag/isInterestItem).

Tier 2 (gated intake): INSERT an unmatched term only when its SES class is
in semaphore.sync.intakeClasses (case-insensitive, URI-leaf tolerant).
New rows land inert (flags forced false), awaiting editor curation. Empty
allowlist = intake OFF (adopt-only), the safe default.

Dry-run summary now carries a pre-formatted classHistogram string so the
real SES class distribution can be read from an in-app dry run and used to
choose semaphore.sync.filter / intakeClasses from data. dryRun defaults TRUE.

Also fixes a scheduler mislog: a job fn RETURNING { ok:false } (rather than
throwing) was recorded as SUCCESS. The chassis now inspects the return value,
logs a FAILED PipelineLog row, records JobLastRun.lastErrorAt, and raises an
alert. Covered by new admin-job-controls tests.

Config keys (all ImsConfig, DB-driven -- no env vars):
  semaphore.sync.intakeClasses (new), .dryRun, .filter, .model, .lang,
  .actualTagClasses, .interestItemClasses

Refs #2184, #2478
…er-sync

feat(semaphore-sync): two-tier tag sync + scheduler mislog fix
The scheduler only branched on a thrown exception; a runner that RETURNS
{ ok:false, error } (a soft failure, e.g. semaphore-tag-sync's fail-shut
fetch/mapping path) fell through to the SUCCESS path — logging green in
PipelineLog and JobLastRun and masking a silent no-op.

runWithLock now inspects fn(logId)'s return value: an object with
`ok === false` is recorded as outcome `error` via
logPipelineEnd(logId, 'FAILED', summary, errorMessage) with the error in
the errorDetails column (arg 4), a failed JobLastRun, and a
ScheduledJobFailed alert. errorMessage derives from result.error, else
"<jobName> returned ok:false". Thrown exceptions fail as before; {ok:true}
and plain-summary returns still succeed.

Covered by 5 new cases in run-job-by-name-opts.test.js asserting on the
actual PipelineLog/JobLastRun rows.

Ref: #2184
…duler-soft-fail

# Conflicts:
#	srv/jobs/scheduler.js
…er-soft-fail

fix(scheduler): record FAILED when a job returns ok:false (#2478)
Expose the semaphore.sync.* tuning keys in the Admin UI so operators can
configure the taxonomy-sync job without hand-editing ImsConfig rows.

- Shared descriptor srv/lib/semaphore-sync/config-keys.js is the single
  source of truth for the 7 keys (model, lang, filter, actualTagClasses,
  interestItemClasses, intakeClasses, dryRun): their defaults, value types,
  and help text. The sync job's readConfig now imports the key list from it.
- AdminService.SemaphoreConfig: a @cds.persistence.skip viewer (modeled on
  FeatureFlags) synthesizing one row per key, layering the live ImsConfig
  value over the default. Bound setValue(value)/clearValue() actions upsert
  or delete the raw row; both allowlisted to semaphore.sync.* (exact match).
- New Fiori Elements app app/admin/semaphoreConfig (auto-discovered by the
  admin-shell component copier) + shell manifest wiring + nav entry.
- intakeClasses is surfaced + read for forward-compat but NOT yet consumed
  by the mapper/applier — intake enforcement is a separate feature (#2477
  scopes to surfacing only; 'No behavior change to readConfig').

Tests: 7 new (READ default synthesis, string/csv/bool upsert, clearValue
reset, non-allowlisted 400, unauth 401). Job + admin-shell/bundle/registry
regression suites green.
…hore-config-panel

feat(admin): Semaphore sync config panel (#2477)
Explains the org-wide OSPO branch-protection ruleset (no direct pushes
to the default branch, PR + >=1 approval required) with concepts
(branch/fork/PR), the exact enforced rules, and step-by-step CLI and
web-UI instructions for both write-access and fork-based contributors.

Registers the page in the VitePress authors sidebar and cross-links it
from the authors README and writing-tutorials.md (workflow, quick edits,
known-gaps).
PR #2484 added the Semaphore Config panel with a manifest route and a
navigation.json entry, but never registered semaphoreConfig in
NAV_KEY_TO_ROUTE / NAV_KEY_TO_TITLE. The sidebar click therefore silently
no-ops. The cross-file consistency guard in admin-shell-pats-nav.test.ts
caught this and is now red on DEV.

Add semaphoreConfig to both maps so the sidebar entry routes and titles
correctly.
…ch-protection

docs(authors): add branch protection & pull request guide
…fig-nav-route

fix(admin): route semaphoreConfig nav-key (#2477)
Fixes the docs:build failure: challenge-widget-cost-sizing.md was added
to docs/developers/reference in #2441 without a sidebar entry, so the
predocs:build guard (check-docs-sidebar.cjs) exits 1. Adds the missing
entry, alphabetically after CAP / CDS gotchas.
…llenge-widget

docs: register challenge-widget-cost-sizing in sidebar (fix docs build)
The applier writes via cds.db directly, bypassing AdminService's
before(CREATE) hook that auto-assigns legacyId. New synced tags landed
with a NULL legacyId (the Admin UI 'ID' column showed blank). Assign it
from the Tags HANA sequence via getNextLegacyId on each intake INSERT,
guarded so dryRun never burns a sequence value.

Existing 119 null-legacyId tags on DEV backfilled separately.
…yrun

fix(semaphore-sync): assign legacyId on Tier-2 tag inserts (#2479)
@jung-thomas
jung-thomas merged commit de1e2fe into main Sep 23, 2026
11 checks passed
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.

1 participant