Add Calendar / Calculation Group / Function templates + Phase M modernization (2.0.0)#60
Open
marcosqlbi wants to merge 70 commits into
Open
Add Calendar / Calculation Group / Function templates + Phase M modernization (2.0.0)#60marcosqlbi wants to merge 70 commits into
marcosqlbi wants to merge 70 commits into
Conversation
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>
…into add-calendar
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
…e 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>
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>
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>
… 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>
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>
…e 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>
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>
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>
…e 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>
…age 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>
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>
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>
…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>
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>
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>
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>
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>
…tem 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>
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>
… 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>
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>
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>
…ulture) 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>
…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>
…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>
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>
…nt (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>
…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>
…ctions (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>
…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>
…oncile 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This branch delivers two coordinated workstreams on top of
main(70 commits):Three new TOM template entities (the feature goal), each JSON-driven, idempotent, and covered by offline golden-file tests + an opt-in live-server test:
Tables/Calendars/CalendarTemplate): attaches a native TOMCalendar(+CalendarColumnGroups) to an existing table via the publicTimeUnitColumnAssociation/TimeRelatedColumnGroupAPI — no reflection, no TMSL. Requires compat ≥ 1701.Tables/CalculationGroups/CalculationGroupTemplate): generic (non-time-intelligence) calc-group generator — builds its own table +CalculationItems, with a foreign-table ownership guard and ordinal-uniqueness enforcement. Requires compat ≥ 1470 / 1500 / 1605.Functions/FunctionLibraryTemplate): model-level DAX UDFs attached toModel.Functionsfrom a hybrid structured/RawExpressionschema. Requires compat ≥ 1702.Phase M — modernization & refactor (ships as 2.0.0): C# 14 / .NET 10 migration, analyzer +
dotnet formatCI gates, CI-only warnings-as-errors (allowlist now empty), file-scoped namespaces,requiredmembers, public-API change-detector baseline, coverage floor (80%) + Stryker, plus a batch of behavior-preserving refactors and defect fixes surfaced by characterization testing.Conventions & guarantees
TemplateEntryschema change).SQLBI_Templateannotation, orCalendar.Namefor calendars).Testing
-p:TreatWarningsAsErrors=true;dotnet format --verify-no-changesclean.EmptyTestTemplates.pbix(compat ≥ 1702), non-destructive (ApplyTemplates+GetModelChanges, neverSaveChanges). The Phase-3 UDF pass confirms the generated( params ) => bodysignatures compile server-side — the only DAX check TOM performs, sinceFunction.Expressionis opaque offline.Breaking changes (2.0.0)
net6.0;net8.0).requiredmembers + public-identifier renames (seeCHANGELOG.md[Unreleased]). Public-API baseline regenerated accordingly.internal.Review notes
Every code change on this branch already passed the
dotnet-claude-kit:code-reviewergate per phase (see.claude/SESSION_HANDOFF.md). Docs (CHANGELOG, AGENTS.md,docs/design/*) were synced alongside each change.🤖 Generated with Claude Code