Controllers
SearchIndexRefreshAuthorizationResult
SourceRepresents the internal authorization outcome for an AppSurface Docs search-index refresh request.
Parameters
IsAllowedIndicates whether the refresh action may invalidate the search-index cache.ReasonThe denial reason whenIsAllowedisfalse; otherwisenull.
Remarks
Use Allowed and Denied(SearchIndexRefreshAuthorizationFailure) to preserve the two valid states: allowed results have IsAllowed == true and no reason, while denied results have IsAllowed == false and a concrete reason. Consumers should check IsAllowed before reading Reason because the reason is intentionally nullable for successful authorization. This immutable record struct is internal to the controller layer and should not become part of a public wire contract.
Allowed
SearchIndexRefreshAuthorizationResult Allowed()
Source
Creates an authorization result that permits cache invalidation.
Returns
An allowed result with no failure reason.
Denied
SearchIndexRefreshAuthorizationResult Denied(SearchIndexRefreshAuthorizationFailure reason)
Source
Creates an authorization result that denies cache invalidation for a specific reason.
Parameters
reasonThe concrete denial reason to expose to internal logging and branch-specific tests.
Returns
A denied result whose Reason is set to reason.
SearchIndexRefreshAuthorizationFailure
SourceEnumerates the internal reasons a search-index refresh request can be denied before cache invalidation.
Remarks
These reasons intentionally distinguish host setup failures from caller authorization failures so logs and tests can identify the remediation path. They are not serialized to clients; the packaged endpoint returns HTTP 403 for every denial after anti-forgery validation succeeds.
DocsController
SourceController for serving documentation pages.
Index
Task<IActionResult> Index()
Source
Displays the documentation index view containing either curated proof paths from the repository-root landing doc metadata or the neutral docs landing fallback.
Returns
A view result whose model is a DocLandingViewModel. The model includes curated featured cards when the repository-root README.md metadata authors featured_page_groups through inline front matter or a paired sidecar such as README.md.yml; otherwise it includes the neutral fallback landing data.
VersionEntry
IActionResult VersionEntry()
Source
Displays the stable docs entry fallback when versioning is enabled but the recommended released tree is not mounted.
Returns
A view result describing the available released versions plus the current preview surface, or a redirect to the live docs home when versioning is disabled.
Versions
IActionResult Versions()
Source
Displays the public docs version archive.
Returns
A view result that lists the published versions described by the configured version catalog, or a redirect to the live docs home when versioning is disabled.
Section
Task<IActionResult> Section(string sectionSlug)
Source
Enters one normalized public documentation section.
Parameters
sectionSlugThe stable slug for the public section.
Returns
A redirect to the authored landing doc when one exists, otherwise a grouped section fallback or unavailable view.
Details
Task<IActionResult> Details(string path)
Source
Displays the full or partial details view for a documentation item identified by the given path.
Parameters
pathThe public docs route, redirect alias, or.partial.htmlresource identifier of the documentation item to retrieve. Full-page source-shaped Markdown routes for public pages redirect to the clean canonical route.
Returns
An IActionResult rendering the details view or the doc-content RazorWire frame; returns NotFoundResult when the path is invalid or no document is found after fallback resolution.
Remarks
Partial requests ending in .partial.html are resolved through the same DocAggregator.GetDocDetailsAsync(string, CancellationToken) flow as full-page requests. When a partial path resolves to an /index resource, such as /index.partial.html, the action transparently retries the parent document before returning NotFoundResult. Successful requests load the complete docs corpus and public-section snapshots with DocAggregator.GetDocsAsync(CancellationToken) and DocAggregator.GetPublicSectionsAsync(CancellationToken), then build the response model with BuildDetailsViewModel. All aggregator calls observe HttpContext.RequestAborted. Visible caller side effects are limited to returning either the full details view or a doc-content frame for partial navigation.
Search
Task<IActionResult> Search()
Source
Displays the dedicated docs search workspace shell.
Returns
A ViewResult whose model is a SearchPageViewModel describing the search shell and its server-rendered recovery paths.
Remarks
The action returns a SearchPageViewModel immediately so the workspace can render starter, loading, and retry UI before the client downloads the search index. Fallback link generation shares a linked cancellation token with the current request and is capped by SearchShellFallbackBudget so slow aggregation does not block the shell from rendering. If aggregation times out or throws, the view still renders with default recovery links.
SearchIndex
Task<IActionResult> SearchIndex()
Source
Returns docs search index data for live-hosted docs.
Returns
A JSON result containing searchable document metadata, including normalized page-type badge fields that keep search result rendering consistent with the built-in landing and details experiences. When HttpRequest.PathBase is non-empty, only documents[*].path values that already start with / are rebased onto that path base before serialization so a mounted app returns links like /some-base/docs/guide.html instead of /docs/guide.html.
Remarks
The path-base rewrite is intentionally narrow. Only rooted documents[*].path values are prefixed; blank, missing, or already non-rooted values such as guide.html remain unchanged. The rewrite trims trailing slashes from the request path base before concatenation, so /some-base/ and /some-base produce the same output. For example, a typed payload that contains documents[0].path = "/docs/guide.html" becomes /some-base/docs/guide.html when the request path base is /some-base. This action always receives the typed DocsSearchIndexPayload produced by DocAggregator.GetSearchIndexPayloadAsync(System.Threading.CancellationToken); raw JSON payloads without a top-level documents array are outside this method's contract and must be handled before this action is invoked. The rewrite is idempotent when HttpRequest.PathBase is null, empty, or /.
RefreshSearchIndex
Task<IActionResult> RefreshSearchIndex()
Source
Invalidates the current live docs search-index cache after host-owned operator authorization succeeds.
Returns
NoContentResult when the configured policy authorizes the current user; otherwise HTTP 403. MVC anti-forgery validation runs before this action body and rejects missing or invalid tokens before policy checks.
Remarks
This endpoint is intentionally POST-only and side-effecting. Readers should fetch SearchIndex; host operators should post to DocsUrlBuilder.Routes.SearchIndexRefresh with a valid anti-forgery token and a user that satisfies the effective AppSurface Docs operator-write policy. New hosts should configure AppSurfaceDocsDiagnosticsOptions.OperatorWritePolicy; existing hosts may keep AppSurfaceDocsDiagnosticsOptions.SearchIndexRefreshPolicy as the compatibility fallback.
Harvest
Task<IActionResult> Harvest(string? returnUrl = null, string? rebuild = null)
Source
Displays the live harvest observatory for a trusted operator rebuild or an active startup harvest.
Parameters
returnUrlOptional app-relative docs URL to revisit after the active harvest completes. Unsafe values and harvest-loop targets fall back to the current docs home.rebuildOptional rebuild request result emitted byRebuildHarvestso the observatory can show whether the operator request started, queued, or was already queued.
Returns
The harvest observatory while a harvest is active or queued; otherwise a redirect to the validated return URL.
RebuildHarvest
Task<IActionResult> RebuildHarvest(string? returnUrl = null)
Source
Starts or queues a full source-backed AppSurface Docs harvest rebuild after trusted operator authorization.
Parameters
returnUrlOptional app-relative docs URL to revisit after rebuild completion. Unsafe values, non-docs paths, and harvest loop targets fall back to the current docs home.
Returns
A redirect to the live harvest observatory when authorization succeeds; otherwise HTTP 403. MVC anti-forgery validation runs before this action body.
HarvestHealth
Task<IActionResult> HarvestHealth()
Source
Displays the redacted operator-facing harvest health page for the current live docs surface.
Returns
A health page when health routes are exposed for the current environment; otherwise NotFoundResult. Healthy and empty snapshots return HTTP 200. Degraded and failed snapshots render the same page with HTTP 503 so local verification and CI checks can fail quickly.
HarvestHealthJson
Task<IActionResult> HarvestHealthJson()
Source
Returns redacted machine-readable harvest health for the current live docs surface.
Returns
A JSON health response when health routes are exposed for the current environment; otherwise NotFoundResult. Healthy and empty snapshots return HTTP 200 with verification.ok=true. Degraded and failed snapshots return HTTP 503 with verification.ok=false.
RouteInspector
Task<IActionResult> RouteInspector(string? path = null)
Source
Displays the human-facing route inspector for the current live docs surface.
Parameters
pathOptional path to probe. Values are trimmed, may be docs-root-relative or app-relative, may include the activeHttpRequest.PathBase, and have any query string or fragment stripped before route lookup. Absolute URLs, protocol-relative URLs, paths outside the active docs root, empty post-strip values, and.or..path segments produce an invalid-input probe instead of route lookup.
Returns
A no-store route inspector page when diagnostics are exposed for the current environment; otherwise NotFoundResult. The page uses BuildRouteInspectorResponseAsync(string?) for the same manifest and optional probe shape as the JSON endpoint.
Remarks
Use this endpoint for interactive maintainer inspection. It is intentionally separate from reader navigation and is hidden by AppSurfaceDocsDiagnosticsVisibility.IsRouteInspectorExposed(AppSurfaceDocsOptions, IHostEnvironment) when the current environment or explicit diagnostics settings do not expose route diagnostics.
RouteInspectorJson
Task<IActionResult> RouteInspectorJson(string? path = null)
Source
Returns machine-readable route identity for the current live docs surface.
Parameters
pathOptional path to probe. Values are trimmed, may be docs-root-relative or app-relative, may include the activeHttpRequest.PathBase, and have any query string or fragment stripped before route lookup. Absolute URLs, protocol-relative URLs, paths outside the active docs root, empty post-strip values, and.or..path segments produce an invalid-input probe in the JSON response.
Returns
A no-store JSON route inspector response when diagnostics are exposed for the current environment; otherwise NotFoundResult.
Remarks
Use this endpoint for scripts, tests, and maintainer tools that need the AppSurfaceDocsRouteInspectorResponse wire contract produced by BuildRouteInspectorResponseAsync(string?). Use RouteInspector(string?) instead when a human needs the compact HTML probing surface.
CollectMetrics
Task<IActionResult> CollectMetrics()
Source
Accepts low-trust browser AppSurface Docs metrics submissions for hosted collection.
Returns
NoContentResult for accepted, invalid, or dropped submissions while hosted collection is enabled; otherwise NotFoundResult. Unsupported media type and oversized bodies return HTTP 415 and 413 respectively without echoing submitted values.
Remarks
Route mapping constrains this collector to HTTP POST. The action intentionally opts out of antiforgery validation because it is an anonymous, low-trust JSON collector for hosted docs and static exports rather than an authenticated state-changing form post. The request body uses a narrow DTO containing only name, properties, and an optional client timestamp. Browser-supplied identity, route, URL, cookies, headers, and request metadata are not accepted into the event envelope. Every submitted event is revalidated through AppSurfaceProductEventRegistry before the process-local read model or host-owned product-intelligence sinks see it.
SearchQuality
IActionResult SearchQuality()
Source
Displays recent hosted AppSurface Docs search-quality diagnostics.
Returns
A no-store diagnostics page when hosted review is enabled and exposed; otherwise NotFoundResult.
AuthorizeSearchIndexRefreshAsync
Task<SearchIndexRefreshAuthorizationResult> AuthorizeSearchIndexRefreshAsync(CancellationToken cancellationToken)
Source
Authorizes a side-effecting search-index refresh request against the host-configured operator policy.
Parameters
cancellationTokenToken observed while resolving the configured authorization policy; caller cancellation does not turn policy or authorization failures into exceptions.
Returns
A SearchIndexRefreshAuthorizationResult whose allowed state means cache invalidation may proceed. Denied results carry the specific SearchIndexRefreshAuthorizationFailure reason for logging and explicit 403 responses.
Remarks
The decision flow is intentionally ordered from host configuration to caller identity: a missing or blank no effective AppSurfaceDocsDiagnosticsOptions.OperatorWritePolicy or AppSurfaceDocsDiagnosticsOptions.SearchIndexRefreshPolicy fallback returns SearchIndexRefreshAuthorizationFailure.MissingPolicyOption; missing HttpContext, request services, or IAuthorizationPolicyProvider returns SearchIndexRefreshAuthorizationFailure.MissingPolicyProvider; a missing IAuthorizationService returns SearchIndexRefreshAuthorizationFailure.MissingAuthorizationService; an unresolved configured policy returns SearchIndexRefreshAuthorizationFailure.PolicyNotFound; a missing or unauthenticated user returns SearchIndexRefreshAuthorizationFailure.Unauthenticated; and a policy evaluation failure returns SearchIndexRefreshAuthorizationFailure.AuthorizationFailed. Authorization denials are reported as denied results rather than thrown exceptions. The policy lookup uses Task.WaitAsync(CancellationToken) so cancellation can interrupt a slow policy provider before refresh side effects occur.
BuildHarvestUrlWithReturnUrl
string BuildHarvestUrlWithReturnUrl(string returnUrl, AppSurfaceDocsHarvestRebuildRequestResult rebuild)
Source
Builds a path-base-aware harvest observatory URL that carries the verified docs return URL and rebuild result.
Parameters
returnUrlThe app-relative docs URL to revisit after the active or queued harvest completes.rebuildThe trusted rebuild request result to expose in the observatory status copy.
Returns
A local harvest URL containing URL-encoded returnUrl and rebuild query-string values.
Remarks
Callers should pass only return URLs produced by ResolveHarvestReturnUrl(string?) or the current docs request. This helper only encodes the value for transport; it does not re-run the docs-root containment policy. Unknown rebuild enum values serialize as an empty marker and are ignored by the observatory parser.
ResolveHarvestReturnUrl
string ResolveHarvestReturnUrl(string? returnUrl)
Source
Resolves request-provided harvest navigation back to a safe docs URL.
Parameters
returnUrlThe candidate app-relative return URL from the query string or rebuild form.
Returns
returnUrl when it stays under the current docs root and avoids harvest routes; otherwise the path-base-aware docs home URL.
Remarks
This is the docs-specific return URL policy for the harvest loop. It intentionally rejects same-origin paths outside the active HttpRequest.PathBase and docs surface, raw or encoded traversal, and _harvest loops so a terminal progress update cannot navigate an operator away from the docs context being verified.
ResolveDocsOperatorWritePolicyName
string? ResolveDocsOperatorWritePolicyName()
Source
Resolves the authorization policy used for trusted docs write operations.
Returns
The configured docs operator-write policy when present, otherwise the legacy search-index refresh policy, or null when neither policy is configured.
Remarks
AppSurfaceDocsDiagnosticsOptions.OperatorWritePolicy is preferred so hosts can give rebuild actions a neutral docs-operator policy name. The fallback to AppSurfaceDocsDiagnosticsOptions.SearchIndexRefreshPolicy preserves compatibility for applications that already opted into the older refresh endpoint.
GetRebuildAuthorizationStatus
string GetRebuildAuthorizationStatus(SearchIndexRefreshAuthorizationFailure? failure)
Source
Converts an authorization failure reason into the compact health-page rebuild status.
Parameters
failureThe failure reason returned by the shared operator authorization helper.
Returns
Unauthorized for user/auth policy denials; otherwise Unavailable.
Remarks
Missing policies and missing services are shown as unavailable rather than unauthorized so operators can distinguish host configuration problems from account permission problems.
GetRebuildAuthorizationDescription
string GetRebuildAuthorizationDescription(SearchIndexRefreshAuthorizationFailure? failure)
Source
Builds the visible health-page explanation for a disabled rebuild action.
Parameters
failureThe failure reason returned by the shared operator authorization helper.
Returns
A short operator-facing explanation for why the rebuild form is disabled.
Remarks
The text avoids exposing policy internals while still separating sign-in, authorization, missing-policy, and unavailable-service cases for production troubleshooting.
GetHarvestRebuildRequestResultQueryValue
string GetHarvestRebuildRequestResultQueryValue(AppSurfaceDocsHarvestRebuildRequestResult result)
Source
Gets the stable query-string marker used to carry a harvest rebuild request result into the observatory view.
Parameters
resultThe rebuild request result returned by the shared harvest coordinator.
Returns
The stable marker for a known result, or an empty string for an unknown enum value.
ParseHarvestRebuildRequestResult
AppSurfaceDocsHarvestRebuildRequestResult? ParseHarvestRebuildRequestResult(string? value)
Source
Parses the stable query-string marker used by the harvest observatory rebuild status.
Parameters
valueThe rawrebuildquery-string value.
Returns
The matching rebuild request result, or null for missing, blank, or unknown markers.
Remarks
Unknown values are ignored instead of displayed so stale links and hand-written URLs do not create misleading operator status copy.
IsSafeDocsHarvestReturnUrl
bool IsSafeDocsHarvestReturnUrl(string? url, string? pathBase, string docsRootPath)
Source
Determines whether a harvest completion return URL stays inside the active docs surface and cannot loop back to the harvest observatory.
Parameters
urlThe candidate app-relative return URL.pathBaseThe active request path base, ornullwhen none is mounted.docsRootPathThe configured current docs root path.
Returns
true when url is a safe app-relative path under the current docs root and not _harvest or _harvest/rebuild; otherwise false.
TryNormalizeReturnUrlPathForValidation
bool TryNormalizeReturnUrlPathForValidation(string path, out string normalizedPath)
Source
Normalizes a harvest return URL path for containment checks after rejecting encoded traversal tricks.
Parameters
pathThe path portion of the candidate return URL.normalizedPathThe normalized decoded path when validation succeeds.
Returns
true when path can be decoded without revealing control characters, backslashes, raw dot segments, or encoded dot-segment traversal; otherwise false.
TryValidateReturnUrlPercentEscapes
bool TryValidateReturnUrlPercentEscapes(string path)
Source
Rejects malformed percent escapes and double-encoded sensitive path tokens in a harvest return URL path.
Parameters
pathThe path portion of the candidate return URL.
Returns
true when every percent escape is syntactically valid and does not hide a second encoded control character, slash, backslash, or dot; otherwise false.
IsSafeAppRelativeUrl
bool IsSafeAppRelativeUrl(string? url)
Source
Determines whether a return URL is safe to use as an app-relative navigation target.
Parameters
urlThe candidate URL to validate.
Returns
true when url is a non-empty app-relative path that starts with /, is not protocol-relative, is not slash-backslash rooted, and contains no backslashes or control characters; otherwise false.
Remarks
Use IsSafeAppRelativeUrl(string?) before echoing request-derived return URLs into redirects, links, or local path decisions. The helper intentionally rejects null, empty or whitespace input, paths that do not start with /, //, /\, any \ character, and any control character such as CR or LF. Checks are ordinal and culture-invariant; the method does not URL-decode or normalize Unicode, so callers should decode first when validating encoded input.
NormalizeReturnUrlPath
string NormalizeReturnUrlPath(string? path)
Source
Normalizes a local return URL path into the shape used by docs containment checks.
Parameters
pathA candidate path, path base, or docs root value.
Returns
A leading-slash path with trailing slashes removed, or an empty string for blank input.
Remarks
The helper only trims and applies slash shape. It does not decode, collapse dot segments, or decide whether a path is safe; callers must validate encoded input and traversal before using the normalized value for redirects.
ContainsDotSegment
bool ContainsDotSegment(string path)
Source
Detects dot-segment traversal after splitting a return URL path on literal path separators.
Parameters
pathThe raw or once-decoded path being evaluated for harvest return navigation.
Returns
true when any path segment is exactly . or ..; otherwise false.
Remarks
This helper is deliberately segment-based instead of substring-based so ordinary filenames containing dots remain valid. Callers must invoke it for both the raw path and the once-decoded path because percent-encoded traversal can be hidden until after decoding.
ContainsSensitivePercentEscape
bool ContainsSensitivePercentEscape(string path)
Source
Detects percent escapes that still hide sensitive path bytes after the first decode pass.
Parameters
pathThe once-decoded path being checked for nested percent escapes.
Returns
true when path contains an encoded control character, slash, backslash, or dot; otherwise false.
Remarks
Harvest return URLs are decoded once before redirect decisions. A second decoder later in the stack could otherwise turn split encodings such as %25%32%65 into %2e and then into .. Rejecting sensitive nested escapes keeps the docs-only containment decision stable across downstream URL normalization.
StartsWithHarvestRouteSegment
bool StartsWithHarvestRouteSegment(string relativePath)
Source
Determines whether a docs-root-relative path begins with the reserved harvest route segment.
Parameters
relativePathThe decoded path relative to the current docs root.
Returns
true when the first non-empty path segment is _harvest; otherwise false.
Remarks
Duplicate separators can appear after decoding an otherwise local URL. The harvest loop guard therefore evaluates the first meaningful segment instead of comparing the unnormalized relative path string.
IsHex
bool IsHex(char value)
Source
Determines whether a character can participate in a percent-encoded byte.
Parameters
valueThe candidate hexadecimal digit.
Returns
true for ASCII hexadecimal digits; otherwise false.
Remarks
URL percent escapes are byte-oriented and ASCII-only. This intentionally does not accept Unicode lookalikes or culture-sensitive digits, and callers should only pass characters adjacent to a literal %.
HexToByte
int HexToByte(char high, char low)
Source
Converts two validated hexadecimal digits into the byte value represented by a percent escape.
Parameters
highThe high-order hexadecimal digit.lowThe low-order hexadecimal digit.
Returns
The decoded byte value from 0 through 255.
Remarks
Callers must guard both inputs with IsHex(char) before calling this helper. The method is intentionally allocation-free because it runs in the return-URL validation hot path.
HexValue
int HexValue(char value)
Source
Maps a single validated hexadecimal digit to its numeric value.
Parameters
valueThe ASCII hexadecimal digit to convert.
Returns
An integer from 0 through 15.
Remarks
This helper assumes value has already passed IsHex(char). Passing any other character falls into the uppercase branch and produces a meaningless value, so validation order is part of the contract.
IsUnderPath
bool IsUnderPath(string candidatePath, string rootPath)
Source
Determines whether a normalized candidate path is at or below a normalized root path.
Parameters
candidatePathThe normalized candidate path to evaluate.rootPathThe normalized root path that bounds the allowed surface.
Returns
true when candidatePath equals rootPath or is a child path; otherwise false.
Remarks
The root path / accepts any absolute app path. Non-root comparisons are ordinal-ignore-case to match the existing docs route policy. Callers must pass already-normalized, decoded, traversal-free paths.
PrefixSearchIndexPathsForPathBase
DocsSearchIndexPayload PrefixSearchIndexPathsForPathBase(DocsSearchIndexPayload payload, string? requestPathBase)
Source
Prefixes rooted search-document paths in a cached search-index payload for the active request path base.
Parameters
payloadThe cached search-index payload whose document paths may need rebasing.requestPathBaseThe current request path base that should be prepended when it is non-empty and not/.
Returns
The original payload when no rewrite is needed; otherwise a cloned payload whose rooted DocsSearchIndexDocument.Path values are prefixed with the normalized path base.
Remarks
This helper operates on the typed DocsSearchIndexPayload contract, which always exposes a top-level DocsSearchIndexPayload.Documents list. It does not inspect or reshape arbitrary JSON payloads, so callers that hold raw JSON without a top-level documents array must deserialize or otherwise handle that mismatch before calling this method. Within the typed payload, only rooted DocsSearchIndexDocument.Path values are rewritten. Non-rooted, blank, or otherwise unchanged values such as guide.html are returned as-is, so callers should supply leading-slash browser paths for docs-local navigation when rebasing is expected. For example, rebasing a payload that contains documents[0].path = "/docs/guide.html" with /some-base/ produces /some-base/docs/guide.html. The supplied path base is trimmed of trailing slashes before concatenation, and the method is idempotent when requestPathBase is null, empty, or /.
IsApiSurfaceDoc
bool IsApiSurfaceDoc(DocNode doc)
Source
Determines whether a documentation node should render with the API reference reading surface.
Remarks
Non-Markdown generated docs use the API surface by default because AppSurface Docs cannot assume extensionless generated pages have authored prose rhythm. Markdown docs opt into the API surface only when page_type normalizes to api or api-reference. Extensionless authored content is therefore treated as generated API/reference content unless a future harvester exposes a stronger authorship signal.
IsApiSurfacePageType
bool IsApiSurfacePageType(string? pageType)
Source
Determines whether raw page-type metadata explicitly requests the API reference reading surface.
Remarks
Values are normalized with DocMetadataPresentation.NormalizeToken(string?) before comparison, so values such as api_reference and API Reference match api-reference. Null or blank metadata does not opt a Markdown document into the API surface.
IsMarkdownDoc
bool IsMarkdownDoc(string path)
Source
Determines whether a source path represents authored Markdown by checking known Markdown filename suffixes.
Remarks
Matching is case-insensitive and currently recognizes .md and .markdown. Callers should pass a non-null harvested path; extensionless paths are intentionally treated as non-Markdown generated docs.