Skip to content

Tags: server tags to NVARCHAR(4000); DB_Type becomes a real database tags column#774

Open
hegsie wants to merge 31 commits into
mainfrom
claude/tag-capacity-expansion
Open

Tags: server tags to NVARCHAR(4000); DB_Type becomes a real database tags column#774
hegsie wants to merge 31 commits into
mainfrom
claude/tag-capacity-expansion

Conversation

@hegsie

@hegsie hegsie commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

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_NameNVARCHAR(4000) at every layer (SQL column, usp_Insert_Server_Detail parameter, EF, [StringLength] → automatic 400s, swagger maxLength, 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_Type as semicolon-split chips, but the whole backend matched it as one opaque category string — Endur;Reporting rendered as two chips and matched nothing. This PR makes the tag semantics real:

  • Tag membership everywhere: all nine surveyed whole-string match sites rewritten to exact per-tag matching (never substring) — both GetDatabaseByType overloads, the Endur users query, the permissions dbType filter, the config-file path lookup, the resolver's Endur Reporting/Endur External lookups, 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 (ToQueryString artifact: COALESCE + parameterized LIKE with escaping); in-memory sites share a single TagString type.
  • Per-tag deployment variables: the resolver now emits DbServer_<tag> and DbName_<tag> per individual tag (scalar for one database, array for several) instead of grouping by the whole string; a null Type is 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).
  • Capacity: DB_Type 250→4000, usp_Insert_Database_Detail parameter 50→4000, EF 4000, [StringLength], swagger maxLength, UI joined-limit validation. ArrayName/Name/ServerName stay pinned at their widths (ArrayName is storage-array metadata — see the correction history below).
  • API hardening: lookup tags must be single non-empty tags — ;-bearing parameters get a 400 (an empty needle would otherwise match every untagged database); an omitted optional dbType keeps 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.
  • UI: add-edit-database gets 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".
  • Operational scripts: 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_Name as tags; user domain review overturned that (Array_Name records the storage array the source database sits on) and the work was reverted in dc8fc53, then rebuilt on DB_Type under a fresh HLPS. DeploymentContextTagWidthTests pins the corrected widths.

Tests

Final counts: Dorc.Core.Tests 150/150 · Dorc.Api.Tests 241 pass (same 22 pre-existing platform failures) · Dorc.Monitor.Tests baseline · dorc-web 137/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, DatabasePermissions verbatim 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)

  1. Run AuditDatabaseTags.sql against prod first — pre-existing multi-tag/padded/colliding rows change behaviour at deploy and are enumerated by the audit.
  2. Dacpac first (widened columns + normalization script) before the new API/UI.
  3. Multi-tagging a database emits per-tag variables; a tag shared by several databases flips that variable scalar→array. DatabasePermissions[].Database.Type stays the raw joined string.
  4. Live round-trips transfer to a deployed environment (no SQL Server in the dev sandbox): >1000-char server tags; a multi-tag database resolving through ByType and DbServer_<tag>.
  5. Follow-up when Environment details: implement Containers, Cloud, and APIs component tabs #773 merges: widen the three component Tags columns the same way — mechanical, constants in place.

🤖 Generated with Claude Code

https://claude.ai/code/session_01ThrVYPPi2eSj92hR44XYLb

claude added 5 commits July 17, 2026 08:25
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
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
@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown

Test Results

420 tests  +42   420 ✅ +42   13s ⏱️ +6s
  3 suites ± 0     0 💤 ± 0 
  3 files   ± 0     0 ❌ ± 0 

Results for commit 2c344d7. ± Comparison against base commit a537bdc.

♻️ This comment has been updated with latest results.

claude added 10 commits July 17, 2026 12:31
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
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
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
@hegsie hegsie changed the title [Draft] Tag capacity expansion: raise server/database tag limits well past 250 chars Tag capacity expansion: server/database tags to NVARCHAR(4000) with chip editing Jul 17, 2026
… 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
@hegsie hegsie changed the title Tag capacity expansion: server/database tags to NVARCHAR(4000) with chip editing Tag capacity expansion: server tags to NVARCHAR(4000) Jul 17, 2026
claude added 9 commits July 17, 2026 15:32
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
Comment thread src/Dorc.ApiModel/TagString.cs Fixed
…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
claude added 2 commits July 17, 2026 16:31
…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
@hegsie hegsie changed the title Tag capacity expansion: server tags to NVARCHAR(4000) Tags: server tags to NVARCHAR(4000); DB_Type becomes a real database tags column Jul 17, 2026
claude added 2 commits July 17, 2026 16:47
…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
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants