Repository navigation
v1.0.0
[1.0.0] - 2026-07-11
Added
-
MCP Skill free-form instructions. Custom Instructions now reach every
model-driven incident stage. OpsMender parses and preserves per-tier
Markdown, injects only the active tier's guidance, and keeps structured tool
policy, deny lists, approval requirements, and backend guardrails
authoritative. -
Deterministic autonomy demonstrations. The demo seed now includes
self-contained Tier 0 autonomous remediation, Tier 1 approval-gated, and
Tier 2 advisory-only sessions with persisted workflow and audit events; the
README screenshots show each policy producing a distinct outcome without a
live model or network dependency. -
Standalone SQLite evaluation.
opsmender servenow initializes a local
SQLite schema from ORM metadata, stamps it at the bundled Alembic head, and
starts in development mode for a zero-PostgreSQL product trial. The
PostgreSQL production migration path and migration-skip flag remain intact. -
Release verification gate. Added reusable pull-request and release-tag
verification for backend lint/tests, empty-Postgres migrations, frontend
lint/typecheck/tests/build, and a container health smoke test. Backend tests
now deselect live integration cases and enforce a 180-second per-test timeout. -
Platform-neutral health probes. Added dependency-free
/health/liveand
role-aware/health/readyendpoints with database and migration checks, while
retaining/healthas the backward-compatible liveness alias. -
Simplification sprint — Phase 5 Voice & SMS calling. Added admin
Settings for workspace Voice/SMS calling, with encrypted auth-token storage,
DB-over-env resolution, availability for My Routing, and disabled
Voice/SMS routing options when calling is not configured. -
Noise and analytics sprint — Phase 0 tier clarity. Approval rows now show
the requesting session tier, the demo seed separates Tier 1 approval-gated
sessions from Tier 2 advisory blocking, and team-scoped maintenance windows
mark status-page components as maintenance. -
Noise and analytics sprint — Phase 1 alert-noise intelligence. Services
now support inherited/on/off similar alert grouping, grouped Incident badges,
flapping detection with P0 safety behavior, and workspace default controls. -
Noise and analytics sprint — Phase 2 analytics. Added read-only Noise
and Response analytics APIs with CSV export plus a dashboard Analytics page
for alert volume, noise reduction, MTTA, and MTTR trends. -
Noise and analytics sprint — Phase 3 API tokens. Added named,
revocable, role-scoped REST API bearer tokens with Settings management,
one-time secret display, audit attribution, and self-service denylist
protections. -
A+ closure sprint — final audit proof. Added
scripts/live_update_proof.mjs, a Playwright two-context acceptance script
that proves the dashboard's live incident refresh updates within the
two-second budget with one notification WebSocket and no skeleton flash.
Sprint-end re-grade remains GATE PASS 0/0/0.
Changed
-
Production configuration validation. Production startup now rejects
placeholder secrets, non-PostgreSQL database URLs, weak configured bootstrap
passwords, and invalid autonomy tiers, with warnings for wildcard CORS,
missing public URLs, and enabled API documentation. -
Simplification sprint — Phase 3 Models. Services now expose their
service-specific model list as Models, backed byservices.model_config_ids,
and the session model picker is restricted to active service Models plus the
workspace default model.
Breaking
-
Explicit Skill tier policies. Every executable Skill operation must now
declare complete T0/T1/T2 behavior; underspecified runtime policies fail
closed and deny entries remain unconditional. Existing stored and imported
classification-only operations are deterministically upgraded to the prior
conservative behavior and surfaced for operator review. -
Simplification sprint — Phase 4 MCP servers. Service MCP server lists are
now strict allowlists, backed byservices.mcp_server_ids; empty service
lists and sessions without a service get no MCP tools instead of falling back
to any configured server.
Removed
-
Workspace Status Page. Removed the public/private Status Page feature
entirely — the/statuspage, the Settings section, the per-incident status
update publishing, thestatus_page_*tables and Organization columns, and
the related API. Stakeholder communication is served by scheduled reports; the
separate Atlassian Statuspage integration connector is unaffected. Migration
n1o2p3q4r5s6drops the tables and columns (downgrade recreates them). -
Simplification sprint — Phase 1 Agent Teams. Removed Agent Teams from
the API, database model, Settings, session start flow, and AI workflow
runner; sessions now use the single-agent path by design. -
Simplification sprint — Phase 2 Session workflow. Removed the Session
Profiles page and per-session profile picker; admins now manage one
workspace-wide Session workflow from Settings, and every new AI session stamps
that default workflow configuration.
Fixed
-
Session notification task lifecycle. Scheduled session chat
notifications are tracked in the application background-task registry so
shutdown can drain them cleanly and session-state transitions cannot race an
unmanaged SQLite read transaction. -
Role-aware dashboard loading. Viewer and operator dashboard routes no
longer issue requests their roles cannot make, while workspace branding is
readable by every member and protected mutations remain admin-only. -
Session audit history and approval layout. Completed and paused sessions
hydrate their event streams from persisted audit entries, the desktop work
area retains usable height as context grows, and approval actions fit within
the table at supported desktop widths. -
Integration and notification-channel brand icons. Restored the ServiceNow
mark in the integration catalog; added recognizable Microsoft Teams,
Lark/Feishu, DingTalk, and WeCom marks to the notification-platform picker;
and replaced the monochrome Slack glyph with its official four-color mark. -
A+ closure sprint — A-8 README screenshot set. The screenshot capture
helper now uses the design-audit credential pattern with environment
overrides and.envbootstrap fallback, captures the four launch README
surfaces intosite/public/screenshots/, refuses to capture an empty
approval inbox, and the README now embeds the current screenshots with
product captions. Skill parsing now fails closed on non-mapping YAML roots so
malformed skill records cannot 500 session-tier resolution. -
A+ closure sprint — A-9 motion micro-polish. Toasts now ease in with a
short fade/rise and fade out before removal, shared modals open with a
conservative backdrop fade plus panel fade/scale, andglobals.cssnow has
a globalprefers-reduced-motion: reducekill-switch for existing and new
animations/transitions. -
A+ closure sprint — A-6 command-palette incident search. Cmd/Ctrl+K now
debounces incident search throughlistIncidents({ q, limit: 5 }), renders
an Incidents section with severity badges and relative created times, drops
stale responses, fails silently, and lets keyboard selection traverse static
nav/actions plus incident hits as one list. -
A+ closure sprint — A-5 memory severity tags. Incident memory severity
tags now canonicalize toseverity-<level>across recall derivation, LLM
writeback, manual memory create/update, and postmortem memory extraction;
an Alembic data migration normalizes existingincident_memories.tagsrows
while preserving order and deduping duplicates. -
A+ closure sprint — A-4 Activity grouping. Activity now defaults to
collapsible session groups with short session IDs, View session links, time
spans, entry counts, blocked-count chips, grouped Search/Type/Tier/Status
filters, and a toggle that restores the previous flat DataTable with its
date range, column picker, expansion, and CSV behavior intact. -
A+ closure sprint — A-3 mobile approvals. Pending approvals now use
thumb-sized, full-width Approve, Reject, and Redirect actions belowsmon
the session-detail card and Approvals page; action JSON collapses behind an
accessible disclosure on mobile while reason and expiry stay visible, and
the Approvals page renders stacked mobile cards instead of the desktop
table. -
A+ closure sprint — A-2 dashboard onboarding. The first-run setup
checklist now lives only on the Dashboard, has a slim progress bar and
explicit "N of 5 steps" status, refreshes on focus/live events, silently
hides when unavailable or complete, and no longer duplicates itself on the
Incidents page. -
A+ closure sprint — A-1 live updates. Dashboard, Approvals,
Orchestration, Incidents, the notification Inbox bell, and the sidebar
Approvals badge now share one live notification WebSocket with
category-filtered, debounced subscribers; manual and fire-test incident
creation emitincident.createdin-app events so existing silent refreshers
update without loading flashes. -
UI/UX Polish sprint (design-audit remediation) — Phase C: Reliability
maintenance polish (P-15). Maintenance windows now cross-link to Paging
maintenance windows, explicitly note that they suppress paging too, and
populate the Targets column with "All Targets" or scoped target badges. -
UI/UX Polish sprint (design-audit remediation) — Phase C:
Settings/Profile (P-20). Settings now uses a compact pointer row for
Models, MCP Servers, and Notification Channels; Organization save is
bottom-right in its card, and the profile page is titled "My profile." -
UI/UX Polish sprint (design-audit remediation) — Phase D:
Inbox/preferences (P-18). Inbox rows now coalesce duplicate notifications
with ×N chips and grouped read/delete actions, linked subjects render as real
dashboard deep links, preference switches use the shared tracked switch
styling, and quiet-hours timezone uses the offset-labelled IANA select. -
UI/UX Polish sprint (design-audit remediation) — Phase D: heading effect
policy (P-22). Dashboard headings are now covered by a regression test that
forbids glitch/chromatic/text-shadow treatment in in-app pages and the shared
PageHeader, preserving the auth-only visual decision. -
UI/UX Polish sprint (design-audit remediation) — Phase D: Approvals
zero-state copy (P-21). The default Pending empty state now explains that
no approvals are waiting, All statuses keeps the onboarding/test-incident
branch, and other status filters use filter-specific empty copy. -
UI/UX Polish sprint (design-audit remediation) — Phase D: People detail
(P-19). People detail display-name headers and Joined metadata are now
covered by focused regression tests; the People list Joined / Sent column is
covered as well. -
UI/UX Polish sprint (design-audit remediation) — Phase D: Reports empty
state (P-16). Scheduled reports now show the shared empty-state treatment
when no schedules exist, with report-specific copy and a compact action that
returns focus to the schedule form. -
UI/UX Polish sprint (design-audit remediation) — Phase C: Sidebar IA
(P-17). The global sidebar Paging group now exposes only On Call Schedule
and the Paging hub. Detailed Paging setup remains in the hub tabs, and old
nested Paging routes stay protected for admin/operator access. -
UI/UX Polish sprint (design-audit remediation) — Phase C: Integrations
layout (P-14). The Integrations page now shows configured connectors first,
then a brand-icon kind gallery. The connector form stays collapsed until a
kind is selected or an existing connector is edited, while preserving the
existing field/API behavior. -
UI/UX Polish sprint (design-audit remediation) — Phase C:
Models/MCP/Skills (P-12). MCP runtime Error chips now explain themselves
on hover/focus with last successful call, last error detail, and a "Run Test
for details" hint. Earlier Phase C work already covered the shared header,
action, provider-casing, duplicated Skills action, and labelled delete
cleanup for these surfaces. -
UI/UX Polish sprint (design-audit remediation) — Phase C: incidents list
(P-7). The incidents list now shows one unclipped Last activity value with
an absolute timestamp tooltip, and "Fire Test Incident" stays prominent only
for empty/synthetic-only lists before moving into a More menu once real
incident history exists. -
UI/UX Polish sprint (design-audit remediation) — Phase C: keyboard entry
(G-10). Dashboard pages now expose a first-focus "Skip to content" link
that targets the main content region. Session activity streams use
keyboard-focusable politerole="log"regions with accessible labels, while
co-pilot chat panes remain labelled regions. -
UI/UX Polish sprint (design-audit remediation) — Phase C: native
form-control skinning (G-8). Shared date/time inputs and selects now carry
theme-aware native chrome, including calendar indicators and select chevrons.
DataTable rows-per-page uses the sharedSelectinstead of a one-off raw
native control, and Reports aligns its on-demand from/to range with matching
7d/30d quick range buttons. -
UI/UX Polish sprint (design-audit remediation) — Phase C:
destructive-action pattern (G-7). Row/default destructive actions now use
quiet ghost styling with icon labels and confirmations instead of filled-red
buttons across config, paging, integrations, and reports. Busy action rows
have stable minimum widths so they do not stack vertically. Filled danger
styling remains reserved for confirmation-style flows. -
UI/UX Polish sprint (design-audit remediation) — Phase C: button
taxonomy (G-6). Creation controls now use "New ..." labels on the affected
config and Reliability surfaces, with "New profile" replacing the wrapped
"Add Session Profile" label. MCP Skills no longer repeats the same action
row inside the empty state, model setup no longer duplicates the create
action, and Reliability keeps one primary action by demoting maintenance
window creation to secondary. -
UI/UX Polish sprint (design-audit remediation) — Phase B:
investigation cleanup (I-1..I-4). Services now resolve the "On call now"
user from the service's linked escalation-chain calendar before falling back
to a team roster, so service-level paging configuration is reflected in the
list. Dashboard active AI sessions now flag stale long-running rows and share
the same stale threshold as Orchestration. The Settings optional-config and
Reliability stale-id paths remain verified clean in the full design-audit
run. -
UI/UX Polish sprint (design-audit remediation) — Phase B: scoped live
data refresh (G-9). Approvals and Orchestration now distinguish initial
loading from background refreshes, so 30-second/focus/visibility refetches
keep existing rows on screen instead of flashing loading UI. Incidents now
silently refetches AI-session badges on window focus in addition to its
visible-tab interval and visibility refresh. Dashboard already used the
same background-refresh pattern. -
UI/UX Polish sprint (design-audit remediation) — Phase B: no machine
strings (G-3). Addedlib/displayNames.tsas the shared source for
audit-entry labels, workflow-node labels, provider brand casing, generic
identifier labels, and dashboard session primary labels. Session Profile
phase chips now show operator-facing labels such as "Tier gate" instead of
raw node ids, session-detail workflow events use the same labels, provider
names preserve brand casing such as OpenAI/OpenAI-compatible/AWS Bedrock,
and dashboard session cards no longer fall back to a short UUID as the
primary title. -
UI/UX Polish sprint (design-audit remediation) — Phase B: one date/time
system (G-4). Every user-facing timestamp now routes through
lib/formatDate.tsinstead of ad-hoctoLocale*calls (there were three
visibly different formats before). Added calendar-label helpers
(formatMonthYear,formatDayLong,formatWeekdayDate,formatWeekday)
so month pickers and weekday labels share the same source. Swept ~50 call
sites across incidents, sessions, activity, people, skills, memories,
postmortem, reports, integrations, reliability (incl. chart hover
tooltips), paging, config, and roster calendar; consolidated three
duplicated local relative-time helpers onto the sharedformatRelative
(which switches to an absolute date past 7 days — no more "215h ago").
Seconds are dropped from timestamps. Only number formatters (row_count)
and chart axis-tick formatters keep locale APIs, per the audit spec. -
UI/UX Polish sprint (design-audit remediation) — Phase A.
- Session detail no longer contradicts itself on ended sessions. A
terminal session (cancelled/stopped/failed/timed-out/completed) previously
showed a green "Live" pill, a running "Tier 0 time left 0:00" countdown,
an "Initializing…" workflow state, and "Waiting for events…" all at once,
with an open co-pilot input. Now every header/stream signal derives from
the session status: terminal sessions show a neutral "Ended · {relative}"
chip (no Live/countdown), the workflow strip freezes reached stages and
shows a terminal label ("Cancelled — {summary}"), the empty event stream
reads "No events were recorded for this session.", and the co-pilot input
is disabled read-only (a message to an ended session was saved but never
answered). Rollback is hidden for sessions that never executed
(still-queued or cancelled-out-of-queue). - Co-pilot markdown renderer no longer leaks literal
*markers from
malformed LLM bold/italic (e.g. "*Severity: Medium"): bold matching is
now resilient and orphan emphasis markers are stripped. Markdown list
lines (* item/- item) render with a real bullet glyph and hanging
indent instead of a raw star. - Incidents "Critical" tile is no longer alarm-red at zero — the number
uses the critical color only when the count is > 0, matching the
dashboard's value-based stat coloring. - Mobile page headers no longer overlap their actions.
PageHeader
now stacks the title over its action buttons belowsm(they previously
sat in a non-wrappingshrink-0row that collided with the title at
390px, e.g. Incidents' "Refresh" rendering under the heading). Incident
stat tiles render as a 2×2 grid with tighter padding on phones instead
of four full-height stacked cards. - Session detail no longer overlaps its panels on mobile. The event
stream and co-pilot columns collapsed into a fixed-height grid at 390px,
so the co-pilot header overlapped the event-stream summary and the chat
input floated over the memories panel. On phones the two panels now stack
as full-width blocks with a bounded height and their own internal scroll,
in normal page flow; the fill-remaining-height behavior is kept atlg+. - Added
lib/formatDate.ts(formatDate / formatDateTime / formatTime /
formatRelative) as the single date/time formatting system; used here for
the "Ended · {relative}" chip (call-site sweep continues under G-4). - On-Call Schedule: readable in light theme, on phones, and to screen
readers. Calendar person-chips hardcoded dark-only classes
(text-purple-100on a translucent tint), so in light theme they became
near-white text on pale tints — unreadable.lib/calendarColor.tsnow
carries alight:override per hue (dark text on a light tint), keyed
off the app's[data-theme]via a newlight:Tailwind custom-variant.
Day cells no longer declarerole="gridcell"without arowparent
(cleared 42aria-required-parentviolations). On phones the 7-column
month (unreadable at 390px) is replaced by an agenda list — one row
per covered day with the date tappable to open details and the same
clickable level chips — and the "Show times in" timezone picker no longer
collapses to a zero-width box (given a min width; controls stack on
mobile). - Muted-text contrast (WCAG AA). Raised
--color-fg-mutedfrom
#6B7280→#8A94A3(dark) and#64748b→#55617a(light) in
globals.css. The old values failed WCAG AA on panels, hover rows, and
tinted light surfaces — the single largest source of accessibility
violations app-wide (~1,100 nodes). Both new values clear ≥5:1 on every
surface in their theme. No component-level gray text utilities exist
(the token is used consistently), so this is the complete fix. - Accessible names for controls (G-2). The shared table controls now
label date-range inputs and rows-per-page selectors; modal close buttons,
quiet-hour toggles, session chat/send controls, Reliability detail's back
button/custom range fields, People detail's role picker, profile password
fields, and Skills row delete actions all expose explicit accessible
names. Model status dots no longer putaria-labelon non-interactive
spans. The design audit now reports 0 axe critical nodes (0
select-name,label,button-name, andaria-prohibited-attrnodes).
- Session detail no longer contradicts itself on ended sessions. A
Added
-
On Call Schedule — full-depth calendar with per-person, timezone-aware
shifts and direct on-call replacement. The team calendar got a major
usability pass:- Cells show the real coverage picture with shift times. Every
escalation chain renders as a caption with its L1/L2/L3 chips; each
chip shows the level, the on-call person (color-coded), and that
level's shift window in the roster's own time zone (e.g.
L1 · on-call · 09:00–17:00 EDT). Overflow collapses to "+N more
levels/chains"; tooltips carry the full detail. - Click a person to replace who's on call. Each roster-backed level
chip is individually clickable (admin): it opens a "Replace who's on
call" modal showing the chain/level, the roster, and the current
person, with a Cover with picker (the current person excluded) and a
Through date so one action can cover a single day or a whole span —
the "X is out this week, Y covers" flow. Creates a roster override for
the range. Direct user-target levels (not roster-backed) are shown but
not reassignable. - Maintenance stays day-level. Drag across day backgrounds (or
Ctrl/Cmd-click) to select days → Maintenance window… in the floating
bar; non-contiguous selections create one window per contiguous run. The
day detail modal also has an inline maintenance action. - Calendar edits are admin-only (previously admin/operator); operators
and viewers get a read-only schedule. - Sidebar move: On Call Schedule now lives under Paging & On-call
(still visible to everyone, read-only) instead of Incident Management. - Display time zone picker. A "Show times in" dropdown (left of the
month nav) re-expresses every shift in a zone of the viewer's choosing —
rosters store their own zone (usually UTC) and the calendar converts on
the fly (e.g. a09:00–17:00 UTCshift reads04:00–12:00 CDT), with a
⁺¹/⁻¹ marker when a converted time crosses midnight. Defaults to UTC. - Backend: the team on-call-calendar payload's levels now include
coverage_time_zonealongsidecoverage_start/coverage_end.
- Cells show the real coverage picture with shift times. Every
-
UTC offsets in every time-zone picker. IANA time-zone dropdowns now label
each option with its current offset —UTC (+00:00),
America/Chicago (-05:00),Asia/Kolkata (+05:30)— across the roster form,
quiet-hours settings (upgraded from a free-text field to a proper picker),
and the new calendar display-zone selector. Roster tables and the roster
calendar modal show the offset next to the zone too. (Newlib/timezones
helpers:tzOffsetLabel,timeZoneOptionsWithOffset,convertWallTime.)
Fixed
- Feishu/Lark outbound messages crashed with
NameError. The Feishu
connector'ssend_messagecalledjson.dumps(...)butjsonwas only
imported locally inside the inbound-parse method, so every outbound
Feishu/Lark send raisedNameError: name 'json' is not defined. The import
is now module-scoped. (Found by the 2026-07-03 security/quality audit.)
Security
- Full SAST / dependency / complexity audit (bandit, ruff
F,B,S, radon,
pip-audit) against the production Docker image. Remediations:- XXE hardening (CWE-611). Inbound WeCom/Weixin webhook XML (three
attacker-reachable parse sites inbot_webhooks.py,wecom.py,
weixin.py) now parses viadefusedxmlinstead of stdlib
xml.etree.ElementTree, guarding against entity-expansion and
external-entity attacks.defusedxmladded as a declared runtime
dependency. - SHA-1 signature checks annotated. WeCom/Weixin callback signatures
are SHA-1 by Tencent's protocol spec; marked
usedforsecurity=Falsewith explanatory comments (no behavior change). pydantic-settings2.14.1 → 2.14.2 (GHSA-4xgf-cpjx-pc3j; transitive
viamcp, not exploitable here — bumped as hygiene).- Container image CVE scan (trivy).
apt-get upgrade -yadded to the
Dockerfile runtime stage pulls the base-image security patches (fixes the
3libssh2HIGH CVEs; re-scan shows 0 fixable CRITICAL/HIGH). Remaining
OS findings are Debianfix_deferred(no upstream patch yet) and clear on
base-image refresh. - Ollama URL scheme allowlist.
OllamaProvidernow rejects any
OLLAMA_BASE_URLthat isn'thttp/https, so a misconfigured value
can't turn the client'surllibcalls intofile:/ftp:reads. - API docs no longer exposed in production.
/docs,/redoc, and
/openapi.jsonwere served unauthenticated in production mode,
disclosing the full API surface. They are now disabled when
OPSMENDER_ENVIRONMENTresolves to production (development keeps them;
OPSMENDER_ENABLE_API_DOCS=trueopts back in). - Security response headers. Every response now carries
X-Content-Type-Options: nosniff,X-Frame-Options: DENY, and
Referrer-Policy: strict-origin-when-cross-origin(CSP/HSTS left to the
operator's TLS-terminating proxy). Covered by new tests in
tests/test_security_headers_and_docs.py. - Fixed an undefined
AsyncSessionannotation name in
backend/api/session_runner.py(added aTYPE_CHECKINGimport).
- XXE hardening (CWE-611). Inbound WeCom/Weixin webhook XML (three
Added
- On Call Schedule (team calendar). A new viewer-visible month-grid calendar
(Incident Management → On Call Schedule) shows who is on call for a selected
team across all of its escalation chains, by level, color-coded per person,
navigable up to ~12 months out. Clicking a day opens a per-chain / per-level
breakdown. Global + team-scoped maintenance windows blank that day's coverage
and show a maintenance marker. Backed by a new
GET /paging/teams/{team_id}/on-call-calendar?start=&days=endpoint
(readable by any authenticated user, including viewers). Admins/operators can
click a day to create a coverage override (pick roster + covering person)
or a team maintenance window (defaults to the full day); viewers get a
read-only calendar.
Changed
- Roster time zone is now a dropdown. The roster schedule form's "Time zone
(IANA)" free-text field is replaced with a select of all IANA zones
(Intl.supportedValuesOf("timeZone"), with a fallback list); a legacy/free-form
saved value is preserved as the selected option. - People show real names, not usernames, across paging. Team member, roster
rotation/override, and escalation "User" pickers (and the resolved on-call /
escalation-target labels) now render the user's display name ("First Last",
falling back to username when no name is set) via a shareddisplayName()
helper. - Paging nav reordered to the build flow. Both the in-page tabs and the
"Paging & On-call" sidebar now read Teams → Rosters → Escalation Chains →
Services → Maintenance Windows → Notifications (rosters before chains, chains
before services) so the order matches how you actually set things up. No
behavior change; deep-link routes/ids unchanged. - Standardized on Node.js 24 (Active LTS). Previously the toolchain was split
— Docker ran Node 22 while CI,@types/node, and the docs said Node 20 (which
is now EOL). Everything is now Node 24:docker/Dockerfile(frontend-builder +
node-runtime →node:24-*),.github/workflows/release.yml+
deploy-site.yml(node-version: 24),frontend/package.json
(@types/node ^24+engines.node >=24), a newfrontend/.nvmrc, and the
README badge / CONTRIBUTING requirement. Verified: frontend build + 203 vitest
pass; clean Docker build boots healthy with bundled nodev24.18.0. - Raised the Python floor to 3.12.
requires-pythonis now>=3.12(was
>=3.11); the 3.11 classifier is dropped and the mypy / ruff targets move to
3.12/py312(Docker + CI already ran 3.12).uv.lockregenerated; README
badge + CONTRIBUTING updated. Backend suite green (2020 passed, 2 skipped).