diff --git a/devdocs/modernization/NET10-PORT.md b/devdocs/modernization/NET10-PORT.md index 938b6590..93ce8eeb 100644 --- a/devdocs/modernization/NET10-PORT.md +++ b/devdocs/modernization/NET10-PORT.md @@ -84,7 +84,7 @@ them. Items touching those three areas carry more weight here than they did in S - [ ] Exercise LINQ-based queries screen by screen. Data-list filters build raw SQL and are unaffected - [x] **BouncyCastle.** Already `BouncyCastle.Cryptography` 2.3.1 (`AyaNova.csproj:28`) with MailKit/MimeKit 4.5.0; no `Portable.BouncyCastle` in the graph. Version bump only: now 2.7.0, moved together with MailKit 4.18.1, as in Sockeye. The Sockeye note about `PemReader` and PKCS#1 key pairs does not apply: AyaNova reads a public key (see the licence gate) - [x] **Swashbuckle 10 / OpenAPI.NET 2.** Done: all six `Swashbuckle.AspNetCore*` 6.5.0 → 10.3.1 (Sockeye: 10.2.3), explicit `Microsoft.OpenApi` reference removed (now 2.7.5 transitively), and the three code changes below. Applies. `Microsoft.OpenApi.Models` merged into `Microsoft.OpenApi`: `using` at `Startup.cs:15` and `ConfigureSwaggerOptions.cs:6`. Bearer requirement (`Startup.cs:235-244`) becomes `c.AddSecurityRequirement(document => new OpenApiSecurityRequirement { [new OpenApiSecuritySchemeReference("Bearer", document)] = [] })`. Remove the explicit `Microsoft.OpenApi` 1.6.14 reference (`AyaNova.csproj:47`) -- [ ] **NLog 6.** `NLogBuilder.ConfigureNLog` is already replaced (`Program.cs:206`, `LogManager.Setup().LoadConfiguration`). `ArchiveNumbering = Rolling` (`Program.cs:136`) is gone in 6. Archive numbers keep incrementing and the un-numbered file is always current. **The log viewer sorts by file name as a string** (`LogFilesController.cs:120`, `OrderByDescending(z => z)`), so `-10` sorts after `-2`. Fix the sort in the same commit +- [x] **NLog 6.** Done: NLog and NLog.Web.AspNetCore 6.2.2; `ArchiveNumbering` removed; **`ArchiveFileName` kept and `ArchiveSuffixFormat = "-{0}"` added** (`Program.cs`); log viewer sorts archives by file date, newest first. Tested in a scratch program and on the server, against a folder seeded with NLog 5 archives `-0` to `-8`: without `ArchiveFileName`, NLog 6 writes straight into numbered files (no `log-ayanova.txt`) and appends to the old `-8`; with it but no suffix format, archives become `log-ayanova_NN.txt` (NLog 6 ignores `{#}`) and the old `-0` to `-8` are never deleted. Sockeye 8.1.0 ships that second form. With both settings: `log-ayanova.txt` stays current, archives are `log-ayanova-N.txt` as the manual (`docs/.../ops-log.md`) says, numbering continues after the old files, and the old files are cleaned up. `NLogBuilder.ConfigureNLog` is already replaced (`Program.cs:206`, `LogManager.Setup().LoadConfiguration`). `ArchiveNumbering = Rolling` (`Program.cs:136`) is gone in 6. Archive numbers keep incrementing and the un-numbered file is always current. **The log viewer sorts by file name as a string** (`LogFilesController.cs:120`, `OrderByDescending(z => z)`), so `-10` sorts after `-2`. Fix the sort in the same commit - [ ] **Newtonsoft.Json.** `Mvc.NewtonsoftJson` 10.x sets a 13.0.3 floor (NU1605). Already resolves 13.0.3 transitively; add it as a direct reference - [ ] **JwtBearer 10.x.** The IdentityModel part is n/a: it resolves 7.1.2, not the vulnerable 6.10.0 - [x] **SMTP.** n/a: `util/Mailer.cs` already uses MailKit's `SmtpClient` (`:1`, `:84`); no `System.Net.Mail` @@ -182,6 +182,7 @@ Found during the port, out of scope. One line each. - Stray `raven-client\ayanova\cypress.json`: the Cypress suite lives in `raven-test\e2e` - **Race, affects production after every restart:** `FormFieldReference.FormFieldReferenceList` (`biz/FormFieldReference.cs:57-59`) builds a static dictionary lazily with no lock, assigning it before filling it. A concurrent first request sees it half-built and throws `FormFieldReferenceList: is not valid`. Seen once (`VendorCrud.CRUD`) on the first .NET 10 suite run after boot. Fix: build the dictionary fully before publishing it (`Lazy` or a static initialiser). Pre-existing, not a port change. John's decision: its own case, fixed with the other cases going into this release, not under the port - `raven-launcher` `Program.cs:98` calls `PGProcess.StandardInput.Close()` but standard input was never redirected, so it throws `InvalidOperationException` at shutdown: the "Postgres shut down" step never runs and the launcher exits with an error when the server stops. Postgres was started detached by `pg_ctl start`, so it probably stays running. Pre-existing; candidate for its own case +- Sockeye 8.1.0 has the NLog 6 archive naming problem fixed here for AyaNova: archives are `log-sockeye_NN.txt`, old `-0` to `-8` archives are never deleted, and its log viewer still sorts by name (it also filters on `ayanova.txt`, not `sockeye.txt`, so the current file is listed twice). Same fix applies: `ArchiveSuffixFormat = "-{0}"` and the date sort ## Log @@ -197,3 +198,4 @@ Found during the port, out of scope. One line each. | 2026-10-22 | `raven-launcher` → `net10.0` (`d2079c9`) | Release-style publish (`-r win-x64 --self-contained`, trimmed): no warnings, 42 files / 20.3 MB (was 37 / 18.7 MB on .NET 8). Smoke run in a scratch layout: prints version 8.3.0, parses `config.json`, reaches the `pg_ctl.exe` start. Installer test still in section 4 | | 2026-10-22 | MailKit/MimeKit 4.5.0 → 4.18.1, BouncyCastle 2.3.1 → 2.7.0, Pkcs 10.0.13 pinned | Builds (only CA2022 left); no vulnerable packages. Licence gate 730/730, per-row outcomes identical to before. Mail end to end: `test-smtp-settings` sent through `Mailer` (connect, AUTH, send) to a local SMTP sink: well-formed `text/plain; charset=utf-8` message received. Integration suite = baseline. PDF-attachment path not exercised (report emailing) | | 2026-10-22 | Swashbuckle 6.5.0 → 10.3.1, OpenAPI.NET 1.6 → 2.7.5 | Builds (only CA2022). `/swagger/v8/swagger.json` compared with the pre-change document: same 315 paths, 462 operations (ids, parameters, request bodies, response codes) and 137 schemas; same global Bearer requirement and scheme. Only change: `openapi` 3.0.1 → 3.0.4. `/api-docs` UI serves. Integration suite = baseline | +| 2026-10-22 | NLog 5.3 → 6.2.2; archive suffix `-{0}`; log viewer sorted by file date | Builds (only CA2022). Server at Trace level, logging to a scratch folder seeded with NLog 5 archives `-0` to `-8`, through suite runs: `log-ayanova.txt` current, 9 archives `log-ayanova-23` to `-31`, old files removed; `GET log-file` lists the current file, then `-31` down to `-23`. Integration suite = baseline. In one of six Trace-level runs `LogFiles.MostRecentLogShouldFetch` most likely failed: a just-rolled current file can hold only TRACE/DEBUG lines (not reproduced; not at Info level) | diff --git a/server/AyaNova/AyaNova.csproj b/server/AyaNova/AyaNova.csproj index d9e9af0a..4a875f07 100644 --- a/server/AyaNova/AyaNova.csproj +++ b/server/AyaNova/AyaNova.csproj @@ -48,8 +48,8 @@ - - + + diff --git a/server/AyaNova/Controllers/LogFilesController.cs b/server/AyaNova/Controllers/LogFilesController.cs index 0f552f2b..fbe9fae6 100644 --- a/server/AyaNova/Controllers/LogFilesController.cs +++ b/server/AyaNova/Controllers/LogFilesController.cs @@ -117,7 +117,8 @@ namespace AyaNova.Api.Controllers //Iterate all log files and build return var files = System.IO.Directory.GetFiles(ServerBootConfig.AYANOVA_LOG_PATH, "log-ayanova*.txt"); - var ret = files.Where(z => !z.EndsWith("ayanova.txt")).OrderByDescending(z => z).Select(z => System.IO.Path.GetFileName(z)).ToList(); + //newest archive first by file date, archive numbers only increase since NLog 6 so name order breaks past 9 + var ret = files.Where(z => !z.EndsWith("ayanova.txt")).OrderByDescending(z => System.IO.File.GetLastWriteTimeUtc(z)).Select(z => System.IO.Path.GetFileName(z)).ToList(); ret.Insert(0, "log-ayanova.txt"); return Ok(ApiOkResponse.Response(ret)); } diff --git a/server/AyaNova/Program.cs b/server/AyaNova/Program.cs index 12c64a10..448e71cf 100644 --- a/server/AyaNova/Program.cs +++ b/server/AyaNova/Program.cs @@ -132,10 +132,15 @@ namespace AyaNova fileTarget.FileName = Path.Combine(ServerBootConfig.AYANOVA_LOG_PATH, "log-ayanova.txt"); fileTarget.Layout = "${longdate}|${uppercase:${level}}|${logger}|${message:exceptionSeparator==>:withException=true}"; + //NLog 6: ArchiveFileName must stay set, it keeps log-ayanova.txt as the current file and moves full files to archives + //(without it NLog 6 writes straight into numbered files). The archive number comes from ArchiveSuffixFormat, not {#}: + //"-{0}" keeps the documented log-ayanova-1.txt names and lets MaxArchiveFiles clean up archives from earlier versions + //(the NLog 6 default would be log-ayanova_01.txt and leave the old -0 to -8 files behind forever) + //archive numbers keep increasing (no more Rolling), the un-numbered log-ayanova.txt is always the current one fileTarget.ArchiveFileName = Path.Combine(ServerBootConfig.AYANOVA_LOG_PATH, "log-ayanova-{#}.txt"); - fileTarget.ArchiveNumbering = ArchiveNumberingMode.Rolling;//so expect to see 1 to 10 or 0 to ten as may be - fileTarget.ArchiveAboveSize = 1000000;//1mb maximum file size - fileTarget.MaxArchiveFiles = 9;//max 10 files totalling 10mb, the current log-ayanova.txt and log-ayanova-[0-9].txt + fileTarget.ArchiveSuffixFormat = "-{0}"; + fileTarget.ArchiveAboveSize = 1000000;//1mb maximum file size + fileTarget.MaxArchiveFiles = 9;//max 10 files totalling 10mb, the current log-ayanova.txt and the 9 most recent numbered archives // Step 4. Define rules