AppSurface Search
Guide

issue-183-concept-preserving-version-switching

Source of truth

View source Edit this page

Last updated

URGENT, outside this plan

None.

Current QA plan: andrew-main-eng-review-test-plan-20261030-213607.md (/Users/andrew/.gstack/projects/forge-trust-Runnable/andrew-main-eng-review-test-plan-20261030-213607.md). Current task aggregation: aggregated-tasks.md (/Users/andrew/Projects/forge-trust/runnable__mercury/.gstack/tmp/autoplan/aggregated-tasks.md). Artifact links in the retained baseline task section identify earlier review artifacts; these current links include the accepted owner decisions and engineering refresh.

Implementation plan

Design: Preserve the guide concept when switching versions

Generated by /office-hours on 2026-10-27 Branch: main Repo: forge-trust/AppSurface Issue: #183 Status: Owner decisions accepted on 2026-10-30; approved scope is planning only Mode: Builder Approved: 2026-10-27, D8/A; engineering review requested with R2-1 carried forward.

Problem Statement

The reader's requirement is: “I’m reading a guide and switch versions. Keep me on the same concept even if the page moved, with a clear notice. If it doesn’t exist in that version, say so and offer a useful fallback.”

The current archive links to release roots. Detail pages have no concept-preserving selector. A canonical route identifies a page inside one release, but a reorganization can change that route without changing the subject. This design adds the missing switch experience and explicit cross-release identity. It extends the published-version catalog, frozen archive routes, and approved alias design.

What Makes This Useful

A reader can move from “Initial setup” in one release to “Getting started with configuration” in another and understand why the title and location changed. A fallback identifies itself as a fallback and keeps navigation and search inside the selected version. This is an intended reader benefit; no user study or measured demand is claimed.

Approved Decisions and Constraints

  • Approach B: optional stable concept_id metadata for new guides, with an explicit legacy bridge outside immutable archives. This reduces repeated mapping work as releases accumulate.
  • Preserve canonical-path continuity where no explicit identity evidence contradicts it. Resolve authored identities before applying that baseline. Never guess from titles, body text, source filenames, translations, or redirect aliases.
  • Keep translation_key for locale relationships and redirect_aliases for routing within a release. Neither becomes a cross-release identity.
  • Preserve exact release identity, archive verification, startup catalog snapshots, alias policies, custom route roots, named instances, and PathBase behavior. No runtime Git or network discovery.
  • Deliver hosted and static output together. Existing release publication remains the distribution path. Engineering D6/A reuses Versioning.MaxRewrittenFileSizeBytes for the new per-artifact switch budget as well as its existing HTML/search-input role, retaining the current default/range and input rejection. D7/A approves bounded hosted guide recovery; D8/A stops static publication before docs-asset/Pages uploads on switch-artifact overflow; D9/A selects a bounded hosted choice-page 503 recovery response, or an empty 503 when that complete page cannot fit.
  • The accepted wireframe covers unchanged, moved, and absent counterparts. Insufficient legacy evidence and unavailable releases receive different explanations.

Approaches Considered

  • A, pairwise release maps: rejected as the main model because ordinary identity would need repeated declarations as releases grow.
  • B, stable IDs plus a legacy bridge: selected for simple new authoring and explicit treatment of existing releases.
  • C, a central ledger for all identities: rejected because it separates every new guide's identity from its source and can drift.

Metadata and Integrity Contract

Add optional DocMetadata.ConceptId, authored as concept_id in the existing front matter or metadata sidecar. Use existing precedence and merge rules; null/blank means unspecified. Trim surrounding whitespace. Nonblank IDs contain 1–128 lowercase ASCII letters, digits, dots, underscores, or hyphens, start and end with a letter or digit, and compare ordinally. Example: guides.configuration.setup. Reject invalid values rather than silently changing their spelling. Renaming or moving a guide retains its ID; a different subject receives a different ID. An ID is independent of a release, URL, title, and language.

Require at most one public canonical page per (instance, release, locale, concept_id). Locale variants may share an ID. Preserve the selected locale using the existing normalized locale identity; do not silently change language. A split or merge needs an authored canonical hub that preserves the old concept, or recovery; no automatic winner.

New exports write .appsurface-docs-concepts.json, schema appsurface-docs-concepts-v1, before release integrity hashing. Each public canonical entry records canonicalRoutePath, locale, nullable conceptId, title, emitted anchor IDs, and optional sectionLanding eligibility (default false). Export sectionLanding: true only for the actual resolved public-section landing winner, not every raw authored candidate (engineering D4/A). Routes must exactly reference public winners in the frozen route manifest. Physical destinations come from its validated artifact mapping, never from concatenating a route into a filename. The sidecar covers every public canonical page. identityCoverage is complete only when every entry has an authored ID; otherwise it is partial. Missing sidecar means legacy/unknown coverage. A missing ID in a partial index cannot prove absence.

Add optional ConceptMapPath / conceptMapPath and ConceptMapSha256 / conceptMapSha256 to the public version catalog descriptor. Both must be supplied together; the default is no bridge. The path is relative to the existing trusted release root and uses its confinement rules. The digest pins the exact bridge bytes. The bridge schema appsurface-docs-concept-map-v1 has concept records containing conceptId and release memberships. Each membership requires an exact version, its releaseManifestSha256, normalized locale, and either kind: page with canonicalRoutePath, or kind: absent. A page may also declare fallbackSectionRoute for recovery and optional sectionLanding eligibility (default false); historical eligibility must be explicitly authored in a digest-bound page membership (engineering D4/A). Absence may declare a fallback section but cannot itself be a landing page. The same concept record associates old routes with new identities without editing the old trees.

Every membership is checked against the pinned, verified release and public frozen route inventory. Page and section destinations must exist and be ordinary serveable canonical targets. Membership keys must be unique; a bridge cannot override an embedded different ID, erase an embedded counterpart with absent, or assign one legacy page to multiple concepts. Conflicts disable the affected mapping group, never choose by declaration order. Authored publication fails early on these errors. At host startup, a bad optional concept sidecar or bridge disables affected concept resolution with operator diagnostics; healthy archive serving keeps its existing availability rules. A bad release pin or frozen route manifest still follows the existing archive failure behavior.

Do not disclose hidden release IDs, paths, titles, or mapping errors through reader projections. Operator diagnostics explain the artifact and problem; public recovery uses stable generic wording. Catalog and concept snapshots load once per host lifetime. Changing the bridge or its pin requires restart/redeploy.

Switch Resolution and Reader States

Introduce one pure internal resolver shared by hosting and publication. Engineering D5/A places the combined identity loader/index, resolver, neutral records, and small encoded switch-markup projection in the existing ReleaseContracts package as internal BCL-only core code with no added core dependencies. Keep filesystem staging, HTTP/CSP handling, and DOM rewriting in caller-owned adapters; do not move the entire Docs HTML rewriter or reference the Docs web project from Release tooling. Document the expanded internal ownership and prove clean packed-Docs restore and the Release dependency boundary. The resolver input is a validated source page identity, exact source release pin, normalized locale, and selected target exact release. Its result contains a reason code, optional validated target identity, optional validated section fallback, source return identity, and presentation text keys. A URL projection uses DocsUrlBuilder and frozen physical mappings for every destination.

Resolve in this order:

  1. Resolve a selected alias to its eligible exact release using the existing catalog. A hidden or unavailable target cannot be matched. Freeze the selected exact target in the switch result; never borrow a different release.
  2. Determine the source's embedded or explicitly bridged concept. Consult that concept's target membership/index. A unique counterpart wins even when the old path now belongs to another concept. Explicit absence wins over path continuity. No matching ID in a complete target identity index also establishes absence.
  3. If identity has not settled the result, preserve the same public canonical route when it exists in the target and no authored evidence contradicts continuity. Different known concept IDs, explicit absence, or complete-index absence are contradictions. One missing ID alone is not. This is the retained route-compatibility rule, not inferred semantic identity.
  4. Otherwise return unknown when evidence is incomplete. Conflicting identity evidence returns an ambiguous recovery reason. Never turn either into “removed” or pick a similar page.

All resolution and absence checks are locale-specific. A counterpart available only in another locale is not a match. Explicit absence for the selected locale, or no matching ID in that locale within a complete target identity index, produces known absence; partial or legacy evidence produces unknown. The notice names both target version and selected language. Canonical-path compatibility also requires an established target locale matching the source; unverifiable locale identity produces unknown. If the target has no documentation in the selected locale, omit section, search, and home actions that would change language; offer the public version archive and original guide instead. Never switch to the target's default language implicitly.

Outcome Reader behavior
Same canonical counterpart Open the target page; the selected exact version stays visible. No move warning.
Explicit same concept at a different route or title Open the target page with a notice above the title: “This guide has a different location in 2.0.” Show the original title/version and a return link. A title-only change uses “This guide has a different title in 2.0.” Comparing two endpoints does not claim when the change first happened.
Known absent Show “This guide is not available in 1.0.” Explain that it has no documented counterpart in that release, without claiming that the product feature itself is absent.
Unknown or ambiguous Show “No known counterpart in 1.0.” Explain that available identity metadata does not establish one.
Target unavailable Show a distinct availability message and archive/return actions; do not claim that this guide is missing. Hidden targets receive a generic unavailable result with no hidden metadata.

Recovery stays in the selected available version and locale. Offer a valid authored fallbackSectionRoute first. Otherwise select only a public, serveable target-release/selected-locale page with verified sectionLanding eligibility whose canonical path is a strict non-root ancestor of the source canonical path; ignore fragments and use existing route normalization/comparison. Choose the ancestor with the most path segments. Missing or ambiguous eligibility yields no section action, followed by the existing search/home ladder (engineering D4/A). Label this as browsing recovery, not a counterpart. Then offer version-local search when that release has valid search, and its documentation home. Omit unavailable actions rather than linking to live or source-version search. Always offer the original verified guide as a separate return action. Invalid mapping data uses unknown recovery, never a broken target link. Required proof includes authored override, nearest qualified ancestor, ordinary-page/root exclusion, missing legacy flags, ambiguity, and locale isolation.

Preserve the source fragment only when it names an anchor recorded in the target's emitted anchor inventory. A target's canonical fragment takes precedence. Otherwise land at the target page top. Clear unrelated source query parameters. Canonical tags always identify the exact target page and exclude transition context.

Hosted and Static Presentation

Keep the control beside the documentation identity, show the exact selected version, and use encoded plain text for all labels and notices. The archived guide body remains release-specific. Navigation, search, assets, and canonical metadata use the target tree; only the explicit return action crosses back.

Build a public, deterministic switch plan from eligible catalog entries and validated identity snapshots. Source keys are SHA-256 identifiers of instance, exact release pin, locale, and canonical route; they are public lookup keys, not authorization tokens. The plan exposes only public page projections. Precompute identity dictionaries and embed only each page's required validated projection as inert, encoded JSON. The runtime performs no plan fetch, network lookup, or scan of archived HTML. Engineering D3/A approved this page-local data delivery while retaining connect-src 'none'.

Use a packaged external switch runtime, authorized only by its build-owned exact SHA-256 in CSP and matching integrity attribute (engineering D3/A). Enable sandbox scripts only when that verified package runtime is present; add no host-wide script source, unsafe-inline, unsafe-eval, or dynamic code loading. Keep connect-src 'none' and the existing sanitizer. Enhanced version links go directly to the resolved target with docsFrom=<sourceKey> as optional transition context. The destination derives notice and return data from its embedded validated projection and its own identity; it never trusts a caller-provided URL or notice. Unknown keys, keys with no valid relationship to that destination, and stale pins produce no claimed match. Hosted rendering can emit the validated notice immediately; static output adds it above the title through the same runtime. A bad context must not prevent the destination guide from being read. Full-page loads and RazorWire navigation both initialize the notice accessibly; keyboard selection and focus behavior receive browser checks. Include hosted/static and negative CSP regression proof for the packaged runtime, its integrity attribute, blocked unrelated/changed scripts, and blocked connections. The exact runtime hash must be updated and verified with each runtime change.

Provide a plain-link alternative at {RouteRootPath}/versions/switch/{sourceKey}.html, one generated version-choice page per source page, with results for eligible target versions. Each result states any move or recovery condition before its counterpart/section/search/home links. This supplies the complete flow without JavaScript, with an extra confirmation link before opening a moved counterpart. Enhanced controls bypass that extra page. The reserved switch namespace is checked for collisions during assembly. Query transition context is optional; direct guide links still work.

For a hosted standalone choice request with a verified public, still-readable source guide, engineering D9/A selects HTTP 503 on switch-artifact overflow. Return a complete recovery page within the same artifact budget: “Version switching is unavailable for this guide.” Include only independently verified public archive and original-guide return links. If the complete page cannot fit, return an empty 503 response; never emit partial choices or infer concept absence from rejected data. Use existing operator diagnostics and retain the existing source-availability, input-rejection and runtime/CSP rules. This does not change static publication's D8/A failure gate or promise an automatic recovery time.

Hosted output extends the existing verified HTML rewriter with immutable switch context and keeps input integrity checks before transformation. Original archive files and release hashes stay unchanged. Legacy shells receive the control through that presentation boundary when a canonical public page can be established; unsupported HTML remains readable and receives an operator diagnostic instead of guessed injection.

For hosted guide presentation, engineering D7/A rejects an oversized switch artifact as a whole while keeping an otherwise valid guide readable. Show a complete budget-checked notice, “Version switching is unavailable for this guide.” Include only independently verified public archive and original-guide return actions. If that complete notice/actions fragment cannot fit, omit the optional UI and retain existing navigation; log the failure through the operator diagnostic boundary. Unusable metadata cannot establish concept absence or restore a quarantined compatibility match. Preserve the old HTML/search-input rejection and D3/A runtime/CSP rules. Standalone choice-page overflow and static publication disposition are not selected by this hosted recovery decision.

Static release assembly consumes the same resolver and generates the plan, choice pages, packaged runtime, and transformed serving copies. Keep original exact archives separately as verification inputs. Record a distinct serving-output inventory binding each derived output to its source release pins and transformation version. Never label transformed bytes with the original archive hash. Validate all generated destination links beneath the selected mount, including aliases, custom roots, and subpath deployments. This serving-output stage is required for static support, not an assumed hosted endpoint.

Engineering D8/A requires static publication to fail with an operator diagnostic before docs-asset or Pages uploads when a new switch artifact exceeds the approved limit. Preserve original verification inputs and previously published content; do not publish a partial serving inventory or replace affected static surfaces with degraded recovery outputs. Maintain the current rejection of invalid authored metadata and digest failures. Maintainers must reduce the artifact or deliberately adjust the existing option within its unchanged supported range before retrying. Verify the failure gate and document the correction path; this review does not run a publication or upload.

Accepted wireframe: repository image. The full local preview is /Users/andrew/.codex/visualizations/2026/10/24/01a16cc8-0b72-79a2-8ec6-fae615ab3144/issue-183-wireframe.png. It is a layout sketch; its example versions and guide text are illustrative.

Success Criteria and Verification

  • A two-release fixture demonstrates unchanged paths, moved paths, renamed titles, a reused old path belonging to a different ID, explicit absence, partial legacy uncertainty, and unavailable targets. Each produces the documented reason and notice.
  • Cover duplicate IDs, malformed sidecars, conflicting bridge memberships, stale release/bridge digests, unsafe destinations, missing physical files, hidden targets, locale separation, a counterpart only in another locale, a target without the selected locale, and canonical/fragment handling through supported test seams.
  • Run the same resolver fixtures against hosted output and a served static publication. Follow enhanced and plain-link flows; verify keyboard behavior and notice placement on full and partial navigation. Inspect target-local navigation/search and exact canonical URLs.
  • Verify original archive inventories and SHA-256 values remain unchanged after serving-output generation. Reject broken authored targets during export/assembly. Exercise valid and invalid custom roots, named instances, alias entry points, and PathBase.
  • Document public metadata/catalog additions, the sidecar and bridge schemas, resolver outcomes, publication artifacts, authoring decisions, and pitfalls in package/repository guides. Add regression tests, formatting, and relevant solution coverage under the repository contribution rules.

These are implementation acceptance criteria. This office-hours session performs source review and wireframe rendering only; it does not claim passing feature tests.

Distribution and Next Steps

Ship coordinated Docs, export, and Release-tool changes through the existing package/release pipeline. Older archives stay valid without concept metadata and receive route continuity or honest unknown recovery. No alias-policy redesign, hot reload, cross-version search, fuzzy matching, broad archive-model unification, or migration timeline is included.

  1. Define and document the metadata, sidecar, bridge, validation, and resolver contracts with small immutable fixture releases.
  2. Wire the hosted selector, notices, and recovery to that resolver and existing URL/rewrite boundaries.
  3. Implement static serving-output generation and plain-link choice pages from the same plan, then verify both surfaces together.

The approved product and engineering contracts remain binding, with the actual owner reply rec on 2026-10-30 accepting gate1 UC1/A, d1/A and d2/A. UC1/A chooses static meta CSP without sandbox and hosted HTTP sandbox; d1/A settles C1 by omitting a separate copy-link control; d2/A settles Dsg1 by selecting the disclosure with ordinary links. Earlier provisional-until-owner-answer phrases in retained phase obligations describe their previous approval state and are superseded by this actual answer. Engineering refresh verifies these amendments; plan approval grants no implementation or PR authorization.

Current binding decisions

  • Office Hours D6/B and D8/A: the full product contract above remains approved.
  • Engineering D2/B: use the combined identity loader/index, pure resolver and static-publication generator; preserve the full scope.
  • ER1 / D3/A: amend the plan to authorize only the build-owned switch runtime by exact SHA-256 CSP hash and matching integrity attribute, with sandbox script permission only when that verified runtime is present. Embed validated page-local switch data as inert encoded JSON; retain connect-src 'none', no host-wide script source, no unsafe-inline, no unsafe-eval, and no dynamic code loading. Preserve enhanced/plain-link coverage and carry hosted/static and negative CSP regression proof into the implementation plan. Retain the combined-loader arrangement, archive integrity and locale rules. This approves the plan amendment only, not implementation.
  • R2-1 / D4/A: add optional sectionLanding eligibility to new concept-index pages from the resolved public landing winner, default false, with explicit digest-bound legacy page eligibility. Keep a valid authored fallbackSectionRoute first. Otherwise choose the eligible public serveable target-release/selected-locale strict non-root canonical ancestor with the most path segments, ignoring fragments and using existing route normalization/comparison. Ambiguity or missing eligibility skips the section action and continues to the existing search/home ladder and original-guide return. Carry explicit-override, nearest-ancestor, ordinary/root-exclusion, missing-legacy, ambiguity and locale regression proof forward. Preserve the combined-loader arrangement and all D3/A runtime/CSP rules.
  • ER2 / D5/A: put the combined identity loader/index, pure resolver, neutral records and small encoded switch-markup projection in the existing ReleaseContracts package as internal BCL-only code, with no added core dependencies or new first-party support package. Keep filesystem staging, HTTP/CSP and DOM rewriting in caller-owned adapters. Do not move the whole Docs HTML rewriter or reference the Docs web project from Release tooling. Document expanded internal ownership and require clean packed-Docs restore plus Release dependency-boundary proof. Preserve D2/B structure, D3/A runtime/data policy, D4/A fallback policy, all original reader states, selected release/locale, and archive integrity. Plan only; implementation remains unauthorized.
  • ER3 / D6/A: reuse Versioning.MaxRewrittenFileSizeBytes as the per-artifact UTF-8 budget for each concept-index file, bridge file, added switch fragment plus embedded data, and standalone choice-page artifact. Preserve the 4 MiB default, minimum 1 byte, maximum 32 MiB, and current HTML/search-input rejection behavior. Require bounded reads, generated-byte counts, no partial/truncated acceptance, matching hosted/static propagation, exact/over-limit classification tests, and documentation of the expanded option scope and coupled tuning. Preserve all D2/B–D5/A contracts.
  • ER4-H / D7/A: for new switch-artifact overflow in hosted guide presentation, discard the whole oversized artifact and keep the otherwise valid guide readable. Show the complete budget-checked generic notice: “Version switching is unavailable for this guide.” Include only independently verified public archive and original-guide return actions. If the complete fragment cannot fit, omit optional UI and retain existing navigation. Require operator diagnostics and fitting/nonfitting recovery, guide-readability, and unchanged HTML/search-input rejection regression tests/docs. Preserve the D6/A budget/default/range, whole-artifact rejection, selected version/locale, matching/fallback, D5/A core/adapters and D3/A runtime/CSP contracts.
  • ER4-P / D8/A: when a new switch artifact exceeds the approved limit during static publication, fail with an operator diagnostic before any docs-asset or Pages upload. Preserve original verification inputs and previously published content; do not publish a partial serving inventory or substitute degraded static recovery outputs. Require failure-gate tests and correction-path documentation; retry requires reducing the artifact or deliberately adjusting the existing option within its approved range. Preserve all prior D2/B–D7/A contracts, including normal static scope, whole-artifact rejection, archive integrity, invalid-metadata rejection, selected version/locale, matching/fallback, core/adapters and runtime/CSP.
  • ER4-C / D9/A: for switch-artifact overflow at a hosted standalone choice request with a verified public, still-readable source guide, return HTTP 503 with a complete recovery page inside the same byte budget. Use the generic notice, “Version switching is unavailable for this guide.” Include only independently verified public archive and original-guide return links; if the complete page cannot fit, return an empty 503. Reject the whole oversized artifact, never truncate choices or claim concept absence, and use the existing operator diagnostic boundary. Require fitting/nonfitting response, safe-link, guide-readability and unchanged input-rejection tests/docs. Preserve D6/A's limit/default/range, D7/A hosted guide recovery, D8/A static failure gate, source verification, selected version/locale, identity/fallback, D5/A core/adapters and D3/A runtime/CSP rules. No automatic recovery time, implementation or publication is authorized.

Required integration details

Necessary integration work for already-approved contracts:

  • DocMetadata.Merge explicitly reconstructs the record (Models/DocModels.cs:272–307), so add ConceptId at every metadata parse/merge/projection boundary. Invalid authored identity must remain diagnostic evidence; do not silently replace it with a fallback ID. Keep translation keys and route aliases independent.
  • Frozen PublicRoutePaths includes aliases (AppSurfaceDocsFrozenRouteManifest.cs:35–37). Give production consumers a read-only canonical-entry projection rather than accepting any alias as an index page; tests use that real internal API through existing friend assemblies, with no reflection or test-only seam.
  • Keep detached immutable BCL snapshots after JSON parsing. Do not retain JsonElement values backed by a disposed JsonDocument or expose mutable arrays/dictionaries through read-only interfaces.
  • Remember invalid/conflicting identity groups so route compatibility cannot silently turn them into a match. Preserve existing OrdinalIgnoreCase logical route lookup/continuity and segment comparisons, ordinal concept IDs and the normalized locale graph; preserve exact anchor and verified physical-path rules. Never infer locale or concept identity from a title or filesystem path.
  • D4/A landing eligibility comes from the resolved public landing winner. A legacy bridge can add a missing historical fact; it cannot override a contradictory published identity/eligibility fact.
  • Generate the source-key hash from an unambiguous normalized tuple, preserving the distinction between a known nonlocalized surface and unknown legacy locale evidence. Keep caches instance-scoped and snapshot-scoped; unknown caller keys do not create cache entries.
  • Use one encoded switch-markup projection from the accepted core. Caller adapters retain trusted route/physical mapping, PathBase, CSP and DOM responsibilities; no options-heavy renderer framework, new public core API, or wholesale rewrite extraction.
  • Package the actual runtime bytes whose SHA-256 and integrity values are emitted, and extend asset embedding/export/validation. Retain the existing generated-search asset workflow and unrelated theme/CSP rules.

Architecture and required failure handling

metadata adapters / frozen canonical facts / pinned legacy bridge
                         |
          ReleaseContracts combined identity loader/index
                         |
          ReleaseContracts pure resolver + encoded projection
                     /                         \
Docs verified HTML adapter                Release serving generator
(source digest checked first)             (separate staging and inventory)
         |                                         |
HTTP/CSP + DOM insertion                 static DOM + choice pages + assets
                     \                         /
            exact-hash runtime / inert embedded data
            plain-link alternative remains available
New path Realistic failure Required handling and proof
Metadata into frozen index Merge reconstruction drops ConceptId, or raw losing landing candidates are exported Extend parser/merge/export tests; export resolved canonical winners and resolved landing winners only.
Combined loader/index Bridge pin or canonical destination disagrees with the selected archive Preserve archive verification first; reject/quarantine invalid identity data with honest recovery and operator diagnostics.
Pure resolver Reused old path beats an explicit concept match, or partial data claims absence Table-driven resolution tests distinguish identity, compatibility, absence, ambiguity and locale outcomes.
Hosted presentation An untrusted script/context runs, or a notice survives navigation to another guide D3/A CSP/SRI/data tests plus real full/partial/history browser flows.
Static generator Derived copies are stamped with original hashes or partially staged output appears complete Verify original inputs separately, generate serving inventory from completed derived bytes, validate links, and abort failed assembly.
Package delivery Core code introduces the Docs web graph into Release, or packed Docs cannot restore the internal support code D5/A clean packed-consumer and Release dependency-boundary proof.

The existing release workflow runs docs verify-archive after staging (.github/workflows/release-publish.yml:313–329). Keep that check on the untouched verification-input trees and add serving-output validation before publication; never run the original-archive verifier against transformed copies as though their bytes were original.

Algorithm and artifact budgets

Required algorithmic work already follows the approved immutable snapshot and precomputed dictionary design: load validated indices once per host snapshot; resolve selected identity by dictionary rather than HTML scans or a retained all-page/all-version result matrix; walk route depth for fallback; generate static pages incrementally. Static link count necessarily scales with source pages times eligible releases. Per-page embedded data includes only the validated transitions needed by that page; unknown request keys do not populate a cache. Do not invent latency, throughput or memory benchmarks.

The D6/A budget counts UTF-8 bytes per concept-index file, bridge file, complete added switch fragment with embedded data, and standalone choice-page artifact. It uses the current effective MaxRewrittenFileSizeBytes: default 4 * 1024 * 1024 bytes, supported range 1 through 32 * 1024 * 1024 bytes. Keep the old HTML/search-input rejection contract unchanged. Use bounded reads (including growth during read), detached snapshots, and generated-byte counting before accepting a complete artifact; never accept a truncated prefix. Pass the same effective value to the portable core and both hosted/static callers, document the expanded option scope, and classify exact-limit/over-limit cases in tests. No new switch-specific setting was selected; raising the existing option also raises this new budget. The per-artifact guard is not a total-catalog or total-process memory promise.

Planned coverage map

Use existing xUnit, friend-assembly access and Playwright infrastructure; do not use reflection or create a test-only production seam.

PLANNED CODE PATHS                                      USER FLOWS
[NEW/GAP G1] metadata parse/merge                       [NEW/GAP G6] [->E2E] hosted switch
  missing/blank -> unspecified                            same / moved / title-only
  valid -> preserved; invalid -> diagnostics               selected release + locale + canonical
[NEW/GAP G2] combined index loader                      [NEW/GAP G7] [->E2E] static serving
  missing -> legacy; present -> pin/schema check           derived copies + inventory + valid links
  canonical coverage / duplicates / conflicts              enhanced + plain-link/no-JS parity
  partial vs complete; locale and landing facts         [NEW/GAP G8] [->E2E] navigation lifecycle
[NEW/GAP G3] resolver                                      keyboard / focus / full + partial
  alias -> exact eligibility; hidden/unavailable           back-forward / repeated activation
  explicit match -> route/title comparison                 bad/stale context -> no claimed match
  absent evidence -> absent; route compatibility        [NEW/GAP G9] packed consumer + boundary
  contradiction/incomplete/conflict -> honest recovery     Docs restores; Release has no web graph
[NEW/GAP G4] recovery
  authored section first -> valid target
  else eligible strict non-root ancestor -> deepest
  missing/ambiguous -> search/home; original return
[NEW/GAP G5] presentation trust/fragment rules
  input verified before transform
  exact script hash + integrity; inert JSON; no fetch
  canonical fragment first; validated target anchor
  caller URL/notice strings never become authority

Required QA assertions

Affected Pages/Routes

  • Catalog-backed guide pages at the configured exact-version and alias mounts: counterpart selection, notices, return links and target-local navigation/search.
  • Proposed {RouteRootPath}/versions/switch/{sourceKey}.html: plain-link version choices and honest recovery, including JavaScript-disabled use.
  • Generated static serving copies under their configured public mounts: the same outcomes and usable relative asset links on subpath deployments.
  • Existing live Docs, archive, search, canonical alias and asset routes: preserve their approved behavior while adding the version-switch presentation.

Key Interactions to Verify

  • Switch unchanged, moved and renamed guides across two real exported fixture releases; assert selected release/locale, title, canonical URL and notice.
  • Follow known-absent, incomplete-legacy, conflicting and unavailable results; distinguish their explanations and recover only within the selected available version/locale.
  • Exercise authored section override and verified longest strict non-root ancestor fallback, then search/home and original-guide return.
  • Use keyboard selection, full navigation, RazorWire partial navigation, back/forward and JavaScript-disabled plain links without stale notices or duplicate handlers.
  • Serve actual HTTP CSP/SRI and static policy artifacts; run the matching packaged runtime and reject unrelated/changed scripts and network connections.

Critical Paths

  • CP1: Authored metadata passes through front matter, sidecar precedence, merge, canonical export, concept sidecar, and release hashing without losing identity. Value: protects=authored identity reaches the pinned canonical artifact; fails_when=merge drops ConceptId or export hashes before final metadata; why_new=current metadata/export tests have no concept identity; seam=none; no_claim=real maintainer authoring usability
  • CP2: Pure shared resolver returns the exact counterpart and reason for same route, moved route and title-only rename, including reused-path contradiction and valid fragments. Value: protects=deterministic exact-version concept continuity; fails_when=path reuse beats explicit identity or title changes go unexplained; why_new=no existing concept resolver tests; seam=none; no_claim=browser rendering
  • CP3: Known absence, unknown/conflicting metadata and unavailable targets produce distinct recovery, authored section first and then the verified ancestor rule. Value: protects=honest target-local recovery and return; fails_when=unknown becomes absent or an ordinary ancestor is selected; why_new=existing section tests do not switch archives; seam=none; no_claim=human relevance of an authored fallback
  • CP4: Hosted verified HTML receives the correct notice, encoded inert data and exact-hash runtime without changing archive bytes, CSP connection policy or unrelated pages. Value: protects=archive trust before presentation and narrow script execution; fails_when=unverified bytes are rewritten or unrelated script/network runs; why_new=theme CSP coverage has no switch runtime; seam=none; no_claim=static-host headers
  • CP5: Real publication generates serving copies, choice pages and a distinct output inventory; enhanced and plain-link flows agree and all emitted links resolve. Value: protects=static parity and original archive immutability; fails_when=generated links break or derived bytes receive original hashes; why_new=publication tests currently copy archives only; seam=none; no_claim=live GitHub Pages deployment
  • CP6: Browser navigation restores guide, notice, selected version and focus correctly on full/partial/history navigation, with a complete no-JS alternative. Value: protects=reader continuity through actual navigation lifecycle; fails_when=notice sticks to another page or controls attach twice; why_new=existing shell/history tests lack version switching; seam=none; no_claim=all assistive technology/browser combinations
  • CP7: A clean packed-Docs consumer restores the exact ReleaseContracts dependency; Release and the new internal core retain the approved dependency boundaries. Value: protects=portable internal core and usable package graph; fails_when=Release acquires the Docs web graph or packed core is missing; why_new=no existing concept-core boundary proof; seam=none; no_claim=downstream production deployment

Edge Cases

  • E1: Missing/blank IDs remain unspecified; trimming is allowed; invalid spelling/length/type is rejected, including invalid primary metadata with a valid fallback. Value: protects=ID grammar and authored precedence; fails_when=invalid identity is silently normalized or replaced; why_new=extend parser/merge tables for the new field; seam=none; no_claim=all YAML parser behavior

  • E2: Duplicate IDs/memberships, one page assigned to multiple concepts, split/merge ambiguity, explicit absence erasing an embedded match and conflicting embedded IDs cannot select a winner by order. Value: protects=ambiguity stays honest and deterministic; fails_when=first/last declaration wins a conflicting identity; why_new=existing route collisions lack concept groups; seam=none; no_claim=semantic correctness of maintainer-authored IDs

  • E3: Stale bridge/release digests, unsafe routes, missing/case-mismatched/changed files and symlinks cannot become destinations; hidden targets do not leak metadata. Value: protects=pins, confinement and public projection; fails_when=stale or hidden target is exposed as a counterpart; why_new=extend archive tests at the new lookup boundary; seam=none; no_claim=trusted-host filesystem security outside tested paths

  • E4: Same concept only in another locale, no target docs in the selected locale, and unverifiable legacy locale never silently change language. Value: protects=selected locale and honest absence/unknown; fails_when=default locale fills a missing selected-locale counterpart; why_new=locale graph tests do not switch releases; seam=none; no_claim=translation quality

  • E5: Missing sidecar, partial coverage and complete coverage produce the documented outcomes; a complete index must cover the verified canonical inventory rather than aliases or collision losers. Value: protects=absence requires complete verified evidence; fails_when=partial inventory or alias presence proves false absence/match; why_new=extend frozen export tests for concept coverage; seam=none; no_claim=meaning of a concept ID

  • E6: Authored fallback overrides ancestry; longest eligible ancestor wins; ordinary pages, root, raw losing landing candidates and missing legacy eligibility are excluded; ambiguity skips the section action. Value: protects=D4/A fallback selection; fails_when=any prefix or a losing raw landing candidate qualifies; why_new=current section winner tests lack frozen recovery; seam=none; no_claim=automatic semantic relevance beyond declared ancestry

  • E7: Target canonical fragment wins; source fragment is retained only when the target emitted anchor exists; unrelated source query parameters are removed. Value: protects=valid anchors and canonical target identity; fails_when=unverified fragment/query state is copied across releases; why_new=extend URL and mounted-route cases; seam=none; no_claim=external fragment aliases

  • E8: Unknown/stale/mismatched docsFrom values and markup-bearing titles/data cannot produce a claimed match, executable markup or caller-controlled return URL. Value: protects=trusted transition context and inert JSON; fails_when=query strings inject content or control a destination; why_new=no current transition-context implementation; seam=none; no_claim=broader site XSS coverage

  • E9: Missing, blocked or stale runtime assets and disabled JavaScript keep the plain-link flow usable; matching script hashes/SRI run, wrong/missing integrity and unrelated scripts fail. Value: protects=progressive enhancement and exact script trust; fails_when=script failure strands the reader or broad script permission appears; why_new=extend CSP/browser fixtures for the switch; seam=none; no_claim=all CSP implementations

  • E10: Static failure/cancellation never rewrites verification inputs or publishes a partial serving inventory; repeated assembly from identical inputs is deterministic. Value: protects=separate immutable inputs and derived output; fails_when=staging errors mutate archives or leave a valid-looking partial result; why_new=extend publication determinism/failure cases; seam=none; no_claim=remote deployment atomicity

  • E11: Each new artifact is accepted at the effective UTF-8 byte limit and rejected above it, including multibyte text and file growth during reads; no parsed prefix or truncated output is accepted. Preserve the existing default/range and HTML/search-input rejection behavior in hosted/static option propagation. Value: protects=D6/A per-artifact byte budget; fails_when=characters replace bytes or a partial artifact is accepted; why_new=existing rewrite tests do not cover new switch artifacts; seam=none; no_claim=hosted overflow presentation tested separately

  • E12: On hosted guide switch-artifact overflow, the otherwise valid guide stays readable. Show the complete bounded generic notice and only verified public archive/return actions when they fit; otherwise omit optional UI and retain navigation. Keep existing HTML/search-input 404 rejection and runtime/CSP rules. Value: protects=D7/A readable guides with bounded recovery; fails_when=optional overflow hides a guide or emits unverified actions; why_new=existing rewrite tests lack switch-overflow recovery; seam=none; no_claim=standalone response tested separately

  • E13: Static switch-artifact overflow fails publication before docs-asset/Pages uploads, with an operator diagnostic. Original verification inputs and published content stay intact; no partial serving inventory or degraded substitute is published. Existing invalid-metadata/digest failures and normal within-limit output remain covered. Value: protects=D8/A static publication failure gate; fails_when=overflow permits uploads or publishes partial output; why_new=existing publication tests do not exercise switch budgets; seam=none; no_claim=live deployment or hosted choice-page behavior

  • E14: Hosted choice-page overflow for a verified public readable source returns a complete bounded 503 recovery page with the generic unavailable notice and only verified archive/return links. If the complete page cannot fit, return an empty 503; keep the guide readable and preserve input 404, locale, integrity and CSP rules. Value: protects=D9/A standalone overflow response; fails_when=overflow emits partial choices or unsafe links or bypasses the cap; why_new=no existing choice-route overflow response tests; seam=none; no_claim=automatic recovery time or live deployment

Existing Tests to Extend

  • Web/ForgeTrust.AppSurface.Docs.Tests/DocModelsTests.cs:139, MarkdownFrontMatterParserTests.cs:110 and DocMetadataFactoryTests.cs: metadata precedence and normalization.
  • FrozenArtifactRouteManifestTests.cs:27, AppSurfaceDocsFrozenRouteManifestTests.cs:166, AppSurfaceDocsReleaseArchiveVerifierTests.cs:298: canonical physical bindings, immutability and integrity.
  • AppSurfaceDocsPublishedTreeHandlerTests.cs:435 and :453, plus AppSurfaceDocsPublishedTreeContentRewriterTests.cs:24: existing CSP, verified rendering and URL projection.
  • Cli/ForgeTrust.AppSurface.Cli.Tests/ProgramEntryPointTests.cs:2397 and :2429: real export-host and route-manifest integration seams.
  • tools/ForgeTrust.AppSurface.Release.Tests/ReleaseToolTests.cs:6452 and :6807: deterministic publication and digest mismatch.
  • Web/ForgeTrust.RazorWire.IntegrationTests/AppSurfaceDocsShellPlaywrightTests.cs:140 and AppSurfaceDocsSearchPlaywrightTests.cs:389: navigation/focus/history fixtures.

Add focused shared-loader/resolver fixture tables in the existing friend test assembly and a real two-archive browser/static fixture. Use xUnit and the existing Playwright infrastructure. No reflection or production seam solely for testing is needed. Keep existing theme bootstrap, SVG sandbox, alias, canonical, localization and archive-integrity assertions.

Verification Order

Run focused Docs, CLI/export and Release tests, asset validation and the two-release browser/static fixture. Perform packed-consumer verification separately from solution coverage, then rebuild at the final unchanged Git HEAD and run the relevant coverage workflow. Existing project learnings prohibit concurrent package verification and coverage in one checkout. This review has not run these feature suites.

Tests to Retire

None. Extend the existing suites; retain their independent archive, package, CSP, routing and locale contracts.

Worktree Parallelization Strategy

This is a future execution plan, not creation of worktrees. Keep two implementation lanes only where modules are disjoint, using isolation: worktree if execution later calls for isolated checkouts.

Step Modules touched Depends on
S1 / T1: portable identity/resolution/projection core ReleaseContracts; its friend test fixtures —
S2 / T2: authoring/export/catalog adapters Docs Models/Services; RazorWire export; CLI Docs S1 contract
S3 / T3a: package and freeze runtime bytes/hash/SRI plus export asset contract Docs assets/packaging and export asset integration S2
S4A / T3b: hosted response/DOM/CSP/navigation integration Docs Controllers/Services/Views; hosted/browser fixtures S3
S4B / T4: static serving generator and publication gate Release publication; CLI publication arguments; release workflow/tests S3
S5 / T5: integrate parity/browser/consumer proof Docs/CLI/Release/PackageIndex test modules; RazorWire integration tests Merge S4A + S4B
S6 / T6: final reference/decision/pitfall docs and validation Package/repository docs; formatter/build/coverage workflows S5 fixtures and merged production scope

Lane A owns S4A; Lane B owns S4B. Launch A + B after S3, merge both, then run S5 and S6. S3 is the asset-contract checkpoint; the full hosted test suite is not a prerequisite for starting the independent static lane. Lane B consumes the frozen runtime contract and does not edit Docs assets. Shared core, CLI and asset-contract edits are sequential; coordinate any later discovered cross-lane changes before merging. Final package/build-sensitive proof runs at the final unchanged HEAD; do not run package verification and solution coverage concurrently in the shared checkout. This yields 2 lanes, 2 parallel work steps and 5 sequential work steps.

Implementation Tasks

Synthesized from this review's findings. These are planned tasks, not dispatched implementation. Checkbox as they ship; no product code has been written in this review.

Machine-readable task artifact: six engineering tasks (/Users/andrew/.gstack/projects/forge-trust-Runnable/tasks-eng-review-20261029-043901.jsonl), serialized with one jq -nc invocation per task. The QA artifact (/Users/andrew/.gstack/projects/forge-trust-Runnable/andrew-main-eng-review-test-plan-20261028-014657.md) contains the corresponding planned assertions.

  • T1 (P1, human: 1–2 days / CC+gstack: 4–8 hours) — ReleaseContracts — Implement the internal combined identity index, pure resolver and encoded markup projection.

    • Surfaced by: ER2; R2-1; ER3; Test G2–G5.
    • Files/modules: tools/ForgeTrust.AppSurface.ReleaseContracts/, Web/ForgeTrust.AppSurface.Docs.Tests/ (new internal filenames remain implementation details).
    • Verify: Extend friend-assembly xUnit tables for validated canonical/locale identity, conflicts, explicit absence, verified ancestor landings, detached snapshots and exact/over-limit UTF-8 classification. New core code is BCL-only and performs no I/O.
  • T2 (P1, human: 1.5 days / CC+gstack: 3–5 hours) — Docs metadata and export — Carry concept identity and resolved landing facts through authoring, export, hashing and catalog adapters.

    • Surfaced by: R2-1; ER2; ER3; Test G1–G3.
    • Files/modules: Web/ForgeTrust.AppSurface.Docs/Models/DocModels.cs, Web/ForgeTrust.AppSurface.Docs/Services/MarkdownFrontMatterParser.cs, Web/ForgeTrust.AppSurface.Docs/Services/DocAggregator.cs, Web/ForgeTrust.AppSurface.Docs/Services/AppSurfaceDocsFrozenRouteManifest.cs, Web/ForgeTrust.AppSurface.Docs/Services/AppSurfaceDocsVersionCatalogService.cs, Web/ForgeTrust.RazorWire.Cli/, Cli/ForgeTrust.AppSurface.Cli/DocsCommand.cs (new internal filenames remain implementation details).
    • Verify: Extend existing Docs and CLI/export fixtures for merge precedence, invalid primary IDs, canonical inventory excluding aliases, resolved landing winners, complete/partial coverage, bridge pins, locale and unchanged original archive verification.
  • T3 (P1, human: 1.5–2.5 days / CC+gstack: 5–9 hours) — Hosted Docs and runtime — Package the exact runtime and implement hosted guides, notices and bounded choice responses.

    • Surfaced by: ER1; ER4-H; ER4-C; Test G5, G6, G8.
    • Files/modules: Web/ForgeTrust.AppSurface.Docs/Controllers/DocsController.cs, Web/ForgeTrust.AppSurface.Docs/Services/AppSurfaceDocsPublishedTreeHandler.cs, Web/ForgeTrust.AppSurface.Docs/Services/DocsUrlBuilder.cs, Web/ForgeTrust.AppSurface.Docs/assets/, Web/ForgeTrust.AppSurface.Docs/Views/, Web/ForgeTrust.AppSurface.Docs/ForgeTrust.AppSurface.Docs.csproj (new internal filenames remain implementation details).
    • Verify: First freeze the actual packaged runtime bytes/hash/SRI and export asset contract. Then verify actual HTTP CSP/sandbox behavior, encoded inert JSON, unrelated/changed script and fetch rejection, full/partial/history flows, no-JS choice pages, D7/A bounded optional UI and D9/A fitting/empty 503 responses; preserve existing HTML/search-input 404 behavior.
  • T4 (P1, human: 1.5–2.5 days / CC+gstack: 5–9 hours) — Release publication — Generate separate static serving output and stop publication before uploads on overflow.

    • Surfaced by: ER2; ER3; ER4-P; Test G7.
    • Files/modules: tools/ForgeTrust.AppSurface.Release/ReleaseDocsPublication.cs, tools/ForgeTrust.AppSurface.Release/ReleaseCommands.cs, Cli/ForgeTrust.AppSurface.Cli/DocsCommand.cs, .github/workflows/release-publish.yml, tools/ForgeTrust.AppSurface.Release.Tests/ (new internal filenames remain implementation details).
    • Verify: Extend deterministic publication tests with two real archive fixtures, original hashes unchanged, separate serving inventory, all destination links validated, bounded artifacts, and switch overflow failing before docs-asset/Pages uploads. Preserve current authored-invalid-data/digest gates and the no-Docs-web dependency boundary.
  • T5 (P1, human: 1–2 days / CC+gstack: 4–8 hours) — Integrated and package proof — Prove hosted/static parity, browser lifecycle and packed dependency boundaries.

    • Surfaced by: ER1; ER2; Test G6–G9; ER3 and all ER4 branches.
    • Files/modules: Web/ForgeTrust.AppSurface.Docs.Tests/, Cli/ForgeTrust.AppSurface.Cli.Tests/, tools/ForgeTrust.AppSurface.Release.Tests/, Web/ForgeTrust.RazorWire.IntegrationTests/, tools/ForgeTrust.AppSurface.PackageIndex.Tests/ (new internal filenames remain implementation details).
    • Verify: Implement the saved QA plan's seven critical paths and fourteen edge-case families through existing xUnit/Playwright/packed-consumer seams. Serve the static fixture, test custom roots/PathBase/aliases/locales and enhanced/plain links, and run a bounded scaled correctness fixture. This fixture repeats the approved resolver/output assertions over a finite larger input, without timing thresholds, throughput targets, memory ceilings or benchmark claims. Keep package verification separate from coverage in the shared checkout; freeze final HEAD before package/build-sensitive verification.
  • T6 (P2, human: 0.5–1 day / CC+gstack: 2–4 hours) — Documentation and final validation — Document all public/internal contracts and complete formatting, warnings and relevant coverage checks.

    • Surfaced by: ER2 ownership; ER3 option scope; all Test gaps and approved overflow contracts.
    • Files/modules: Web/ForgeTrust.AppSurface.Docs/README.md, Cli/ForgeTrust.AppSurface.Cli/README.md, tools/ForgeTrust.AppSurface.Release/README.md, tools/ForgeTrust.AppSurface.ReleaseContracts/README.md, README.md, docs/ (new internal filenames remain implementation details).
    • Verify: Document metadata/catalog API shapes, defaults, constraints, schemas, resolver outcomes, fallback eligibility, CSP/SRI, publication artifacts, coupled size tuning, error recovery and internal adapter ownership with canonical cross-links. Run dotnet format ForgeTrust.AppSurface.slnx, resolve introduced compiler/analyzer/XML-doc warnings, then final focused/package/browser checks and ./scripts/coverage-solution.sh when practical, without concurrent package/coverage runs.

No new tasks from Code Quality Review as independent findings; its necessary approved integration details are included with their owning tasks above.

Refined effort estimate: human team 7–11.5 days / agent-assisted 23–43 hours, summing the six tasks without adding the earlier incremental decision estimates again. The original D2 estimate was 5–8 days / 2–3 days before detailed ownership, error-response and integrated-proof work was itemized. These are planning ranges, not measured performance or a schedule commitment. Two lanes can reduce elapsed time after their explicit prerequisites; they do not remove integration work.

NOT in scope

  • Fuzzy/title/body/source-name inference and automatic split/merge selection were excluded by product approval; identity must remain explicit.
  • A concept timeline, migration diffs, confidence scoring, cross-version search and default-locale substitution are outside this slice.
  • Alias-policy redesign, broad archive-model unification, hot reload and a new public/support-package framework remain excluded.
  • Runtime Git/network discovery, fetched switch plans and broad script permissions are excluded by the snapshot and D3/A trust contracts.
  • Degraded static publication after size overflow was rejected by D8/A. Reducing the artifact or deliberately adjusting the existing supported option is the correction path.
  • Product implementation, new worktrees, commits, tests built for this feature, acceptance suites, benchmarks, a PR, uploads and deployment are not authorized by this planning review.

Consolidated implementation obligations

  • amend the plan to authorize only the build-owned switch runtime by exact SHA-256 CSP hash and matching integrity attribute, with sandbox script permission only when that verified runtime is present. Embed validated page-local switch data as inert encoded JSON; retain connect-src 'none', no host-wide script source, no unsafe-inline, no unsafe-eval, and no dynamic code loading. Preserve enhanced/plain-link coverage and carry hosted/static and negative CSP regression proof into the implementation plan. Retain the combined-loader arrangement, archive integrity and locale rules. This approves the plan amendment only, not implementation.
  • add optional sectionLanding eligibility to new concept-index pages from the resolved public landing winner, default false, with explicit digest-bound legacy page eligibility. Keep a valid authored fallbackSectionRoute first. Otherwise choose the eligible public serveable target-release/selected-locale strict non-root canonical ancestor with the most path segments, ignoring fragments and using existing route normalization/comparison. Ambiguity or missing eligibility skips the section action and continues to the existing search/home ladder and original-guide return. Carry explicit-override, nearest-ancestor, ordinary/root-exclusion, missing-legacy, ambiguity and locale regression proof forward. Preserve the combined-loader arrangement and all D3/A runtime/CSP rules.
  • put the combined identity loader/index, pure resolver, neutral records and small encoded switch-markup projection in the existing ReleaseContracts package as internal BCL-only code, with no added core dependencies or new first-party support package. Keep filesystem staging, HTTP/CSP and DOM rewriting in caller-owned adapters. Do not move the whole Docs HTML rewriter or reference the Docs web project from Release tooling. Document expanded internal ownership and require clean packed-Docs restore plus Release dependency-boundary proof. Preserve D2/B structure, D3/A runtime/data policy, D4/A fallback policy, all original reader states, selected release/locale, and archive integrity. Plan only; implementation remains unauthorized.
  • reuse Versioning.MaxRewrittenFileSizeBytes as the per-artifact UTF-8 budget for each concept-index file, bridge file, added switch fragment plus embedded data, and standalone choice-page artifact. Preserve the 4 MiB default, minimum 1 byte, maximum 32 MiB, and current HTML/search-input rejection behavior. Require bounded reads, generated-byte counts, no partial/truncated acceptance, matching hosted/static propagation, exact/over-limit classification tests, and documentation of the expanded option scope and coupled tuning. Preserve all D2/B–D5/A contracts.
  • for new switch-artifact overflow in hosted guide presentation, discard the whole oversized artifact and keep the otherwise valid guide readable. Show the complete budget-checked generic notice: “Version switching is unavailable for this guide.” Include only independently verified public archive and original-guide return actions. If the complete fragment cannot fit, omit optional UI and retain existing navigation. Require operator diagnostics and fitting/nonfitting recovery, guide-readability, and unchanged HTML/search-input rejection regression tests/docs. Preserve the D6/A budget/default/range, whole-artifact rejection, selected version/locale, matching/fallback, D5/A core/adapters and D3/A runtime/CSP contracts.
  • when a new switch artifact exceeds the approved limit during static publication, fail with an operator diagnostic before any docs-asset or Pages upload. Preserve original verification inputs and previously published content; do not publish a partial serving inventory or substitute degraded static recovery outputs. Require failure-gate tests and correction-path documentation; retry requires reducing the artifact or deliberately adjusting the existing option within its approved range. Preserve all prior D2/B–D7/A contracts, including normal static scope, whole-artifact rejection, archive integrity, invalid-metadata rejection, selected version/locale, matching/fallback, core/adapters and runtime/CSP.
  • for switch-artifact overflow at a hosted standalone choice request with a verified public, still-readable source guide, return HTTP 503 with a complete recovery page inside the same byte budget. Use the generic notice, “Version switching is unavailable for this guide.” Include only independently verified public archive and original-guide return links; if the complete page cannot fit, return an empty 503. Reject the whole oversized artifact, never truncate choices or claim concept absence, and use the existing operator diagnostic boundary. Require fitting/nonfitting response, safe-link, guide-readability and unchanged input-rejection tests/docs. Preserve D6/A's limit/default/range, D7/A hosted guide recovery, D8/A static failure gate, source verification, selected version/locale, identity/fallback, D5/A core/adapters and D3/A runtime/CSP rules. No automatic recovery time, implementation or publication is authorized.
  • Keep all specified authoring metadata and schema constraints, canonical/locale precedence, explicit legacy pins, complete/partial coverage rules, verified section/fragment recovery and original archive immutability. Retain the complete CP1–CP7 and E1–E14 assertions and their conditions in this Implementation plan, plus integration details, algorithms, task dependencies and final verification order.
  • C1 is provisionally skipped: use ordinary exact-version links and native browser URL copying, with no separate clipboard-copy control in the current proposed slice. The owner resolves this Taste choice at Autoplan’s final approval gate after CEO, Design, DX and Engineering review; until that actual answer, do not describe the exclusion as settled or implementation as authorized.
  • All seven final binding contracts apply together: R2-1=D4/A; package placement=D5/A; budget=D6/A; hosted guide overflow=D7/A; static overflow=D8/A; standalone overflow=D9/A; runtime/CSP=D3/A. Earlier pending-state language belongs only to the preserved historical review record.
  • Treat the T5 bounded scaled fixture as a finite correctness fixture using the approved resolver/output assertions; add no benchmark, timing, throughput or total-memory guarantee. Preserve C1 as provisional until the final owner gate, and label the current plan accordingly.
  • Document author-maintained identity limits: moves/renames retain the ID, unrelated concepts change it, and valid but wrong IDs can change documented counterpart outcomes. Preserve the approved complete-index absence rule and its “no documented counterpart” explanation.
  • Use existing operator logging/CLI diagnostics with operation, stable reason, relevant artifact/pin and configured budget/observed bytes where available; keep public recovery generic. Document correction/restart/redeploy paths. Finite correctness fixtures record input dimensions and generated artifact bytes for their existing assertions, without a production total-size, latency, throughput or RSS guarantee.
  • Dsg1 provisional Taste: enhance the baseline Change version link as a labeled disclosure button with current exact version/language, aria-expanded/controls and a list of real destination links. Tab and ordinary link activation apply; Enter/Space opens, Escape closes/returns focus; arrow movement never navigates. Include the full choice-page link in the disclosure. With no other eligible version show exact identity and No other versions available, omit an empty interactive picker and retain verified archive navigation. Preserve the accepted control placement and all reason/URL rules; final owner may revise this provisional control treatment before implementation.
  • Dsg2: use identity/exact version/language, reason H1, one plain reason sentence, then verified target-local section/search/home actions in the already-approved authored-first ladder, with original return separately. Distinguish known absent/no documented counterpart, unknown/ambiguous/no known counterpart, availability, and no-selected-locale-docs; the latter two offer only verified archive/original when target-local actions would change language. Omit invalid actions/placeholders, keep the first valid target-local action visually primary, and obey D7/A/D9/A bounded omission/empty503 rules.
  • Dsg3 clarification of approved plain links: source guide opens the choice page; its move explanation precedes an ordinary counterpart link that opens the target directly. There is no second confirmation page, modal or acknowledgement. Enhanced links bypass this choice page and show validated destination context.
  • Dsg4: compact <768px puts identity on its row and change control full width with one wrapping notice/action column; 768–1279px wraps identity/control within the article; >=1280px retains existing global sidebar/local-outline ownership. Preserve full titles/reasons/labels; long locales/releases wrap, logical spacing supports directionality, prose measure is 65–75ch where width allows, and 320px/200% zoom has no horizontal chrome overflow. New targets are >=44px, text >=16px, contrast >=4.5:1 and visible focus not obscured. Do not redesign existing navigation.
  • Dsg4 focus: intentional switching focuses destination H1/main without a valid retained fragment, or the verified fragment target without an extra scroll jump. Describe that focus target with the short validated transition explanation for one announcement, avoiding duplicate assertive live output. Recovery/choice pages focus their reason heading/main; ordinary reading and invalid context do not steal focus. Preserve semantic no-JS headings/links, current-document binding, idempotent full/partial/history initialization and target canonical/fragment rules. Extend the already required CP6/E8/E9 browser assertions for these outcomes at narrow width, keyboard and accessible-description checks.
  • Dsg5: reuse repository Inter and existing --font-inter, --docs-color-surface-raised/panel, --docs-color-border-default, --docs-color-text-strong/default/muted, --docs-color-link/link-visited and --docs-focus-outline roles. Use shared semantic hooks for shared switch/notice DOM/CSS and existing 8/12/16/24px utility spacing; no new public theming API, non-search dependency on search.css, decorative cards, modal or motion requirement. Preserve all prior core/adapter, CSP/SRI, connect-src none, byte-budget, archive and locale requirements. Required visual/accessibility proof remains implementation work, not a claimed result.
  • DX1: classify each new validation/index/map/conflict/overflow/runtime/unexpected-processing diagnostic with stable operator codes docs-concept-id-invalid, docs-concept-index-invalid, docs-concept-map-invalid, docs-switch-artifact-overflow, docs-switch-runtime-invalid and docs-switch-processing-failed. Document them at the new canonical Docs README concept-switch-diagnostics anchor with nearby CLI/Release links. Identify operation/field/artifact and safe expected/actual context, put an exact authorized correction or precise maintainer escalation first or last, and say whether re-export, repin and restart/redeploy are required. Never suggest patching immutable archives, changing a conflicting identity arbitrarily, broadening CSP or raising the coupled cap automatically.
  • DX1 publication: use the existing ReleaseDiagnostic and command failure boundary for stderr plus nonzero exit; retain available structured report output without adding a new mode. Validation before work has no publish side effects. Generation failure may leave disposable staging only, never uploads/changed original or published bytes under D8/A. For dependency/partial work, document side effects and reconcile before retry; correct first, retry once, then escalate. Preserve reader-generic known absence/unknown/unavailable and D7/A/D9/A budget behavior; no internal diagnostic or hidden target data enters reader HTML.
  • DX2: unexpected local CLI failures include compact sanitized exception type/stack frames or caller-owned local log reference. Host failures correlate existing request Activity/TraceIdentifier with protected operator diagnostics/trace; keep safe cause/operation/artifact/version context. Expected validation/overflow has no stack dump. Allowlist context and redact secrets, credential URLs, raw query strings, environment values and nested exception text. Use existing browser-document and logging surfaces; do not add a public JSON API, response header, diagnostic service or universal catch that hides defects. Add caller-level assertions for fix/recovery/code/anchor/channel/redaction and unexpected trace in the existing diagnostic regression fixtures.
  • DX3: deliver one tested two-release moved-guide walkthrough using the already-required fixtures and existing authoring/export/verify/publication workflow. Include complete front matter, optional legacy bridge, full catalog entries with generated exact pins and locale, expected sidecar/serving inventory and reader results, safe disposable paths, full actual command arguments and correction examples. Show unchanged original archive hashes and distinct derived inventory; validate copyable commands/examples against implemented fixtures before declaring them runnable, with no fake digests or live uploads. Link this how-to to schema/reference, authoring decisions, pitfalls, diagnostic anchors and package/repository entry points under T6.
  • Preserve D6/A exactly and document concrete host AppSurfaceDocs:Versioning:MaxRewrittenFileSizeBytes configuration plus the necessary Release docs-publication adapter --max-rewritten-file-size-bytes option, matching the existing verifier spelling. Default remains 4194304 and accepted range 1..33554432 with the existing rejection behavior; carry the same effective value to core/host/static. Label the publication option as new until implemented. An 8388608 example must explain the shared increase to HTML/search-input and switch-artifact budgets; no independent switch-only cap. Cover default, override and invalid range through existing caller tests/docs.
  • Record the primary reader clock from first opening an existing published guide through understanding its selected exact-version counterpart or valid recovery, including orientation and the no-JS choice step. Competitive2–5min is an unmeasured target, not actual rank, automated execution time, new release gate, analytics collection or recurring process. Keep secondary authoring/build time separate. All prior ID/locale/fallback/integrity/runtime/CSP/budget/overflow, core/adapters and Design obligations remain mandatory; C1 and Dsg1 stay provisional for the final owner gate.
  • ENG2: build one combined identity index for each immutable catalog snapshot from all distinct optional concept maps referenced by its descriptors. Resolve and pin each map under the existing trusted root before loading it; deduplicate identical path/digest references and identical validated memberships. Membership authority comes from each exact version, release-manifest pin and normalized locale, not from which descriptor referenced the file. A map referenced only by a source descriptor or only by a target descriptor participates equally for either switching direction. Do not pick a first/last descriptor or map winner. Across-map contradictory memberships, incompatible embedded facts and one canonical page assigned to multiple concepts quarantine the affected complete concept groups, including every group sharing that ambiguous page; suppress compatibility resurrection. Authored publication rejects those conflicts before publication; startup optional-data quarantine preserves healthy archive serving with generic recovery and operator-only diagnostics. Keep malformed/unreadable-map handling conservative under the existing optional-data rule, never infer absence from the rejected data. Add source-only, target-only, duplicate-reference, identical-fact and reversed-order conflicting-map fixtures; document catalog-wide ownership, pinning, restart and conflict behavior in schema/reference/decisions/pitfalls.
  • ENG4: keep core outcomes and validation causes as internal transport-neutral data. Caller-owned adapters map those causes to the approved stable diagnostic codes, CLI envelopes/logging, CSP and HTML behavior; no HTTP, ILogger, filesystem or Docs web types enter the new BCL-only core. Share the schema/precedence/resolver/projection implementation between hosted and static callers; do not create two domain resolvers or a new general error framework. Extend existing friend-assembly and caller tests to verify cause-to-diagnostic mapping, safe context/redaction, immutable detached collections and absence of Release-to-Docs dependencies; document the intentionally internal API and its caller responsibilities.
  • ENG5: extend CP2/CP3/E2/E3/E5 with the catalog-wide cross-map fixtures in focused new internal loader/resolver tests in the existing Docs friend test assembly, plus hosted/static caller parity. Extend existing CP6/E8/E9 for Dsg1 disclosure keys/ordinary links/zero-other-version state, all Dsg2 recovery ordering, Dsg3 direct no-JS counterpart link and Dsg4 narrow-width/zoom/focus/one-description/idempotent full/partial/history behavior. Extend CP1/CP5/E10–E14 and existing CLI/Release diagnostic tests for DX1/DX2 safe remediation/channel/trace, publication default/override/invalid size propagation and the generated-pin two-release walkthrough. Retain all original seven critical-path and fourteen edge-case value cards. Tests remain required future implementation work; no feature suite, browser/security result, coverage percentage or package result is claimed by this planning run.
  • Retain the approved incremental generation, dictionary lookup/route-depth recovery, no retained page-by-version result matrix, no unknown-key cache, bounded UTF-8 reads and whole generated-artifact counting with the existing default/range. Aggregate startup memory and total output capacity remain unmeasured and outside the per-artifact guarantee. Report finite fixture input dimensions and output counts/bytes under the CEO obligation; do not add a new aggregate setting, arbitrary threshold, benchmark gate or capacity promise. Preserve the separate immutable verification inputs and derived serving inventory, and the existing pre-upload static failure gate.
  • Owner gate1 UC1/A is approved by the actual user reply rec on 2026-10-30: static serving uses a meta-delivered CSP with the exact packaged switch-runtime hash and matching SRI, keeps connect-src 'none' and all approved narrow script/data/sanitizer constraints, and explicitly has no CSP sandbox enforcement. Hosted HTTP responses retain the existing sandbox contract, allowing scripts only when the verified package runtime is present. This resolves and supersedes the earlier UC1-pending condition and any assumption that static meta provides sandbox; no header-capable deployment layer is added. All other previous user approvals, byte limits, hosted/static scope, locale/fallback/canonical and overflow contracts remain binding.
  • UC1/A integration: the static caller emits its valid restrictive meta policy at the start of head, before any resource or script that needs the policy; bind the actual packaged runtime bytes/hash/SRI once and preserve the existing verified theme/style policy without broad script sources, unsafe-inline/eval, dynamic loading or connections. Do not emit sandbox, frame-ancestors or report-uri as purported meta enforcement. Keep HTTP policy ownership in the host adapter and static policy/DOM ownership in the Release adapter. Unsupported/colliding shells or inability to emit the complete verified static switch/policy artifact fail static assembly before uploads with the existing operator diagnostic boundary, never rewrite original archives or publish degraded/partial output. Hosted valid-guide optional UI retains D7/A; standalone overflow retains D9/A; old input404 and D6/A budget scope remain unchanged.
  • Required UC1/A proof extends existing CP4/CP5/E9: serve the real static fixture without a synthesized CSP response header, assert the early meta policy is present and accepted, the exact packaged runtime with matching integrity runs, changed/absent integrity or changed runtime/unrelated scripts and attempted connections fail, and no sandbox guarantee is claimed. Independently serve hosted responses and assert the actual sandbox header and same exact-runtime/no-connections constraints. Include generated choice pages and direct guide/full/partial/history/no-JS behavior already required. Reuse actual browser/HTTP fixtures and record evidence kind; a parsed policy string alone is insufficient. Document the hosted/header versus static/meta difference, limits, operator failure correction and the unchanged default/range; this is required proof of the owner-approved security delivery, not a new infrastructure project.
  • Owner gate1 d1/A resolves C1 and d2/A resolves Dsg1, both approved by the same actual rec reply: omit a separate copy-link control and use the specified disclosure with ordinary links, including zero-other-version and no-JS behavior. Earlier provisional-until-owner-answer language in prior phase blocks is satisfied by this answer and no longer leaves these choices pending. Plan approval p1/A is recorded subject to completing this engineering refresh; it grants no feature implementation, commit, PR, upload or deployment.
  • ENG6 exact transition-context follow-through: build each destination page's incoming sourceKey projection incrementally from the validated immutable catalog/identity snapshot and the shared resolver's actual landing decisions. Source candidates come from indexed canonical/concept/locale facts, not caller input or hash reversal. Include only public verified source identities whose validated switch outcome really lands on this exact destination identity, with source key, safe original title/version/return identity and the correct outcome/reason. Bind every row to the destination instance, exact release pin, locale and canonical identity. Counterpart and browsing-recovery relations remain distinct; a fallback never becomes a claimed concept match. At request/runtime lookup, require an exact key in that page's embedded incoming projection and current destination binding; unknown/unrelated/stale or mismatched keys give no transition claim or caller-derived return URL. All evidence is relative to the served immutable snapshot, not a live global catalog check; no fetch, Git lookup or unbounded unknown-key cache.
  • ENG6 budget/performance proof: the incoming rows, outgoing choices, notices and encoded inert data form the complete per-page added switch artifact under D6/A's existing effective UTF-8 budget. Generate one bounded destination projection at a time, using immutable fact indexes and temporary iteration rather than retaining an all-page/all-version outcome matrix. Reject the entire artifact on overflow, including overflow caused only by incoming rows; apply D7/A readable-guide notice/omission, D8/A static pre-upload failure, and D9/A standalone503 rules as applicable. Extend CP2/CP4/CP5/E8/E11–E14 with two or more incoming sources, counterpart versus fallback, unrelated keys, stale/mismatched pins and exact/over-limit incoming-data fixtures in hosted/static parity. Document the inbound/outbound record contract, destination binding, no live freshness claim and existing budget in internal reference/decisions/pitfalls.
  • ENG7 route comparer correction: logical canonical-route lookup, continuity and strict ancestor path-segment comparison use the existing OrdinalIgnoreCase routing semantics after existing normalization (DocRouteIdentityCatalog and frozen canonical dictionaries). Preserve the target winner's authored canonical spelling and verified physical mapping in emitted URLs. A case-only logical route change is continuity rather than a location-change warning unless another actual title/location fact requires a notice. Concept IDs retain ordinal case-sensitive grammar/comparison; emitted anchor IDs and fragments retain their existing exact rules; locale normalization and filesystem/archive confinement rules do not change. Resolve aliases to the actual canonical winner before deriving source keys so alias/request casing never creates new identities. Extend existing route/frozen/resolver parity tables for case-only routes, mixed-case eligible ancestors, segment boundaries and case-distinct invalid IDs/anchors; do not lowercase filesystem paths or accept a missing physical artifact.

Review record

Autoplan Run: main-20261030-002606

Source and active plan: this file. Exact restore point: .gstack/tmp/autoplan/main-autoplan-restore-20261030-002606.md, SHA-256 c070de5cb7e3e90b0efad49b6ac2a5467bdd89839423eac44d087a9c5d26c812, 137303 bytes [measured: snapshot init]. Review only; the user invoked Autoplan on 2026-10-30. Intermediate decisions are authorized by its six principles; existing user decisions remain binding and any proposed reversal waits for the final gate.

Full scope input hash is the restore hash above. The helper reports UI required and DX required; DX has 191 term matches [measured: scope helper] and the explicit developer-tool flag is true. Read journal: CEO methodology offsets/limits 1/600, 601/600, 1201/600, 1801/600, 2401/74, all successfully read through line 2474. No later phase methodology has been loaded.

CEO Step 0: Intake and system audit

Review depth is implementation-ready. GitHub base is main, confirmed by repository default-branch lookup; there is no PR for this main checkout. Working HEAD remains c579bc79; fetched origin/main is cc9b4bb91. The latest source contains no concept_id/ConceptId implementation in Docs/export/ReleaseContracts/publication, and no matching cross-version-switch implementation commit was found. Upstream's selected-path change is a Release preparation verifier/README correction from #900, not this switch feature. No current #183 code or task is claimed as already delivered.

Seven open PR titles/branches were inspected [measured: GitHub query]; none matched #183/docs/concept/version-switch. The git worktree list has this main checkout and three prunable unrelated scratch entries; no worktree mutation is needed. Six stashes belong to other work and are left intact. No touched-source TODO blocks this plan; the related #176 offline alias preflight remains its separate adoption-dependent TODO. The prerequisite #142 is CLOSED [measured: issue query]. The requested #183 is OPEN and explicitly asks for authored remapping, clear move notices, export/assembly validation, fallback tests and authoring guidance; all remain included.

Taste references: use the typed canonical/source distinction in DocRouteIdentityCatalog.cs:8–58, deterministic landing-winner selection in DocAggregator.cs:2535–2561, and verified-input-before-staging order in ReleaseDocsPublication.cs:73–113. Avoid treating frozen PublicRoutePaths (canonical plus aliases) as a canonical identity inventory. Avoid applying WriteRejectedRewrittenFile's whole-document input 404 to optional switch metadata; the approved hosted recovery contracts own that case.

Web/ForgeTrust.AppSurface.Docs/DESIGN.md calls for a focused editorial docs workspace, semantic shared UI hooks, muted metadata and clear distinct failure/empty states. The existing Versions view is a release archive, not a concept-switch control. Retain that design language rather than introducing a new component system.

Prior learnings applied: coverage-gate-committed-diff (confidence 10, 2026-10-29) and package-proof-shared-build-output-mapping (confidence 10, 2026-10-22) require a frozen final candidate and separate package/coverage execution. The brain digest lookups returned no usable product/goals/decisions/profile digest; the user's explicit goal, approved design and durable decision log supply that context without another question.

CEO 0A: Premise challenge

The reader is comparing versions of one guide concept; losing the concept during a documentation reorganization is the pain. The plan addresses that directly through authored identity, a visibly explained move and honest target-local recovery. Doing nothing retains archive-root navigation and leaves readers to rediscover the guide. This is an open-source documentation usability improvement; no measured demand, conversion lift or time-saving result is claimed.

Premise Claim and receipt Verdict
Version/catalog foundation exists AppSurfaceDocsVersionCatalogService.cs:46; DocsController.cs:288–318 Verified source
Route aliases are not cross-release concept IDs DocRouteIdentityCatalog.cs:8–58; frozen manifest PublicRoutePaths at line 37 Verified source distinction
Live landing eligibility has a deterministic winner DocAggregator.cs:2535–2561 Verified source
Current CSP needs the approved runtime authorization change AppSurfaceDocsPublishedTreeHandler.cs:31,899; D3/A Verified source; future integration still needs proof
A shared portable package boundary already exists ReleaseContracts.csproj:3–23 and existing Docs/Release references Verified source
Existing size default/range are fixed AppSurfaceDocsOptions.cs:1170–1180 Verified constants: 4 MiB; 1 byte–32 MiB
Reader benefit is plausible, not measured User goal and #183; no observed usage study provided Explicit hypothesis, not a factual success claim

Unverified factual premises: 0. Future behavior, performance and demand remain unmeasured and are labeled accordingly.

Landscape research: Aside readiness returned NEEDS_ASIDE; read-only web fallback used official sources. Docusaurus separates a document ID from its URL, and its version navigation can preserve a constant ID. Antora's alias documentation distinguishes redirect aliases from page-version relationships. Material for MkDocs versioning provides corresponding-page switching. These are prior art, not dependencies or proof of AppSurface behavior.

Three-layer synthesis: [Layer 1] stable authored identity plus exact versioned URLs is established practice. [Layer 2] current tools expose version selectors, banners and aliases, but their conventions do not supply AppSurface's digest-bound historical mapping and trust boundary. [Layer 3] immutable archived bytes and incomplete historical identity require a separate pinned bridge and distinct absent/unknown/unavailable states; a guessed redirect would undermine the reader's trust. No competitor migration or extra framework is needed.

CEO 0B: Existing code leverage

Reuse catalog snapshots, frozen public canonical/artifact mappings, the locale graph, URL construction and verified rewriting. Add one combined identity loader/index and pure resolver to the existing internal ReleaseContracts boundary, with small encoded presentation projection and caller-owned adapters. Extend existing export and publication rather than creating a second release pipeline. The new public switch route is proposed work; it does not exist in today's controller or route catalog.

Current complexity remains approximately 25 production files and three behavior components [estimated: prior D2/B task decomposition]; test/docs changes are additional. The current six-task estimate is human team 7–11.5 days / agent-assisted 23–43 hours [estimated, not benchmarked]. These are planning ranges, not limits or delivery promises. No new database, hosted background worker, live identity-discovery network request or support package is required. Default publication grouping remains one PR per wave because no ship policy exists [measured: policy-file check]; this is planning, not authorization to create a PR.

REALITY: premise-table pass factual claims have source receipts; user-value and runtime outcomes remain hypotheses, unverified=0 Web/ForgeTrust.AppSurface.Docs/Services/AppSurfaceDocsVersionCatalogService.cs:46 REALITY: already-done pass fetched origin/main cc9b4bb91; no concept identity implementation or matching landed switch commit Web/ForgeTrust.AppSurface.Docs/Controllers/DocsController.cs:288 REALITY: surface-check pass docs export, docs verify-archive and release docs-publication exist; source-key switch route is explicitly new Cli/ForgeTrust.AppSurface.Cli/DocsCommand.cs:36 REALITY: binding-decisions pass retain actual D2/B–D9/A; no settled contract is reopened docs/designs/issue-183-concept-preserving-version-switching.md:25 REALITY: numbers pass source constants re-read; file/time counts are estimates, no measured latency claimed Web/ForgeTrust.AppSurface.Docs/AppSurfaceDocsOptions.cs:1170 REALITY: deferred-asks pass #183 requested remapping, notices, validation, tests and authoring docs remain included docs/designs/issue-183-concept-preserving-version-switching.md:103 REALITY: pr-count pass no ship-policy file; default one PR per wave, no shipping authorization .gstack/tmp/autoplan/issue183-run-context.json:1

CEO 0C: Dream state mapping

CURRENT                      THIS PLAN                     12-MONTH IDEAL
exact archives + aliases  ->  explicit concept continuity -> maintainers routinely retain IDs
release-root navigation      visible moves + safe recovery  across reorganizations; readers
no cross-release identity    hosted and static parity        trust exact-version links

This slice creates the identity seam and authoring guidance needed for that ideal without making semantic guesses. Its remaining adoption risk is maintainer discipline: IDs and legacy memberships must reflect intended concepts. Publication validation can prove route/pin consistency, not that two prose guides mean the same thing.

CEO decision ledger

ID and owner Contract and evidence Current Proposed Status Exact approval and scope
BASE / product Stable IDs plus explicit pinned bridge, exact release/locale and full hosted/static scope Approved Preserve approved Office Hours D6/B and D8/A; full product design above
ARR / structure Combined loader/index, pure resolver, static generator Approved Preserve approved Engineering D2/B; organization only
ER1 / runtime Build-owned exact hash + SRI; embedded validated data; connect-src none Approved Preserve approved Engineering D3/A
R2-1 / fallback Authored fallback first; verified nearest strict non-root ancestor landing Approved Preserve approved Engineering D4/A
ER2 / ownership Internal BCL-only core in ReleaseContracts; caller-owned I/O, HTTP/CSP and DOM Approved Preserve approved Engineering D5/A
ER3 / bytes Reuse existing option, 4 MiB default, 1 byte–32 MiB range, no partial acceptance Approved Preserve approved Engineering D6/A
ER4-H / hosted guide Bounded notice/actions or optional UI omission; valid guide readable Approved Preserve approved Engineering D7/A
ER4-P / publication Stop before docs-asset/Pages uploads on switch-artifact overflow Approved Preserve approved Engineering D8/A
ER4-C / choice route Complete bounded 503 recovery page or empty 503; verified actions Approved Preserve approved Engineering D9/A
C0 / review layout Historical reviews currently share Implementation plan Separate current requirements from verbatim history, without a behavior change approved Autoplan P3/P5 authorizes mechanical presentation repair; exact restore and baseline replacement required
C1 / adjacent copy affordance Exact canonical URLs already support native browser copying Skip a new copy button for this slice provisionally skipped (Taste) Autoplan P4/P5; final gate can override, current scope retained

Mode: SELECTIVE EXPANSION, selected by Autoplan's explicit phase override; no mode question asked. The normal >15-file heuristic would suggest reduction, but this invocation preserves the user's approved scope. The handoff helper ran and its exact Mode line was sent to the user.

CEO 0F–0G: Scope and delight scan

Minimum sufficient delivery is the approved identity contract plus both adapters, runtime/plain-link presentation, validation and regression proof. Removing static output, locale isolation, trust checks or no-JS recovery would cut explicit accepted behavior. The combined loader already reduces organizational overhead; no general renderer framework or central identity platform is needed.

The 10x ambition is simple: reorganizing documentation should no longer make the reader restart their version comparison. That ambition comes from reliable identity and truthful recovery, not a larger version-history product. Future timeline/search features remain excluded.

Opportunity Felt reader experience Incremental scope / effort / risk Disposition
Original title/version beside return I know exactly where I came from Already approved; no expansion Keep D8/A product notice contract
Visible exact version and language I can trust which release/language I am reading Already approved; no expansion Keep version/locale contract
Validated fragment continuity Switching keeps my section when it actually exists Already approved; no expansion Keep anchor inventory rule
Working plain-link recovery A blocked script does not strand me Already approved; no expansion Keep no-JS and D7/A–D9/A contracts
Copy exact-version permalink button I can copy a citation in one click New 3–4-file affordance [estimated], human S / agent S, low risk; browser address bar already offers copying C1: provisionally SKIPPED, Taste at final gate

C1 comparison: Current has no copy control. A adds a copy control plus accessible success/failure fallback and tests, preserving exact URLs/CSP/no-JS behavior; B defers that optional affordance to a TODO after demonstrated citation friction; C skips it and uses native browser URL copying. All columns preserve the baseline switch behavior, matching, budgets, static support and tests. A adds UI/runtime/test maintenance for a convenience rather than the stated continuity goal; B adds an unproven backlog item; C is recommended under P4/P5. Effort for each nonimplementation option is S with zero implementation work. The provisional C outcome is a Taste item because both convenience and UI restraint are reasonable and the added scope crosses 3–4 files.

C0 comparison: Current retains historical analysis among reviewable implementation text. The selected mechanical repair moves that history verbatim to Review record and retains all approved product contracts, detailed integration requirements, test assertions, budgets, failure handling, tasks and ordering in the current Implementation plan. No content is erased: the exact restore and baseline replacement record retain original bytes. This creates an independent review input without presenting earlier reviewer conclusions as implementation requirements.

Decision Audit Trail

# Phase Decision Classification Principle Rationale Rejected
A1 CEO Normalize review layout with exact history/requirement retention Mechanical P3, P5 Blind reviewers need the current implementation contract, not previous verdicts; no product scope changes Leave prior review narrative in Implementation plan
A2 CEO Skip a separate exact-version copy button Taste P4, P5 Existing exact canonical URL and browser copying serve citation; keep continuity UI focused Add 3–4-file copy affordance or an unproven TODO

The final human approval gate has not run. No product implementation, PR or publication is authorized.

CEO accepted obligations and exact baseline transformation

  • amend the plan to authorize only the build-owned switch runtime by exact SHA-256 CSP hash and matching integrity attribute, with sandbox script permission only when that verified runtime is present. Embed validated page-local switch data as inert encoded JSON; retain connect-src 'none', no host-wide script source, no unsafe-inline, no unsafe-eval, and no dynamic code loading. Preserve enhanced/plain-link coverage and carry hosted/static and negative CSP regression proof into the implementation plan. Retain the combined-loader arrangement, archive integrity and locale rules. This approves the plan amendment only, not implementation.
  • add optional sectionLanding eligibility to new concept-index pages from the resolved public landing winner, default false, with explicit digest-bound legacy page eligibility. Keep a valid authored fallbackSectionRoute first. Otherwise choose the eligible public serveable target-release/selected-locale strict non-root canonical ancestor with the most path segments, ignoring fragments and using existing route normalization/comparison. Ambiguity or missing eligibility skips the section action and continues to the existing search/home ladder and original-guide return. Carry explicit-override, nearest-ancestor, ordinary/root-exclusion, missing-legacy, ambiguity and locale regression proof forward. Preserve the combined-loader arrangement and all D3/A runtime/CSP rules.
  • put the combined identity loader/index, pure resolver, neutral records and small encoded switch-markup projection in the existing ReleaseContracts package as internal BCL-only code, with no added core dependencies or new first-party support package. Keep filesystem staging, HTTP/CSP and DOM rewriting in caller-owned adapters. Do not move the whole Docs HTML rewriter or reference the Docs web project from Release tooling. Document expanded internal ownership and require clean packed-Docs restore plus Release dependency-boundary proof. Preserve D2/B structure, D3/A runtime/data policy, D4/A fallback policy, all original reader states, selected release/locale, and archive integrity. Plan only; implementation remains unauthorized.
  • reuse Versioning.MaxRewrittenFileSizeBytes as the per-artifact UTF-8 budget for each concept-index file, bridge file, added switch fragment plus embedded data, and standalone choice-page artifact. Preserve the 4 MiB default, minimum 1 byte, maximum 32 MiB, and current HTML/search-input rejection behavior. Require bounded reads, generated-byte counts, no partial/truncated acceptance, matching hosted/static propagation, exact/over-limit classification tests, and documentation of the expanded option scope and coupled tuning. Preserve all D2/B–D5/A contracts.
  • for new switch-artifact overflow in hosted guide presentation, discard the whole oversized artifact and keep the otherwise valid guide readable. Show the complete budget-checked generic notice: “Version switching is unavailable for this guide.” Include only independently verified public archive and original-guide return actions. If the complete fragment cannot fit, omit optional UI and retain existing navigation. Require operator diagnostics and fitting/nonfitting recovery, guide-readability, and unchanged HTML/search-input rejection regression tests/docs. Preserve the D6/A budget/default/range, whole-artifact rejection, selected version/locale, matching/fallback, D5/A core/adapters and D3/A runtime/CSP contracts.
  • when a new switch artifact exceeds the approved limit during static publication, fail with an operator diagnostic before any docs-asset or Pages upload. Preserve original verification inputs and previously published content; do not publish a partial serving inventory or substitute degraded static recovery outputs. Require failure-gate tests and correction-path documentation; retry requires reducing the artifact or deliberately adjusting the existing option within its approved range. Preserve all prior D2/B–D7/A contracts, including normal static scope, whole-artifact rejection, archive integrity, invalid-metadata rejection, selected version/locale, matching/fallback, core/adapters and runtime/CSP.
  • for switch-artifact overflow at a hosted standalone choice request with a verified public, still-readable source guide, return HTTP 503 with a complete recovery page inside the same byte budget. Use the generic notice, “Version switching is unavailable for this guide.” Include only independently verified public archive and original-guide return links; if the complete page cannot fit, return an empty 503. Reject the whole oversized artifact, never truncate choices or claim concept absence, and use the existing operator diagnostic boundary. Require fitting/nonfitting response, safe-link, guide-readability and unchanged input-rejection tests/docs. Preserve D6/A's limit/default/range, D7/A hosted guide recovery, D8/A static failure gate, source verification, selected version/locale, identity/fallback, D5/A core/adapters and D3/A runtime/CSP rules. No automatic recovery time, implementation or publication is authorized.
  • Keep all specified authoring metadata and schema constraints, canonical/locale precedence, explicit legacy pins, complete/partial coverage rules, verified section/fragment recovery and original archive immutability. Retain the complete CP1–CP7 and E1–E14 assertions and their conditions in this Implementation plan, plus integration details, algorithms, task dependencies and final verification order.
  • C1 is provisionally skipped: use ordinary exact-version links and native browser URL copying, with no separate clipboard-copy control in the current proposed slice. The owner resolves this Taste choice at Autoplan’s final approval gate after CEO, Design, DX and Engineering review; until that actual answer, do not describe the exclusion as settled or implementation as authorized.
  • All seven final binding contracts apply together: R2-1=D4/A; package placement=D5/A; budget=D6/A; hosted guide overflow=D7/A; static overflow=D8/A; standalone overflow=D9/A; runtime/CSP=D3/A. Earlier pending-state language belongs only to the preserved historical review record.
  • Treat the T5 bounded scaled fixture as a finite correctness fixture using the approved resolver/output assertions; add no benchmark, timing, throughput or total-memory guarantee. Preserve C1 as provisional until the final owner gate, and label the current plan accordingly.
  • Document author-maintained identity limits: moves/renames retain the ID, unrelated concepts change it, and valid but wrong IDs can change documented counterpart outcomes. Preserve the approved complete-index absence rule and its “no documented counterpart” explanation.
  • Use existing operator logging/CLI diagnostics with operation, stable reason, relevant artifact/pin and configured budget/observed bytes where available; keep public recovery generic. Document correction/restart/redeploy paths. Finite correctness fixtures record input dimensions and generated artifact bytes for their existing assertions, without a production total-size, latency, throughput or RSS guarantee.
\n## Reviewer Concerns\n\nDisposition: COMPLETED\n\nStop: PASS\n\nNo blocking findings remain. The 1 open minor finding(s) below are recorded for the user, not fixed.\n\n### R2-1 — clarity (minor)\n\n**Problem**\n\n> The recovery rule still says a nearest canonical ancestor landing page may be offered without defining what qualifies as a landing page or how to choose among qualifying ancestors.\n\n**Remedy**\n\n> Clarify the deterministic landing-page eligibility and selection rule, or state that recovery omits this action unless an explicitly authored section is available.\n\n### Prior finding evidence\n\n**R1-2 → R2-1 (persisting)**\n\n> The recovery paragraph still says 'otherwise a nearest existing canonical ancestor landing page may be offered' without an eligibility or selection rule.\n\n\n## Engineering Review Scope\n\nTarget and report: this design, “Preserve the guide concept when switching versions,” issue #183, branch `main`. Engineering review started on 2026-10-27 and completed its four sections on 2026-10-29. The APPROVED status above applies to the product design; engineering remedies and remaining implementation proof are recorded separately below.\n\nFeature answers: office-hours D6/B selected stable concept IDs and a digest-bound legacy bridge; D7/A accepted the wireframe; D8/A approved the design and explicitly requested engineering review carrying R2-1 forward. No feature cut was proposed during the engineering Scope Challenge.\n\nStructure: engineering D2/B, actual answer: “B — use the smaller arrangement with the combined identity loader, preserve the approved scope, and continue engineering review with R2-1 carried forward.”\n\nAccepted scope: one combined identity loader/index for concept sidecars and legacy-bridge inputs, one pure switch resolver, and one static-publication generator. Format-specific parsing remains inside the loader; shared identity validation produces one normalized snapshot. Preserve every approved feature, integrity rule, reader state, locale rule, and required verification. Component/package placement remains an engineering-review question. This is a plan organization decision, not authorization to implement.\n\nArchitecture dispositions through D5/A: ER1 accepted at D3/A, R2-1 resolved at D4/A, and ER2 accepted at D5/A. No architecture remedy remains pending. These plan approvals do not establish implemented behavior; later review sections may identify their own choices.\n\nScope Challenge result: **scope accepted as-is**. The smaller arrangement changes organization, not feature scope. Scope Challenge findings: **No issues found** requiring a feature cut. Disposition: structure accepted at engineering D2/B; R2-1 carried to Architecture review.\n\nPlanning estimates from D2: approximately 25 production files and three new behavior components for the selected arrangement; tests and documentation are additional. Human-team effort 5–8 days / agent-assisted effort 2–3 days. These are estimates, not measurements or a schedule commitment; file ownership and task inventory will refine them during review.\n\n## Engineering Evidence and Reality Checks\n\nThe review uses checkout `c579bc7920e35c8c883891400ea3065319bf769e`. Refreshing `origin/main` found `ebb562be6`; the intervening commits change Config/Evidence, with no diff in the selected Docs, RazorWire export, or Release publication paths. No concept-switch implementation was found in those paths. This is a source audit, not executed feature evidence.\n\n| Premise | Evidence receipt | Verdict |\n| --- | --- | --- |\n| Existing routes and artifacts remain the authority inside one release. | `Services/AppSurfaceDocsFrozenRouteManifest.cs:35` and `:563` record route/alias/artifact data. | Verified |\n| Live section landing eligibility already exists. | `Models/DocModels.cs:109`; `Services/DocAggregator.cs:2535` resolves the winning landing doc. | Verified |\n| Frozen route records do not publish section-landing eligibility. | `Services/AppSurfaceDocsFrozenRouteManifest.cs:563` lists the stored fields. | Verified |\n| Hosted HTML is checked before rewriting. | `Services/AppSurfaceDocsPublishedTreeHandler.cs:702` gates the call at `:707`. | Verified |\n| The proposed external switch runtime can use the current archive CSP unchanged. | `Services/AppSurfaceDocsPublishedTreeHandler.cs:31` and `:899` permit no such runtime. | Contradicted; ER1 |\n| Docs and Release already share a framework-independent dependency. | Their project files reference `tools/ForgeTrust.AppSurface.ReleaseContracts`; that project has no ASP.NET Core framework reference. | Verified |\n| Proposed reader behavior and static output already pass tests. | They have not been implemented. | Not claimed |\n\nSource paths in this table are relative to `Web/ForgeTrust.AppSurface.Docs`, except the explicitly named project references. Premise table: `unverified=0` for the factual claims above; proposed behavior remains future work, not a measured result.\n\nThe linked case requests explicit remapping, a move notice, export/assembly target validation, fallback interaction coverage, and authoring guidance. The approved design includes all of them. Fuzzy matching, default cross-version search, and alias-policy redesign remain excluded by the case. Existing TODOS were checked; none blocks or replaces #183. This review creates implementation task descriptions only; no product code, worktree, commit or PR has been created. Absent a repository ship policy, retain the skill's default one-PR wave as a planning assumption, not permission to ship.\n\nPrior-art classification: **[Layer 1]** reuse the BCL JSON/cryptography facilities; **[Layer 2]** extend existing catalog, verified archive, URL and release-publication seams; **[Layer 3]** the application-specific concept identity/resolution contract. No new framework is needed. [Microsoft's static-file guidance](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/static-files?view=aspnetcore-10.0) describes file delivery rather than cross-release identity; [Antora's page-version selector](https://docs.antora.org/antora/3.1/navigation/) is comparable prior art, not a dependency. Existing exact-version and alias route registries were inspected; the switch route in the design is proposed and does not exist today.\n\nREALITY: premise-table finding factual premises checked; current-policy runtime compatibility is contradicted, unverified=0 Web/ForgeTrust.AppSurface.Docs/Services/AppSurfaceDocsPublishedTreeHandler.cs:899\nREALITY: already-done pass fetched main; selected-path diff is empty and no concept switch has landed ebb562be6\nREALITY: surface-check pass existing Docs/export/publication surfaces inspected; switch route explicitly proposed tools/ForgeTrust.AppSurface.Release/ReleaseCommands.cs:527\nREALITY: binding-decisions pass product approval and combined-loader answer retained; remedies remain separate docs/designs/issue-183-concept-preserving-version-switching.md:156\nREALITY: numbers pass D2 counts and effort remain estimates, not measured throughput or fixed scope docs/designs/issue-183-concept-preserving-version-switching.md:166\nREALITY: deferred-asks pass linked case obligations retained, exclusions remain explicit docs/designs/issue-183-concept-preserving-version-switching.md:109\nREALITY: pr-count pass default one-PR wave is recorded as a planning assumption, with no implementation or shipping authorization docs/designs/issue-183-concept-preserving-version-switching.md:184\n\n## Architecture Review\n\n- **ER1 [CRITICAL] (confidence: 9/10), native reviewer, `AppSurfaceDocsPublishedTreeHandler.cs:899`:** the proposed external runtime is blocked by the current CSP; a network-loaded switch plan would also be blocked. This is a contradiction in the proposed integration, not a claim that existing pages are broken.\n- **R2-1 [INFORMATIONAL] (confidence: 9/10), carried from spec review, original design line 77:** automatic ancestor recovery lacked a definition of eligible landings and tie-breaking. Live sections have authored landing winners, while frozen routes omit that fact. Engineering D4/A resolves the plan with verified landing eligibility and a longest strict non-root ancestor rule; implementation proof remains required.\n- **ER2 [INFORMATIONAL] (confidence: 9/10 for the source boundary), native reviewer and independent in-host audit:** package placement was open. ReleaseContracts is already shared by both callers and contains internal release-content composition; a full Docs web-project dependency is unnecessary. Engineering D5/A accepts internal BCL-only core/projection code there, with caller-owned I/O, HTTP/CSP and DOM adapters, no added core dependencies, and packed-consumer/dependency-boundary proof.\n\nProposed flow; shared core ownership is now fixed by D5/A:\n\n```text\nauthor metadata + explicit legacy bridge\n |\n pinned archive verification\n |\n combined identity loader/index\n |\n pure switch resolver\n / \\\nhosted verified static-publication generator\nHTML presentation + separate serving inventory\n \\ /\n page-local switch data + plain links\n |\n exact-hash browser runtime + inert embedded data [D3/A]\n```\n\nApproved failure handling carries forward: invalid identity groups yield honest recovery; release-integrity failures retain existing archive denial; changed static inputs must not be stamped with the source archive hash. The optional concept-syntax degradation rule applies only after the required archive/digest boundary succeeds. It is not permission to accept tampered or unpinned bytes. This clarification implements the already-approved integrity contract, with regression proof required; no new archive-availability policy is selected here.\n\nDispositions: ER1 accepted at engineering D3/A; R2-1 accepted at engineering D4/A; ER2 accepted at engineering D5/A. No engineering remedy is approved by the scope answer alone. Accepted amendments are reflected in Metadata and Integrity, Switch Resolution, and Hosted and Static Presentation above; feature implementation remains unauthorized.\n\n### Bounded CSP mechanism evidence\n\nAfter engineering D3/A, a local browser probe used a meta CSP containing only an exact external-script hash, matching SRI, and `connect-src 'none'`. The exact script ran once; the same URL without integrity, an unrelated script, and tampered bytes were blocked. Encoded inert JSON round-tripped a closing-script payload without execution, and a fetch was blocked. Observed result: `trustedCount=1`, `unrelatedRan=false`, `tamperedRan=false`, `injectedRan=false`, `embeddedDataRoundTrips=true`, `fetchBlocked=true` (engine: gstack browse).\n\nRetained source/result evidence: `/Users/andrew/.gstack/projects/forge-trust-Runnable/issue183-eng-evidence/csp-probe-e7ECMd/`. This checks browser hash/SRI, meta CSP and inert-data mechanics only. It does not verify HTTP/sandbox headers, hosted archive handling, static publication, RazorWire navigation, or actual switch behavior; those remain required implementation proof.\n\n\n## Architecture Review Conclusion\n\nThree architecture findings were addressed in the plan: ER1 at D3/A (exact-hash runtime and embedded data), R2-1 at D4/A (verified ancestor landings), and ER2 at D5/A (internal ReleaseContracts core). No architecture decision remains pending. These are plan resolutions, not implemented fixes.\n\n```text\nmetadata adapters / frozen canonical facts / pinned legacy bridge\n |\n ReleaseContracts combined identity loader/index\n |\n ReleaseContracts pure resolver + encoded projection\n / \\\nDocs verified HTML adapter Release serving generator\n(source digest checked first) (separate staging and inventory)\n | |\nHTTP/CSP + DOM insertion static DOM + choice pages + assets\n \\ /\n exact-hash runtime / inert embedded data\n plain-link alternative remains available\n```\n\n| New path | Realistic failure | Required handling and proof |\n| --- | --- | --- |\n| Metadata into frozen index | Merge reconstruction drops ConceptId, or raw losing landing candidates are exported | Extend parser/merge/export tests; export resolved canonical winners and resolved landing winners only. |\n| Combined loader/index | Bridge pin or canonical destination disagrees with the selected archive | Preserve archive verification first; reject/quarantine invalid identity data with honest recovery and operator diagnostics. |\n| Pure resolver | Reused old path beats an explicit concept match, or partial data claims absence | Table-driven resolution tests distinguish identity, compatibility, absence, ambiguity and locale outcomes. |\n| Hosted presentation | An untrusted script/context runs, or a notice survives navigation to another guide | D3/A CSP/SRI/data tests plus real full/partial/history browser flows. |\n| Static generator | Derived copies are stamped with original hashes or partially staged output appears complete | Verify original inputs separately, generate serving inventory from completed derived bytes, validate links, and abort failed assembly. |\n| Package delivery | Core code introduces the Docs web graph into Release, or packed Docs cannot restore the internal support code | D5/A clean packed-consumer and Release dependency-boundary proof. |\n\nThe existing release workflow runs `docs verify-archive` after staging (`.github/workflows/release-publish.yml:313–329`). Keep that check on the untouched verification-input trees and add serving-output validation before publication; never run the original-archive verifier against transformed copies as though their bytes were original.\n\n## Code Quality Review\n\n**No additional independently selectable issues found.** The plan is not implemented; this evaluates proposed ownership and current integration seams.\n\nNecessary integration work for already-approved contracts:\n\n- `DocMetadata.Merge` explicitly reconstructs the record (`Models/DocModels.cs:272–307`), so add ConceptId at every metadata parse/merge/projection boundary. Invalid authored identity must remain diagnostic evidence; do not silently replace it with a fallback ID. Keep translation keys and route aliases independent.\n- Frozen `PublicRoutePaths` includes aliases (`AppSurfaceDocsFrozenRouteManifest.cs:35–37`). Give production consumers a read-only canonical-entry projection rather than accepting any alias as an index page; tests use that real internal API through existing friend assemblies, with no reflection or test-only seam.\n- Keep detached immutable BCL snapshots after JSON parsing. Do not retain JsonElement values backed by a disposed JsonDocument or expose mutable arrays/dictionaries through read-only interfaces.\n- Remember invalid/conflicting identity groups so route compatibility cannot silently turn them into a match. Preserve existing ordinal route comparison, ordinal concept IDs and the normalized locale graph; never infer locale or concept identity from a title or filesystem path.\n- D4/A landing eligibility comes from the resolved public landing winner. A legacy bridge can add a missing historical fact; it cannot override a contradictory published identity/eligibility fact.\n- Generate the source-key hash from an unambiguous normalized tuple, preserving the distinction between a known nonlocalized surface and unknown legacy locale evidence. Keep caches instance-scoped and snapshot-scoped; unknown caller keys do not create cache entries.\n- Use one encoded switch-markup projection from the accepted core. Caller adapters retain trusted route/physical mapping, PathBase, CSP and DOM responsibilities; no options-heavy renderer framework, new public core API, or wholesale rewrite extraction.\n- Package the actual runtime bytes whose SHA-256 and integrity values are emitted, and extend asset embedding/export/validation. Retain the existing generated-search asset workflow and unrelated theme/CSP rules.\n\nShared-code rubric: the two proposed callers are the verified HTML rewrite boundary at `AppSurfaceDocsPublishedTreeHandler.cs:702–707` and static staging at `ReleaseDocsPublication.cs:107–113`. The new concept rules do not exist as duplicated authored blocks today. Existing implementation lines removed for this reuse: **0**. New implementation/test line counts are not measured; no net line saving is credited. The benefit is one future identity/projection contract used by both callers. Preserve their different I/O, failure and hosting contracts. D5/A already authorizes this narrow shared ownership; no second extraction decision is needed.\n\nDisposition: carry these necessary code/tests/docs details under office-hours D8/A and engineering D2/B–D5/A. No extra scope or remedy is silently added. Code-quality findings requiring a new decision: **0**.\n\n## Test Review\n\nFramework evidence: `Web/ForgeTrust.AppSurface.Docs.Tests/ForgeTrust.AppSurface.Docs.Tests.csproj` references xUnit; existing browser fixtures use Playwright. Reuse those and existing friend-assembly access. No new test framework or production seam is proposed.\n\nThe dedicated source/test reads and independent test inventory show these foundations:\n\n| Existing assertion family | Evidence | Assessment |\n| --- | --- | --- |\n| Metadata precedence and parse normalization | `DocModelsTests.cs:139`; `MarkdownFrontMatterParserTests.cs:110` | Behavioral assertions exist; new ConceptId cases are gaps. |\n| Frozen mappings and defensive snapshots | `FrozenArtifactRouteManifestTests.cs:27`; `AppSurfaceDocsFrozenRouteManifestTests.cs:166` | Existing edge/boundary contracts; concept metadata is new. |\n| Verified archive serving and CSP | `AppSurfaceDocsPublishedTreeHandlerTests.cs:435,453` and malformed-bootstrap cases | Existing positive/negative security assertions; switch hash/SRI/data is new. |\n| Real Docs export configuration | `ProgramEntryPointTests.cs:2397,2429` | Existing export-host and route integration; new sidecar/finalization is a gap. |\n| Deterministic publication and digest mismatch | `ReleaseToolTests.cs:6452,6807` | Existing artifact/error contracts; derived serving output is new. |\n| Browser partial navigation/history | `AppSurfaceDocsShellPlaywrightTests.cs:140`; `AppSurfaceDocsSearchPlaywrightTests.cs:389` | Existing actual navigation assertions; switch UI is a gap. |\n\nThese are source-reviewed assertions, not test executions in this review. No existing test proves the new feature or the new core's dependency boundary.\n\n```text\nPLANNED CODE PATHS USER FLOWS\n[NEW/GAP G1] metadata parse/merge [NEW/GAP G6] [->E2E] hosted switch\n missing/blank -> unspecified same / moved / title-only\n valid -> preserved; invalid -> diagnostics selected release + locale + canonical\n[NEW/GAP G2] combined index loader [NEW/GAP G7] [->E2E] static serving\n missing -> legacy; present -> pin/schema check derived copies + inventory + valid links\n canonical coverage / duplicates / conflicts enhanced + plain-link/no-JS parity\n partial vs complete; locale and landing facts [NEW/GAP G8] [->E2E] navigation lifecycle\n[NEW/GAP G3] resolver keyboard / focus / full + partial\n alias -> exact eligibility; hidden/unavailable back-forward / repeated activation\n explicit match -> route/title comparison bad/stale context -> no claimed match\n absent evidence -> absent; route compatibility [NEW/GAP G9] packed consumer + boundary\n contradiction/incomplete/conflict -> honest recovery Docs restores; Release has no web graph\n[NEW/GAP G4] recovery\n authored section first -> valid target\n else eligible strict non-root ancestor -> deepest\n missing/ambiguous -> search/home; original return\n[NEW/GAP G5] presentation trust/fragment rules\n input verified before transform\n exact script hash + integrity; inert JSON; no fetch\n canonical fragment first; validated target anchor\n caller URL/notice strings never become authority\n```\n\nCoverage: **9 planned gap families** are identified, not nine executable branches. All new behavior is unimplemented; executable branch coverage is unknown, and no feature-test percentage is claimed. Existing strong assertions should be extended rather than copied into existence-only tests.\n\nQA artifact: [issue #183 test plan](/Users/andrew/.gstack/projects/forge-trust-Runnable/andrew-main-eng-review-test-plan-20261028-014657.md), containing seven critical paths and fourteen edge-case families, each with a value card. The assertions carry the exact approved contracts, including archive/alias/canonical/locale regression, CSP negatives, fallback rules, static parity, packed dependency proof, D6/A byte-budget classification, D7/A hosted recovery, D8/A static overflow gating and D9/A standalone 503 recovery. No LLM/prompt/eval scope is involved. No tests are proposed for retirement.\n\nDispositions: G1–G9 are required implementation proof under the product approval and D3/A–D5/A, not additional feature choices. Engineering D6/A additionally approves exact/over-limit UTF-8 classification tests for the reused artifact budget; D7/A approves hosted guide recovery and omission-when-unfittable proof; D8/A approves static overflow stopping before uploads, immutable verification inputs and no published partial inventory; D9/A approves complete bounded choice-page recovery with HTTP 503 and an empty 503 when recovery cannot fit. No test-policy decision remains open. Test suites were not run because this is planning-only work.\n\n## Performance Review\n\n**ER3 [INFORMATIONAL] (confidence: 9/10 for the original plan gap):** the original design required bounded loading/output but selected no concrete size limit. Engineering D6/A resolves budget ownership by reusing `MaxRewrittenFileSizeBytes` for new switch artifacts. The current implementation still documents HTML/search input only (`AppSurfaceDocsOptions.cs:1219–1228`), and its response writer encodes transformed output without that input check (`AppSurfaceDocsPublishedTreeHandler.cs:976–986`). The accepted change is planned, not implemented; actual catalog, anchor and generated-output sizes remain unknown.\n\nRequired algorithmic work already follows the approved immutable snapshot and precomputed dictionary design: load validated indices once per host snapshot; resolve selected identity by dictionary rather than HTML scans or a retained all-page/all-version result matrix; walk route depth for fallback; generate static pages incrementally. Static link count necessarily scales with source pages times eligible releases. Per-page embedded data includes only the validated transitions needed by that page; unknown request keys do not populate a cache. Do not invent latency, throughput or memory benchmarks.\n\nThe D6/A budget counts UTF-8 bytes per concept-index file, bridge file, complete added switch fragment with embedded data, and standalone choice-page artifact. It uses the current effective `MaxRewrittenFileSizeBytes`: default `4 * 1024 * 1024` bytes, supported range `1` through `32 * 1024 * 1024` bytes. Keep the old HTML/search-input rejection contract unchanged. Use bounded reads (including growth during read), detached snapshots, and generated-byte counting before accepting a complete artifact; never accept a truncated prefix. Pass the same effective value to the portable core and both hosted/static callers, document the expanded option scope, and classify exact-limit/over-limit cases in tests. No new switch-specific setting was selected; raising the existing option also raises this new budget. The per-artifact guard is not a total-catalog or total-process memory promise.\n\nNo database, query index, LLM or background-worker change is in scope. Verify growth and actual artifact byte counts during implementation using the required real two-release fixtures and a bounded scaled fixture. ER3 is accepted at D6/A. D7/A resolves ER4-H: keep otherwise valid hosted guides readable, reject the whole oversized switch artifact, and render the bounded generic notice with independently verified public archive/return actions when the complete fragment fits; otherwise omit optional UI and retain existing navigation, with operator diagnostics. D8/A resolves ER4-P: stop static publication with diagnostics before docs-asset/Pages uploads, preserve original verification inputs and published content, and never publish a partial serving inventory. D9/A resolves ER4-C: for a verified readable source, return a bounded complete 503 recovery page with verified archive/return links, or an empty 503 if it cannot fit. All four performance findings now have accepted planning dispositions; runtime sizes and implementation proof remain outstanding work, not pending choices.\n\n## Decision ledger\n\n### ER1: Authorize the exact packaged switch runtime\n\nFinding: ER1, CRITICAL, confidence 9/10, `Web/ForgeTrust.AppSurface.Docs/Services/AppSurfaceDocsPublishedTreeHandler.cs:899–901`, native reviewer.\nPlan baseline: office-hours D8/A approved an external runtime with the instruction “Do not relax the existing CSP or sanitizer.” Engineering D2/B preserves that scope. The trust-policy conflict is reopened because actual source contradicts unchanged-policy execution.\nRuntime evidence: legacy HTML uses `script-src 'none'; connect-src 'none'` at line 31. Modern HTML permits only the theme-bootstrap hash. The proposed switch runtime has not been implemented or executed.\n\nMotivating current code, verbatim:\n\n```csharp\n var builder = new StringBuilder(\"sandbox allow-same-origin allow-scripts; default-src 'self'; script-src '\");\n builder.Append(AppSurfaceThemePreferenceCsp.ScriptHash);\n builder.Append(\"'; connect-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'; frame-src 'none'; worker-src 'none'; img-src 'self' data:; font-src 'self'; style-src 'self'\");\n```\n\nThe original office-hours design, before engineering D3/A, said “Use a packaged external switch runtime” and “Do not relax the existing CSP or sanitizer.” [MDN documents hash authorization for external scripts with matching integrity attributes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/script-src). This establishes a supported browser mechanism, not browser verification of this feature.\n\nComparison grid:\n\n| Choice or contract | Current / approval | A: Exact runtime hash | B: Investigate first |\n| --- | --- | --- | --- |\n| Switch-runtime authorization | Not allowed by current archive CSP; remedy pending | Approve exact build-owned SHA-256 in CSP plus matching integrity, with conditional sandbox script permission | Keep current CSP; authorization remains pending |\n| Network connections | Current `connect-src 'none'`; preserve approved security constraint | Keep none; use inert encoded page-local validated data | Keep none; data-delivery mechanism remains pending |\n| General script permission | No host-wide permission for archive scripts | No host-wide source, unsafe-inline, unsafe-eval or dynamic loading | Unchanged; no implementation approved |\n| Enhanced switch and plain-link coverage | Approved at office-hours D8/A | Preserve both; verify matching-hash success and negative policy cases | Preserve approved requirement; investigate without cutting it |\n| Combined loader, resolver, static generator | Approved engineering D2/B | Fixed | Fixed |\n| Archive integrity and locale behavior | Approved office-hours D8/A | Fixed | Fixed |\n| R2-1 ancestor fallback | Pending | Pending | Pending |\n| Package placement | Pending | Pending | Pending |\n| Work and bounds | No feature implementation authorized | Future plan change with hosted/static and negative CSP proof; incremental estimate human 0.5–1 day / agent 1–3 hours | One read-only compatibility probe plus source review, then return; estimate human 1–2 hours / agent 15–30 minutes; no conditional implementation |\n\nQuestion D3:\nD3 — Authorize only the packaged switch runtime?\nProject: AppSurface #183, approved design on main.\n\nHosted archives currently block the proposed enhancement. AppSurfaceDocsPublishedTreeHandler.cs:899–901 allows only the known theme-bootstrap hash and sets connect-src 'none'; legacy pages use script-src 'none' at line 31. The design calls for an external switch runtime and partitioned plan data at lines 85–87. The external runtime cannot run as written, and fetching a plan would also be blocked.\n\nRecommendation: A because an exact runtime hash preserves the direct switch experience while keeping arbitrary scripts and network connections blocked.\n\nNote: options differ in approach, not coverage. This is plan approval only; the combined loader, resolver, static generator, reader states, archive integrity, and locale rules remain fixed. R2-1 and package placement remain pending.\n\nPros / cons:\nA) Exact runtime hash (recommended)\nAllow the build-owned switch runtime by its exact SHA-256 hash in CSP and matching integrity attribute, enabling sandbox scripts only when that verified package runtime is present. Embed the page's validated switch data as inert encoded JSON, keep connect-src 'none', and add no host-wide script permission, unsafe-inline, unsafe-eval, or dynamic code loading. This keeps the enhanced flow and rejects changed or unrelated scripts, but adds a packaged-script trust entry that must be updated and verified with each runtime change. Include hosted/static and negative CSP regression proof; incremental effort estimate: human team 0.5–1 day / agent-assisted 1–3 hours.\n\nB) Investigate first\nKeep runtime authorization pending and the current CSP unchanged. Perform one bounded read-only browser compatibility probe plus a source review of alternatives, then return with evidence; do not implement a conditional remedy or cut the approved enhanced flow. This can expose compatibility limits before selecting the mechanism, but delays the architecture decision; investigation estimate: human team 1–2 hours / agent-assisted 15–30 minutes.\n\nNet: A approves one exact packaged runtime in the plan; B gathers evidence before choosing. Neither resolves R2-1 or authorizes feature implementation.\nHeader: Archive scripts\nOptions:\nA) Exact runtime hash\nAllow the build-owned switch runtime by its exact SHA-256 hash in CSP and matching integrity attribute, enabling sandbox scripts only when that verified package runtime is present. Embed the page's validated switch data as inert encoded JSON, keep connect-src 'none', and add no host-wide script permission, unsafe-inline, unsafe-eval, or dynamic code loading. This keeps the enhanced flow and rejects changed or unrelated scripts, but adds a packaged-script trust entry that must be updated and verified with each runtime change. Include hosted/static and negative CSP regression proof; incremental effort estimate: human team 0.5–1 day / agent-assisted 1–3 hours.\n\nB) Investigate first\nKeep runtime authorization pending and the current CSP unchanged. Perform one bounded read-only browser compatibility probe plus a source review of alternatives, then return with evidence; do not implement a conditional remedy or cut the approved enhanced flow. This can expose compatibility limits before selecting the mechanism, but delays the architecture decision; investigation estimate: human team 1–2 hours / agent-assisted 15–30 minutes.\n\nState: approved\nActual answer: engineering D3/A on 2026-10-28: “A — approve the exact packaged runtime hash and matching integrity attribute in the plan, keep connect-src 'none' with embedded validated data, and continue engineering review with R2-1 carried forward.”\nAccepted scope: amend the plan to authorize only the build-owned switch runtime by exact SHA-256 CSP hash and matching integrity attribute, with sandbox script permission only when that verified runtime is present. Embed validated page-local switch data as inert encoded JSON; retain connect-src 'none', no host-wide script source, no unsafe-inline, no unsafe-eval, and no dynamic code loading. Preserve enhanced/plain-link coverage and carry hosted/static and negative CSP regression proof into the implementation plan. Retain the combined-loader arrangement, archive integrity and locale rules. R2-1 and package placement remain pending. This approves the plan amendment only, not implementation.\nHistory: First presentation reopened the original unchanged-CSP wording because actual archive CSP blocked the proposed runtime. Previous state: pending; actual answer: unanswered; accepted scope: none. Product design and engineering D2/B remain approved. D3/A resolves this trust/data-delivery mechanism and leaves all other pending choices pending.\n\n### R2-1: Deterministic section recovery\n\nFinding: R2-1, INFORMATIONAL, confidence 9/10, native reviewer carrying the independent spec-review concern. Motivating plan text, design line 77: “otherwise a nearest existing canonical ancestor landing page may be offered.” The final spec review left this concern open. Office-hours D8/A and engineering D2/B/D3/A explicitly carried it forward, without choosing a remedy.\n\nPlan baseline: an explicitly authored section is first; an optional nearest ancestor landing may follow; selected-version search/home and the original guide return remain required where valid. No automatic eligibility/tie-breaking rule has been approved.\n\nRuntime evidence: `DocMetadata.SectionLanding` exists at `Web/ForgeTrust.AppSurface.Docs/Models/DocModels.cs:109`. `DocAggregator.ResolveSectionLandingDoc` at lines 2535–2561 chooses the authored landing among visible canonical pages by existing order and canonical-route rules. `AppSurfaceDocsFrozenRouteManifest.cs:563–569` stores routes, aliases and artifact mapping, not landing eligibility. This is an integration gap in the plan, not observed broken runtime behavior.\n\nMotivating existing source, verbatim:\n\n```csharp\n var landingCandidates = sectionDocs\n .Where(doc => doc.Metadata?.SectionLanding == true)\n .OrderBy(doc => doc.Metadata?.Order ?? int.MaxValue)\n .ThenBy(doc => GetSnapshotCanonicalPath(doc), StringComparer.OrdinalIgnoreCase)\n .ToList();\n```\n\nComparison grid:\n\n| Choice or contract | Current / approval | A: Verified ancestor landings | B: Authored section only |\n| --- | --- | --- | --- |\n| Explicit section priority | Authored `fallbackSectionRoute` first, office-hours D8/A | Fixed | Fixed |\n| Automatic section eligibility | Optional ancestor action lacks a defined eligibility record | Optional `sectionLanding` from the actual resolved public landing winner; legacy eligibility only from digest-bound authored page membership; default false | No automatic eligibility field or selection |\n| Automatic section selection | Unspecified | Target-release/selected-locale public serveable strict non-root canonical ancestor of source route; most segments; fragment ignored; existing comparison; ambiguity yields no section action | Omit optional automatic ancestor action |\n| Missing/invalid explicit target | Existing validation and fallback ladder, office-hours D8/A | Preserve diagnostics, then use only valid eligible ancestor or search/home | Preserve diagnostics, then search/home |\n| Counterpart matching and notices | Stable IDs/legacy bridge; honest absent/unknown, office-hours D8/A | Fixed | Fixed |\n| Selected language and return | Preserve locale, omit invalid actions, retain original guide return | Fixed | Fixed |\n| Components | Combined loader, resolver, static generator, engineering D2/B | Fixed | Fixed |\n| Script trust/data delivery | Exact runtime hash/integrity, embedded data, connect-src none, engineering D3/A | Fixed | Fixed |\n| Package placement | ER2 pending | Pending | Pending |\n| Proof and maintenance | Relevant regression proof required, implementation not authorized | Explicit priority, nearest qualified ancestor, ordinary/root exclusions, missing legacy flag, ambiguity and locale tests; extra eligibility metadata; estimate human 0.5–1 day / agent 1–3 hours | Explicit validation plus missing/invalid/other-locale fallback tests; maintainer-authored section per concept where needed; estimate human 2–4 hours / agent 30–60 minutes |\n\nQuestion D4:\nD4 — How should section recovery be selected?\nProject: AppSurface #183, carried concern R2-1.\n\nThe approved design permits a nearest canonical ancestor landing page when no authored section fallback exists (design line 77), but archived routes do not record which pages qualify as landings. Live Docs already resolves authored section-landing winners in DocAggregator.cs:2535–2561. Without an explicit archive rule, hosted and static output could choose different or ordinary pages and call them section recovery.\n\nRecommendation: A because publishing the resolved landing fact makes the existing ancestor fallback deterministic without guessing from folders or HTML.\n\nNote: options differ in fallback policy, not implementation quality. Both keep authored fallbackSectionRoute first, preserve absent/unknown notices, selected locale, version-local search/home, and the return link. The combined loader and exact-hash runtime with embedded data/connect-src 'none' remain approved. Package placement remains pending. This approves planning only.\n\nPros / cons:\nA) Verified ancestor landings (recommended)\nRecord optional sectionLanding in new concept indexes from the resolved public landing winner (default false); legacy eligibility must be explicitly authored in a digest-bound page membership. When no authored fallbackSectionRoute exists, choose the eligible, public, serveable target-release/selected-locale page with the longest strict non-root canonical ancestor path of the source, using existing route normalization/comparison and ignoring fragments; ambiguity or missing eligibility skips the section action and continues to search/home. This preserves automatic recovery and identical hosted/static selection, but adds eligibility metadata. Include explicit-override, nearest-ancestor, ordinary/root-exclusion, missing-legacy, ambiguity, and locale tests; incremental estimate: human team 0.5–1 day / agent-assisted 1–3 hours.\n\nB) Authored section only\nUse only a validated authored fallbackSectionRoute for the target release and selected locale; otherwise continue to version-local search/home and the original-guide return link. This avoids automatic ancestor selection and needs no eligibility field, but maintainers must author a section destination for each concept needing one. Include explicit-target validation and missing/invalid/other-locale tests; incremental estimate: human team 2–4 hours / agent-assisted 30–60 minutes. The optional automatic ancestor action is omitted; all other approved recovery behavior stays fixed.\n\nNet: A derives automatic recovery from verified landing metadata; B makes section recovery entirely authored. Neither changes counterpart matching or script trust.\nHeader: Section fallback\nOptions:\nA) Verified ancestor landings\nRecord optional sectionLanding in new concept indexes from the resolved public landing winner (default false); legacy eligibility must be explicitly authored in a digest-bound page membership. When no authored fallbackSectionRoute exists, choose the eligible, public, serveable target-release/selected-locale page with the longest strict non-root canonical ancestor path of the source, using existing route normalization/comparison and ignoring fragments; ambiguity or missing eligibility skips the section action and continues to search/home. This preserves automatic recovery and identical hosted/static selection, but adds eligibility metadata. Include explicit-override, nearest-ancestor, ordinary/root-exclusion, missing-legacy, ambiguity, and locale tests; incremental estimate: human team 0.5–1 day / agent-assisted 1–3 hours.\n\nB) Authored section only\nUse only a validated authored fallbackSectionRoute for the target release and selected locale; otherwise continue to version-local search/home and the original-guide return link. This avoids automatic ancestor selection and needs no eligibility field, but maintainers must author a section destination for each concept needing one. Include explicit-target validation and missing/invalid/other-locale tests; incremental estimate: human team 2–4 hours / agent-assisted 30–60 minutes. The optional automatic ancestor action is omitted; all other approved recovery behavior stays fixed.\n\nState: approved\nActual answer: engineering D4/A on 2026-10-28: “A — use verified ancestor landings, keep authored fallbacks first, preserve the selected version and locale, and continue engineering review with the approved runtime/CSP rules unchanged.”\nAccepted scope: add optional sectionLanding eligibility to new concept-index pages from the resolved public landing winner, default false, with explicit digest-bound legacy page eligibility. Keep a valid authored fallbackSectionRoute first. Otherwise choose the eligible public serveable target-release/selected-locale strict non-root canonical ancestor with the most path segments, ignoring fragments and using existing route normalization/comparison. Ambiguity or missing eligibility skips the section action and continues to the existing search/home ladder and original-guide return. Carry explicit-override, nearest-ancestor, ordinary/root-exclusion, missing-legacy, ambiguity and locale regression proof forward. Preserve the combined-loader arrangement and all D3/A runtime/CSP rules. Package placement remains pending; implementation is not authorized.\nHistory: Spec finding R1-2 recurred as R2-1. The user retained it at office-hours D8/A and engineering D2/B/D3/A. Previous state: pending; actual answer: unanswered; accepted scope: none. Engineering D4/A now resolves its planning policy without claiming runtime verification. The generated office-hours Reviewer Concerns section remains unchanged as historical review evidence.\n\n### ER2: Shared switching-core package ownership\n\nFinding: ER2, INFORMATIONAL, confidence 9/10 for the existing dependency boundary, native reviewer with an independent in-host ownership audit. This is a placement decision, not an observed runtime defect.\nPlan baseline: office-hours D8/A requires one pure internal resolver shared by hosting and publication (design line 58); engineering D2/B approved combined loading but explicitly left package placement pending. D3/A and D4/A are fixed.\n\nRuntime evidence, verbatim project references:\n\n```xml\n\n