Tags: server tags to NVARCHAR(4000); DB_Type becomes a real database tags column#774
Open
hegsie wants to merge 31 commits into
Open
Tags: server tags to NVARCHAR(4000); DB_Type becomes a real database tags column#774hegsie wants to merge 31 commits into
hegsie wants to merge 31 commits into
Conversation
Kickoff document for the tag work split out of the environment component tabs feature (PR #773), carrying over the verified panel findings: the usp_Insert_Server_Detail 1000-char truncation path, the shared 50-char UI constant guarding four fields, the full tag-consumer survey, and the corrected NVARCHAR(4000) rationale. Awaiting panel review and user checkpoint before an IS is drafted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
…code-safe Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
Adds Success Criteria and Constraints (dual-source same-step schema, rollout ordering, client regen, dev-environment limits), corrects the enforcement-layer statement (EF HasMaxLength is metadata - effective limits are 1000/250 and EnsureCreated drift is already live), gives the PR #773 dependency an executable fallback, registers the stored-proc liveness and Contains-false-positive decisions, splits the chip-editor and relabel questions. Triage in REVIEW-HLPS-round1.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
NVARCHAR(4000); chip-style editor for database tags with Array Name to Tags display relabel; Contains false-positive behaviour accepted and documented; stored-proc parameter widened rather than deleted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
Five sequential steps ordered so no layer ever accepts more than the schema beneath it: baselines + U-4 merge-state check, dual-source schema widening (columns + proc), API StringLength validation with a single shared limit constant and spec splice, UI per-field limits + chip editor + relabel, final sweep with rollout-ordering release note. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
States the cross-language limit mechanism honestly (C# + TS constants with a spec-agreement test), scopes the EF change to ArrayName only, gives consumer re-verification and the U-4 follow-up owners, moves splice-workflow feasibility to pre-flight with an S-003 fallback, resolves the server-tags mirror question (databases get a grid-launched tags dialog) and splits the UI work into two gated steps. Triage in REVIEW-IS-round1.md; steps renumbered S-001..S-006. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
Two cosmetic fixes from the verification pass (width wording, U-2a index attribution). IS awaits user checkpoint. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
…p item Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
SERVER.Application_Server_Name 1000->4000, DATABASE.Array_Name 250->4000, usp_Insert_Server_Detail parameter 1000->4000 (U-6: widen, not delete), EF HasMaxLength to 4000 for ApplicationTags and ArrayName only - the other three Database properties stay at 50 (unique index constraint) and a model test locks both the widened and frozen widths. TagLimits constant introduced here so the model test and S-003 attributes share one backend source. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
StringLength(TagLimits.MaxTagStringLength) on ApplicationTags and ArrayName (System.ComponentModel.Annotations added for the netstandard2.0 ApiModel); ApiController turns violations into 400s - boundary tests assert 4000 accepted / 4001 rejected with a readable message, plus a >1000-char mapping round-trip. Consumer re-verification per HLPS 3.3: resolver emits all ~190 per-tag variables from a near- limit string; Contains substring semantics asserted as the U-5 accepted behaviour. swagger.json gains maxLength via fragment splice from the running API - agreement proven by a web test parsing the spec against the new TS constant, and generator output shown byte-identical (zero client churn). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
…rcement add-edit-database's shared 50-char constant becomes per-field limits (name/type/instance unchanged at 50) and the Array Name text field is replaced by the tags-input chip editor backed by tag-parser split/join. New database-tags component mirrors server-tags for grid-launched editing (wired in S-005). Both tag save paths now validate the joined semicolon string against the shared TS limit before submitting - the UI never sends what the API will 400. Six web tests incl. over-limit rejection with no API call (Tagify faked in the test browser). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
… fixes Completes main's dead manage-database-tags wiring: tags buttons in database-controls and database-env-controls (vaadin:tags, readonly- gated), a database-tags dialog on page-databases-list (the stub handler now opens it) and attached-databases (paper-dialog, environment-stale refresh), and the ArrayName column relabelled to Tags. Three grid integration web tests incl. near-limit rendering. Backend gate fixes (REVIEW-STEPS.md): real-source consumer tests for the daemons projection and the GetAppServerDetails appserv filter at near-limit lengths, the missing MemberNames assertion, the swagger trailing newline, and the DTO-level-validation evidence form recorded. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
env-databases now subscribes to environment-stale so the Tags column refreshes after a grid tag save (the mirror had copied the dispatch but not the subscription). At-limit accept test now uses exactly 4000 chars; over-limit rejection asserts the visible notification and the dialog's inline error. All gates closed; HLPS/IS marked delivered; verification finalized at 130/130 web, suites at baseline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
… tags
User domain review overturned U-3: Array_Name records the storage array the
source database sits on — infrastructure metadata, not a field available for
repurposing as tags. The code survey ("pure pass-through, no in-code
consumers") was accurate but could not settle the field's domain meaning.
Reverted to main: DATABASE.sql (Array_Name NVARCHAR(250)),
DatabaseEntityTypeConfiguration (HasMaxLength(50)), DatabaseApiModel
validation, the database-tags chip editor, the "Array Name" -> "Tags"
relabels across grids/dialogs, and the swagger DatabaseApiModel fragment.
Kept: server ApplicationTags capacity at NVARCHAR(4000) across all layers
(SQL, proc parameter, EF, API [StringLength], swagger maxLength, UI
pre-validation). Tests rescoped to server-only, plus a regression test
pinning all four Database field widths at 50
(DeploymentContextTagWidthTests.DatabaseFields_KeepTheirCurrentWidths).
Docs carry a v3 Correction section recording the overturned unknown and
that genuine database tagging would need a new dedicated column (DB_Type is
exact-match category semantics, also unavailable).
Post-revert verification: Dorc.Core.Tests 140/140; Dorc.Api.Tests 218 pass
(same 22 pre-existing platform failures); dorc-web 121/121; web build clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
User directive after the ArrayName correction: make database tags real, using DB_Type. The draft records the complete consumer survey (8 exact whole-string match sites, incl. the resolver's DbServer_<type> emission loop and the narrower-than-column usp_Insert_Database_Detail parameter), proposes per-tag exact membership semantics with single-tag-invariance characterization, capacity 4000 at every layer, and chip editing. Status DRAFT — adversarial panel in progress. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
All three reviewers returned REVISE; 18 findings triaged, all accepted (REVIEW-HLPS-round1.md). Key corrections: survey completed with the env-control-center ThinClient lookup (site 9), site 7's DbName_<type> emission half, the DatabasePermissions projection, and the RefreshEndur CLI as an external ByType consumer; the false "opt-in" claim replaced with a pre-deploy data audit (U-7); the SQL trailing-space delta admitted and U-2 widened to normalize all rows; SC-1 rescoped honestly to the unit seam with ToQueryString as the EF-translation evidence form; null contract, semicolon-in-tag invariant, and write-normalization rules (order/dedup/empty) made explicit. Round-2 delta review in progress. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
Round-2 delta panel verified all 18 round-1 findings resolved and every new factual assertion against code, returning APPROVE conditional on two edits, both applied: empty/whitespace lookup params rejected as 400 (an empty needle ";;" would otherwise match every null-Type database), and the U-2 script/U-7 audit lists aligned with write-normalization rules. Awaiting user checkpoint (U-2/U-4/U-6, U-7 acknowledgement). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
Checkpoint (user, 2026-07-17): U-2 ship the normalization script; U-4 relabel displays to Tags; U-6 same PR #774; U-7 audit acknowledged; execution auto-pilot. IS drafted with six steps (characterization freeze, dual-source schema widening, tag-membership rewrite with ToQueryString artifact, API validation + swagger splice, UI chip editor/overlap/ ThinClient membership/relabel, operational scripts + final sweep). IS panel review in progress. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
Both reviewers returned REVISE; 14 findings triaged, 13 accepted, 1 deferred (REVIEW-IS-round1.md). Key fixes: the SC-3 spec-to-UI agreement test now owned by S-004 with a gate item; explicit absent-vs-empty reconciliation of NEW-1/SC-4 wording plus an absent-param regression test; SC-5 gate enumerates all five assertions and gains relabel verification; U-1 collision-throw and U-5 casing fixtures added to S-003; CI/user-environment evidence routing restored in S-002/S-006; compat-100-safe T-SQL pinned for the U-2 script; the ThinClient test seam named; the U-7 audit location pinned. Round-2 delta in progress. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
Round-2 delta review verified all 13 accepted round-1 rows resolved (row 8 deferred with re-trigger condition) and the S-004 absent-vs-empty reconciliation factually grounded against the controllers. Three LOW nits applied: Step Index attribution (C-3, U-1, U-5), repo-relative test path, module-level-state wording. Execution begins at S-001. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
Eleven tests freeze today's whole-string semantics at the unit seam: resolver sites 6-7 (fixed Endur lookups; per-type DbServer_/DbName_ emission with scalar/array shapes and space-to-underscore naming; multi-tag values match nothing and emit the joined name verbatim; null Type throws NRE today; duplicate whole-Type throws) and source sites 1-5 (both GetDatabaseByType overloads, the Endur users join, the permissions dbType filter incl. omitted-means-no-filter, and the config-file path lookup). Multi-tag misses and the null-Type NRE are the two declared flip candidates for S-003; duplicate-throw (U-1) and omitted-filter assertions must survive the rewrite. Baselines recorded in SPEC-S-001; post-step: Core 144/144, Api 225 pass / same 22 platform failures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
DATABASE.sql DB_Type 250->4000, usp_Insert_Database_Detail @DB_TYPE 50->4000 (U-3: widened, not deleted), EF HasMaxLength 50->4000 in the same step (C-1). Width-lock test gains DatabaseTags_AreWidenedToTheLimit; ArrayName/Name/ServerName stay pinned at 50 with the ArrayName correction comment retained (C-3). DDL-to-EF side-by-side recorded in SPEC-S-002; dacpac build delegated to CI. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
…nsumers TagString (ApiModel) carries the semicolon convention: split/trim/ drop-empties, Ordinal HasTag (false for null/empty/whitespace), and order-preserving keep-first Normalize. DatabaseTagMatch (PersistentData) is the EF-translatable delimiter-wrap expression, used at the GetDatabaseByType EF overload and inlined (with pointers) at the Endur users join and the permissions dbType filter; captured ToQueryString proves server-side translation (COALESCE + parameterized LIKE, spec has the verbatim SQL). In-memory sites (the second overload, config-file path, resolver fixed lookups) use TagString.HasTag; the resolver's per-type loop now groups per tag, emitting DbServer_<tag> and DbName_<tag> with unchanged scalar/array shapes, and a null Type is skipped instead of throwing. Writes normalize Type on Add/Update. Characterization flips are exactly the two declared groups (multi-tag misses -> membership hits; null NRE -> skip); duplicate/shared-tag throws (U-1) and omitted-filter behaviour survive, with new fixtures for shared-tag collision, Ordinal casing, padded entries (EF-vs- in-memory contrast documenting the U-2 need), and DatabasePermissions verbatim pass-through. DbContextMock now yields a fresh enumerator per call - the site-2 cross join exposed the single-enumerator hazard that silently dropped rows on re-iteration. Suites: Core 150/150; Api 227 pass / same 22 platform; Monitor 107/4. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
…ment DatabaseApiModel.Type gains StringLength(4000) with the established member-naming message. GetByType rejects null/empty/whitespace or semicolon-bearing tags as 400 before touching the source (an empty needle would match every untagged database); GetDbUsersPermissions applies the same rule to a supplied dbType while an omitted one keeps its no-filter semantics (the SC-4 reconciliation), with an explicit absent-param regression test. Swagger gains maxLength/minLength on DatabaseApiModel.Type (2-line splice mirroring the server fragment); the generator emits no param descriptions, so membership semantics live in the updated XML docs. The web spec-to-UI agreement test now asserts the database maxLength too. Api 238 pass / same 22 platform; web agreement 2/2; Core unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
…embership, relabel; close S-001..S-003 gate findings add-edit-database swaps the 50-char free-text field for the tags-input chip editor (per-tag charset kept via Tagify pattern), with live tags-changed wiring into the existing validation and a save-time joined-string guard that visibly rejects over-limit input without calling the API. attach-database's duplicate check becomes tag-set overlap and names the overlapping tag(s); env-control-center resolves the ThinClient app database server by tag membership; grid header and filter placeholder relabelled to Tags. tag-parser gains hasTag mirroring the backend TagString contract; tags-input gains an optional per-tag pattern and a guarded tags-changed event. Also closes the S-001..S-003 gate findings (verdict PASS, log in REVIEW-STEPS.md): site-5 collision-throw and sites 1-5 null-Type fixtures added (F-1/F-2); lookup params trimmed at both controllers so the EF pattern and the in-memory tokenizer see the same needle (F-3). Suites: Api 241 pass / same 22 platform; web 136/136 (16 files); build clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
NormalizeDatabaseTags.sql (dacpac post-deploy, registered in the sqlproj and Script.PostDeployment.sql) normalizes ALL DB_Type rows once with the app's write rules: compat-100-safe CHARINDEX walk, idempotent, order-preserving, binary-collation keep-first dedup, all-dropped to NULL. AuditDatabaseTags.sql (install-scripts, read-only) reports multi-tag rows, padded rows, and per-environment tag collisions via a recursive-CTE splitter - run before deploy (U-7). VERIFICATION-S-006 records the SC table, off-box evidence routing (dacpac build + script execution + live round-trip), and rollout notes (audit first; dacpac first; variable shape rule). HLPS/IS statuses updated to delivered. Final suites: Core 150/150; Api 241 pass / same 22 platform; Monitor 107/4 baseline; web 136/136; build clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
…aims Gate F-A (HIGH): hosts bind freshly-built tag arrays on every render and the tags-input setter rebuilt the chips unconditionally - with real Tagify (which fires add events on programmatic addTags, unlike the test shim) that cascades into per-keystroke chip teardown and network calls. The setter now dirty-checks against the live chips and no-ops when the set is unchanged; a regression test counts rebuilds across an unrelated host re-render. In-place chip edits (edit:updated) now also emit tags-changed so live validation stays current. Gate F-B (MED, docs): over real HTTP the binder turns ?dbType= into null, so supplied-empty binds as omitted (no filter, safe via the source's null guard) - SPEC-S-004/VERIFICATION now say so and record the in-action check as defense-in-depth. Gate F-C (MED, docs): the normalization script's LTRIM/RTRIM is space-only - exactly the class SQL '=' forgave - now stated in the script header and VERIFICATION. Relabel evidence completed with a dialog-label assertion; remaining LOWs triaged in REVIEW-STEPS.md. Gate closed; feature DELIVERED. Web 137/137 (16 files), build clean; .NET suites unaffected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
User ran AuditDatabaseTags.sql against the live estate: zero multi-tag rows (single-value invariance covers everything), one whitespace-padded row (auto-remediated by the shipped normalization script before the new API deploys), and 11 collision groups that are all pre-existing whole-string duplicates with identical before/after behaviour - none touching the pipeline's fixed Endur lookups. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
hegsie
marked this pull request as ready for review
July 17, 2026 20:44
Ordinal Distinct keeps the first occurrence in source order, so the keep-first order-stable contract is unchanged (write-normalization test covers it). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb
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
Two related tag features, both taken through the full HLPS → IS → adversarial-gate process:
1. Server tag capacity (docs/tag-capacity-expansion/)
SERVER.Application_Server_Name→ NVARCHAR(4000) at every layer (SQL column,usp_Insert_Server_Detailparameter, EF,[StringLength]→ automatic 400s, swaggermaxLength, UI pre-validation with visible rejection). Machine-checked layer agreement: EF width test, spec↔UI constant test, DDL gate artifact.2. Database tags via DB_Type (docs/database-tags/)
The grids already rendered
DB_Typeas semicolon-split chips, but the whole backend matched it as one opaque category string —Endur;Reportingrendered as two chips and matched nothing. This PR makes the tag semantics real:GetDatabaseByTypeoverloads, the Endur users query, the permissionsdbTypefilter, the config-file path lookup, the resolver'sEndur Reporting/Endur Externallookups, the attach-database duplicate check (now tag-set overlap), and the env-control-center ThinClient app-server resolution. EF sites use a delimiter-wrap pattern proven to translate server-side (ToQueryStringartifact: COALESCE + parameterized LIKE with escaping); in-memory sites share a singleTagStringtype.DbServer_<tag>andDbName_<tag>per individual tag (scalar for one database, array for several) instead of grouping by the whole string; a nullTypeis skipped instead of crashing variable resolution. Single-value data emits an identical variable set (characterization-frozen before the rewrite, flip-contract enforced by an independent gate).DB_Type250→4000,usp_Insert_Database_Detailparameter 50→4000, EF 4000,[StringLength], swaggermaxLength, UI joined-limit validation.ArrayName/Name/ServerNamestay pinned at their widths (ArrayName is storage-array metadata — see the correction history below).;-bearing parameters get a 400 (an empty needle would otherwise match every untagged database); an omitted optionaldbTypekeeps its no-filter semantics (over HTTP, an empty query value binds as omitted — safe by construction via the source's null guard); parameters are trimmed at the boundary so the EF pattern and in-memory tokenizer see identical needles.add-edit-databasegets the chip editor (per-tag charset preserved, chip-set dirty-checking so host re-renders never rebuild the chips); writes normalize the tag string (trim/dedup/order-preserving); displays relabelled to "Tags".NormalizeDatabaseTags.sql(dacpac post-deploy, compat-100-safe, idempotent) normalizes legacy rows once — SQL=forgave trailing spaces, the tag pattern doesn't;install-scripts/AuditDatabaseTags.sql(read-only) reports multi-tag rows, padded rows, and per-environment tag collisions to run before deploy.Correction history
The database half originally repurposed
Array_Nameas tags; user domain review overturned that (Array_Namerecords the storage array the source database sits on) and the work was reverted in dc8fc53, then rebuilt onDB_Typeunder a fresh HLPS.DeploymentContextTagWidthTestspins the corrected widths.Tests
Final counts:
Dorc.Core.Tests150/150 ·Dorc.Api.Tests241 pass (same 22 pre-existing platform failures) ·Dorc.Monitor.Testsbaseline ·dorc-web137/137 (16 files) · build clean. Coverage includes characterization freezes with a declared flip contract, shared-tag collision throws (kept behaviour), Ordinal casing, padded-row EF-vs-in-memory contrast,DatabasePermissionsverbatim pass-through, boundary 4000/4001, lookup-param rejection, chip round-trip incl. a no-rebuild-on-rerender regression, overlap warning, and ThinClient membership. Both step gates closed (logs in docs/database-tags/REVIEW-STEPS.md); the final gate's line-by-line SQL review stands in for execution, which this sandbox cannot do.Rollout (see docs/database-tags/VERIFICATION-S-006.md)
AuditDatabaseTags.sqlagainst prod first — pre-existing multi-tag/padded/colliding rows change behaviour at deploy and are enumerated by the audit.DatabasePermissions[].Database.Typestays the raw joined string.ByTypeandDbServer_<tag>.Tagscolumns the same way — mechanical, constants in place.🤖 Generated with Claude Code
https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb