elsa-workflows / elsa-workflows/elsa-foundation
PRD: Tenant-scoped tagging for workflow definitions in Elsa Server and Studio
- Dominant language
- C#
- Stars
- 5
- Forks
- 1
- Avg merge
- 3h 56m
- Merged PRs (30d)
- 210
Description
## Problem Statement
Workflow authors working with a growing number of logical workflow definitions cannot consistently classify, group, filter, report on, or bulk-organize them. Names, folders, and ad hoc naming conventions do not provide reusable dimensions such as environment, owning team, business domain, lifecycle, criticality, or cost center. Elsa Server has no tenant-scoped tagging vocabulary or assignment/query contract, and Elsa Studio cannot display tags, construct tag filters, group results, save tagged views, or perform safe bulk tag edits.
Authors need the immediacy of GitHub-style marker labels together with Azure-style key/value classification. The solution must remain descriptive metadata: ordinary tags must not silently acquire runtime, deployment, scheduling, retention, authorization, or policy meaning. It must also fit Elsa's domain boundaries, tenant model, Groundwork persistence direction, workflow-definition lifecycle, API capability model, and future source reconciliation without storing tags in authored workflow state or overwriting one authority's assertions with another's.
## Solution
Add a tenant-scoped Tagging domain that owns reusable tag definitions and controlled tag values, then integrate it with Workflow Design, which owns assignments to logical workflow definitions, tag-aware workflow-definition query projections, and workflow-definition view presets.
Elsa Server will support marker, controlled-value, and free-text tags; single and multiple cardinality; stable identities; deprecation rather than destructive deletion; optimistic concurrency; action-scoped permissions; audit facts; bounded server-side filtering/facets/paging; explicit-selection bulk operations; and informational post-commit change events. Version 1 creates manual assignments only, while retaining an origin-aware assertion model that can safely admit source, system, policy, and automation origins later.
Elsa Studio will show compact tag chips, provide a tag editor and filter builder, group by marker or single-valued tags, preserve filters in shareable URLs, save private or tenant-shared workflow-definition view presets, and perform previewed bulk add/remove operations with per-target outcomes.
## User Stories
1. As a workflow author, I want to mark a workflow definition with a value-less tag such as `critical`, so that I can classify it with one click.
2. As a workflow author, I want to assign a controlled value such as `environment=production`, so that classification uses a consistent tenant vocabulary.
3. As a workflow author, I want to enter a free-text value such as `cost-center=CC-1042`, so that I can classify work without waiting for catalog administration.
4. As a workflow author, I want one workflow definition to carry several values for a multiple-valued tag, so that I can represent dimensions such as supported regions or business domains.
5. As a workflow author, I want single-valued tags to reject a second incompatible manual value, so that the classification remains unambiguous.
6. As a workflow author, I want surrounding whitespace and casing differences in free-text values to compare consistently, so that equivalent values do not fragment filters and reports.
7. As a workflow author, I want entered free-text display text to remain recognizable, so that normalization does not make Studio unpleasant to read.
8. As a workflow author, I want tag chips on workflow-definition rows, so that I can scan classifications without opening each definition.
9. As a workflow author, I want excess chips summarized behind a popover, so that heavily tagged definitions do not make the list unreadable.
10. As a workflow author, I want to edit tags from a workflow definition's details surface, so that classification is available where I manage the definition.
11. As a workflow author, I want marker tags to toggle directly, controlled tags to use a picker, and free-text tags to use an input with suggestions, so that each value mode has an appropriate interaction.
12. As a workflow author, I want deprecated tags and values to remain visible on existing assignments, so that history is not silently rewritten.
13. As a workflow author, I want Studio to prevent adding deprecated tags or values to new targets, so that retired classifications stop spreading.
14. As a workflow author, I want an idempotent save to retain an already-assigned deprecated value, so that unrelated edits do not force destructive cleanup.
15. As a workflow author, I want tag changes to apply to the logical workflow definition rather than a draft or version, so that the classification remains stable across authoring and publishing.
16. As a workflow author, I want publishing, creating or discarding a draft, and adding a workflow version to leave tags unchanged, so that authoring lifecycle operations do not copy or lose classification metadata.
17. As a workflow author, I want a soft-deleted workflow definition to retain its tags, so that restoring it restores the same organization.
18. As a workflow author, I want Studio to require restoration before changing tags on a soft-deleted definition, so that deleted resources are not edited accidentally.
19. As a workflow author, I want a stale tag edit to produce a clear concurrency conflict, so that I do not overwrite another author's changes.
20. As a workflow author, I want Studio to reload the current tag set after a concurrency conflict, so that I can reconcile intentionally.
21. As a workflow reader, I want to filter for definitions where a tag exists, so that I can find all classified resources regardless of value.
22. As a workflow reader, I want to filter for definitions where a tag is missing, so that I can find classification gaps.
23. As a workflow reader, I want an `AnyOf` filter, so that I can find definitions carrying at least one selected value.
24. As a workflow reader, I want an `AllOf` filter for multiple-valued tags, so that I can require every selected value.
25. As a workflow reader, I want a `NoneOf` filter, so that I can exclude selected values while allowing untagged definitions.
26. As a workflow reader, I want to combine `Exists` with `NoneOf`, so that I can require a tag while excluding particular values.
27. As a workflow reader, I want filters for different tag definitions combined with `AND`, so that I can narrow by several dimensions.
28. As a workflow reader, I want multiple selected values within one tag filter combined with `OR`, so that selecting alternatives behaves naturally.
29. As a workflow reader, I want invalid operator/value-mode combinations rejected clearly, so that malformed filters are not silently ignored.
30. As a workflow reader, I want controlled filters and saved views to reference stable identities, so that display renames do not break them.
31. As a workflow reader, I want free-text filtering to use normalized exact values in version 1, so that results are predictable.
32. As a workflow reader, I want filtering, total counts, sorting, and paging performed on the server, so that large tenants remain responsive and complete.
33. As a workflow reader, I want facet counts over the full filtered result universe, so that counts do not change merely because I moved to another page.
34. As a workflow reader, I want each facet calculated without its own active clause while retaining other clauses, so that I can see useful alternative values instead of zeros.
35. As a workflow reader, I want controlled facet values ordered by catalog order and then display name, so that common choices remain predictable.
36. As a workflow reader, I want bounded prefix suggestions for high-cardinality free-text tags, so that I can discover existing values without loading an unbounded list.
37. As a workflow reader, I want to group the workflow-definition list by a marker or single-valued tag, so that I can compare organized cohorts.
38. As a workflow reader, I want an explicit `Untagged` group, so that missing classifications remain visible.
39. As a workflow reader, I want conflicting single-valued assertions placed in a `Conflicted` group rather than assigned an arbitrary winner, so that authority disagreements remain visible when additional origins arrive later.
40. As a workflow reader, I want active filters represented as removable chips and in the URL, so that I can understand, modify, bookmark, and share the current view.
41. As a workflow reader, I want to save filter, grouping, sorting, and column state as a private workflow-definition view preset, so that I can return to an operational view.
42. As a workflow reader, I want private presets to be available without permission to share them, so that personal organization does not require tenant administration.
43. As an authorized workflow author, I want to share a view preset with the tenant, so that a team can use one agreed view.
44. As a workflow reader, I want a saved preset with a missing or deprecated reference to show a diagnostic without silently dropping that reference, so that the preset can be repaired intentionally.
45. As a workflow author, I want to select explicit visible workflow definitions and bulk-add or bulk-remove a tag, so that I can organize a set efficiently.
46. As a workflow author, I want a preview of a bulk change, so that I can catch an incorrect target or value before applying it.
47. As a workflow author, I want bulk operations to return success, authorization failure, validation failure, missing target, or concurrency conflict per definition, so that partial outcomes are understandable.
48. As a workflow author, I want retrying a bulk command with the same idempotency identity to be safe, so that transient failures do not duplicate work.
49. As a workflow author, I want bulk actions limited to a bounded explicit ID list, so that an old or changing implicit query cannot unexpectedly modify every matching definition.
50. As a tag catalog manager, I want to create marker, controlled, and free-text tag definitions, so that the tenant can use the right classification semantics.
51. As a tag catalog manager, I want to choose single or multiple cardinality before assignments exist, so that target-level rules match the business dimension.
52. As a tag catalog manager, I want stable opaque identities and immutable canonical keys, so that API clients, views, assignments, and future source declarations remain durable.
53. As a tag catalog manager, I want to change display name, description, and decorative color without rewriting assignments, so that presentation can evolve safely.
54. As a tag catalog manager, I want controlled values to have stable identities, immutable keys, display metadata, optional color, and explicit sort order, so that renames and presentation changes preserve meaning.
55. As a tag catalog manager, I want value color to take presentation precedence over definition color, so that controlled values can be distinguished consistently.
56. As a tag catalog manager, I want value mode and cardinality to become immutable after the first assignment has ever existed, so that removing assignments cannot permit semantic repurposing.
57. As a tag catalog manager, I want to deprecate and reactivate definitions and controlled values, so that I can manage lifecycle without destroying history.
58. As a tag catalog manager, I want a semantic replacement to be handled as an explicit migration to a new identity, so that clients are not broken by a disguised rename.
59. As a tenant administrator, I want canonical keys unique within my tenant and all tag data tenant-isolated, so that classifications cannot leak or collide across tenants.
60. As a host administrator, I want the `elsa.` canonical-key namespace reserved for host-provisioned tenant-local definitions, so that framework classifications cannot be impersonated.
61. As a host administrator, I want version 1 to avoid a global definition identity shared by several tenants, so that tenant ownership remains explicit.
62. As a security administrator, I want separate permissions for reading the tag catalog, managing it, assigning workflow-definition tags, and sharing view presets, so that responsibilities can be delegated narrowly.
63. As a security administrator, I want workflow-definition read authorization to remain a prerequisite for seeing its tags, so that tagging does not bypass resource visibility.
64. As a security administrator, I want assignment authorization enforced by Workflow Design, so that the Tagging domain cannot mutate targets it does not own.
65. As an auditor, I want catalog and assignment changes to record actor, tenant, timestamp, origin, correlation identity, semantic before/after values, and idempotency identity, so that changes are attributable.
66. As an integration author, I want a post-commit workflow-definition-tags-changed event, so that caches, search projections, audit integrations, and reporting can react to committed facts.
67. As an integration author, I want events to include revision, effective-value, origin, and conflict changes, so that consumers can update projections accurately.
68. As a runtime operator, I want ordinary tags to have no built-in execution meaning, so that classification cannot unexpectedly alter scheduling, routing, deployment, retention, authorization, or runtime behavior.
69. As a future source-integration author, I want assignments represented as origin-owned assertions, so that source reconciliation can add or remove only its own slice.
70. As a future source-integration author, I want identical assertions from several origins to collapse into one effective assignment while retaining origin detail, so that users see one value without losing provenance.
71. As a future source-integration author, I want incompatible single-valued assertions to produce a visible conflict without implicit precedence, so that reconciliation does not silently erase intent.
72. As a future source-integration author, I want incompatible source-declared catalog semantics to produce a diagnostic without mutating an existing definition or manual assertion, so that source activation is safe.
73. As an Elsa Studio user, I want the tagging UI to follow existing list, selection, permission, error, and accessibility conventions, so that it feels native to Studio.
74. As an Elsa Studio user, I want unsupported tagging capability discovery to fail closed, so that Studio does not show controls that the active Elsa shell cannot fulfill.
75. As an Elsa operator, I want tag queries to have explicit bounds for page size, clauses, values per clause, facet values, and bulk target IDs, so that workload cost is predictable.
76. As an Elsa operator, I want equivalent behavior and bounded provider work across supported Groundwork providers, so that choosing SQLite, PostgreSQL, SQL Server, or MongoDB does not change tagging semantics.
## Implementation Decisions
- Introduce a Tagging domain that owns tag definitions, controlled tag values, normalization rules, lifecycle, and target-kind eligibility. It does not own target resources or polymorphic assignments.
- Workflow Design owns `WorkflowDefinitionTagAssignment`, origin assertions, the per-definition tag-set revision, tag-aware workflow-definition query projections, lifecycle cleanup, authorization, and workflow-definition view presets.
- Assignments attach to the logical workflow definition. They are not fields on `WorkflowDefinitionState`, drafts, immutable versions, workflow executables, instances, or runtime state.
- Version 1 permits only the logical workflow-definition target kind and only manual assignment creation.
- Core contracts remain provider-neutral. Elsa Foundation's concrete durable implementation uses Groundwork in accordance with the repository's Groundwork-only persistence decision.
- The implementation plan may select Groundwork documents, physical entity tables, or a deliberately denormalized projection after representative benchmarks. Provider-specific storage types do not cross domain core boundaries.
- The semantic model retains one record per origin-owned assertion even when multiple origins later collapse into one effective value.
- Tag definitions have a stable opaque identity, immutable tenant-unique canonical key, mutable display name/description/color, value mode, cardinality, lifecycle, allowed target kinds, and a durable indication that an assignment has ever existed.
- Canonical keys use a portable lowercase representation, with a recommended maximum of 64 characters. The exact grammar is specified before implementation. `elsa.` is reserved for host-provisioned tenant-local definitions.
- Value modes are `Marker`, `Controlled`, and `FreeText`. Cardinality is independently `Single` or `Multiple`; marker tags are inherently single-valued.
- Value mode and cardinality become immutable after the first assignment is created, even if every assignment is later removed.
- Controlled values have stable opaque identities, immutable canonical keys, mutable display metadata, optional decorative color, lifecycle, and explicit sort order. Assignments reference identity rather than copying display text.
- Free-text values have a maximum recommended display length of 256 characters. The application trims surrounding whitespace, applies a specified Unicode normalization form and case-insensitive normalization, persists the normalized comparison key, and preserves display text.
- Definitions and controlled values use `Active` and `Deprecated` lifecycle states. Version 1 does not hard-delete catalog identities. Deprecated identities remain readable/filterable and valid on unchanged existing assignments but cannot be newly assigned.
- Semantic replacement creates a new identity and uses an explicit migration; changing a display label is non-semantic.
- Definition color is a fallback; controlled-value color takes presentation precedence. Color has no semantic meaning.
- Every assignment assertion carries origin kind and stable origin key separately from audit actor. Reserved origin kinds are `Manual`, `Source`, `System`, `Policy`, and `Automation`; version 1 writes only the logical `manual` origin.
- An origin can mutate only its own assertion slice. Multiple-valued effective assignments are the union across origins. Identical values de-duplicate while retaining origins. Incompatible single-valued assertions create a conflict without precedence.
- Soft deletion preserves assignments and blocks tag mutation until restore. Restore retains assignments. Permanent deletion atomically removes assignments, revision head, and target-owned projections with the workflow definition.
- Extend the provider-neutral workflow-definition list/query contract rather than adding a disconnected client-only tag query. Results, stable sort, total count, paging, tag filters, requested facets, and grouping metadata are produced server-side.
- Supported filter operators are `Exists`, `Missing`, `AnyOf`, `AllOf`, and `NoneOf`. Marker tags accept only `Exists`/`Missing`; `AllOf` is valid only for multiple-valued tags.
- Clauses across tag definitions use `AND`; values within one clause use `OR`. Version 1 excludes arbitrary nested Boolean expressions.
- `NoneOf` matches an absent tag. `Exists` plus `NoneOf` expresses "tag exists but not with these values."
- Controlled filters use controlled-value identities. Free-text filters use normalized exact values. Version 1 excludes substring and fuzzy value matching.
- Facet counts cover the full filtered universe and exclude the facet's own active clause while retaining all other clauses. Free-text suggestions use a bounded prefix query rather than unbounded enumeration.
- Version 1 groups only by marker and single-valued tags and exposes explicit `Untagged` and, when necessary, `Conflicted` groups. Multiple-valued grouping is deferred to avoid duplicate rows.
- Expose provider-neutral commands for catalog lifecycle, controlled-value lifecycle, reading a tag set with revision/origin/conflict detail, atomic replacement of one definition's manual slice, explicit bulk add/remove, and workflow-definition view-preset lifecycle/sharing.
- Represent a tag-set revision through the HTTP concurrency contract, preferably `ETag`/`If-Match`. Stale replacement returns a structured `409 Conflict` with current revision and safe summary data.
- Add/remove operations are idempotent. Bulk operations require an idempotency key, bounded explicit target IDs, per-target atomicity, and per-target outcomes; they are not one cross-target transaction.
- Commands return revisions or operation receipts. Clients re-query authoritative views rather than relying on reconstructed command responses.
- Recommended action-scoped permissions are `tagging.read`, `tagging.manage`, `workflow-design.tags.assign`, and `workflow-design.views.share`.
- A user with ordinary workflow-definition read access may create private view presets. Shared presets require the explicit share permission.
- A `WorkflowDefinitionViewPreset` is server-persisted, schema-versioned, private by default, optionally tenant-shared, and stores tag filters, grouping, sort, and columns by stable identities. Missing/deprecated references remain with visible diagnostics.
- Publish a background, post-commit workflow-definition-tags-changed event carrying tenant/definition identity, previous/new revision, added/removed effective values, origin/conflict changes, actor, correlation identity, and idempotency identity.
- No built-in Runtime subscriber interprets ordinary tags. Behavioral requirements must use an explicit policy concept in a future PRD.
- Advertise tagging through the authenticated, shell-scoped API capability contract so Studio can fail closed when the active shell does not provide the feature. Domain endpoints still enforce action-scoped permissions.
- Elsa Studio adds compact chips, overflow detail, a mode-aware editor, bounded suggestions, a tag-filter builder, active filter chips, single grouping, URL state, private/shared presets, selection-based bulk operations, previews, concurrency recovery, and per-target result summaries.
- The implementation spans Elsa Foundation and Elsa Studio. Server contracts are authoritative; Studio consumes the advertised versioned capability and does not infer support from composition feature names.
- Audit records are append-only. Tags inherit target visibility and are ordinary classification metadata; users are warned not to place sensitive business data in tag values.
- Provider work must remain bounded independently of repository history. Every key and route enforces tenant scope, deterministic paging, maximum `IN` cardinality, and application-produced normalized lookup values.
- Provider conformance covers every supported Groundwork provider and proves the chosen query routes at representative acceptance scale.
## Testing Decisions
- Test external behavior and durable contracts, not internal document/table shape, private handler structure, or implementation-specific query syntax.
- Use the public Workflow Design/Tagging API through a composed Elsa host as the primary acceptance seam. One scenario suite should cover catalog lifecycle, assignment semantics, filters/facets/paging/grouping, permissions, concurrency, bulk outcomes, events, and workflow-definition deletion/restore behavior.
- Use one additional Elsa Studio browser seam for behavior that cannot be observed through the API: chip rendering, editor interactions, filter construction, URL state, grouping, preset management, bulk preview/results, capability gating, and concurrency recovery.
- Keep lower-level unit tests only where they localize pure contract rules efficiently, such as canonical/free-text normalization, filter validation, effective-assignment reduction, and conflict classification. Do not duplicate full acceptance scenarios at every layer.
- Reuse the existing Workflow Design API contract-test style for route shape, lifecycle behavior, permission enforcement, and workflow-definition list projections.
- Reuse the existing endpoint-security coverage pattern so every new endpoint is enumerated and proven non-anonymous with the intended action-scoped permission.
- Reuse existing workflow-definition lifecycle tests to prove tags survive soft delete/restore and are removed transactionally on permanent delete.
- Reuse existing event-surface tests to prove the post-commit event exists, contains the promised semantic facts, is not published on failed transactions, and has no default runtime interpretation.
- Reuse Groundwork revision/CAS contract tests to prove stale revisions do not mutate state and create-only/idempotent semantics remain explicit.
- Add one tagging provider-acceptance runner and execute the same behavior suite for SQLite, PostgreSQL, SQL Server, and MongoDB, following existing Groundwork provider-conformance catalogs/runners.
- Add bounded-route/native-plan evidence at representative tenant, workflow-definition, assignment, and free-text cardinalities. Prove paging and facet provider work is bounded independently of history and that maximum clause/`IN`/facet limits are enforced.
- Test tenant isolation at the API and provider seams: identities, assignments, suggestions, counts, presets, and bulk outcomes from one tenant must never appear in another.
- Test the complete value-mode/cardinality/operator matrix, including invalid combinations, deprecated identity retention versus new assignment rejection, and normalized free-text equality.
- Test disjunctive facets against multiple active tag dimensions and verify counts describe the full result universe rather than the current page.
- Test bulk retry idempotency and mixed per-target outcomes without requiring a cross-target transaction.
- Test authorization without leaking hidden target existence or tags through bulk errors, facets, suggestions, events, or presets.
- Test saved-view schema versioning and diagnostics for missing/deprecated references without silent rewrites.
- Test Studio requests rather than client-side list slicing: browser scenarios must prove that filter, paging, facet, grouping, and bulk operations use bounded server contracts.
- Use deterministic representative performance fixtures and explicit budgets in the implementation plan; do not make wall-clock microbenchmarks the only correctness gate.
## Out of Scope
- Tags on workflow drafts, workflow versions, activities, activity definitions, workflow instances, workflow executions, runtime artifacts, or any non-workflow-definition resource.
- Source-, system-, policy-, or automation-produced assignments in version 1. The origin model and conflict projection are reserved now; reconciliation is a separate future PRD.
- Runtime routing, scheduling, execution, retention, deployment, authorization, incident, or policy behavior derived from ordinary tags.
- A universal polymorphic assignment store owned by Tagging.
- A universal saved-view framework shared by all Elsa resource lists.
- A global tag-definition identity shared across tenants.
- Hard deletion of tag definitions or controlled tag values.
- Arbitrary nested Boolean filter expressions.
- Substring, fuzzy, or full-text matching of free-text tag values.
- Grouping by multiple-valued tags in version 1.
- Bulk mutation of an implicit "all results matching this query" set.
- Cross-resource reporting beyond Workflow Design's workflow-definition projection.
- Freezing a specific Groundwork physical document/entity-table layout before implementation benchmarks.
- Production migration from an existing Elsa tagging schema; this is a greenfield feature.
## Further Notes
- Canonical domain language is established by the Elsa glossary terms `Tag definition`, `Controlled tag value`, `Tag value mode`, `Tag cardinality`, `Tag assignment origin`, `Effective tag assignment`, `Workflow definition tag assignment`, and `Workflow definition view preset`.
- Architectural ownership follows ADR 0046: Tagging owns vocabulary and target domains own assignments.
- Stable identity, value semantics, normalization, and deprecation follow ADR 0047.
- Origin-owned assertions and no implicit precedence follow ADR 0048.
- Concrete persistence follows the accepted Groundwork-only foundation direction while core contracts remain provider-neutral.
- The draft `WorkflowDefinitionState` scope policy supports keeping classification/listing metadata outside authored state, but its provisional status is supporting evidence rather than the ratification source for this PRD.
- Recommended default limits are 64 characters for canonical keys and 256 characters for free-text display values. The specification/implementation plan must finalize exact grammar, normalization form, page size, clause count, values per clause, facet result limit, bulk target limit, and performance budgets.
- This PRD is intentionally self-contained and ready for an implementation agent. The recommended delivery order is: catalog; manual assignments/concurrency/audit; server-side query/facets/provider conformance; Studio editor/filter/group/bulk; saved views/sharing; then source reconciliation under a separate PRD.
Contributor guide
No contributing guide indexed for this repository
Research direction
No files or tests are named. Start by reading the Elsa Server, Elsa Studio, and Workflow Design entry points, focusing on tenant boundaries, workflow-definition lifecycle, and API capabilities. Done would require the complete tagging domain, query and bulk-operation behavior, presets, and Studio integration described in the requirements.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- backend-api-design, frontend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100