Growth persists Funnel, Experiment, and ShortLink aggregates and uses AnalyticsEvent as exposure/conversion/click facts. Management routes (funnels, experiments, short-links, dashboard, web-vitals, QR) now use [GenerateEndpoint] with declarative IAuthorizedRequest authorization — the authorization decision travels with the message through the Mediator pipeline (governance effort G-10), rather than the Host endpoint calling RequirePermission. Anonymous telemetry, public experiment assignment, short-link redirect, privacy self-service, and the analytics dead-letter ops endpoints still use Host-side RequirePermission. Funnels/experiments are tenant-scoped; short links are global.
1. Surface
Create Funnel/Experiment/ShortLink and QR require website.growth.manage; result/dashboard/Web Vitals/funnel projection require website.analytics.read.
2. Funnel definition
FunnelKey is canonical and tenant-unique; stages are ordered name + EventName pairs. Store prechecks duplication but source evidence for a database unique constraint as final concurrent guard is absent.
Projection orders visitor events and calculates sequential arrival, optionally segmented by supported dimensions. Read windows are half-open [from,to).
3. Experiment definition
ExperimentKey is tenant-unique; GoalMetric names a conversion event; Variants carry key/weight. Create gets tenant from ICurrentTenant.
There is no start/end, Draft/Running/Completed state, traffic allocation, mutual-exclusion layer, sample-size plan, or guardrail metric. A created definition is immediately assignable; this is not a complete experimentation platform.
4. Stable assignment
The public endpoint sends server PublicTenantId and VisitorId to AssignAsync. It returns variant and a Data Protection assignment token, then server code captures experiment-assigned.
If analytics is full, assignment has been computed but the endpoint returns failure; retry can create another exposure. Assignment and exposure are not one durable transaction.
// ① Tenant and visitor are server-derived.Result<ExperimentAssignment> assigned = await mediator.Send( new AssignExperimentQuery(publicTenantId, experimentKey, visitor.VisitorId), ct);if (assigned.IsFailure) return Problem(assigned.Error);
// ② The server records an exposure with the protected token.Result captured = collector.Capture(context, [BuildAssignmentExposure(assigned.Value!, clock.UtcNow)]);
// ③ Current capture failure blocks the response.return captured.IsFailure ? Problem(captured.Error) : Ok(assigned.Value);5. Conversion validation
Clients cannot send experiment-assigned. When any experiment dimension appears, Handler calls ValidateConversionAsync to bind token to experiment, visitor, variant, and goal.
Test incomplete sets, tampering, cross-experiment/visitor, wrong goal, token rotation, and protector failure.
6. Statistical results
Results count exposures/conversions/rates and calculate chi-square significance. Statistical significance is not business significance and does not replace preregistered hypotheses, sample size, stopping rules, multiple-testing control, or data quality.
Low sample, zero exposure, and sample-ratio mismatch need explicit states; do not automatically label a p-value as a winner.
7. Short-link creation
Codes are 6..8 ASCII alphanumeric and lowercase. Random generation uses cryptographic randomness with eight collision attempts; custom code gets one. A distributed lock reduces concurrency and a database unique index is final defense.
Targets are absolute HTTP(S), ≤2048, with future expiry. There is no destination allow/deny policy, malicious-URL scan, tenant owner, create audit, update, or revoke API.
8. Cache and resolution
Resolution reads a global Redis key first. Nonexpiring links cache 12 hours; expiring links cache at most 30 minutes or remaining life. Database requires not deleted/not expired.
No management mutation exists, so future revoke/update must define immediate cross-instance eviction.
9. Redirect and click
/r/{code} resolves and then enqueues short-link-click before returning 301/302. If analytics is full, navigation fails. Decide whether analytics should block the core redirect or degrade explicitly.
Permanent redirects can be cached by browser/CDN. Default temporary redirects; allow permanent only for immutable reviewed destinations.
HTTP(S) open redirects still enable phishing. Add brand-domain policy, safe-browsing checks, audit, abuse report, emergency takedown, and response-header policy.
10. QR SVG
Management builds {PublicBaseUrl}/r/{escaped-code} and QRCoder returns SVG. Confirm Content-Type and CSP when rendering; do not treat arbitrary SVG as safe HTML.
Production should restrict PublicBaseUrl to the approved site authority, not merely any absolute URL.
11. Features and tenancy
Experiment and ShortLink features are default-off but unused. Experiment/Funnel are tenant-scoped; ShortLink table, lock, and cache key are global, so custom code is globally unique.
Tenant-owned links require a URL-space decision (global code vs tenant/host) and owner fields in aggregate, authorization predicates, and cache key.
12. Tests and source checks
Cover concurrent definition uniqueness, trusted tenant, deterministic weighting, token tampering/rotation, exposure queue-full, duplicate conversion, significance boundaries, funnel order/segment, code collision/lock, URL threat, expiry cache, 301, click failure, QR base, and dual ORM.
# ① Generated management authorization.rg -n "GenerateEndpoint|ResourceDescriptor|AuthorizationAction" \ src/Platform/Website/BitzOrcas.Platform.Website.Application/Commands/GrowthCommands.cs# ② Global short-link vs tenant growth predicates.rg -n "website:global:v1:short-link|TenantId.*ExperimentKey|TenantId.*FunnelKey" src/Platform/Website -g '*.cs'13. Experiment governance checklist
- When does an experiment start, pause, stop, and archive?
- Are target and guardrail metrics frozen before run?
- Can a visitor enter mutually exclusive experiments?
- How is sticky assignment interpreted after weight change?
- What are assignment-token purpose, version, and expiry?
- Does exposure failure return assignment or fail closed?
- How are duplicate exposures and conversions deduplicated?
- How are bots, employees, and test traffic excluded?
- How are SRM, low sample, and multiple tests surfaced?
- Does export include algorithm/window version?
14. Short-link governance checklist
- Which destination domains, ports, and paths are allowed?
- Are IDN homographs, malicious domains, and redirect chains checked?
- Who owns and can urgently revoke a code?
- How is irreversible 301 caching approved?
- Should click-capture failure block navigation?
- What are eviction SLA and tombstone design?
- Is custom code global, tenant, or host scoped?
- Are target changes, expiry, and deletion audited?
- How is QR SVG returned and embedded safely?
- Where are abuse, takedown, and false-positive runbooks?