Policy Attributes
The compile-time half of the feature. Every attribute below is sealed by default — no runtime rule can lift it unless you write Overridable = true. See Precedence.
Type level
| Attribute | Effect |
|---|---|
[DwEntity(RequirePolicy = true)] | Querying this type without a policy context throws PolicyRequired instead of returning rows. |
This is the one that catches a forgotten guard
Without it, a code path that never calls
ApplyPolicy returns everything, and nothing complains. With it, the omission is a startup- loud failure on the first call rather than a silent disclosure.Access control
| Attribute | Effect |
|---|---|
[DwDeny(features)] | The composable primitive. Refuse any combination of the six features. |
[DwDenied] | Refuse all six. |
[DwNoWhere] | Refuse filtering. |
[DwNoSelect] | Refuse projection. The field stays filterable and countable. |
[DwNoOrder] | Refuse sorting. The fix startup validation names for a masked field. |
[DwNoGroup] | Refuse grouping. |
[DwNoAggregate] | Refuse aggregation. |
[DwOperators(Allow = ..., Deny = ...)] | Restrict which operators may target the field. Restrictions from several sources intersect. |
// Confirmable, not searchable: a caller can check a code it already knows
// and cannot sweep for one it does not.
[DwOperators(Allow = new[] { Operator.Equal, Operator.In })]
public string EmployeeCode { get; set; }Injection
| Attribute | Effect |
|---|---|
[DwAlias("name")] | A public name, accepted anywhere a field path is. Renamed back on the way out, after materialization. |
[DwForceWhere(op, Value =, ContextValue =)] | A predicate ANDed into every guarded query, whether the caller asked or not. |
[DwRequireWhere(Operators =)] | The caller must filter on this field. Throws in both tiers. |
// The row-level boundary. ContextValue reads from DwPolicyContext.Values,
// so the tenant comes from the request rather than from the source.
[DwForceWhere(Operator.Equal, ContextValue = "TenantId")]
public int TenantId { get; set; }
// Not a denial: an unscoped read of every department is the query worth
// refusing, and a requirement refuses it without blocking the scoped one.
[DwRequireWhere]
public string Department { get; set; }Forced predicates are ANDed, never elected
Several sources can each force a predicate on the same field, and all of them apply. A conjunction can only narrow, so a low-authority rule can tighten a tenant scope and can never discard one.
Transformation
Covered in full on Transforms & masking.
| Attribute | Effect |
|---|---|
[DwMask(strategy)] | Obscure the value. Eight strategies. |
[DwMutate(typeof(T))] | Hand the value to your own IValueTransformer. |
[DwDefault] / [DwDefault("v")] | Replace with the type default or a constant. |
[DwGeneralize(mode)] | Reduce precision, keeping the type. |
[DwTruncate(n)] | Shorten text. |
[DwFormat("fmt")] | Render through a .NET format string. |
All six also carry AllowAggregate and MinGroupSize — see Security, because those two are the k-anonymity control and are easy to miss.
Discovery, cost and audit
| Attribute | Effect |
|---|---|
[DwDescribe(Label =, Description =, Group =, Order =)] | Describes the field for the schema endpoint, so a front end builds its filter UI from the entity rather than a hand-maintained copy. |
[DwAllowedValues(...)] | Offer a list rather than a free-text box. |
[DwCost(weight)] | Charge the field against the query budget, so an expensive field costs more of a caller allowance. |
[DwAudit(features)] | Record every use to IDwAuditSink. |
Overridable
Every policy attribute carries Overridable, which defaults to false. One attribute can be sealed while another on the same member is replaceable.
// The mask is absolute; the description is a suggestion an operator may change.
[DwMask(MaskStrategy.Full)]
[DwDescribe(Label = "National ID", Overridable = true)]
public string NationalId { get; set; }A carrier on a self-referencing type
[DwAlias], [DwRequireWhere] and [DwForceWhere] describe the entity being queried, so they are not replicated onto reflections of a type reached from itself — Employee.Manager, Category.Parent. A scope declared one navigation away on a different type, such as Order.Buyer.TenantId, still applies.