Commit graph

131 commits

Author SHA1 Message Date
7082b278e3 Collection: Windows servers over CIM (WS-Man, DCOM fallback) and synthetic check runners
New project SQLInsight.Servers:
- CimWindowsServerSource (Microsoft.Management.Infrastructure, the API behind
  Get-CimInstance): WS-Man first, DCOM if that fails, a local session for the
  agent's own computer, as the agent's identity. Only SELECT queries (CimQueries)
  and Task Scheduler's read-only GetInfo. CPU and paging from raw counters, disks
  (Win32_Volume, Win32_LogicalDisk fallback), services, scheduled tasks, and the
  System/Application logs through the Event Log service (MS-EVEN6, XPath filter on
  the server) with Win32_NTLogEvent as fallback.
- ServerErrors: access denied, missing namespace, RPC/WinRM unreachable and
  timeouts become "ikke målbar" reasons that name the missing right and the grant
  script; never local administrator.
- WindowsServerTester: tries every area once for the web UI's test.
- SyntheticCheckRunner: TCP, HTTP(S) with expected status/text and TLS certificate
  expiry (also for refused certificates), SQL login + read-only query (re-validated,
  in a transaction that is always rolled back), DNS, and the Dynamics AX logon:
  AOS port, then a logon through the .NET Business Connector in a separate Windows
  PowerShell process with a timeout when it is installed; otherwise the result
  says clearly that only the port was tested.

Tests: error mapping, queries, AX command/parsing, runners against local TCP and
HTTP(S) servers with a self-signed certificate, the CIM source against this
computer, and the SQL check against the test instance.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:58:18 +02:00
25ecdbc3e7 Repository: Windows servers, samples, events, synthetic checks and their findings (0018)
Migration 0018_windows_servers_synthetic_checks:
- insight.WindowsServers (SQL Server hosts kept in step by the agent, servers added
  in SQLInsight Web), ServerPerformance (per minute, one row per sample),
  ServerDiskSamples (forecast history), ServerEvents (grouped like ErrorLogEvents),
  ServerSnapshots (latest per area).
- insight.SyntheticChecks (SQL login password sealed for the agent, as in 1.3) and
  SyntheticCheckResults.
- insight.ServerFindings and ServerRuleStates, scoped to a server or a check.
- AgentRequests.TargetId and the kinds ServerTest and SyntheticCheckRun.
- SQLInsight Web gets INSERT and column UPDATE only; host, name and kind are fixed,
  nothing the agent records can be written, no DELETE.

Findings are opened, updated and resolved like instance findings; a round where a
rule is not measurable keeps what was found open. Retention for samples, disk
history, events, check results and resolved findings (RunServerMaintenanceAsync).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:58:18 +02:00
ba7c751b5c Rules: Windows servers (SQLI-WIN-001..013) and synthetic checks (SQLI-SYN-001..003)
Scoped rules with their own context (a Windows server, a synthetic check) that
produce the same findings and outcomes as the instance rules; missing data is
"ikke målbar" with the collector's reason. Thresholds are settings
(SQLInsight:Servers, SQLInsight:SyntheticChecks).

- Sustained CPU, low memory (share and MB), paging with low memory, commit charge.
- Free disk space and "fuld om ca. N dage" forecasts.
- Stopped automatic services (minus those that stop by design, and a grace
  period after a restart) and watched services that must run.
- Windows event log groups: important kinds at once, ordinary errors when they
  repeat, capped per server; sample messages are sensitive.
- Unexpected restart (Kernel-Power 41, EventLog 6008, BugCheck), optional uptime.
- Scheduled tasks that failed or did not run as planned.
- Synthetic checks failing (twice in a row), "svarer ikke, selv om tjenesten
  kører", slow answers, TLS certificates about to expire.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:58:18 +02:00
ae20954a91 Windows servers and synthetic checks core: data, disk forecast, event dedup, grant script
- Core/Servers: what the agent collects from a Windows server (CPU and memory from
  raw counters, disks, services, events, scheduled tasks), roles and sources, host
  validation and the computer behind a SQL Server data source.
- DiskForecaster: "fuld om ca. N dage" from a least-squares line over 14 days of
  SQLInsight's own samples, only since the volume last changed size, with sanity
  bounds (history, samples, R², max one year).
- ServerEventAnalysis: noise filter, grouping and a 24-hour window for System and
  Application events, like the SQL Server error log in 1.2.
- db/permissions/grant-windows-server.ps1: least-privilege rights for the agent's
  account (groups, WMI namespaces, SCM read), never local administrator; filled in
  by ServerGrantScript.
- Core/SyntheticChecks: kinds, settings, validation (read-only SQL), results.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:58:17 +02:00
90d37be8a5 Version 1.4.0
Some checks failed
deploy community / deploy (push) Successful in 18s
deploy hub / deploy (push) Successful in 19s
CI / Build og unit tests (push) Successful in 8s
CI / MSI-installer (push) Successful in 15s
CI / Integration (mcr.microsoft.com/mssql/server:2025-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2017-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2019-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2022-latest) (push) Has been cancelled
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:45:31 +02:00
31fa89b385 Review 1.4: AX detection survives passing errors; revoke only what the grant gave; smaller fixes
- A lock timeout or deadlock while detecting (e.g. during an AX table
  synchronisation) dropped the database from AX monitoring for an hour, so
  its findings resolved and reopened. The earlier detection is kept and
  retried within 5 minutes. A cancelled collection is no longer reported as
  "læsningen fejlede".
- A lock timeout kept the earlier batch picture forever; now at most 15
  minutes (MeasuredAt), so a job that has started is not reported late from
  an old picture.
- revoke-dynamics-ax-read.sql revoked SELECT on same-named tables in every
  schema, also a DENY set by a DBA. It now finds the AX schema like the grant
  script and revokes only granted SELECT there.
- SQLI-AX-006 skips blocked sequences (as -005/-008 do); forecasts under a
  day say "inden for et døgn" instead of "om ca. 0 dage".
- Integration tests: the SQL Scripts run as-is in main's format, and the
  shared connection has the agent's session settings after the AX reads.
- docs/dynamics-ax.md: history bounds, lock-timeout window, session settings,
  /detection-problems, API redaction.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:45:31 +02:00
13f22c7541 Review 1.4: AX batch rules: one finding per job, stuck jobs only once, no crash without a start
- Axapta 3.0/4.0 keep a BATCH row per recurrence, so a failed run and the
  next waiting run share a job key and gave two SQLI-AX-001 findings with the
  same subject (the repository kept the last, the hub the first). The batch
  rules now keep one finding per subject, the most severe.
- SQLI-AX-004 treated a single earlier 10-hour run as "normally that long"
  and stayed silent; it now needs BatchHistoryMinimumRuns runs, like
  SQLI-AX-002. A stuck job is SQLI-AX-004's finding only, not also -002's.
- Running(now)!.Value threw for an executing job with neither a start time
  nor a first-seen time (and a negative duration meant clock skew); such jobs
  are skipped.
- SQLI-AX-010 no longer calls every session in an AX database "from Dynamics
  AX", and shows the raw CONTEXT_INFO as sensitive evidence.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:29:35 +02:00
8365ba5357 Review 1.4: AX pages and API leave out removed instances; bounded, complete reads
- DynamicsAxView (pages and API v1) listed instances removed from monitoring;
  /batch-runs without ?instance= returned runs of every instance. Both now
  only cover the instances the overview shows.
- AX blocking read the instance's newest 200 blocking events and filtered
  for AX afterwards, so other blocking could push AX blocking out entirely.
  GetBlockingInDatabasesAsync filters on the AX databases in SQL (OPENJSON)
  with its own TOP (1000).
- /sequence-samples loaded every sample and cut at 10,000 in memory; the limit
  is in SQL now, with X-Truncated when there are more.
- Two new runs with the same run key in one snapshot would break the
  primary key of insight.AxBatchRuns; they are deduplicated first.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:29:34 +02:00
f4d2603181 Review 1.4: AX texts in API v1 and the hub without logins or host names; API has what the pages show
- "Ikke målbar" texts, notes and detection problems carry the GRANT text with
  the agent's login and database user. API v1 (batch-jobs, number-sequences,
  recids, databases, rule-states) returned them as they were, although logins
  are redacted everywhere else. AxReadAccess.WithoutPrincipals replaces them
  with the placeholder unless sensitive values are allowed; the grant script
  also hides a database user that differs from the login.
- SQLI-AX-010 put the host and program name (in 2-tier AX 3.0 the user's own
  PC) in the finding's explanation, which reaches the hub unredacted for an
  opted-in database. They are only in the sensitive evidence now, with the raw
  CONTEXT_INFO; the CONTEXT_INFO parsing says it is not verified against
  customer data.
- A database named e.g. "AX · test" was read from a subject as "AX", whose
  hub opt-in then applied. Middle dots in the database are doubled in AX
  subjects, and the AX community rules' subjects are built the same way.
- API v1 now also has the detection problems (/detection-problems), what each
  raw batch STATUS means, and the thresholds behind the number sequence and
  RecId pages; the blocking explanation is no longer redacted (it holds
  nothing sensitive now).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:29:34 +02:00
66ed4e11b3 Review 1.4: AX 3.0/4.0 local times in the AX time zone, with its DST rules
Axapta 3.0 and AX 4.0 store batch and cleanup times as a local date plus
seconds. They were turned into UTC with the SQL Server's offset at collection
time, so every time from the other side of a DST change was an hour off (a job
planned 01:50 CET on the spring night looked 70 minutes late at 03:00 CEST),
and a SQL Server running UTC behind a CET AOS shifted every time.

AxLocalTime converts with the rules of the AX time zone on that date:
SQLInsight:DynamicsAx:LocalTimeZone (Windows or IANA id), else the agent's own
zone when its offset matches the SQL Server's, else the server's offset (to the
nearest quarter hour) with a Danish data note. The skipped spring hour and the
repeated autumn hour get standard time (deterministic, never throws, never a
false "late"); seconds outside 0..86400 are unknown and 86400 is midnight. The
AX 3.0 cleanup time is local by version, also when LATESTCLEANTIME is missing.
AX 2009+ UTC times are untouched. Also: remaining numbers cannot overflow for
HIGHEST = Int64.MaxValue, and run keys use the invariant culture.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:19:17 +02:00
fdaa7e76bf Review 1.4: the AX batch history watermark no longer gets stuck
After the first read, BATCHJOBHISTORY was read from the watermark minus an
overlap of 2000 RecIds with TOP (1000), oldest first. From AX 4.0 RecIds are
handed out per table and are dense, so once 1000 rows existed in the overlap
every read returned the same old rows: the watermark never moved and new runs
were never read again (every AX 2009/2012 installation with more than ~1000
history rows). The read after the watermark now takes the overlap plus 1000
rows (TOP (3000)), so at least 1000 new runs are read each time. Test with
20,000 dense RecIds.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:15:49 +02:00
be039a6f73 Review 1.4: AX reads put the agent's session settings back
AxSql.Reset ran SET LOCK_TIMEOUT -1 and READ COMMITTED after every AX read
(and after the AX detection in discovery). The collectors of an instance
share one connection, so every collector after the AX ones waited on locks
without limit instead of the agent's 5 s, and no longer read uncommitted.
Reset now restores the agent's own settings, from one place
(Core.Instances.AgentSession) that SqlConnectionFactory.SessionSettings also
uses. The AX reads themselves are unchanged: READ COMMITTED (or SNAPSHOT)
with a 2 s lock timeout inside sp_executesql, never NOLOCK.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:14:24 +02:00
3a0bbe22f1 Review 1.4: SQL Scripts for Dynamics AX in the 1.3 structure
The AX scripts written in 1.4 (own header style and a generator) now follow
main's "SQL Scripts" structure: the review header, "-- Kilde:" blocks
registered in SqlScriptSources and checked word for word against the code.

- SqlScriptSources: the shared AX detection (DatabasesSql, LoginSql,
  BuildDetectionSql), companies, batch jobs, batch history (first read and
  from the watermark) and number sequences / RecId for Axapta 3.0, AX 4.0,
  AX 2009 and AX 2012; BlockingCollector split 2008 / 2005, as CONTEXT_INFO
  comes from sys.dm_exec_sessions from 2008 and from sysprocesses on 2005.
- SQLI-AX-001..010 and _collectors/Ax*.sql in main's format; COM-AX-001..007
  with the 1.4 queries, the AX detection and USE [AXDB]; BLK-001,
  BlockingCollector and DatabaseDiscovery updated (CONTEXT_INFO,
  sp_executesql).
- Scripts_are_read_only stays strict (no INTO #temp anywhere) and now also
  checks the batch inside EXEC [db].sys.sp_executesql N'...'.
- SQLINSIGHT_UPDATE_SQL_SCRIPTS=1 (from 1.4) rewrites out-of-date blocks from
  the code; the 1.4 generator test now checks that every AX script shows its
  sources for every AX version and says what is unverified.
- README: SQLI-AX-001..010, the AX collector scripts, 108 rules.
- AxDatabaseDetector: LoginSql and BuildDetectionSql as members, so the
  registry sees them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:11:45 +02:00
08cd741b52 Dynamics AX: notice a GRANT within minutes, one AX lookup per custom-rule run
The detector looks again every 5 minutes at an AX database where the agent
lacks part of the read access, so a GRANT takes effect quickly. The AX SQL
test checks that only standard AX tables are read. The hub end-to-end test
expects no AX rule outcomes without the opt-in. Docs: what 1.5's synthetic
AX checks can reuse.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:54:36 +02:00
cd72f14883 Docs: Dynamics AX (1.4)
docs/dynamics-ax.md: detection, the optional read access per version, how
AX is read (SELECT only, TOP, 2 s lock timeout or snapshot, never NOLOCK),
the rules SQLI-AX-001..010 with thresholds, batch times and the unverified
AX 3.0 STATUS mapping, forecasts from SQLInsight's own samples, AX users
behind blocking (CONTEXT_INFO, honest for 3.0), the hub opt-in, the API and
how to drop in a real 2009/2012 schema. AX 2009/2012/F&O are marked as based
on Microsoft's documented schema, not verified against customer data.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:54:36 +02:00
d574057114 Integration tests: fake Axapta 3.0 (bmssa) and AX 2012 R2 databases
Built from the schema files in the SQL Server 2022 container and read as the
least-privilege agent login: detection and discovery, 'ikke målbar' with the
exact GRANT and measuring after it (no customer table readable, no data
changed), the shipped grant/revoke scripts, AX 2012 history by RECID read
once, a locked AX table skipped within seconds, the SQL Scripts run as-is,
repository retention and read-back, and the web pages and API v1.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:54:23 +02:00
e2284657ae Web and API v1: Dynamics AX pages and endpoints
Dynamics AX in the menu (shown when AX databases are recognised): AX-databaser
(version, schema, companies, read access and the exact GRANT per database),
Batchjob, Nummerserier, RecId and AX-brugere bag blokering. The same data in
API v1 under /api/v1/dynamics-ax/ (databases, grant-script, batch-jobs,
batch-runs, number-sequences, recids, sequence-samples, blocking), documented
in OpenAPI, with AX users, hosts, statements and the agent's login redacted
unless ApiIncludesSensitiveEvidence is on.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:54:23 +02:00
3e1cc74f0e Dynamics AX rules SQLI-AX-001..010 and their SQL Scripts
Batch jobs: failed (001), longer than their own history (002), late or never
started (003), stuck in Executing (004). Number sequences: running out with a
forecast from SQLInsight's own samples (005), format shorter than HIGHEST
(006), blocked but still referenced (007), continuous and not cleaned up
(008). RecId running out, flagged far earlier for AX 3.0's 32-bit RecId
(009). AX user behind blocking from CONTEXT_INFO, honest where AX 3.0 cannot
tell (010). Subjects carry the database, so findings follow the hub opt-in.

SQL Scripts/<rule>.sql (and _collectors/) for every AX rule added or changed
in 1.4, incl. COM-AX-001..007: a Danish header plus the exact SQL SQLInsight
sends, generated from the code and checked by SqlScriptTests (rewrite with
SQLINSIGHT_UPDATE_SQL_SCRIPTS=1).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:54:23 +02:00
36ad4c0656 Repository and agent: Dynamics AX history (migration 0017), seeding, hub opt-in
0017_dynamics_ax adds insight.AxBatchRuns (completed batch runs, EventDays
retention) and insight.AxSequenceSamples (one NEXTREC/NEXTVAL sample per
sequence per day, HourlyDays retention). The agent reads them back after a
restart so job comparisons and forecasts continue, takes its thresholds from
SQLInsight:DynamicsAx, and only sends findings of the built-in SQLI-AX rules
to the hub for databases with the AX opt-in ticked; AX rule reasons (which
can name tables, logins and GRANT text) are replaced by a fixed text.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:53:34 +02:00
6b945295d0 Dynamics AX collectors: shared detector, batch jobs, number sequences and RecId
dynamics-ax (hourly) lists the monitored AX databases with version, schema,
companies and read access; discovery and the AX community rules (now marked
dynamicsAx) use the same detection. dynamics-ax-batch (5 min) reads BATCH
(3.0/4.0) or BATCHJOB/BATCHJOBHISTORY (2009+), keeps each job's own run
history; dynamics-ax-sequences (hourly) reads NUMBERSEQUENCETABLE and
SYSTEMSEQUENCES and forecasts from SQLInsight's own daily samples. All reads
run through [db].sys.sp_executesql under read committed with a 2 s lock
timeout (snapshot when allowed), bounded with TOP, never NOLOCK, never
written. Blocking now captures CONTEXT_INFO for the AX user mapping.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:53:34 +02:00
4ebae4efe6 Dynamics AX core: central detector, per-version schema adapters, read-access GRANT
One place recognises AX (version 3.0/4.0/2009/2012/2012 R2/F&O, schema owner,
model store) from metadata only. Per-version adapters hold all table/column
knowledge, check every column in sys.columns before use and degrade to
'ikke målbar' with the exact column or the exact GRANT text. Only AX 3.0 is
verified against a real customer schema; 2009/2012 are written from
Microsoft's documented schema (docs/dynamics-ax/*.documented.tsv, replaced
by a real export when one is dropped in). grant-dynamics-ax-read.sql is
version- and schema-aware (bmssa) and covers exactly the adapters' tables.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:52:58 +02:00
66aa47ecb3 Deadlocks: read system_health files without a temp table
The 2012+ query used SELECT ... INTO #system_health, a write to tempdb.
One SELECT with ROW_NUMBER() now returns the deadlock rows and the newest
row (the read position) in the same pass. SQL Scripts and the read-only
test no longer allow any exception.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:47:31 +02:00
631edc2adc Review 1.3: SQL Scripts for database discovery and the permission check
Some checks failed
deploy community / deploy (push) Successful in 15s
deploy hub / deploy (push) Successful in 19s
CI / Build og unit tests (push) Successful in 7s
CI / MSI-installer (push) Successful in 17s
CI / Integration (mcr.microsoft.com/mssql/server:2017-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2019-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2022-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2025-latest) (push) Has been cancelled
Registers 1.3's SQL in SqlScriptSources (DatabaseDiscovery: databases 2005/2000
and the Dynamics AX signature per database; PermissionCheck: login/sysadmin, the
HAS_PERMS_BY_NAME checks for 2005-2012, 2014-2019/MI and 2022+, databases without
access) and adds _collectors/DatabaseDiscovery.sql and PermissionCheck.sql with
Danish review headers. The constants are renamed *Sql and the login query moved to
PermissionCheck.WhoSql so the registry test finds them. The scripts of the
collectors that now skip unchosen databases say so (their SQL is unchanged).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:26:16 +02:00
473dccafff Version 1.3.0
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:43 +02:00
12cf5a1e94 Review 1.3: a request that fails in an unexpected way is completed as failed, not left Running
HandleAsync only turned a few exception types into the request's error; anything
else (a lock or command timeout, a JSON or argument error) escaped, leaving the
row Running and the page waiting until housekeeping failed it minutes later.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:43 +02:00
a6a72661e7 Review 1.3: a routine discovery that could not be stored is not retried every 2 seconds
When saving a discovery failed in the repository (deadlock victim, lock timeout,
connection), LastDiscoveryAt stayed unset and the next round 2 seconds later
listed the same instance's databases again, loading the customer's server. The
agent now waits at least 15 minutes between routine discoveries of an instance.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
f0c5d01294 Review 1.3: the overview works before the agent has migrated to 0016
GetInstancesAsync now reads insight.ConfiguredInstances only when it exists, so a
1.3 web UI (e.g. upgraded first on its own server) still shows the overview
against a repository a 1.2 agent has not migrated yet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
a66e3f2b3b Review 1.3: upgrade note on AX findings to the hub (now opt-in per database)
1.2 sent Dynamics AX findings to the hub with the rest; 1.3 sends them only for
databases where AX hub sharing is ticked, default off, so sharing stops at the
upgrade until it is turned on. docs/opdatering.md gets an upgrade section that
says so first, with the other 1.3 changes, and the release text points to it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
f5ff42ca83 Review 1.3: an unreadable agent key file is kept, not overwritten
LoadOrCreate replaced a key file it could not decrypt, which made the old key
unrecoverable even when only the machine (DPAPI) had changed and the file could
have been put back. The file is now moved aside as credential-key.bin.unreadable-*
and the RSA object of the failed import is disposed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
356e102126 Review 1.3: redact logins and user names in the configuration API
API v1 returns logins and people's names as [udeladt] unless
ApiIncludesSensitiveEvidence is on, but /monitored-instances and /agent-requests
returned the SQL login, createdBy/updatedBy/requestedBy/selectionChangedBy, the
login the agent arrived as and 'Login failed for user ...' texts, and the GRANT
script filled in with the login. They are now redacted the same way (the GRANT
script falls back to the shipped template). ChooseAsync also answers 404 for an
unknown instance instead of 422.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
cade91aa4a Review 1.3: a request still waiting for the agent is reused, not queued again
Pressing Test forbindelsen repeatedly, or a script calling the API in a loop,
added a row per call to insight.AgentRequests, each run by the agent in turn. The
same pending request (kind, instance, parameters) is now returned instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
2adeef4597 Review 1.3: local Administrators may change the configuration until AdminGroups is set; MSI property ADMIN_GROUPS
With SQLInsight:Web:AdminGroups empty nobody could add the first server. The
installer now takes ADMIN_GROUPS (';'-separated) and writes it to the web's
sqlinsight.ini; while the setting is empty, members of the web server's
BUILTIN\Administrators (S-1-5-32-544 among the user's group SIDs; a remote
Negotiate login has the full token, a UAC-filtered deny-only SID does not count)
may change it. The page says who may.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
8e1b877fda Review 1.3: overview leaves out removed instances and marks paused ones
The overview kept listing removed and paused instances with their last data and
counted them in the totals and alerts. Removed instances now disappear from the
overview, Alle fund, /api/v1/instances and /metrics (their history stays for
reports and the instance page); paused ones are shown as "Sat på pause" and
are left out of the totals, the urgent findings and the alerts (Prometheus gets
sqlinsight_instance_paused instead of instance_up = 0).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
4c65248727 Review 1.3: an instance removed from the configuration file is marked removed, not deleted
The agent deleted ConfigFile rows no longer in the file, and with them (cascade)
the pause and the database choices: commenting an instance out for a restart lost
them. It is now soft-deleted like a Web instance and brought back, choices and
all, when it returns to the file.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
01c04ac95a Review 1.3: database choice and discovery take turns (sp_getapplock)
The web's database choice and the agent's discovery both update many rows of an
instance's insight.ConfiguredDatabases in one transaction, in different orders,
which can deadlock. Both now take an application lock per instance first, as
the finding saves do.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
29e1909e98 Review 1.3: one implementation of a service account's SQL login
RepositorySetup.LoginFor/IsLocal duplicated Core's ServiceAccounts, which the web
uses for the GRANT script. Setup now uses ServiceAccounts too, so the login the
installer grants and the one the web shows cannot drift apart.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:41 +02:00
5ad9355130 Docs: Servere og databaser (1.3)
New docs/servere-og-databaser.md (on the community site too): adding servers,
who may change it (AdminGroups), connection test and GRANT script run by the
agent, database choice, configuration file servers, how SQL login passwords are
sealed and why, the repository grants, and the API. Installation, integration
(scope config:write) and data processing (AX findings to the hub are off by
default) point to it; AdminGroups in the web's appsettings.json.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:41 +02:00
f3ef0cbec7 Web: Servere og databaser, and the same in API v1 (scope config:write)
A page under Overvågning lists the monitored servers (from SQLInsight Web and,
read-only, from the agent's configuration file), adds a server (name\instance,
optional port, Windows via the agent's account or a SQL login, encryption),
pauses, resumes and removes it, and finds instances via the agent (SQL Server
Browser). Each server's page shows the agent's connection test with the rights
it has and lacks, the least-privilege GRANT script for the login the agent
arrives as, and the discovered databases (state, size, recovery model,
compatibility level, Dynamics AX) to tick for monitoring, with "overvåg
automatisk nye databaser" and AX findings to the hub per database. Pages
refresh while the agent works on a request.

Only SQLInsight:Web:AdminGroups (new) may change anything; others see it.
SQL login passwords are sealed with the agent's public key on arrival and
never shown or returned; a new server or login needs the password again.

API v1: /monitored-instances (list, add, get, patch pause/monitor-new,
connection, delete), …/connection-test (start, latest with missing
permissions), …/discovery, …/databases (read, choose), …/grant-script,
/instance-discovery and /agent-requests/{id}. Changes need the new token scope
config:write, or AdminGroups for Windows users.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:41 +02:00
81b8376026 Agent: instances from the repository, request queue, sealed passwords, AX hub opt-in
The agent mirrors SQLInsight:Instances into the repository at start (read-only
"fra konfigurationsfil" rows, every database monitored as before) and reads the
configuration on every tick: instances added, paused, removed or changed in
SQLInsight Web, and database choices, take effect without a restart. If the
repository cannot be read before the first load, the configuration file alone
is used.

AgentRequestService runs connection tests (with the permission check), database
discovery and instance discovery (SQL Server Browser, registry) as the agent's
own identity, and refreshes every instance's database list hourly. The agent
keeps an RSA key (DPAPI LocalMachine, restricted folder) and publishes its public
key and identity; SQL login passwords sealed by the web UI are opened only here.

Dynamics AX findings and AX rule outcomes are sent to SQLInsight Hub only for
databases where it is turned on (default off).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:41 +02:00
b2fa78411f Collectors: monitor only chosen databases; database discovery and permission check
CollectorContext carries the instance's DatabaseSelection; the database,
index, Query Store and community-rule collectors skip databases that are not
chosen (system databases always run). DatabaseDiscovery lists every database
with state, size, recovery model, compatibility level and access, and
recognises Dynamics AX with the same signature as the AX rules, through
three-part names only (2000 and 2005+ variants). PermissionCheck reports which
rights from grant-least-privilege.sql the login has (HAS_PERMS_BY_NAME,
version-aware; not possible on 2000).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:41 +02:00
1199b1aa86 Repository: configured instances, discovered databases, agent request queue (0016)
insight.ConfiguredInstances (Web or ConfigFile source, soft remove, sealed
password), insight.ConfiguredDatabases (discovery result, monitored, AX to hub),
insight.AgentRequests (queue for connection tests and discovery run by the agent)
and insight.AgentKeys (the agent's public key and identity). The web login gets
INSERT on ConfiguredInstances and AgentRequests and UPDATE on named columns only;
no DELETE, no Source/Name change, nothing the agent records.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:21:59 +02:00
8776faefb3 Instance configuration core: database selection, sealed credentials, agent request contracts
DatabaseSelection decides which databases the database-specific checks run in
(system databases always), and which may send Dynamics AX findings to the hub
(default none). CredentialEnvelope seals a SQL login password with the agent's
RSA public key (RSA-OAEP-SHA256 + AES-256-GCM), bound to server and login, so only
the agent can open it. Shared contracts for connection tests and discovery, server
address validation, the service account login rule, and the least-privilege GRANT
script filled in for one login.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:21:59 +02:00
2284e7f082 SQL Scripts: one reviewable script per rule
A DBA should be able to review every statement SQLInsight sends to a
monitored server before granting it access. "SQL Scripts/<rule-id>.sql"
holds, per rule (82 built-in, 16 community), a Danish header (what and
why, how C# judges the data, thresholds with config keys, severity,
exact GRANTs, versions, collector file and member) and the collector's
SQL verbatim, every version variant as its own batch.
"_collectors/<Collector>.sql" holds each collector's queries once.

The only statement that is not strictly read-only is documented in the
scripts: the deadlock collector's session temp table (#system_health).

Inline SQL in the collectors moved to internal constants and Build*Sql
methods (no text changed), so SqlScriptTests can check that the scripts
still contain the SQL the code sends, that every rule has a script, that
every collector query is registered, and that nothing writes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:20:06 +02:00
28bd148691 Version 1.2.0
Some checks failed
deploy community / deploy (push) Successful in 16s
deploy hub / deploy (push) Successful in 23s
CI / Build og unit tests (push) Successful in 6s
CI / MSI-installer (push) Successful in 15s
CI / Integration (mcr.microsoft.com/mssql/server:2017-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2019-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2022-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2025-latest) (push) Has been cancelled
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:47:20 +02:00
d9e3b4ee6d Review 1.2: read only the default trace's current file between rollovers
With ALTER TRACE the error log collector read every default trace file
(up to 5 x 20 MB) every 5 minutes. While the current file is the one read
last time, everything since then is in it, so only that file is read;
after a rollover (and the first time) all files are read once as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:38:34 +02:00
8e551bcaa6 Review 1.2: document the new collection limits
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:35:54 +02:00
3f2e959f18 Review 1.2: list the new API v1 endpoints in the integration docs
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:35:41 +02:00
d070cd872b Review 1.2: scan the version store for its generators at most hourly
sys.dm_tran_top_version_generators reads the whole version store. It ran
every 5 minutes while the version store was over half the threshold,
i.e. exactly when it is large and expensive to scan, and a timeout failed
the whole tempdb collection. It now runs at most once per hour (the
previous answer is kept in between, with its time) and a failed scan
keeps the previous answer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:35:00 +02:00
4ceb64f2d5 Review 1.2: index usage after a counter reset without a restart
sys.dm_db_index_usage_stats also starts over when a database is taken
offline, closed by AUTO_CLOSE or (2012/2014) an index is rebuilt. The
delta was then clamped to 0, so the usage since the reset was lost from
the daily history and the tracked totals. A lower counter now counts as
new usage. IndexInfo.Key also used the literal text "001f" as its
separator instead of the unit separator character.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:34:17 +02:00
a01ec374a4 Review 1.2: keep SQL Server's error message out of the replication reason
When the distribution database could not be read, SQL Server's message
(which names the agent's login) was appended to Unavailable. That text is
the rules' "not measurable" reason and is returned unredacted by
/rule-states and /replication. It is now UnavailableDetail: shown on the
local instance page and [udeladt] in the API unless
ApiIncludesSensitiveEvidence. The agent list is also capped (TOP 500).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:33:25 +02:00