Skip to content

Commit 7466db2

Browse files
marcosqlbiclaudealbertospelta
authored
Add Calendar / Calculation Group / Function templates + Phase M modernization (2.0.0) (#60)
* Phase 0: TOM 19.114.0 bump + offline golden-file test harness Foundations for adding new DAX template entities (calendars, calc groups, UDFs) with regression safety. - Bump Microsoft.AnalysisServices + AdomdClient 19.86.6 -> 19.114.0. - Guard the 3 Table.RequestRefresh calls in Engine via RequestTableRefresh: a disconnected (in-memory) model has no Server and cannot be refreshed (TOM throws), so skip the request when not connected. Server deployments always run connected, so behavior is unchanged; this enables offline metadata generation and tests. - Offline golden-file harness (Dax.Template.Tests): - OfflineModelFixture builds a synthetic disconnected model (Sales fact + target measures, Orders table) to drive the real Engine.ApplyTemplates dispatch. - GoldenFile serializes to BIM, normalizes volatile lineageTag GUIDs + line endings, and snapshot-compares against _data/Golden (UPDATE_GOLDEN=1 regenerates). - ApplyTemplatesGoldenTests snapshots the Standard config (holidays, custom date table + reference, and time-intelligence measures). - LiveServerFactAttribute: opt-in, skippable live-server test category (env-gated), never gating CI. - Copy HolidaysDefinition.json into test _data/Templates (Standard config needs it). Suite: 7 passed + 1 skipped on net6.0 and net8.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Small config changes for Claude plugins * Include current state * Updated my-claude-teams reference * Updated claude and session_handoff to correctly use agents * Bumped to 0.5.1 with tool name * Bump to 0.5.3 * Removed user plugin * Added my-claude-teams at project level * Added MCP permissions * Fix AddHierarchies internal Tabular back-references AddHierarchies populated Level.TabularLevel with an orphaned object and never assigned Hierarchy.TabularHierarchy at all: a first loop created a TabularLevel, stored it on level.TabularLevel, then discarded it, while a second loop created and added a different instance to the model without storing it back. The emitted BIM was correct, so the golden-file test could not catch the defect (it lived purely in the internal back-references). Collapse to a single loop mirroring the correct AddColumns pattern: create each TabularHierarchy/TabularLevel once, assign it to the model object's back-reference, and add that same instance to the model. Preserve ordinal, names, column binding, IsHidden/DisplayFolder and the compat-level LineageTag guard, and restore a per-level cancellation check for parity with AddColumns. Also remove a redundant Description re-assignment in CustomTableTemplate (value already set in the object initializer; no-op). Add HierarchyTabularReferenceTests covering the back-reference identity contract plus ordinal/column-binding and Reset() behavior. Output unchanged (golden BIM byte-identical); offline suite 13/13 on net6.0 and net8.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Add CHANGELOG with Unreleased AddHierarchies fix entry Introduce a Keep a Changelog-style CHANGELOG.md. Document the AddHierarchies internal back-reference fix and the new HierarchyTabularReferenceTests under [Unreleased] (version is build-managed, so no concrete version assigned yet). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Target .NET 10 only with C# 14 and SDK 10 Migrate the whole solution off multi-targeting net6.0;net8.0 to a single net10.0 target (net10.0-windows for the TestUI), raise LangVersion to 14.0, and pin the build to .NET SDK 10 (global.json 8.0.400 -> 10.0.301). - global.json: SDK 8.0.400 -> 10.0.301 (rollForward latestFeature unchanged) - Dax.Template / Dax.Template.Tests: net6.0;net8.0 -> net10.0, C# 12 -> 14 - Dax.Template.TestUI: net8.0-windows -> net10.0-windows, add LangVersion 14.0 - GitHub Actions ci.yml: install .NET 10.0.x SDK (was 6.0.x) - Azure pipeline ci.yml: drop orphaned ".NET 6.0 runtime" install step (SDK comes from useGlobalJson; net6 is EOL) Verified with SDK 10.0.301: restore/build/pack succeed, offline suite 13/13 on the single net10.0 target, golden BIM snapshot unchanged. BREAKING: the published Dax.Template NuGet now targets net10.0 only and no longer supports net6.0/net8.0 consumers. Documented in CHANGELOG [Unreleased]. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Bump test tooling and config packages to latest stable for net10 Align lagging NuGet dependencies with the net10.0 / C# 14 / SDK 10 toolchain. Test project (Dax.Template.Tests): - Microsoft.NET.Test.Sdk 17.11.1 -> 18.7.0 (major) - xunit 2.9.2 -> 2.9.3 - xunit.runner.visualstudio 2.8.2 -> 3.1.5 (major; 3.x runs v1/v2/v3 assemblies, so it remains compatible with xunit 2.9.3) - coverlet.collector 6.0.2 -> 10.0.1 (major) TestUI (Dax.Template.TestUI): - Microsoft.Extensions.Configuration.CommandLine 8.0.0 -> 10.0.9 Note: three coupled major test-tooling bumps land together here (Test.Sdk 18, runner.visualstudio 3, coverlet 10) -- natural bisection point if a CI test- runner regression ever surfaces. Verified with SDK 10.0.301: clean restore, 0 build errors, offline suite 13/13 on net10.0 (golden snapshot unchanged). Shipped Dax.Template package unaffected (test/TestUI-only deps). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Add AGENTS.md + docs/design architecture docs Onboard the repo with agent-facing documentation: - AGENTS.md (root, tool-agnostic, <200 lines): what the project is, build/test/single-test commands, local run, architecture mental model, layout & dependency direction, repo-specific conventions (additive JSON templates, offline golden-file harness, SQLBI_Template idempotency, internal Tabular* back-references), a Documentation map, and a documentation-maintenance rule. - docs/design/: index + focused design docs (overview, apply-templates lifecycle with a Mermaid dispatch flow, table generation, measures, domain model & conventions, testing). - CLAUDE.md: add @AGENTS.md import alongside the existing experiment-team delegation policy (policy retained verbatim). - Dax.Template.sln: add a Solution Items folder referencing the new docs (verified: dotnet sln list still lists all three projects). Content verified against source with Serena; reviewer-approved. Root README.md intentionally left untouched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Allowlist read-only git --no-pager forms Add Bash(git --no-pager log|diff|status *) to project permissions.allow to reduce prompts for read-only inspection commands. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Plan Phase M (modernization/refactor) in session handoff Record the Phase M initiative that precedes Phase 1 (Calendars): modernize to C# 14 / .NET 10 idioms, uniform style, readability, and safe behavior-preserving refactors. Captures Stages 0-4 (test-hardening safety net first, then style/ analyzer infra, mechanical modernization sweeps, deeper refactors, docs sync), the per-subsystem specialist cadence, and the locked decisions: warnings-as-errors (CI-only), file-scoped namespaces + primary constructors, required-member migration under a major version bump, public API open to improvement, and a coverage target (80% core floor / ~90% on refactor targets + Stryker.NET mutation testing) instead of a flat 100%. Planning only -- no code/tests/infra changed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Integrate dotnet-claude-kit with experiment-team delegation Adopt the dotnet-claude-kit plugin alongside experiment-team and codify how the two work together, without weakening existing conventions. - CLAUDE.md: add "Using dotnet-claude-kit" delegation subsection (lead-only cross-plugin composition, modern-C# injection into briefs, direct kit-agent delegation for .NET-idiom work, unchanged mandatory reviewer gate, scope discipline); rewrite navigation section to cover Serena + cwm-roslyn-navigator as complementary; document disabled post-edit-format hook and active pre-bash-guard / post-scaffold-restore hooks. - AGENTS.md: note Roslyn Navigator MCP availability and C# 14 / .NET 10 baseline, cross-referencing CLAUDE.md. - .claude/settings.json: enable dotnet-claude-kit plugin for this project. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Align Phase M and feature phases with dotnet-claude-kit tooling Add an additive tooling layer to the roadmap mapping work to kit skills, Roslyn Navigator tools, and kit agents. The five LOCKED Phase M decisions and all stage/phase scope, targets, and checklists are unchanged. - New "Phase M — dotnet-claude-kit alignment" subsection: Stage 0-4 -> skills (testing/tdd, ci-cd, modern-csharp, de-sloppify, error-handling, verify, security-scan), Roslyn tools (get_public_api, get_test_coverage_map, get_diagnostics, find_dead_code, detect_antipatterns, detect_circular_dependencies), and agents (build-error-resolver, refactor-cleaner, dotnet-architect, code-reviewer, security-auditor), with scope-out of web/EF/HTTP-oriented material and a de-sloppify dead-code safety note for the reflection-heavy paths. - New "Feature phases (1-3) — kit defaults" subsection extends the same baseline to Calendars/calc-groups/UDFs: modern-csharp + dotnet-architect for new code, Serena+Roslyn navigation to wire Engine dispatch, get_public_api API-baseline, tdd/testing (scoped, FakeTimeProvider) + verify self-check, reviewer supplemented by code-reviewer/security-auditor. - Generalize the alignment intro (kit is the repo-wide default per CLAUDE.md; Phase M is its heaviest usage) and relocate the annotated specialist cadence to close the alignment subsection so it no longer forward-references skills. - Hard gates kept authoritative (additive JSON, byte-identical golden BIM, opt-in live-server, mandatory experiment-team:reviewer); kit verify framed as a pre-reviewer self-check. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Flip Phase M to IN PROGRESS (Stage 0 active) in handoff Make SESSION_HANDOFF.md execution-ready for the next session: - Resume banner now points at Phase M Stage 0 (test hardening). - Phase M heading and locked-decisions preamble flipped from PLANNED/under review to IN EXECUTION with Stage 0 as the active work. - "Next session" steps updated: add-calendar is pushed (@6bbad5d); Stage 0 is the active work, using the kit tooling per the alignment subsection. No change to the five LOCKED decisions or any stage/phase scope. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Moved from experiment-team to dotnet-team * Revert pre stage 0 * test: add public-API baseline change-detector (Phase M Stage 0 P0) Reflection-based dump of Dax.Template's public+protected surface, snapshotted via the existing GoldenFile harness (PublicApi.txt). Deterministic (stable ordering, culture-invariant); regenerate with UPDATE_GOLDEN=1. Surfaces intended-vs-accidental public API changes for review per PR (change-detector, not a freeze gate). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test: add P1 characterization tests (Phase M Stage 0 P1) Pin current behavior as a pre-refactor safety net: Engine dispatch (Class->handler, unknown/invalid Class), idempotency (apply-twice + SQLBI_Template orphan cleanup), dependency ordering (TSort DAG + cycle detection), reflection paths (ReflectionHelper, GetModelChanges), and broadened golden coverage (measures-only, holidays-only configs). Additive configs/goldens only; existing snapshots untouched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test: add P2 characterization tests (Phase M Stage 0 P2) Pin StringExtensions helpers, Package load/invalid-config exception mapping, CustomTableTemplate.GetHierarchies non-date path, MeasuresTemplate time-intelligence wrapping, determinism, and cancellation honoring. Also pins two reviewer-surfaced quirks: AutoScan-omitted -> InvalidMacroReferenceException, and the Holidays handler's phantom-table- on-validation-throw. Additive configs only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test: raise Measures + Package coverage to clear 80% (Phase M Stage 0) Targeted top-up on the thin refactor-target subsystems: MeasuresTemplate 86%->100%, MeasureTemplateBase 51%->98%, Package 48%->100% (SaveTo round-trip + packaged-config load branches). Overall core coverage 75.2%->81.1%, clearing the locked 80% target. Meaningful assertions only, no coverage padding. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci: activate coverage baseline + 80% floor, add Stryker (Phase M Stage 0 P0) Restore the missing coverlet.runsettings (Dax.Template-only, Cobertura) that ci.yml already referenced (CI was broken), record the coverage baseline in docs/design/coverage.md, and set the CI gate to the locked 80% line-coverage floor (measured 81.1%, ~1.1pt headroom). Add stryker-config.json (Tables/Measures/dependency-sort) as a non-gating mutation-testing signal. Add three justified [ExcludeFromCodeCoverage] attributes on genuinely live-server-only / trivial members (ModelChanges.PopulatePreview, GetPreviewData; EntityBase.ToString). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: mark Phase M Stage 0 complete, record backlog, tee up Stage 1 Update SESSION_HANDOFF: Stage 0 (test hardening) COMPLETE — suite 13->129 passed + 1 skipped, coverage 81.1% / CI floor 80, public-API baseline + Stryker. Add the defect backlog surfaced by characterization tests (for Stage 2/3) and the deferred PublicApiSurface renderer nits. Repoint "next session" at Stage 1 (style/analyzer infrastructure). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * build: add Directory.Build.props + enable analyzers (Phase M Stage 1A) Centralize LangVersion 14 / Nullable / EnableNETAnalyzers / AnalysisLevel=latest-recommended / EnforceCodeStyleInBuild in src/Directory.Build.props (TargetFramework left per-project so TestUI stays net10.0-windows; no TreatWarningsAsErrors — WAE is CI-only, wired later). Remove the now-redundant LangVersion/Nullable from the three csprojs. Fix the pre-existing CS8602 in TestUI/ApplyDaxTemplate.cs:315 (SelectedItem?.ToString() — turns a latent NRE into the intended TemplateException). Analyzers now surface 200 warnings (WAE off; build green) to be triaged/cleaned in Stage 1B/1C and Stage 2. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * style: dotnet format whitespace/imports baseline — Dax.Template (Stage 1B) Pure mechanical formatting (whitespace, final newlines, using-directive ordering) from `dotnet format`. No file-scoped conversion, no using relocation, no analyzer/style auto-fixes, no logic/token changes. Golden BIM + PublicApi.txt byte-identical; suite 129 passed + 1 skipped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * style: dotnet format whitespace/imports baseline — Tests (Stage 1B) Pure mechanical formatting (whitespace, final newlines, EOL normalization, using ordering) from `dotnet format`. No behavior/token changes; goldens + PublicApi.txt byte-identical; suite 129 passed + 1 skipped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * style: dotnet format whitespace/imports baseline — TestUI (Stage 1B) Pure mechanical formatting from `dotnet format`. No behavior changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci: wire format-verify gate + CI-only warnings-as-errors (Phase M Stage 1C) Add a `dotnet format --verify-no-changes` gate and CI-only TreatWarningsAsErrors to both CI pipelines (local dev stays lenient). A WarningsNotAsErrors allowlist in Directory.Build.props exempts the 17 currently-present analyzer codes as Stage-2-deferred debt (shrinks as Stage 2 fixes each); compiler (CSxxxx) and any new analyzer code still break CI. Disable CA1707 for the test project only (idiomatic xUnit names); set file-scoped namespaces as house style (suggestion; conversion deferred to Stage 2). Document the policy in AGENTS.md. Config/docs only — no production code changes; suite 129 passed + 1 skipped, goldens + API baseline byte-identical. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: mark Phase M Stage 1 complete, tee up Stage 2 Update SESSION_HANDOFF: Stage 1 (style/analyzer infrastructure) COMPLETE — Directory.Build.props + analyzers, dotnet format baseline (72 files), CI format-verify gate + CI-only warnings-as-errors with a shrinking WarningsNotAsErrors allowlist. Add the Stage 2 analyzer-debt ratchet (code->work mapping) and repoint "next session" at Stage 2 (mechanical modernization sweeps, leaf->core). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor: modernize leaf subsystems — Constants/Enums/Exceptions (Stage 2.1) File-scoped namespaces across all 11 leaf files; primary constructors on the 4 single-ctor exceptions (byte-identical messages/base-calls). Multi- overload exceptions (TemplateException, InvalidConfiguration/MacroReference) left explicit — a primary ctor would corrupt their distinct base-call messages. No public API change (PublicApi.txt byte-identical), no const/ enum value change, goldens byte-identical, suite 129 passed + 1 skipped, CI warnings-as-errors simulation clean. UTF-8 BOM preserved. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor: modernize Extensions + tighten WAE allowlist (Stage 2.2) File-scoped namespaces across the 6 Extensions files; CA1510 (ThrowIfNull ×2 in ReflectionHelper), CA1860 + CA1868 (TSort), 4 dead usings removed, expression-bodied StringExtensions helpers. TSort algorithm/recursion/ cycle-detection untouched (pinned by characterization tests). Remove CA1510 and CA1868 from the Directory.Build.props WarningsNotAsErrors allowlist — both now fully eliminated repo-wide (verified: WAE build green with them enforced). No public API change (PublicApi.txt byte-identical), goldens byte-identical, suite 129 passed + 1 skipped, BOM preserved. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor: modernize Model + tighten WAE allowlist (Stage 2.3) File-scoped namespaces across the 7 Model files; CA1805 ×6 (Column/ Hierarchy/Measure redundant init), CA1860 ×4 (ModelChanges), collection expressions, expression-bodied members. ModelChanges.cs treated conservatively — only file-scoped + the 4 CA1860 rewrites (each verified logically identical on the reflection/diff path the offline suite can't exercise); reflection logic, [ExcludeFromCodeCoverage] attrs, and the `= default!` members (EntityBase.Name, Level.Column — Stage 2.9 required targets) left untouched. Remove CA1860 from the WarningsNotAsErrors allowlist (fully eliminated repo-wide; WAE build green with it enforced). No public API change, goldens byte-identical, suite 129 passed + 1 skipped, BOM preserved. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor: modernize Syntax subsystem (Stage 2.4) File-scoped namespaces across the 11 Syntax files; CA1805 ×4 (redundant = false init in DaxElement/Var/VarGlobal), CS8019 ×2 unused usings, expression-bodied members (DaxStep/Var/DaxElement debug/ToString, Var- Global/VarRow ctors). Dependency-ordering logic (Dependencies/Expression/ IDependencies) untouched — DependencySortCharacterizationTests unchanged. Meaningful = true initializers left intact. No public API change (PublicApi.txt byte-identical), goldens byte-identical, suite 129 passed + 1 skipped, BOM preserved. CA1805 stays allowlisted (still present in Tables/CustomTemplateDefinition). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor: modernize Measures subsystem (Stage 2.5) File-scoped namespaces; CA1805 ×2 (MeasuresTemplate); collection expressions ([] for Array.Empty/new List/new); expression-bodied members; removed a dead unused local (Union result never read) and a redundant identity LINQ projection in FindTable. Emitted DAX byte-identical (Config-01/02/03 goldens unchanged); raw-string + [GeneratedRegex] conversions deliberately skipped to protect the golden gate. No public API change (PublicApi.txt byte-identical), suite 129 passed + 1 skipped, BOM preserved. CA1805 stays allowlisted (still in Tables/CustomTemplate- Definition). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor: modernize Tables non-date base classes (Stage 2.6a) File-scoped namespaces across the 5 non-date Tables files; CA1805 ×3 (TableTemplateBase), CA1874 (CalculatedTableTemplateBase Regex.IsMatch), CS8019 ×1, collection expressions, is null/is not null (audited: no type in the codebase overloads ==, so all conversions are reference-equality- identical), expression-bodied members. Calculated-table DAX byte-identical (goldens unchanged); AddHierarchies Description defect deliberately left untouched (pinned by characterization test). Kept columnDependencies as an explicit DaxStep[] to preserve exact runtime array type. Deferred CA1051/ CA1707/CA1716 untouched. No public API change, suite 129 passed + 1 skipped, BOM preserved. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor: modernize Tables date branch + tighten WAE allowlist (Stage 2.6b) File-scoped namespaces across the 6 Tables/Dates files; CA1874 ×5 (BaseDateTemplate Regex.IsMatch), CA1805 ×8 (HolidaysDefinitionTable), collection expressions, expression-bodied members, inlined out-vars (TryGetValue always-assigns). Emitted date/holidays DAX byte-identical (every @"..."/$@"..." literal untouched; goldens unchanged). Collection expressions applied only where the declared type was already identical (no covariance drift). CA1305/CA1309 (culture formatting in date code) deliberately left untouched — deferred correctness decision. Remove CA1874 from the WarningsNotAsErrors allowlist (fully eliminated repo-wide; WAE build green with it enforced). No public API change, suite 129 passed + 1 skipped, BOM preserved. Completes the Tables subsystem. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor: modernize Interfaces subsystem (Stage 2.7a) File-scoped namespaces across the 7 interface files; CA1805 ×1 (ITemplates.TemplateEntry.IsHidden redundant = false). Public interface + TemplateEntry POCO shapes unchanged (JSON-deserialization back-compatible). No public API change (PublicApi.txt byte-identical), goldens byte-identical, suite 129 passed + 1 skipped, BOM preserved. CA1805 stays allowlisted (5 sites remain in CustomTemplateDefinition/Translations — next sweep). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor: modernize Engine/Package/root + tighten WAE allowlist (Stage 2.7b) File-scoped namespaces (Engine/Package/CustomTemplateDefinition/Translations); CA1805 ×5 (CustomTemplateDefinition ×4, Translations ×1), collection expressions, foreach over the Templates array (dropped a redundant ToList() allocation — Templates is a fixed array, no mutation during iteration), dropped unused locals/tuple element, expression-bodied FindTemplateFiles, removed dead usings. Engine dispatch throw-semantics, GetModelChanges reflection, and Package LoadFromFile/SaveTo/ReadDefinition exception types untouched (pinned characterization tests green). Remove CA1805 from the WarningsNotAsErrors allowlist — fully eliminated repo-wide (WAE build green with it enforced). No public API change, goldens byte-identical, suite 129 passed + 1 skipped, BOM preserved. Completes the Dax.Template library modernization. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor: modernize TestUI + tighten WAE allowlist (Stage 2.8) File-scoped namespaces across the 3 hand-written TestUI files; CA2263 ×5 (generic JsonSerializer.Deserialize<T>), CA1869 ×3 (hoisted two static readonly JsonSerializerOptions, settings identical + correctly wired, mutation-safe). Conservative sweep — TestUI has no automated coverage, so mechanical/file-scoped only, no logic/UI change; Designer.cs untouched; CS8602 fix intact; per-file BOM state preserved. Remove CA2263 from the WarningsNotAsErrors allowlist (fully eliminated repo-wide; WAE build green with it enforced). Goldens + PublicApi.txt byte-identical, suite 129 passed + 1 skipped. Completes Stage 2 non-breaking modernization. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor: clear bucket-C analyzer debt + tighten WAE allowlist (Stage 2) Non-breaking fixes for the last 4 mechanical allowlist codes: CA1852 ×2 (seal internal test helpers), CA1816 (GC.SuppressFinalize in PackageSaveToCharacterizationTests.Dispose), CA1859 (ModelChanges. GetPreviewData private return object?->List<object>?, exact widening, caller unaffected), CA1869 (Package.SaveTo JsonSerializerOptions hoisted to static readonly, settings identical -> SaveTo JSON byte-identical). Remove CA1816/CA1852/CA1859/CA1869 from the WarningsNotAsErrors allowlist — all fully eliminated repo-wide (WAE build green with them enforced). No public API change (PublicApi.txt byte-identical), goldens byte-identical, suite 129 passed + 1 skipped, BOM preserved. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat!: required members migration + major version bump to 2.0.0 (Stage 2.10a) BREAKING: convert 4 `= default!` members to `required` — EntityBase.Name (and subclasses Column/DateColumn/Hierarchy/Level/Measure), Level.Column, Var.Name (and VarGlobal/VarRow), DaxStep.Name. Source-breaking for consumers constructing these via object initializers; behavior-preserving at runtime; JSON template config unaffected (none of these types are JSON-deserialized — verified). All existing construction sites already set the members (0 CS9035). Bump Dax.Template to 2.0.0 (Assembly/File/Version- Prefix). Enhance PublicApiSurface to detect RequiredMemberAttribute and regenerate the PublicApi.txt baseline (the 4 members now render `required`). CHANGELOG updated. Golden BIM byte-identical, suite 129 passed + 1 skipped, WAE build green. NOTE: maintainer must bump the Azure DevOps `AppVersionMajor` pipeline variable to 2 (ADO variables can't be changed from the repo). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor!: public API naming renames + empty the rename allowlist (Stage 2.10b) BREAKING (2.0.0): rename public identifiers flagged by CA1707/CA1711/ CA1716/CA1725 — de-underscore 18 public constants (values unchanged, e.g. SQLBI_TEMPLATE_ATTRIBUTE -> SqlbiTemplate = "SQLBI_Template"); drop the Enum suffix on 5 enum types (AutoScanEnum->AutoScan, AutoNamingEnum-> AutoNaming, SubstituteEnum->Substitute, TestUI WeeklyType/QuarterWeekType, DayOfWeekEnum->WeekDay to avoid System.DayOfWeek); rename reserved-keyword identifiers (param template->templateDefinition, nested type Step-> TemplateStep); align BaseDateTemplate.ApplyTemplate param dateTable-> tabularTable with the base. Enum source files renamed to match. All identifier-only: constant values, enum member values, and JSON property names unchanged -> emitted BIM byte-identical, existing template JSON still loads. PublicApi.txt regenerated (renames only); CHANGELOG updated. Remove CA1707/CA1711/CA1716/CA1725 from the WarningsNotAsErrors allowlist — all fully eliminated repo-wide (WAE build green with them enforced). Allowlist now holds only CA1051 (deferred) + CA1305/CA1309 (Stage 3). Suite 129 passed + 1 skipped, BOM preserved. Completes the option-2 breaking pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: sync design docs + test comments to 2.10b renames Update stale identifier references in docs/design/domain-model-and- conventions.md and measures.md (AutoScanEnum->AutoScan, AutoNamingEnum-> AutoNaming, SQLBI_TEMPLATE_ATTRIBUTE->SqlbiTemplate, CONFLICT_RENAME_PREFIX ->ConflictRenamePrefix) and comments in MeasureTemplateBaseCharacterization Tests (ENTITY_*->Entity*). Identifier references only — string values ("SQLBI_Template", "_old") unchanged; CHANGELOG's rename mapping left as historical record. Build + suite unaffected (129 passed + 1 skipped). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: mark Phase M Stage 2 complete, tee up Stage 3 Record Stage 2 outcomes: 10 modernization sweeps + bucket-C, WAE allowlist ratcheted 17->3 (CA1051/CA1305/CA1309 remaining), and the required/2.0.0 API-breaking pass (2.10a/2.10b). Note the deferred CA1051 field->property follow-up and the ADO AppVersionMajor bump. Repoint "next session" at Stage 3 (deeper refactors + Stage 0 defect-backlog fixes). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor!: CA1051 protected fields -> properties + shrink allowlist (Stage 2 tail) BREAKING (2.0.0): convert the 4 remaining visible protected instance fields to properties — MeasureTemplateBase.Template (get-only, ctor-assigned, readonly semantics preserved), TableTemplateBase.FixRelationshipsTo/From (get/set), Translations.LanguageDefinitions (get/set, ctor-assigned). Source/binary-breaking for subclasses; no runtime/DAX-BIM/JSON impact (these hold template-build state, none are JSON-deserialized; verified no ref/out usage). PublicApi.txt regenerated (only the 4 field->property changes); CHANGELOG updated. Remove CA1051 from the WarningsNotAsErrors allowlist — fully eliminated repo-wide (WAE build green with it enforced). Allowlist now holds only CA1305/CA1309 (Stage 3 culture decision). Golden BIM byte-identical, suite 129 passed + 1 skipped, BOM preserved. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: mark CA1051 follow-up done in handoff (Stage 2 fully closed) Record that the CA1051 field->property follow-up completed (9ead9ae); the 2.0.0 public-API cleanup is complete and the WAE allowlist now holds only CA1305/CA1309 (Stage 3 culture decision). Stage 3 remains the active, opt-in-per-item stage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor: dedup annotation upsert into shared extension (Stage 3 item 1) Extract the duplicated annotation-upsert loop (closing the long-standing TODO in TableTemplateBase) into a new internal extension AnnotationCollectionExtensions.UpsertAnnotations, generic over TOM's NamedMetadataObjectCollection<Annotation, TOwner> base. TableTemplateBase .AddAnnotations keeps its protected virtual seam and delegates (retaining its upfront cancellation throw); MeasureTemplateBase's local ApplyAnnotations is removed in favor of the shared helper. Behavior-preserving: build green, offline suite 129 passed + 1 skipped, golden BIM and PublicApi.txt byte-identical. Reviewed (code-reviewer: GO). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor!: exceptions naming consistency (Stage 3 item 4) Fix the 'Circulare'->'Circular' typo in CircularDependencyException and rename misleading public ctor params: daxExpressionmessage->daxExpression (CircularDependency, InvalidVariableReference, InvalidMacroReference x3) and entitymessage->entityName (InvalidAttributeException). These name a DAX expression / entity, not a 'message'. Public ctor param renames -> PublicApi.txt regenerated deliberately (diff limited to the 6 renamed lines); ships under the in-progress 2.0.0. All in-repo throw sites use positional args, so no internal breakage. Golden BIM byte-identical; offline suite 129 passed + 1 skipped. Reviewed (code-reviewer: GO). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: XML summaries for undocumented Syntax types (Stage 3 item 3) Add /// summaries to the 9 previously-undocumented public/internal Syntax types (DaxBase, Var, VarGlobal, VarRow, VarScope, IDependencies<T>, IDaxName, IDaxComment, IGlobalScope), matching the existing DaxElement/DaxStep tone. The subsystem is already modern (Stage 2.4) and holds only POCOs/interfaces, so documentation was the genuine readability win rather than a refactor. Doc-comment-only: no code/signature changes, build green (no CS1570), offline suite 129 passed + 1 skipped, golden BIM and PublicApi.txt byte-identical. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor!: encapsulate TOM reflection + document fragility (Stage 3 item 2) Make ReflectionHelper and its GetPropertyValue/SetPropertyValue extensions internal (were public; tests reach them via InternalsVisibleTo) -> PublicApi.txt loses those 3 lines; ships under the in-progress 2.0.0. SetPropertyValue kept (no prod caller yet; reserved for Phase 1 Calendar internal-member writes). Centralize the 6 reflected TOM-internal member names (TxManager, CurrentSavepoint, AllBodies, Owner, LastParent, Parent) as documented consts in a private nested TomInternalMembers class, extract the TxManager->AllBodies traversal into GetChangedBodies (exact null-propagation + hard (IEnumerable) cast preserved), modernize string.Format->interpolation, and document the TOM-version fragility plus the offline-apply-returns-empty behavior on GetModelChanges. Behavior-preserving: whole-solution build green, offline suite 129 passed + 1 skipped, golden BIM byte-identical. Reviewed (code-reviewer: GO). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: harden two template error paths (Stage 3 Group B: B1+B2) B1 - Engine.ApplyHolidaysDefinitionTable validated an empty Template only AFTER adding the table, leaving a phantom empty table in the model on throw. Hoist the validation above the create/add block so it fails before any model mutation (matching ApplyCustomDateTable/ApplyMeasuresTemplate). B2 - CustomTableTemplate.GetHierarchies used Columns.First(...) for a level's column, throwing a bare InvalidOperationException on an unknown column. Use FirstOrDefault + a TemplateException naming the hierarchy, level, and column. Both are error-path fixes: the two characterization tests that pinned the buggy behavior are converted to fix-tests. Golden BIM + PublicApi.txt byte-identical, offline suite 129 passed + 1 skipped. Reviewed (code-reviewer: GO). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: copy Hierarchy/Level Description into TOM objects (Stage 3 Group B: B3) AddHierarchies built TOM Hierarchy/Level objects without copying Description, silently dropping descriptions supplied in template JSON (the source Model.Hierarchy/Level.Description are populated by GetHierarchies). Add the Description assignment to both initializers, mirroring the existing AddColumns pattern. Converted the characterization test that pinned the drop into a fix-test. Latent-correctness fix: no shipped config carries hierarchy/level descriptions today, so golden BIM + PublicApi.txt are byte-identical; offline suite 129 passed + 1 skipped. Reviewed (code-reviewer: GO). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: detect multi-node dependency cycles promptly (Stage 3 Group B: B4) TSort.VisitDependencies only caught direct self-cycles immediately; a 2+ node mutual cycle (A->B->A) was caught only after the crude nestedCalls > 1000 backstop, throwing a generic {STACK OVERFLOW} message. Replace both the nestedCalls/MAX_NESTED_CALLS guard and the self-only Contains(item) check with proper DFS recursion-path tracking (HashSet path + try/finally backtrack), so self-, 2-node, and N-node cycles all throw CircularDependencyException promptly with the real offending node's expression. Remove the now-unused MAX_NESTED_CALLS. Valid acyclic graphs sort identically (backtrack preserves the level arithmetic and diamond re-joins), so golden BIM + PublicApi.txt stay byte-identical; offline suite 129 passed + 1 skipped. Converted the characterization test that pinned the nested-guard behavior into a prompt-detection fix-test. Reviewed (code-reviewer: GO-WITH-NITS; the tautological assertion nit was fixed). Note: removing the soft 1000-depth backstop means a valid but pathologically deep acyclic graph now overflows the CLR stack (uncatchable) instead of throwing; no current config approaches this. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: remove CustomDateTable output when disabled (Stage 3 Group B: B5) A disabled CustomDateTable entry returned early without removing a previously-created date table, asymmetric with the Holidays handlers that clean up on disable. Remove the date table (and its ReferenceTable, if set) in the disabled branch; orphan relationships are swept centrally by the existing post-pass, so this is safe and consistent with precedent. Converted the characterization test that pinned the no-cleanup behavior into a fix-test, added a Dispatch-08 fixture + test covering the ReferenceTable removal branch, and corrected two now-stale comments. Golden BIM + PublicApi.txt byte-identical; offline suite 130 passed + 1 skipped (+1 new test). Reviewed (code-reviewer: GO-WITH-NITS; the coverage + comment nits are addressed here). Completes Phase M Stage 3 Group B (all five Stage-0 defect-backlog items fixed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: invariant-culture DAX formatting; empty WAE allowlist (Stage 3 culture) Make all year-int -> DAX formatting culture-invariant so generated DAX is locale-independent: int.ToString() -> ToString(CultureInfo.InvariantCulture) and interpolated int holes wrapped in FormattableString.Invariant(...) across GenerateMinYearExpression / GenerateMaxYearExpression / GenerateCalendarExpression (CALENDARAUTO fallback). DAX text (incl. trailing spaces) byte-preserved. Make MeasuresTemplate's measure-name match explicitly StringComparison.Ordinal (was already ordinal via the one-arg overload). Removes CA1305 and CA1309 -- the last two codes -- from the CI warnings-as-errors allowlist in Directory.Build.props, which is now empty: the Stage-2 analyzer debt is fully cleared and any new analyzer/compiler warning fails CI. Behavior-preserving on Latin-digit cultures: golden BIM + PublicApi.txt byte-identical, offline suite 130 passed + 1 skipped, solution build green with -p:TreatWarningsAsErrors=true (0 warnings repo-wide). Reviewed (code-reviewer: GO-WITH-NITS; the CALENDARAUTO-fallback consistency nit is addressed here). Completes Phase M Stage 3 (deeper refactors). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: sync design docs, CHANGELOG, AGENTS for Stage 3 (Phase M Stage 4 closeout) Document all Phase M Stage 3 changes and close out Phase M: - CHANGELOG [Unreleased]/2.0.0: exception ctor param renames (daxExpression/ entityName) + Circular typo; ReflectionHelper -> internal; the five Group B behavior fixes (Holidays phantom table, GetHierarchies TemplateException, Hierarchy/Level Description copy, prompt multi-node cycle detection, CustomDateTable disable cleanup); invariant-culture DAX year formatting. - AGENTS.md: WarningsNotAsErrors allowlist now empty; CA1707 production-clean. - .editorconfig: correct the stale CA1707 'deferred to Stage 2' comment. - docs/design: TSort DFS cycle detection, dispatch disable/validation behavior, GetModelChanges offline note, Description copy-through, invariant year formatting, Syntax XML docs; coverage.md dated Stage-3 suite-count note. Reviewed (code-reviewer: GO-WITH-NITS; all claims verified against source, the coverage.md wording nit fixed). Completes Phase M (Stages 0-4). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(handoff): resolve Calendar column-binding open question (public TOM API) Investigation (reflection over TOM 19.114.0 + cross-check against Tabular Editor 2) shows the Calendar binding has a fully PUBLIC typed API in our pinned 19.114.0 via the concrete subclasses TimeUnitColumnAssociation / TimeRelatedColumnGroup (public ctors, public TimeUnit/PrimaryColumn/ AssociatedColumns/Columns). The earlier RISK looked at the abstract base CalendarColumnGroup and its internal low-level members and missed the subclasses. Phase 1 therefore needs NO reflection and NO TMSL/JSON injection. Remaining constraints recorded: min compatibility level 1701, and Calendar has no Annotations (idempotency must key off the parent table's SQLBI_Template annotation). Updates the RISK section, Open Question #1, and the TOM object-model note in SESSION_HANDOFF.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(handoff): point resume instructions at Phase 1 (Calendars) Phase M (Stages 0-4) is complete; update the top-of-file resume line from the stale 'start Phase M Stage 3' to 'start Phase 1 (Calendars)' with the resolved public-API binding note, and bump Last updated to 2026-07-04. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(calendar): add CalendarTemplate for native TOM Calendar attachment (Phase 1) Add a new template Class "CalendarTemplate" that attaches a native TOM Calendar (with column groups) to an existing table, using the public typed API (TimeUnitColumnAssociation / TimeRelatedColumnGroup) — no reflection, no TMSL. JSON config is purely additive (reuses Class/Table/Template/ IsEnabled; no TemplateEntry change). Backend: - CalendarTemplateDefinition POCO + nested CalendarColumnGroupDefinition (Tables/Calendars/). - CalendarTemplate.ApplyTemplate: find-or-create the Calendar keyed by Calendar.Name (Calendar has no Annotations, so SQLBI_Template can't live on it); re-apply clears+rebuilds column groups; IsEnabled=false removes the named calendar. - Engine dispatch wired via nameof(CalendarTemplate). Disabled entry with a missing target table is a safe no-op, matching sibling handlers; enabled entry with a missing table throws TemplateException. - Compatibility guard: TOM enforces level >= 1701 at Table.Calendars.Add(); the handler pre-checks and throws InvalidConfigurationException (also guards Model?/Database null on the public method). Tests: - Separate compat-1701 CalendarOfflineModelFixture (shared 1600 fixture untouched, existing goldens byte-identical). - CalendarGoldenTests: shape, snapshot (Config-02 - Calendar.bim), idempotency, disabled-removal, disabled-with-missing-table regression, and opt-in live-server. New template fixtures added. - PublicApi.txt regenerated (diff limited to the 3 new Calendar types). - Suite: 135 passed + 2 skipped; build green under warnings-as-errors; dotnet format clean. Known limitation (documented, deferred): deleting/renaming a CalendarTemplate entry orphans its calendar — matches the existing CustomDateTable rename-TODO and MeasuresTemplate entry-deletion precedent. Docs: CHANGELOG, AGENTS, apply-templates-lifecycle, table-generation, SESSION_HANDOFF updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(calcgroup): add CalculationGroupTemplate for generic calc-group generation (Phase 2) Add a new template Class "CalculationGroupTemplate" that generates a native TOM calculation group (a Table whose Table.CalculationGroup holds CalculationItems). Generic generator — the JSON authors any calc group; it has NO dependency on the Measures/Syntax/time-intelligence machinery (calc groups are being deprecated for time intelligence). JSON config is purely additive (reuses Class/Table/Template/IsHidden/IsEnabled; no TemplateEntry change). Backend: - CalculationGroupTemplateDefinition POCO + nested CalculationItemDefinition (Tables/CalculationGroups/); CalculationGroupExpression selection expressions (MultipleOrEmptySelection / NoSelection, each with an optional format-string expression) supported from day 1. - CalculationGroupTemplate.ApplyTemplate validates the whole definition before mutating (non-empty items; per-item name + expression; non-empty ColumnName; ordinal uniqueness), then stamps the table ownership annotation, builds/reuses the CalculationGroup (Precedence/Description + selection expressions), ensures one String backing column + a CalculationGroupSource partition, and reconciles calc items full-replace-by-name (orphans removed). - Engine dispatch wired via nameof(CalculationGroupTemplate). Disabled entry removes the table (missing table = safe no-op). Foreign-table guard: a pre-existing table lacking the SQLBI_Template="CalculationGroup" annotation throws TemplateException rather than being overwritten. Validate-before- mutate + build-then-add => no phantom table on invalid input. Backing column receives a LineageTag at compat >= 1540. No RequestTableRefresh. - New const Attributes.SqlbiTemplateTableCalculationGroup = "CalculationGroup". Compatibility (enforced by TOM at assignment): calc group >= 1470, calc items >= 1500, selection expressions >= 1605. Idempotency: keyed on the calc-group table's SQLBI_Template annotation (the table has Annotations, unlike a Calendar). Re-apply reconciles items and clears omitted selection expressions. Known limitation (documented, deferred): renaming ColumnName orphans the old backing column — matches the Calendar / CustomDateTable rename precedent. Tests: - Dedicated compat-1605 CalcGroupOfflineModelFixture (shared 1600 + Calendar 1701 fixtures untouched, existing goldens byte-identical). - CalculationGroupGoldenTests: shape, snapshot (Config-03), idempotency, orphan-item removal, selection-expression clear-on-empty, disabled-removal, disabled-with-missing-table, foreign-table collision, ordinal uniqueness, and opt-in live-server. - PublicApi.txt regenerated (diff limited to the new types + the new const). - Suite: 144 passed + 3 skipped; build green under warnings-as-errors; dotnet format clean. Docs: CHANGELOG, AGENTS, apply-templates-lifecycle, table-generation, README, SESSION_HANDOFF updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(functions): add FunctionLibraryTemplate for DAX user-defined functions (Phase 3) Add a new template Class "FunctionLibraryTemplate" that generates DAX user-defined functions (UDFs) into Model.Functions (model-level; one sub-template file declares a library of one-or-more functions). Adopts the current DAX UDF standard (GA Sept 2025 + March 2026 reference types), including the full parameter type system and optional parameters. JSON config is purely additive (reuses Class/Template/IsHidden/IsEnabled; no TemplateEntry change). Schema (hybrid): parameters are modeled structurally and the engine assembles the "( params ) => body" signature per the grammar, with a RawExpression escape hatch used verbatim. - Types: ANYVAL (default), SCALAR + subtype, TABLE, and the REF family (ANYREF/MEASUREREF/COLUMNREF/TABLEREF/CALENDARREF, which force EXPR and never carry an explicit passing mode); scalar-subtype shorthands (INT64/DECIMAL/DOUBLE/STRING/BOOLEAN/DATETIME/VARIANT/NUMERIC). - Passing modes VAL/EXPR; a trailing "= <DefaultExpression>" makes a parameter optional. Body via Body or MultiLineBody. Backend: - FunctionLibraryTemplateDefinition + FunctionDefinition (+ GetBody()) + ParameterDefinition POCOs in src/Dax.Template/Functions/. - FunctionLibraryTemplate.ApplyTemplate validates the entire definition before mutating, then find-or-creates each function by name, sets Expression/Description/IsHidden (+ LineageTag on new functions), stamps the ownership annotation, and reconciles by name with orphan cleanup. IsEnabled=false removes all functions carrying this template's annotation. - Validation (all throw InvalidConfigurationException): RawExpression XOR Body/MultiLineBody; SCALAR requires a Subtype; PassingMode rejected on REF types; PassingMode requires an explicit Type; optional params must trail mandatory ones; no duplicate function names in a library; non-blank function/parameter names. - Engine dispatch wired via nameof(FunctionLibraryTemplate); model-level, no table, no RequestTableRefresh. - New const Attributes.SqlbiTemplateFunctions = "Functions". Compatibility: UDFs require level >= 1702 (TOM enforces at Model.Functions.Add); the handler pre-checks and throws a friendly InvalidConfigurationException below that. TOM performs no grammar validation of Function.Expression, so the engine's structured validation is the only offline safety net; the generated signatures are only compiled by the opt-in live-server test. Idempotency: annotation-keyed (SQLBI_Template="Functions"), like Measures and calculation groups. Re-apply reconciles by name and sweeps orphans; a renamed function's old object is correctly removed (no rename limitation, unlike Calendars). Known limitation (documented, deferred): cross-file name collisions across separate library entries are not detected (last-applied-wins) — matches the MeasuresTemplate precedent. Tests: - Dedicated compat-1702 FunctionOfflineModelFixture (the 1600/1605/1701 fixtures untouched, existing goldens byte-identical). - FunctionLibraryGoldenTests: shape (exact rendered Expression strings), snapshot (Config-04), idempotency, orphan cleanup, enable/disable, the compat-1702 guard, RawExpression pass-through, and every validation rule (incl. PassingMode-without-Type, blank names, and the neither-branch). - PublicApi.txt regenerated (diff limited to the 4 new types + the new const). - Suite: 161 passed + 4 skipped; build green under warnings-as-errors; dotnet format clean. Docs: new docs/design/functions.md (indexed in AGENTS.md + README.md), CHANGELOG, AGENTS, apply-templates-lifecycle updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(handoff): mark Phases 1-3 complete; point resume at live-server tests in a new session Offline roadmap (Calendars, Calc groups, UDFs) is done, reviewed, and pushed. Update the top-of-file resume instructions to start a NEW session (this one is ending for maintenance) and run the opt-in [LiveServerFact] tests for all three phases against a compat >= 1702 database — the only place the generated DAX is actually compiled. Record the Phase 3 completion detail and the 161+4 suite count. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Added PBIX for live tests * docs(handoff): record live-server validation PASS for Phases 1-3; reconcile committed/pushed status All three [LiveServerFact] tests plus the Phase-0 baseline passed against the published EmptyTestTemplates.pbix (compat >= 1702): 4 passed / 0 failed / 0 skipped. Non-destructive (ApplyTemplates + GetModelChanges, never SaveChanges). The Phase-3 UDF pass confirms the generated ( params ) => body signatures compile server-side. Also reconciled the stale 'NOT yet committed' per-phase notes with HEAD d70fb32. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Apply suggestion from @albertospelta Co-authored-by: Alberto Spelta <albertospelta@users.noreply.github.com> * Apply suggestion from @albertospelta Co-authored-by: Alberto Spelta <albertospelta@users.noreply.github.com> * Apply suggestion from @albertospelta Co-authored-by: Alberto Spelta <albertospelta@users.noreply.github.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Alberto Spelta <albertospelta@users.noreply.github.com>
1 parent 32b86b0 commit 7466db2

164 files changed

Lines changed: 19381 additions & 4167 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.azure/pipelines/ci.yml

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,6 @@ steps:
4444
inputs:
4545
packageType: sdk
4646
useGlobalJson: true
47-
- task: UseDotNet@2
48-
displayName: 'Install .NET 6.0 runtime'
49-
inputs:
50-
packageType: runtime
51-
version: '6.0.x'
5247
- task: DotNetCoreCLI@2
5348
displayName: '.NET restore'
5449
inputs:
@@ -57,11 +52,21 @@ steps:
5752
feedsToUse: 'select'
5853
verbosityRestore: '${{ parameters.verbosity }}'
5954
- task: DotNetCoreCLI@2
55+
# Phase M / Stage 1 step 1C: CI-only warnings-as-errors, mirroring .github/workflows/ci.yml.
56+
# The matching WarningsNotAsErrors allowlist (Stage-2 deferred debt) lives in
57+
# src/Directory.Build.props so both pipelines share one source of truth.
6058
displayName: '.NET build'
6159
inputs:
6260
command: 'build'
6361
projects: 'src/**/*.csproj'
64-
arguments: '--configuration "$(configuration)" --no-restore --verbosity ${{ parameters.verbosity }} /p:AssemblyVersion="$(assemblyVersion)" /p:FileVersion="$(semanticVersion)" /p:VersionPrefix="$(semanticVersion)" /p:VersionSuffix="$(versionSuffix)" /p:ContinuousIntegrationBuild="true" /p:AdditionalConstants="SIGNED" /p:SignAssembly="true" /p:AssemblyOriginatorKeyFile="$(signKey.secureFilePath)" /m'
62+
arguments: '--configuration "$(configuration)" --no-restore --verbosity ${{ parameters.verbosity }} /p:AssemblyVersion="$(assemblyVersion)" /p:FileVersion="$(semanticVersion)" /p:VersionPrefix="$(semanticVersion)" /p:VersionSuffix="$(versionSuffix)" /p:ContinuousIntegrationBuild="true" /p:AdditionalConstants="SIGNED" /p:SignAssembly="true" /p:AssemblyOriginatorKeyFile="$(signKey.secureFilePath)" /p:TreatWarningsAsErrors=true /m'
63+
- task: DotNetCoreCLI@2
64+
# Verifies the dotnet-format baseline (clean as of Stage 1B) doesn't drift.
65+
displayName: '.NET format check'
66+
inputs:
67+
command: 'custom'
68+
custom: 'format'
69+
arguments: 'src/Dax.Template.sln --verify-no-changes --no-restore'
6570
- task: DotNetCoreCLI@2
6671
displayName: '.NET test'
6772
inputs:

.claude/SESSION_HANDOFF.md

Lines changed: 854 additions & 0 deletions
Large diffs are not rendered by default.

.claude/settings.json

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"mcp__serena",
5+
"mcp__cwm-roslyn-navigator",
6+
"Bash(claude mcp *)",
7+
"Bash(git -C \"C:/Users/MarcoRusso/source/repos/sql-bi/DaxTemplate\" log --oneline -10)",
8+
"Bash(dotnet test *)",
9+
"Bash(dotnet restore *)",
10+
"Bash(dotnet pack *)",
11+
"Bash(dotnet list *)",
12+
"Bash(python *)",
13+
"Bash(dir *)",
14+
"Bash(Get-ChildItem *)",
15+
"Bash(curl *)",
16+
"Bash(git *)"
17+
],
18+
"defaultMode": "bypassPermissions"
19+
},
20+
"enabledPlugins": {
21+
"brainstorm@brainstorm-plan": true,
22+
"andrej-karpathy-skills@karpathy-skills": true,
23+
"experiment-team@my-claude-teams": false,
24+
"dotnet-team@my-claude-teams": true,
25+
"dotnet-claude-kit@dotnet-claude-kit": true
26+
},
27+
"extraKnownMarketplaces": {
28+
"brainstorm-plan": {
29+
"source": {
30+
"source": "github",
31+
"repo": "thepushkarp/brainstorm-plan"
32+
}
33+
},
34+
"karpathy-skills": {
35+
"source": {
36+
"source": "github",
37+
"repo": "forrestchang/andrej-karpathy-skills"
38+
}
39+
},
40+
"my-claude-teams": {
41+
"source": {
42+
"source": "github",
43+
"repo": "marcosqlbi/my-claude-teams"
44+
},
45+
"autoUpdate": true
46+
},
47+
"dotnet-claude-kit": {
48+
"source": {
49+
"source": "github",
50+
"repo": "codewithmukesh/dotnet-claude-kit"
51+
}
52+
}
53+
}
54+
}

.config/dotnet-tools.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"version": 1,
3+
"isRoot": true,
4+
"tools": {
5+
"dotnet-reportgenerator-globaltool": {
6+
"version": "5.5.10",
7+
"commands": [
8+
"reportgenerator"
9+
],
10+
"rollForward": false
11+
},
12+
"dotnet-stryker": {
13+
"version": "4.15.0",
14+
"commands": [
15+
"dotnet-stryker"
16+
],
17+
"rollForward": false
18+
}
19+
}
20+
}

.editorconfig

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,11 @@ csharp_preferred_modifier_order = public,private,protected,internal,static,exter
107107
# Code-block preferences
108108
csharp_prefer_braces = true:silent
109109
csharp_prefer_simple_using_statement = true:suggestion
110-
csharp_style_namespace_declarations = block_scoped:silent
110+
# House style: file-scoped namespaces are preferred going forward. Kept at :suggestion (not
111+
# :warning) because the existing codebase is block-scoped throughout; the conversion is a
112+
# deliberate Stage 2 mechanical sweep (Phase M), not this pass. Escalating to :warning now would
113+
# add ~61 new build warnings and break the CI warnings-as-errors gate.
114+
csharp_style_namespace_declarations = file_scoped:suggestion
111115

112116
# Expression-level preferences
113117
csharp_prefer_simple_default_expression = true:suggestion
@@ -264,3 +268,11 @@ dotnet_diagnostic.IDE0046.severity = none
264268

265269
# IDE0045: Convert to conditional expression
266270
dotnet_diagnostic.IDE0045.severity = none
271+
272+
# CA1707 ("identifiers should not contain underscores") is disabled for the test project only
273+
# (see the [src/Dax.Template.Tests/**.cs] section below). The 105 hits there are the idiomatic xUnit
274+
# `Method_Scenario_Expected` naming convention (intentional, not debt). In src/Dax.Template production
275+
# code CA1707 is fully clean: the Stage 2 public-API rename sweep (2.10b) eliminated all 18 hits and
276+
# CA1707 was removed from the WarningsNotAsErrors allowlist. CA1707 is NOT disabled repo-wide.
277+
[src/Dax.Template.Tests/**.cs]
278+
dotnet_diagnostic.CA1707.severity = none

.github/workflows/ci.yml

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,57 @@ jobs:
2121
- uses: actions/setup-dotnet@v4
2222
with:
2323
dotnet-version: |
24-
6.0.x
24+
10.0.x
2525
global-json-file: global.json
2626
- name: restore
2727
run: dotnet restore ./src
28+
- name: restore dotnet tools
29+
run: dotnet tool restore
2830
- name: build
29-
run: dotnet build ./src/Dax.Template.sln --configuration Release --no-restore
31+
# Phase M / Stage 1 step 1C: CI-only warnings-as-errors. TreatWarningsAsErrors is passed
32+
# here (not in Directory.Build.props) so local dev builds stay lenient. The matching
33+
# WarningsNotAsErrors allowlist (Stage-2 deferred debt) lives in src/Directory.Build.props
34+
# so both this pipeline and .azure/pipelines/ci.yml share one source of truth.
35+
run: dotnet build ./src/Dax.Template.sln --configuration Release --no-restore -p:TreatWarningsAsErrors=true
36+
- name: format check
37+
# Verifies the dotnet-format baseline (clean as of Stage 1B) doesn't drift. Runs against
38+
# the same solution as the build step; --no-restore reuses the restore above.
39+
run: dotnet format ./src/Dax.Template.sln --verify-no-changes --no-restore
3040
- name: test
31-
run: dotnet test ./src/Dax.Template.Tests/Dax.Template.Tests.csproj --configuration Release --no-build --verbosity normal
41+
run: dotnet test ./src/Dax.Template.Tests/Dax.Template.Tests.csproj --configuration Release --no-build --verbosity normal --collect:"XPlat Code Coverage" --settings ./src/Dax.Template.Tests/coverlet.runsettings --results-directory ./artifacts/coverage
42+
- name: coverage report
43+
shell: pwsh
44+
run: |
45+
dotnet tool run reportgenerator "-reports:./artifacts/coverage/*/coverage.cobertura.xml" "-targetdir:./artifacts/coverage/report" "-reporttypes:Cobertura;TextSummary;MarkdownSummaryGithub"
46+
Get-Content ./artifacts/coverage/report/Summary.txt
47+
if (Test-Path ./artifacts/coverage/report/SummaryGithub.md) {
48+
Get-Content ./artifacts/coverage/report/SummaryGithub.md | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
49+
}
50+
- name: coverage threshold gate (Dax.Template core library)
51+
shell: pwsh
52+
# Phase M / Stage 0 / P0-b (see .claude/SESSION_HANDOFF.md, locked decision #5): the CI-enforced
53+
# target is 80% line coverage on Dax.Template only. This is now the enforced LOCKED TARGET, not
54+
# an interim floor: a targeted Measures + Package characterization-test top-up (2026-07-02, 41
55+
# new tests, offline suite grown from 88 to 129 passed + 1 skipped) raised the measured overall
56+
# from 75.2% to 81.1%, clearing 80% with headroom. COVERAGE_LINE_THRESHOLD is set to 80 and the
57+
# gate fails only if line coverage drops below it (a real regression), not as a step toward a
58+
# still-pending target; see docs/design/coverage.md for the measured baseline, the per-subsystem
59+
# breakdown, and the remaining per-subsystem (~90%) gaps.
60+
env:
61+
COVERAGE_LINE_THRESHOLD: "80"
62+
run: |
63+
[xml]$cobertura = Get-Content ./artifacts/coverage/report/Cobertura.xml
64+
$linePct = [math]::Round([double]$cobertura.coverage.'line-rate' * 100, 1)
65+
$thresholdPct = [double]$env:COVERAGE_LINE_THRESHOLD
66+
Write-Host "Dax.Template line coverage: $linePct% (locked target / floor: $thresholdPct%)"
67+
if ($linePct -lt $thresholdPct) {
68+
Write-Error "Dax.Template line coverage $linePct% dropped below the locked target of $thresholdPct%."
69+
exit 1
70+
}
71+
- name: upload coverage report
72+
uses: actions/upload-artifact@v4
73+
with:
74+
name: coverage-report-${{ matrix.os-version }}
75+
path: artifacts/coverage/report
3276
- name: pack
3377
run: dotnet pack ./src/Dax.Template/Dax.Template.csproj --configuration Release --no-build --no-restore --verbosity normal

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ project.lock.json
6363
project.fragment.lock.json
6464
artifacts/
6565

66+
# Stryker.NET mutation testing output
67+
StrykerOutput/
68+
6669
# ASP.NET Scaffolding
6770
ScaffoldingReadMe.txt
6871

@@ -361,3 +364,6 @@ MigrationBackup/
361364

362365
# Fody - auto-generated XML schema
363366
FodyWeavers.xsd
367+
368+
# Serena Claude Plugin
369+
.serena/

0 commit comments

Comments
 (0)