Commit Graph

364 Commits

Author SHA1 Message Date
itsrubberduck
5b6ffe4d73 feat(onboarding): add /start mode chooser, fix pilot-profile-setup UX
- Fix missing icon for the free-tier/self-host pricing option
- Make selected cards visually obvious (checkmark badge, stronger border)
- Add Back navigation between onboarding steps
- Network-experience screen: button reads "No, not yet" instead of an
  ambiguous "Continue" when nothing is selected
- classroom-introduction reverts to Classroom-only content; the
  Classroom/Live ATC chooser moves to a new /start page instead
- Remove the "Skip to Classroom" button in favor of a subtle text link
- New /start page: choose Classroom or Live ATC after login (when
  there's no specific redirect target) or right after finishing
  onboarding. Shows a "start with Classroom" hint only for first-time
  arrivals from pilot-profile-setup. Live ATC is locked for 24h after
  registration.
2026-07-09 23:53:12 +02:00
itsrubberduck
fbfc494f6d fix(pm): re-theme custom CSS classes missed by the light-mode pass
The first light-mode pass only covered Tailwind utility classes in the
template. ~40 hand-written classes (.pm-flow-node, .pm-readback-*,
.freq-*, .pm-seg-*, .pm-bottomnav, signal bars) still hardcoded
rgba(255,255,255,N)/#fff, so scenario chips, the readback panel, and
the frequency display were unreadable in light mode. Switched them to
var(--text/--t2/--t3/--border) and color-mix() so they follow the
theme. Debug panel (dev-only) left as-is.
2026-07-09 22:59:32 +02:00
itsrubberduck
6884c5c011 feat(classroom-intro): add Classroom/Live ATC alpha chooser
LiveATC is now in alpha, not closed testing, so the intro offers both
paths up front: Classroom (default, dry radio practice) or Live ATC
alpha test connected to the simulator, with an honest disclaimer that
it's not fully reliable yet and we want bug reports.
2026-07-09 17:26:29 +02:00
itsrubberduck
8fd643a748 feat(pm): add light/dark/system theme toggle
Defaults to dark (matching the rest of the app) with a menu to switch
to Light or System. Custom CSS-variable overrides handle the
hand-rolled Tailwind classes; a second Vuetify theme is swapped in
sync for Vuetify components. Scoped entirely to /pm.
2026-07-09 17:26:21 +02:00
itsrubberduck
9b245a3f07 fix(onboarding): also ask OS for X-Plane, not just 'other'
X-Plane runs on Windows/Mac/Linux same as any non-MSFS sim, so it
should trigger the OS follow-up too.
2026-07-09 17:26:11 +02:00
itsrubberduck
8bfc4d161c feat(onboarding): add pilot-profile-setup page with all 7 screens
Single-question-per-screen wizard (cockpit, radio confidence, stress
point, network experience, hangar tools + paid-tool duration, feature
wish capped at 2, funding preference) plus the reward callsign card
and skip handling. Verified end-to-end against a mocked API layer in
the browser (MongoDB Atlas isn't reachable from this sandbox).
2026-07-09 16:48:12 +02:00
itsrubberduck
15e6ba0c82 feat(onboarding): add PUT /api/onboarding/profile route with radioLevel side effect 2026-07-09 16:27:18 +02:00
itsrubberduck
eed0cf676b feat(onboarding): add GET /api/onboarding/profile route 2026-07-09 16:26:18 +02:00
itsrubberduck
dc2a8760cf feat(onboarding): add sanitization and callsign computation helpers 2026-07-09 16:25:29 +02:00
itsrubberduck
331d3dcd92 feat(onboarding): add PilotProfile model 2026-07-09 16:24:37 +02:00
itsrubberduck
068299de56 feat(onboarding): add shared pilot-profile option config 2026-07-09 16:24:00 +02:00
leubeem
e32cee5f87 feat(pm): silence auto-advance timer for backend timeout states
Arm a timer whenever the session lands on a pilot state with
auto_advance_on_silence (from the runtime tree); on expiry call the backend
/timeout endpoint and apply the response through applyBackendDecision. Cleared
on pilot transmission, stale-guarded against session/state changes, and
deferred while PTT is recording so ATC never talks over the pilot.

This makes the tower-v1 airborne handoff work without a sim bridge: telemetry
fires it when connected, the silence timeout covers everyone else.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 17:18:43 +02:00
leubeem
04022ef654 Merge branch 'feat/telemetry-atc-backbone' 2026-07-06 17:07:32 +02:00
leubeem
6b8d2b9eb8 feat(pm): runway from live ATIS + bridge position for real taxi routes
- The VATSIM flight plan carries no runway, so the runway in use is now parsed
  from live ATIS text (DEP/ARR/EXPECT...APPROACH phrasing preferred, generic
  RWY mention as fallback, "7L" normalised to "07L"). Falls back to the
  engine-generated runway when no ATIS text mentions one. Local engine vars
  are patched so the HUD matches what ATC says.
- The bridge poller now tracks PLANE_LATITUDE/LONGITUDE; when the bridge is
  connected, the aircraft position is sent at session creation so the backend
  starts the departure taxi route at the real parking position instead of a
  random stand.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 17:00:21 +02:00
leubeem
834df29fbb fix(pm): stop sending placeholder taxi_route so backend computes it
The backend computes the real OSM taxi route from airport_icao + stand/runway
and falls back to the flow's YAML default on its own. Sending a placeholder
taxi_route counted as a caller override and suppressed that computation, so no
session ever got a computed route. Drop it from the create payload.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 09:08:54 +02:00
leubeem
25929b9c06 feat(pm): forward normalized sim telemetry to the backend decision engine
Send live bridge telemetry to the backend so it can drive proactive,
aircraft-state-aware ATC. Purely additive — bridge-less sessions are unchanged.

- NormalizedTelemetry contract + sendTelemetry() + telemetry_fired
  (useRadioBackend.ts), sim-agnostic and matched to the backend contract
- extract applyBackendDecision() so telemetry- and pilot-driven ATC responses
  land through one shared path
- normalizeBridgeTelemetry() + change-detection + forwardTelemetryToBackend()
  wired into the existing bridge poll

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 09:23:53 +02:00
leubeem
dfc836bf2d feat(pm): add Uncontrolled Field (Info) arrival to the Drills chooser (#38)
Surfaces the info-arrival-v1 flow as a standalone drill and gives it a
German D-registration callsign (same as VFR scenarios).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 16:28:06 +02:00
leubeem
3b79adf407 feat(pm): send ICAO for taxi-route compute + show start spinner
- useRadioBackend.createSession: forward airport_icao / destination_icao so the
  backend can compute the real OSM taxi route for taxi flows.
- pm.vue: pass the resolved airport ICAO from startMonitoring, and show a
  full-screen overlay ("Calculating taxi route…") while the session is being
  created — the backend computes taxi routes synchronously for taxi-only
  training, so the create call can take a few seconds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 16:18:13 +02:00
leubeem
54e5029bc2 feat(pm): say-again, sign-off, readback-correct, drop stale transmissions
- "say again" / "repeat" → ATC re-speaks its last transmission locally (#8/#40)
- bare sign-off ("thank you", "tschüss", "good day" with no readback/digits)
  gets a polite "Good day" instead of being evaluated (#32)
- positive "Readback correct" confirmation prefixed when every required
  readback field matched, not only on the IFR clearance (#10)
- a newer pilot transmission supersedes an older one still awaiting its
  backend reply; the stale reply is dropped (#16)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 16:02:07 +02:00
leubeem
e21414e45a feat(pm): ignore sub-threshold voice transmissions (STT noise gate)
Whisper hallucinates short real words ("Test", "Thank you", "Okay") on
near-silent or noisy audio. Unfiltered these reached the backend as a wrong
readback attempt — counting toward the 3x-skip — and triggered paid
LLM-router calls. Drop voice (PTT) transcripts below NUXT_PUBLIC_PTT_MIN_WORDS
(default 2); text input is exempt so deliberate short commands still work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 11:35:47 +02:00
leubeem
9802c438a5 feat(pm): LLM routing endpoint, usage capture, and admin review view
Backend counterpart to the Python engine's semantic router.

- POST /api/decision/route: service-secret-guarded endpoint the Python
  backend calls on regex-miss. Calls gpt-5-mini (ROUTER_LLM_MODEL),
  validates the chosen id against the candidate set, and writes both a
  UsageEvent (central cost ledger) and a routing-review record — including
  timeouts, with timeoutMs + actual latencyMs — so the budget can be tuned
- LlmRoutingDecision model + GET /api/admin/llm-routing (paginated,
  status-filtered, per-status counts)
- admin "LLM Routing" tab: transcript vs expected phrase, candidate chips
  with the chosen one highlighted, latency/budget chip, model reason
- serviceAuth util (mirrors CRON_SECRET pattern)
- .env.example: ROUTER_LLM_MODEL, SERVICE_SECRET

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 10:27:57 +02:00
leubeem
cecd27ea97 feat(pm): bilingual how-it-works help overlay
First-run overlay explaining how /pm works (tune frequency, push-to-talk,
read back, emergency, bug button), reopenable via a new "?" button in the
HUD. Shows one language with a DE/EN toggle; the choice and "seen" state
persist in localStorage so it auto-opens only on first use.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 10:21:33 +02:00
leubeem
4b07609c52 feat(pm): VFR registration callsigns and pronunciation (#28)
- VFR scenarios get a German D-registration (e.g. D-EKLM) and its
  abbreviated form (callsign_short, D-EKLM -> D-LM) instead of the airline
  callsign; the pilot's first call uses the full registration, ATC uses the
  short form thereafter.
- radioSpeech: spell aircraft registrations phonetically for TTS
  ("D-EKLM" -> "Delta Echo Kilo Lima Mike", "D-LM" -> "Delta Lima Mike").

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 10:17:52 +02:00
leubeem
e36f5b3277 feat(pm): add Rejected Take-Off drill to the scenario chooser (#14)
New standalone "Drills" section in the scenario picker, with the
Rejected Take-Off drill (rto-v1). Drills are surfaced separately from
the journey chains via DRILL_IDS / drillScenarios.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 06:34:59 +02:00
leubeem
5b46aed008 feat(pm): accept any published frequency for a position (#43)
The wrong-frequency gate now accepts any of the frequencies a logical
position publishes (e.g. EDDM has two Tower frequencies, 118.700 and
120.500); expectedFrequencyForState() still returns the primary for the
"contact X on <freq>" prompt. Falls back to the single expected value
when no frequency list resolves.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 06:21:48 +02:00
leubeem
9345bf3614 fix(pm): tester-round readback, frequency tuning, and PTT fixes
From /pm tester bug reports:
- pre-tune COM1 to the opening pilot state's frequency on scenario start,
  so the first call isn't met with a "wrong frequency" rejection (#5/#6/#21)
- taxi-route phonetics no longer stop at the first comma: "via A, V" now
  speaks "via Alfa, Victor" (#31)
- barge-in: keying the mic stops any ATC speech still playing
- ignore empty / punctuation-only transmissions (silence, stray PTT taps)
- PTT pad turns green while transmitting (was red)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 06:02:03 +02:00
leubeem
03a49c3b57 Merge branch 'main' of github.com:OpenSquawk/OpenSquawk 2026-06-21 21:00:20 +02:00
itsrubberduck
4ba3a41aea Add bridge connect PTT controls 2026-06-21 13:11:30 +02:00
itsrubberduck
8735f190f1 hotkey anzeige online 2026-06-20 03:25:06 +02:00
itsrubberduck
d9e9be2b54 hotkey support 2026-06-20 03:17:53 +02:00
itsrubberduck
6603677214 fix(pm): standby strictly mirrors sim while bridge connected
Standby used change-detection like active, so a locally tuned standby
lingered as "the last" channel. While the bridge is connected, standby now
always reflects COM_STANDBY_FREQUENCY from telemetry on every poll. Active
keeps its change-detection anchor so flow/manual tuning isn't overridden.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 13:40:20 +02:00
itsrubberduck
22dffc5d69 feat(bridge): map GPS position + true heading into telemetry
Bridge now sends latitude_deg/longitude_deg/heading_deg; map them to
PLANE_LATITUDE/PLANE_LONGITUDE/PLANE_HEADING_DEGREES_TRUE so position and
course land in the telemetry store and surface in /api/bridge/live.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 13:36:30 +02:00
itsrubberduck
1c8febf4b7 fix(pm): preserve redirect when sending logged-out users to login
Unauthenticated /pm visits pushed to /login without a redirect query, so
after sign-in they landed on the classroom fallback instead of returning
to /pm. Pass route.fullPath as ?redirect= so the bridge link (incl.
?token=…) survives the login round-trip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 12:55:12 +02:00
itsrubberduck
ce250901e2 chore(bridge): add bridge logo asset
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 12:42:26 +02:00
itsrubberduck
f4342c7015 feat(pm): mirror full COM1 panel (active + standby) from SimBridge
Sync both COM_ACTIVE_FREQUENCY and COM_STANDBY_FREQUENCY from bridge
telemetry into the radio, each tracked independently so they only retune
on an actual sim change. Active retune still cuts in-progress ATC speech;
standby has no audio side effects.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 12:40:26 +02:00
itsrubberduck
bcd48159b5 feat(pm): auto-tune radio from SimBridge telemetry via ?token
When /pm is opened with ?token=<bridge-token>, poll /api/bridge/live for
fresh telemetry. While the bridge keeps posting, mirror the sim's COM1
active frequency into the radio (only on actual sim change, so manual/flow
tuning isn't clobbered) and show a "Bridge connected" badge in the HUD.

Telemetry now carries COM_ACTIVE_FREQUENCY/COM_STANDBY_FREQUENCY from the
bridge's com_active_frequency/com_standby_frequency fields.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 12:30:23 +02:00
itsrubberduck
d275c027ff refactor(bridge): simplify connect page to a focused linking flow
Strip the overloaded two-step grid, status cards and elaborate success
overlay down to a single centered card with clear states: ask for code,
prompt login, link (auto), and a state-aware success view. Keep the
background photo (more visible) and the server log as a collapsible panel.
Attempt to auto-close the tab once linked.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 12:18:26 +02:00
itsrubberduck
71f5df0fbe fix(pm): clearer arrow-annotation instructions in bug report dialog
Explain plainly that testers drag an arrow to the broken spot (mouse or finger),
add an on-image hint until the first arrow is drawn, and switch the annotation
canvas to pointer events so touch dragging works as the instructions promise.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 10:58:26 +02:00
itsrubberduck
010f365516 ci: add pre-push hook (auto-installed) and API/model smoke tests
- .githooks/pre-push: runs vue-tsc before every push; blocks TypeScript
  regressions locally without any manual developer setup
- postinstall: git config core.hooksPath .githooks activates the hook
  automatically on yarn install (yarn 4, enableScripts: true)
- tests/smoke/apiHandlers.smoke.test.ts: import-level smoke tests for all
  bug-report handlers + 3 core admin handlers — catches broken exports and
  top-level runtime errors without a DB or running server
- tests/server/bugReport.test.ts: 16 unit tests covering comment validation,
  contact-string building, model schema fields, status enum, and patch logic

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 10:46:56 +02:00
itsrubberduck
c16a30d7c1 fix(pm): make bug-report screenshots and state restore actually work
Screenshots: html2canvas 1.4.1 only parses rgb/hsl and throws on color-mix(),
which the app uses app-wide; the throw was swallowed so no screenshot was ever
captured. Swap to modern-screenshot (native SVG foreignObject — supports
color-mix/oklch), capture before opening the dialog, surface failures instead of
hiding them, and show a capture spinner on the Bug button.

State restore: the old handler only patched local vars/flags and loaded the tree
— it never entered the monitor screen or created a backend session, so nothing
happened. New restoreBugReportState() reuses startMonitoring() to spin up a real
session for the same flight/scenario from the snapshot, overlays saved
variables/flags, and replays the captured comm log. The Python backend has no
mid-session resume, so the session restarts at the flow start and the captured
state id is surfaced in a banner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 10:43:55 +02:00
itsrubberduck
8a1d9e1ab6 feat(pm): add bug-report button with screenshot annotation and admin panel
- New BugReport MongoDB model (comment, contact, userId, screenshot, pmState, status)
- POST /api/bug-reports — authenticated submit; emails emanuel@faktorxmensch.com on receipt
- GET/PATCH /api/admin/bug-reports + /[id] — admin list, detail with screenshot, status toggle
- /pm: "Bug" button in HUD captures viewport screenshot (html2canvas), shows annotation
  canvas where testers can draw arrows; submits comment + contact + state snapshot
- /admin: new "Bug Reports" tab with open-count badge, screenshot expand, "Erledigt" toggle,
  and "In /pm öffnen" link that restores captured engine state via ?restoreBugReport=<id>

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 10:05:25 +02:00
leubeem
2749100938 fix(pm): forward arrival/shared variables to the backend session
backendVariables was a departure-shaped subset, so arrival flows (IFR and VFR)
ignored the selected flight and ran on YAML defaults for runway, QNH, surface
wind, aircraft type, cruise level and assigned squawk — even though the engine
generates them. Forward those (with name mapping qnh_hpa→qnh, acf_type→
aircraft_type, cruise_flight_level→cruise_level). Also fixes taxi/tower on
departure, which use runway/qnh/surface_wind.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 21:56:07 +02:00
leubeem
1c3c9ffaa6 fix(pm): require manual tuning at session start; name the correct frequency
Reverses the auto-tune: tuning to the first controller is part of the exercise.
Each scenario now starts from a fixed baseline frequency (121.900), and the
first call is rejected until the pilot dials the correct one — we do NOT silently
put them on the expected frequency.

The wrong-frequency reply now names the position and frequency to switch to,
e.g. "DLH6RK, you are on the wrong frequency. Contact Approach on 119.900."

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 15:28:10 +02:00
leubeem
1aec82fce5 fix(pm): tune radio to the starting position's frequency on session start
The initial active frequency was only set via a hardcoded EDDF special-case
(121.900); every other airport — and any arrival that begins on Center/Approach
rather than Delivery — kept a stale/default active frequency. That triggered an
immediate "check frequency" on the pilot's very first call (e.g. IFR enroute
start: on 121.900 but Center expects 121.800).

Now the radio is tuned to expectedFrequencyForState() for the start position
when the session begins, so the first call is always on the right frequency and
subsequent handoffs change to the correct next-position frequency.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 15:24:26 +02:00
leubeem
b627288f42 feat(pm): group scenario chooser by journey with a phase flow
Reworked the scenario picker so it's clear which single-phase practice belongs
to which chain. Scenarios are grouped by Departure / Arrival; each complete
chain renders as a left-to-right flow of its phases with arrows
(e.g. Clearance → Startup & Taxi → Tower → Departure). Tap any step to practise
just that phase, or "Fly full" to run the whole chain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 15:09:07 +02:00
leubeem
82fe5a125d feat(pm): add IFR arrival scenarios to the chooser
Adds the IFR arrival to the PM scenario picker:
- Complete chain "IFR Arrival" (Enroute → Approach → Landing → Taxi-in),
  starting at ifr-enroute-arrival-v1.
- Individual phases: Enroute Descent, IFR Approach, IFR Landing
  (taxi-in already present). All use the arrival airport for frequencies.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 15:04:10 +02:00
leubeem
6fae54c89f feat(stt): seed Whisper prompt with expected readback + per-field debug UI
Whisper prompt seeding (per request):
- ptt.post.ts builds the prompt as generic ICAO bias + this state's expected
  readback appended LAST (survives the 224-token truncation), in both raw token
  form and spoken ICAO form via new radioSpeech.speakToken().
- pm.vue passes the expected phrase + active variable values; classroom.vue
  passes the lesson's expected field values.

Per-field readback debug:
- sttMatch.matchTranscriptionToFields returns fields[] (matched/missing + which
  view matched) plus normalized/denormalized transcription views.
- useRadioBackend types readback_report on the transmit response.
- pm.vue renders a "Readback check" panel in the right log rail; classroom.vue
  renders per-field rows under the STT panel.

Radio-pronunciation fixes (radioSpeech.ts):
- callsign expander handles multi-letter suffixes (DLH6RK -> Lufthansa six Romeo
  Kilo).
- toRadioSpeech now expands airports (EDDC -> Echo Delta Delta Charlie).
- bare altitudes >=1000 in a clearance context are spoken ("climb initially
  5000" -> "climb initially five thousand feet"); speeds/headings untouched.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 14:12:54 +02:00
leubeem
a1a7e10342 test: cover the core engine and auth rotation/JWT hardening (TEST-03, TEST-04)
TEST-03 — communicationsEngine had zero tests. Add tests/shared/
communicationsEngine.test.ts exercising the deterministic core: system load &
ready state, VariableDefinition unwrapping, dual {{}}/{} template rendering,
patchVariables, moveToSilent (cursor advance + state actions + controller log),
unknown-state handling, getStateDetails, and normalizeATCText expansion.

TEST-04 — auth utils were tested but rotation and JWT verification were not.
Extend tests/server/auth.test.ts with refresh-token rotation (valid rotate,
missing cookie, access-token-as-refresh, version mismatch) and JWT hardening
(alg-confusion rejection, tampered signature, expired, malformed).

97 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 12:14:59 +02:00
leubeem
309bde3fcd fix(security): mandatory cron secret + reject placeholder JWT secrets (SEC-07, OPS-02, SEC-09)
SEC-07 — committed secrets:
- Replace real-looking defaults in .env.example (JWT_SECRET/JWT_REFRESH_SECRET
  "changeme", MANUAL_INVITE_PASSWORD "pm.local@zghl.de") with CHANGE_ME
  placeholders, and drop the personal DOME_LIGHT_WEBHOOK_URL default.
- Add a Nitro startup plugin (server/plugins/validate-secrets.ts) that refuses
  to boot in production when JWT_SECRET is unset, looks like a placeholder, or
  is shorter than 32 chars (warns only in development).

OPS-02 / SEC-09 — cron endpoints:
- requireCronSecret now fails closed: when no CRON_SECRET/KPI_CRON_SECRET is
  configured the endpoint returns 503 instead of being publicly callable
  (previously it allowed the request with a warning). Both cron routes already
  call the guard. Prefer the x-cron-secret header over the loggable ?secret=
  query param; document CRON_SECRET in .env.example.

Operational note: production deployments must now set JWT_SECRET (>=32 chars)
and CRON_SECRET, or the server won't start / crons return 503.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:07:27 +02:00
leubeem
8b943dd287 ci: make typecheck a real blocking gate; bump actions to Node 24 majors
The previous `vue-tsc --noEmit` step was a no-op: the root tsconfig uses
`files: []` with project references, so without `--build` it checks zero files
and always passes. Switch to `vue-tsc --build` (new `yarn typecheck` script)
and make the job blocking.

Fix the one error this surfaced: UsageEventDocument extended mongoose.Document,
whose `model` method collides with the `model: string` field. Use the
recommended pattern — a plain attrs interface passed to the Schema/Model
generics (hydrated docs still expose Document methods). Typecheck is now clean.

Bump actions/checkout@v5 and actions/setup-node@v5 to silence the Node.js 20
runtime deprecation (forced to Node 24 from 2026-06-16).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 15:28:27 +02:00