- "Kør nu" (ServerRequestHandler) goes through SyntheticCheckService.RunNowAsync: it joins a
run of the check that is going on, or runs it under the same MaxParallelChecks throttle
and registers it, so it never overlaps a scheduled run. A paused check is not run: the
answer is "Tjekket er sat på pause." and nothing is stored, evaluated or opened.
- Server requests (test, run now) may take 3 min (the check's max timeout 120 s + the
30 s service lookup + 30 s margin) instead of AgentRequestService's 2 min, so the result
is always stored; a server test gets 2.5 min. Stale Running requests are cleaned after
the longest timeout + 1 min.
- An exception a check run did not foresee becomes a stored failed result ("Tjekket kunne
ikke køres: …"), so LastRunAt advances and the check waits its interval; it is logged
once per change. A run that cannot even be stored is not retried before its interval
(the agent remembers when it started it).
- The service lookup of a failing check has its own 30 s deadline; its error detail
(naming the agent's account) is Sensitive.
- The rules get max(10, FailuresBeforeFinding, SlowRunsBeforeFinding) + 1 recent results
(at most 100), so "Fejlet i træk" and FailuresBeforeFinding > 10 work.
- The interval is never under SyntheticCheckLimits.MinIntervalSeconds agent-side; state
for removed checks is pruned; shutdown waits for running checks before the throttle is
disposed; a TaskCanceledException that is not shutdown no longer ends the service.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Deadlines
- Event log over RPC (MS-EVEN6): the whole read has a deadline (OperationTimeout per log);
the token cancels the EventLogSession's operations and is checked before each
FormatDescription, and the agent stops waiting even if the native call ignores it.
Reported as ServerAccessProblem.Timeout ("svarede ikke i tide over hændelsesloggens
protokol (RPC) inden for N sekunder").
- Every CIM call runs on the thread pool and is awaited with the caller's token; a call
that ignores cancellation is abandoned and the CIM session is closed once it returns.
- Each server cycle has a deadline (new SQLInsight:Servers:CycleTimeout, default 5 min,
1-30 min, at least 2 x OperationTimeout): the areas not read by then are "ikke målbar"
("Serveren svarede ikke inden for N minutter, så indsamlingen blev afbrudt; …"),
logged once, and the slot is freed.
- Scheduled tasks: run information is read within a time budget (CycleTimeout / 3); the
rest keep their state without the last run, and the coverage note says so.
- "Test server" has an overall deadline (WindowsServerTester, default 2 min); areas not
done in time are reported as not answered, so the result is always stored.
Backoff and memory
- A server that does not answer (Unreachable/Timeout, or a timed-out cycle) is backed
off 1, 2, 5, 10, then 15 minutes, reset when it answers.
- Protocol Auto: the protocol that worked (e.g. DCOM after WS-Man was refused, also for
access denied) is tried first for an hour, then WS-Man is tried again; a server whose
event log RPC failed is read over CIM for an hour.
- Over WS-Man with read-only WinRM rights Task Scheduler's GetInfoByName is denied: with
Auto the tasks are read over DCOM instead (remembered for the server); with WSMan forced
the reason says to choose Auto or DCOM. The advice "winrm configSDDL default" (broader
WinRM rights) is gone; texts follow the grant script (-WinRM read right,
-EnableFirewall -AgentAddress), not Remote Management Users.
Events
- Each log continues from its own mark (ServerEventSnapshot.LogReadUntil, a small additive
field in Core): a truncated System log no longer loses the events between its last
read event and the newest Application event.
- CIM fallback: Win32_NTLogEvent is streamed and only the oldest max per log are kept
(memory bounded, deterministic order), consistent with the RPC read and the coverage
text, which now names the logs that had more.
Other
- CimInstances (and method results) are mapped to SQLInsight's types and disposed at once.
- Options are clamped when bound (ServerOptions/SyntheticCheckOptions.Normalize):
OperationTimeout 5 s-5 min, MaxEventsPerCollection 10-10000, MaxWatched 1-200,
MaxParallelServers/Checks 1-32, retention days >= 1, ForecastHistoryDays 1-365,
FailuresBeforeFinding/SlowRunsBeforeFinding 1-99.
- Shutdown: the timer's cancellation is caught, running cycles are awaited before the
throttle is disposed; a cycle's time is taken when it gets its slot; a TaskCanceledException
that is not shutdown no longer ends the service.
- Retention maintenance also runs (hourly) when SQLInsight:Servers:Enabled is false but
synthetic checks are on.
- LocalCimTests run alone (collection without parallelization), share one warmed-up
session, use the same 2 min operation timeout and a 5 min deadline per test, a smaller
workload, and report timings in assertion messages.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- An HTTP answer that breaks off while it is read (HttpIOException/IOException) is a
failed check ("Svaret blev afbrudt, mens det blev læst: …"), not an exception that
made the check rerun every tick. Other unforeseen network errors (IOException,
SocketException, HttpRequestException, AuthenticationException) become
"Forbindelsen fejlede: …"; one that hits the check's deadline is "Intet svar inden
for N sekunder". DNS: an invalid name (ArgumentException) is a failed lookup.
- The HTTP "Adresse" detail (a URL may carry query secrets) and the AX "Object server"
detail are Sensitive, so the API redacts them.
- SyntheticCheckRun's compiler-generated ToString no longer prints the SQL password.
- ProcessRunner (AX Business Connector logon): the PowerShell process tree is killed in
a finally whenever it has not exited, also when the agent stops (before: only on the
check's timeout, leaving an orphaned process and AX session). Output and errors are
capped at 64 K characters each; the rest is read and dropped so the process never
blocks on a full pipe.
- Tests: broken body, password redaction, bounded output, killed on agent stop.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
GET returns the SQL login and the URL query redacted; a client that PUTs it back unchanged
would otherwise replace the real values.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- /servere reads the overview in one query and says which paused servers and checks are left out of the active
findings; admin notes use InstanceSetup.WhoMayChangeText as main.
- The check form refuses a port, interval, timeout or limit that is not a number (Danish message) and clamps
minutes before multiplying; an interval set to part of a minute through the API is shown and kept in seconds.
- "Kør nu" is refused for a paused check (message, no button); ?koersel= shows that run.
- A finding of a removed server or check does not link to a missing page and says why it is not active.
- The forecast text uses the history the forecasts were computed from; the grant script text matches the new
script (-WhatIf first, -WinRM, -EnableFirewall -AgentAddress, -Remove only what it added).
- HTTP addresses are at most 2000 characters.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- The Sensitive helper (Value/Text) is shared (Api/Sensitive.cs) and gains ServerText ("Agentens konto (…)" and the
test's account), Url (query left out), ServerTest and CheckResult.
- Windows servers and checks: CreatedBy/UpdatedBy, RequestedBy, LastError, area details, rule-state reasons, the
check's SQL login and an HTTP address's query (settings.url and target) are "[udeladt]" unless
ApiIncludesSensitiveEvidence is on. The grant script endpoint then returns the shipped template, Account
"[udeladt]" and a note without the service account.
- GET /agent-requests/{id} rewrites ServerTest results (account and texts) and SyntheticCheckRun results (failure
message, sensitive details) like ResultJson does for connection tests, and names serverId/checkId.
- Tests and runs reuse a request already waiting (said in the descriptions); running a paused check is 409.
- GET /server-findings: limit (max 500) and continuation with X-Continuation, evidence's sensitive flag, status,
scopePaused/scopeRemoved; GET /server-findings/{id} has history. GET /windows-servers/{id}/events returns
{ capturedAt, method, ignored, coverage, events }. The server list/one server has latest, worstDisk, soonestFull
and findings per severity; checks have findings per severity.
- Admin texts as main (AdminGroups, else the local Administrators); service lists trimmed and deduplicated.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- AddServerRequestAsync returns a ServerTest/SyntheticCheckRun already waiting for the agent (Pending, same kind and
target) instead of queueing another one, like AddAgentRequestAsync; AgentRequestRow carries TargetId.
- Active server findings and the overview counts leave out removed servers and checks, and paused ones across all
(a paused one's own list keeps its latest findings, as for instances); findings say ScopeRemoved/ScopePaused.
GetServerFindingsAsync pages with OFFSET/FETCH.
- Removing a server (in the web UI or a SQL Server host by the agent's sync) detaches its synthetic checks in the same
transaction (ServerId NULL, serviceName out of the settings); check queries only join servers in use.
- Removing a check clears its sealed password, key id, secret error and last message; a late agent result does not
write to a removed check. (0018: UPDATE on LastMessage for the web login.)
- 0018: settings and service lists as nvarchar(max) (no 500 on long æøå queries); indexes on SampledAt/LastAt/RunAt
for the retention deletes. Run details that do not fit are trimmed from the end instead of all dropped.
- One query for the overview's latest CPU/memory and disk collection per server.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- SQLI-WIN-006 is "ikke målbar" (with the forecaster's note) until a volume has enough
history; titles "Drev X er fuldt (om ca. N dage | inden for et døgn)", as SQLI-WIN-005.
- SQLI-WIN-007/008: the startup grace period is "ikke målbar" instead of passed, so
open findings are not resolved and reopened at every restart. One matcher for both
rules (name or display name, prefix*). SQLI-WIN-007 is capped by
Services:MaxFindings (default 15, 1-100) with one summary finding for the rest.
- SQLI-WIN-009: stable order (category, oldest first, key) and one summary finding
over MaxFindings (now bounded 1-100).
- SQLI-WIN-010 only says "uden tegn på at det skete uventet" when the event log was
read past the boot; before that "ikke målbar", and Low "årsagen kan ikke ses" when
the log cannot be read.
- SQLI-WIN-012/013: Tasks:IgnoreUserAndUpdaterTasks (default true) leaves out per-user
and updater tasks; TaskThresholds.Watches is the one filter for the agent and the
rules. A SID is never in a title or subject, only in sensitive evidence.
- SQLI-SYN-001: neutral title "{navn} fejler ({n} gange i træk)", the failure message
only in sensitive evidence; "mindst N" and "... eller tidligere" when every result
given failed; "findes ikke" for a missing service; evidence "Tjenestens tilstand".
- SQLI-SYN-003: "udløber inden for et døgn" / "om 1 dag".
- Agent (minimal): the task filter is TaskThresholds.Watches, and performance samples
get the configured interval.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- DiskForecast.EnoughHistory: false for "ingen målinger endnu" and "for lidt historik",
so a rule can say "ikke målbar" instead of "fyldes ikke op". Days are counted from
now (a last point older than its hour is subtracted); FullAt takes the time the
forecast was made; Describe says "fuld nu" at 0 days.
- ServerEventGroup.Buckets: counts per clock hour, and Recent sums only the hours with
events in the last 24 hours (FirstAt within the window), so a recurring error no
longer adds up from when it was first seen. Older groups without buckets still merge.
- ServerPerformanceMath.Sample takes the interval: after a gap of more than three
intervals CPU and pages/s are null rather than the gap's average stamped now.
Commit percent is computed in floating point.
- TaskResults.IsUserOrUpdaterTask (a user's SID in the name, OneDrive/Edge/Google
updaters, User_Feed_Synchronization), HasUserSid and Display (SID replaced by a
short hash for titles and subjects).
- Tests: forecast from real history, irregular sampling, identical timestamps,
a shrunk volume, days from now; an error every ten hours over three days; a
sample after a gap.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The grant script changes SCM and WMI security on customer servers. Before, a re-run
replaced every ACE of the account on a WMI namespace (also a customer's Deny), -Remove
took the account out of groups and ACEs it had before, only the SCM SDDL was backed up
(and never used), Remote Management Users also opened PowerShell Remoting, and
-EnableFirewall narrowed Windows' built-in rules for every other tool and was never undone.
- Checks everything before changing anything: elevation, the account resolves (broad
groups refused), Deny ACEs stop the script, -EnableFirewall needs -AgentAddress.
- Backs up the original SDDLs (SCM, WMI namespaces, WinRM), group memberships and
firewall rules before the first change; records what it added in a state file in an
administrators-only folder; -Remove removes only that.
- SDDL handled with RawSecurityDescriptor (GetSD/SetSD, sc sdshow/sdset): exactly one
ACE added in canonical order, verified before writing and after; aliases (NS) work.
- No Remote Management Users: -WinRM adds GENERIC_READ (no Execute) to WinRM's RootSDDL;
DCOM is the default.
- Firewall: its own "SQLInsight Agent: ..." rules only from the agent's address,
removed again with -Remove; Windows' rules untouched.
- -WhatIf, groups by SID, Danish messages, PowerShell 2.0 syntax.
- ServerGrantScript.For does not fill in an account that could end the PowerShell
string (typographic quotes, control characters); tests run the SDDL logic in
Windows PowerShell against real default SDDLs.
- docs/windows-servere.md: rights, safety net, firewall.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
After the rebase onto 1.4 the 16 SQLI-WIN/SQLI-SYN scripts had their own /* */ format,
so SqlScriptTests (rule ids, review header, -- Kilde: blocks) failed.
- The scripts have main's header (Regel-id, Titel, Alvor, Versioner, Vurdering,
Grænseværdier, Rettigheder, Data kommer fra, Regelkode, Read-only); the Windows
WQL/XPath stays word for word in comments.
- The synthetic SQL check's SQL is registered (SyntheticCheckRunner.UseDatabaseSql,
BeginTransactionSql, RollbackSql, SyntheticCheckSettings.DefaultQuery) and shown
as -- Kilde: blocks in SQLI-SYN-001/002 and _collectors/SyntheticCheckRunner.sql.
- SqlScriptTests know ServerRuleCatalog; only those rules may be without SQL blocks.
- ServerScriptTests: every CimQueries query/filter is in a SQLI-WIN script word for word.
- The SQL check cancels the rest of a large answer instead of draining it on close.
- Rule counts: README (108 built-in: 92 SQL Server + 13 Windows + 3 synthetic),
SQL Scripts/README (rule table, 124 rules, Windows/synthetic section), RuleCatalogTests,
CONTRIBUTING (how to add a server rule).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The synthetic SQL check (and custom rules) used a regex that ended an [identifier]
at the first ] and a block comment at the first */, so "[x]]'] DELETE ..." hid a
DELETE inside what it took for a string. T-SQL also needs no semicolon between
statements, so "SELECT 1 COMMIT" or "SELECT 1 WRITETEXT ..." passed.
- ReadOnlyQuery lexes T-SQL as SQL Server does (nested comments, '' ]] ""),
refuses unterminated comments/strings/identifiers, every statement keyword
(COMMIT, ROLLBACK, SET, DECLARE, WRITETEXT, UPDATETEXT, RAISERROR, CHECKPOINT,
ENABLE/DISABLE, RECEIVE/SEND ...), NEXT VALUE FOR (sequences are not rolled
back) and update/exclusive lock hints, and a second SELECT unless joined by
UNION/EXCEPT/INTERSECT.
- CustomRuleDefinition.QueryProblems uses it, so the synthetic check validates the
same way when saved and before it runs.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- docs/windows-servere.md: what is collected and how often, how the agent reads
servers (WS-Man, DCOM fallback, event log over MS-EVEN6), the exact least-privilege
rights and grant-windows-server.ps1 (never local administrator), known limits,
"ikke målbar" with the exact reason, firewall ports, the disk forecast, event
noise filtering, rules with their thresholds, retention, configuration and API.
- docs/syntetiske-tjek.md: the kinds, the AX logon (port, then .NET Business
Connector in a separate process, or clearly only the port), service running but
not answering, sealed SQL passwords, rules, firewall, configuration and API.
- Linked from installation, integration (API table) and databehandling (retention,
nothing sent to the hub); on the website in the documentation index, and the rule
list shows the Windows server and synthetic check rules in their own areas.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Menu group "Servere" with Windows-servere (/servere) and Syntetiske tjek
(/syntetiske-tjek).
- /servere: every server with status (overvåges, delvist målbar, kan ikke læses),
CPU, free memory, the fullest disk and the nearest "fuld om ca. N dage", findings;
add application/AOS/file servers. /servere/{id}: findings and what is not
measurable and why, CPU/memory for the last day, disks with forecasts, watched
and stopped services, grouped event log errors, scheduled tasks, rights per area
with the latest test, the least-privilege PowerShell script (copy or download,
UTF-8 with BOM), role and service lists, pause and remove. /servere/fund/{id}.
- /syntetiske-tjek: checks with status and response time; one form for every kind
(TCP, HTTP(S), SQL login + query, DNS, AX logon) with an optional server and
service; a SQL login password is sealed for the agent at once and never shown.
/syntetiske-tjek/{id}: latest result (incl. "kun porten er testet"), findings,
response times, history, edit, run now, pause, remove.
- API v1: /windows-servers (+ test, grant-script, performance, disks, services,
events, tasks, rule-states), /server-findings, /synthetic-checks (+ run, results,
rule-states); all in OpenAPI. Passwords are write-only; event samples, failure
messages and sensitive evidence are "[udeladt]" unless
SQLInsight:Web:ApiIncludesSensitiveEvidence is on. /api/v1/rules lists the new rules.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
One reviewable file per rule id with a Danish header: what and why, thresholds
with config keys and defaults, severity, required rights (never local
administrator), supported versions, collector file and member, and the
evaluation in plain Danish. The Windows rules use no SQL: their exact CIM/WQL
queries, event log XPath and a read-only PowerShell snippet to verify them are in
a comment block. SQLI-SYN-001/002 carry the exact SQL a SQL check runs (session
settings, transaction that is always rolled back, SELECT 1). A test checks every
server and check rule has its file.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- ServerMonitoringService keeps the SQL Server hosts in step with the monitored
instances (SQLInsight:Servers:IncludeSqlHosts), picks up servers added, paused or
changed in SQLInsight Web within a minute, and runs each server's due areas with
a limit on parallelism. Default intervals: 1 min CPU/memory, 15 min disks and
tasks, 5 min services and events (SQLInsight:Servers:Intervals).
- ServerMonitor: one CIM session per cycle; per-minute samples, disk samples and
the "fuld om ca. N dage" forecast from the stored history, grouped events (read
from where the last read stopped), services and tasks. A missing right makes
only that area "ikke målbar" with the exact reason, logged once per change; a
server that does not answer is notified once, and again when it answers.
Survives an agent restart (samples, boot time, event position).
- SyntheticCheckService runs checks on their interval, opens a SQL login password
sealed for the agent (AgentCredentialKey.OpenContext), looks up the linked
Windows service when a check fails, stores results and findings, notifies with
links to /servere/fund/{id}.
- AgentRequestService hands ServerTest and SyntheticCheckRun to ServerRequestHandler.
- appsettings.json: SQLInsight:Servers and SQLInsight:SyntheticChecks with every
threshold, interval and retention.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
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>
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>
- 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>
- 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>
- 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>
- "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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
The error log page called the default trace "the whole error log" and
said it has every line, and the ring buffer source told SQLI-LOG-002/005
to grant ALTER TRACE, which does not make 833 or stack dumps visible
(they are only in the error log itself). The rules now say that it takes
securityadmin, why SQLInsight does not grant it, and the exact statement.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>