Playwright Tests / test (push) Canceled after 0s
Retires Fuchs_intranet.debug_log() and every direct EXECUTE [dbo].[fds__admin_logdebug] call in favor of a single path: ILogger. A new SQL log sink (OCORE.MultiLogger.SqlLoggerProvider, gated by Fuchs:Logging:SqlSink:Enabled, default on) mirrors every _logger.LogWarning/-Error/ -Critical call into [dbo].[fds__debuglog] via a new fds__admin_logdebug_ext proc, at Warning+. - Program.cs registers the sink and RequestLogScopeMiddleware (request id/path/authenticated user, pushed after UseAuthentication) so every log line in a request — including ones written deep inside OCORE's SQL helpers — is attributed automatically. - FIS_SQLOptions, IntranetController.Do's catch-all, PdfPreviewAsync, and the banking-upload handlers now log once, with an explicit EventId preserving the exact CodeReference text the retired debug_log() calls used to write, so existing dashboards/queries over fds__debuglog keep working unchanged. - Fuchs_DataService/FdsMfr.cs's FdsSqlOptions had the identical double-write hazard (it's hosted in the same process per ADR 0010) — the original 2026-09-16 log review's mfr__getSchema/ fds__getUpdateableRequests deadlock rows came from exactly this class. Fixed the same way. - fds__debuglog gains LogLevel/Machine/Category/Account/AuthUser/IsDebug columns; the legacy fds__admin_logdebug proc is left in place unused, not dropped. - FdsDebug.DebugLog (Fuchs_DataService's other ~9 call sites) deliberately left on its direct DB-write path — no ILogger call sits alongside it there, so no duplication risk today. See ADR 0024 (supersedes 0023) and Docs/Concepts/error-logging-pipeline.md. New tests: ErrorLoggingTests (FIS_SQLOptions), FdsSqlOptionsTests, RequestLogScopeMiddlewareTests. 694/694 pass (the 4 PdfPipelineTests failures seen under full-suite parallel runs are a pre-existing, unrelated PdfSharp global-font-resolver test-isolation flake — they pass both individually and in most full-suite runs). Bumps the OCORE submodule to 3137b56 (SQL sink hardening: redaction matches short fragments as whole identifier segments, per-entry-tolerant serialization, lazy dataSerialized, opt-in SqlLoggerOptions.IncludeCategory, LogEntry.Category/AuthUser capture). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
12 KiB
12 KiB
Copilot Instructions
⚠️ Instruction Sync
This file (
.github/copilot-instructions.md), the Claude Code instructions (/CLAUDE.md), and the Codex instructions (/CODEX.md) are three views of the same project rules and must stay in sync. Whenever you change one, make the equivalent change in the other two in the same commit.CLAUDE.mdandCODEX.mdmay add tool-specific workflow notes, but the shared project facts (architecture, coding standards, configuration, libraries, secrets, observability) must match.
Project Overview
- Fuchs Intranet is an ASP.NET Core (.NET 10) web application — the intranet IS the entire website, served from
/. - Routes:
/{fn?}/{id?}/{code?}→IntranetController.Index;/do/{fn?}/{id?}/{code?}→IntranetController.Do. - Build app:
dotnet build Fuchs/Fuchs.csproj -c Debug. Build all:dotnet build Fuchs_Intranet.slnx -c Debug. - Frontend assets are source-built: run the gulp tasks in
Fuchs/(npx gulp min, ornpx gulp allwhen copied/static assets also need refreshing) whenever JS or SCSS/CSS sources change. The generated files underFuchs/wwwroot/web/are what the app serves. - Test:
dotnet test Fuchs.Tests/Fuchs.Tests.csproj -c Debug. - Submodules include the OCORE projects and
eRechnungLib(ZUGFeRD/Factur-X + XRechnung generation) — invoices are moving to eRechnung output. - Project structure (relative to
Fuchs/):Controllers/—IntranetControllerpartials (no area)code/— business logic, PDF, email, widgets, data modelscss/intranet/— intranet SCSS source filesjs/intranet/— intranet JS source files (modules injs/intranet/modules/)Data/— static data assets (images for PDF, HTML files)Views/Intranet/— Razor views;Views/Shared/_Layout.cshtml;Views/Partials/
Coding Standards
- All code must be written in C#.
- Keep files to a limit of 400 (max 600) lines of code to ensure maintainability and readability. Proactively refactor larger files into smaller, focused classes or components as needed.
- Follow standard C# naming conventions (PascalCase for classes and methods, camelCase for variables and parameters).
- Use modern, performance-oriented C# .NET 10 features and best practices, such as async/await for asynchronous programming, LINQ for data manipulation, and dependency injection for better testability and maintainability.
Configuration
- All application settings live in
Fuchs/appsettings.json— do not useWeb.configorSystem.Configuration.ConfigurationManager. - App-specific settings are nested under the
"Fuchs"key (e.g.,_config["Fuchs:SMS_APIKey"]). - Connection strings are stored under the standard
"ConnectionStrings"key and read viaIConfiguration.GetConnectionString(...). FuchsOcmsIntranet.Initialize(configuration)must be called at app start (inProgram.cs) before DI registration;Fuchs_intranetreceivesIConfigurationvia its constructor.appsettings.Development.json(git-ignored) can override secrets for local development.Fuchs:Email:OverrideRecipient(bound viaIOptions<FuchsEmailSettings>, sectionFuchs:Email) is a dev/test safety net: when non-empty,ProcessWebComService.SendEmailAsyncdiscards the real recipient of every outbound email and redirects it to this single address instead, so a locally-enabled mailer can never reach a real tenant-owner or end-customer. Set this only inappsettings.Development.json— it must stay empty in Production (appsettings.jsondocuments the safe empty default).
Libraries
- Do not upgrade Spire.PDF beyond version 8.10.5.
- Do not upgrade the vendored TinyMCE (
Fuchs/wwwroot/lib/tinymce/; legacy copy atFuchs/Scripts/tinymce/) beyond version 6.8.6 — the last MIT-licensed release; 7.0+ is commercially licensed. SeeFuchs/Docs/Decisions/0013-tinymce-pinned-to-6-8-6.md. - Make use of OCORE libraries where possible, especially for common tasks such as logging, configuration management, and data access.
- Whenever possible, prefer OCORE_web_pdf / OCORE PDF functions for PDF-related tasks over rewriting.
- Do not use OCMS or OCMS_sharp; use only OCORE or OCORE_web.
- For builds failing due to SixLabors.ImageSharp requiring a license (v4.0.0+), check copilot-instructions.md for the SixLabors license key/handling info before downgrading ImageSharp.
Services & Dependency Injection
- Business logic lives in DI-registered services under
Fuchs/Services/behind interfaces; inject them intoIntranetController(constructor injection). Do not reintroduce static God-classes or pass the whole controller into helpers.IComService(email/SMS via ProcessWeb Mailer API, attachments sent inline as base64; honors theFuchs:Email:OverrideRecipientdev safety net — see Configuration),IPdfService(MigraDoc render),IInvoiceService,IReminderService,IReportService(SQL report engine viaFuchsVisualization),IWidgetService,IBankingService,IMfrClientFactory,ISystemStatusService(Admin module diagnostics: config snapshot + DB/Key Vault/blob/MFR connectivity probes + test-email; restricted tofds_sys> 4).- Lifetimes: stateless services (
IPdfService,IBankingService,IMfrClientFactory) are singletons; request-scoped DB services (IInvoiceService,IReminderService,IReportService,IWidgetService,IComService,ISystemStatusService) are scoped. Register inProgram.cs.
FdsInvoiceData/FdsReminderDataare pure data holders (parse + properties). Loading, persistence and PDF generation belong in the services — neverTask.Run(...).Wait()sync-over-async.- Data access stays SQL-first via OCORE helpers (
getSQLDataSet_async,setSQLValue_async) + stored procedures; no EF Core.
MFR ERP integration
MFR_RESTClienttalks to the mfr (Mobile Field Report) ERP over REST/OData. Its contract (base URLs, auth, OData conventions, pagination, error/retry, deep-create + document-upload) is documented inMFR_RESTClient/Docs/mfr_interface_description.md— read it before changing the client.- The client uses HTTP Basic auth, a configurable timeout, and retries idempotent GETs on transient errors (429/5xx, network/timeout) with backoff. Create clients via
IMfrClientFactory(don'tnewthem). The legacy VB project files have been removed; the active project isMFR_RESTClient.csproj.
Database
- The SQL schema source of truth is the
Fuchs_DatabaseSSDT project. The backend is SQL-first (stored procedures, table types likefds__tt__bankingtransactions, functions via OCORE helpers — no EF Core). - When you change a stored proc name/params or a table type, update both the SSDT project and the calling C# in the same change. Verify every
[dbo].[…]the backend calls actually exists inFuchs_Database.
Bank statement parsing (MT940 + CAMT)
- Two parsers feed the same banking pipeline: the external
MT940Parser(SWIFT text) and the in-repoCAMTParserproject (ISO 20022 camt.052/053/054 XML). BankingService.ParseToDatatableauto-detects the format (XML → CAMT, else MT940) and maps both into thefds__tt__bankingtransactionsschema. Thebam/uphandler and the frontend file picker accept both (.sta/.mt940/.txtand.xml/.camt).CAMTParseris namespace-agnostic (matches elements by local name) so it handles every camt schema version. Keep both parsers' column mappings aligned when changing the banking schema.
Observability
- Use OpenTelemetry. The app's instrumentation is centralised in
Fuchs/Observability/FuchsTelemetry.cs(oneActivitySource+ oneMeter). - When adding a meaningful operation: start an activity (
FuchsTelemetry.StartActivity(...)), record the relevant counter/histogram, and log entry/result/timing/errors via the injectedILogger<T>. Prefer structured logging (named placeholders), never string interpolation in log messages — and never{@Foo}: plainMicrosoft.Extensions.Logging(unlike Serilog) does not strip the@, it becomes a literal part of the key. - Tracing/metrics are always collected; OTLP export is opt-in via
Fuchs:Telemetry:OtlpEndpoint. Don't add exporters that fail hard when no collector is present. - DB log sink (ADR 0024): every
_logger.LogWarning/-Error/-Criticalcall additionally lands in[dbo].[fds__debuglog]via the OCORE MultiLogger SQL sink (fds__admin_logdebug_ext), gated byFuchs:Logging:SqlSink:Enabled(default on).RequestLogScopeMiddleware(Fuchs/Logging/) attributes every log line in a request to a request id/path/authenticated user automatically. Give a log call an explicitEventId(itsNamebecomes the row'sCodeReference) when the call site should be individually greppable — seeFIS_SQLOptions,IntranetController.Do's catch-all,PdfPreviewAsync, and the banking-upload handlers for the pattern. Do not also call the retiredFuchs_intranet.debug_log— oneILoggercall is the single write path; a second explicit DB write duplicates the row.
Testing
- xUnit in
Fuchs.Tests. Testing must be extensive, not superficial:- For every service/handler change add tests covering both an intentionally succeeding and an intentionally failing path where feasible (use stubs/mocks; the test project has
InternalsVisibleTo). - Cover edge cases and boundary conditions (empty/null/invalid input, disabled/feature-flag-off states, API/network errors) via
[Theory]/[InlineData]/[MemberData]rather than a single happy-path[Fact]. - When behavior depends on configuration (e.g.
appsettings.jsonvsappsettings.Development.json), prefer loading the real files layered the same way ASP.NET Core does (ConfigurationBuilder+AddJsonFile) over hand-typed literals only, so drift in the actual files is caught (seeProcessWebComServiceTests.SendEmailAsync_OverrideRecipientFromRealAppsettings_ClearsRecipientWheneverConfiguredfor the pattern). - Assert on the observable contract (e.g. the outgoing request payload) and on emitted telemetry (counters/histograms) when the code records them — not just the boolean return value.
- Name tests
MethodName_Scenario_ExpectedResult. - DB-bound paths that can't be unit-tested should at least have their pure logic covered.
- For every service/handler change add tests covering both an intentionally succeeding and an intentionally failing path where feasible (use stubs/mocks; the test project has
Decisions & Concepts
Fuchs/Docs/Decisions/holds immutable ADRs (architecture decision records);Fuchs/Docs/Concepts/holds living design write-ups kept in sync with the code. Each folder'sREADME.mdexplains the format, naming, and required YAML frontmatter — read it before creating or editing entries there.- Accepted decisions must be followed. Before working in an area covered by a decision, read it and conform to it; don't silently deviate. Every file's YAML frontmatter has an
applyToglob — scan frontmatter across the folder first (cheap) and only read the full body of entries relevant to the files you're touching. - Capture new decisions and concepts as they happen. When a non-obvious architectural or cross-cutting choice gets settled (by the user or in the course of implementation), add a decision in
Docs/Decisionsin the same change, and create/update the matching concept doc inDocs/Conceptsif the subsystem's design is otherwise non-obvious from the code.
Azure Key Vault — Secret Naming
- Secret names must satisfy the pattern
^[0-9a-zA-Z-]+$(alphanumerics and hyphens only; no underscores, dots, or spaces). - Hierarchy levels are separated by
--(double hyphen), which maps to:inIConfiguration. - Underscores within a name segment are encoded as a single
-in Key Vault and decoded back to_when the key is reconstructed. - The app prefix
fuchsis prepended to every secret name. - Format:
{appname}--{Section}--{key-with-hyphens-for-underscores} - Examples:
fuchs--ConnectionStrings--ocms-ConnectionString→ConnectionStrings:ocms_ConnectionStringfuchs--Fuchs--SMS-APIKey→Fuchs:SMS_APIKeyfuchs--Fuchs--Email--Main--password→Fuchs:Email:Main:password
- When adding new secrets: replace every
_in the original config key with-for the Key Vault name, and add the entry toManagedSecretKeysinappsettings.json(using the same hyphenated form without thefuchs--prefix).