# Application navigation: synchronous Runtime configuration

Status: local implementation and acceptance verified for installed-application navigation, including browser editing, Runtime/TiDB transactions, initialization/upgrade preservation, and monolithic launch retirement. Published dependency pins and immutable BOM/image acceptance remain pending; this is not a shipped or merge-ready release candidate. The latest evidence section supersedes earlier progress snapshots below.
Issue: https://github.com/pingcap-inc/nexuscrm-app-packs/issues/850
Code inspection: 2026-09-05. Ordinary navigation edits must not mutate, publish,
refresh, or wait for Catalog, SecurityEpoch, HandlerBinding, compatibility
receipts, AppRelease activation, a worker, or a process restart.

## Scope and success

An administrator edits an installed application's navigation, saves once, and
sees the committed navigation in the current sidebar. Subsequent reads from any
Runtime instance see the same current document. Installation supplies a template;
redeployment and upgrade preserve the current document, including an empty list.

Include ordered navigation membership, item presentation, page and web targets,
object and dashboard references, authorization, concurrency, browser refresh,
installation/upgrade behavior, and retirement of competing navigation writers.
Exclude Layout authoring, page-content building, dashboard content editing,
schema authoring, application theme/settings, and general Catalog redesign.
Creating an object or dashboard remains its existing operation; adding its
navigation reference does not make those resource transactions atomic with it.

### Stable page references across application upgrades

A page target stores only its stable L1-owned page ID. Its route is a fact owned
by the L1 page registry, resolved at serving time; the navigation document and
Runtime protocol do not duplicate that path. A release can change a page's route
without changing the saved navigation or invalidating an idempotent request.
An unavailable page remains in the complete administrator document and can be
renamed, reordered, replaced, or removed. Save/replay validate reference shape,
not release availability. The editor explains its unavailable state, and the
serving projection omits it. Sidebar routing never substitutes Home for a
missing target. The page ID must retain its meaning across compatible releases;
a different page gets a different identity. Page-content authoring stays outside
this navigation capability.

Layout has a separate delivery: Core #4786 and app-packs #846 remain open;
API Host #114 is now merged. Reuse established transport and transaction patterns,
not Layout's domain, revision, UI lifecycle, or deployment sequencing.

## Inspected code and consumer map

Frozen main revisions:

| Repository | Revision |
| --- | --- |
| NexusCRM | `31d9bb58b1b9663afc9a75b8fb1e5fd60fe003a9` |
| nexuscrm-app-packs | `cdd25f45b06af7431761d3d63fc47484344d7f79` |
| nexuscrm-contract | `9061787fd2a6ffc9213b5723ef9f7d5f25bd6e91` |
| nexuscrm-api-host | `1549fbbab6e1ec785a5003330465cd7ca3a67ef8` |
| nexuscrm-controlplane | `bd647d21de5a8b76196bddafe2992d398d56128d` |
| L2 delivery repository | `2a108a1d7ece95162fc5e35559997fbb9704643f` |

Paths below refer to these revisions, not the older local working checkouts.

| Owner / current path | Observed behavior | Required change |
| --- | --- | --- |
| L1 `salesforce-crm-standard/frontend/src/platform/pages/AppStudio.tsx` | `loadApp` reads UI Apps with Tooling supplementation; saves full AppConfig through Tooling. Reorder/remove auto-save while other edits use Save. It never calls the host `refreshApps`. | Read an authorized full navigation workspace; one navigation draft and explicit save; refresh the host after commit. |
| L1 `.../features/studio/components/StudioSidebar.tsx` | Every drag-over calls the saving reorder handler. Standard-page selection ignores its argument and creates a web link to `/` named Home. | Drag changes draft only; create the selected registered page reference. |
| L1 `.../infrastructure/api/app-adapters.ts` | Rich items collapse to `Metadata.tabs` strings. New web URLs and dashboard references are lost. Page route resolution can substitute Home. | Navigation authoring uses a lossless typed endpoint; rendering preserves the actual target without a Home fallback. |
| L1 `dialects/dialect-sf-crm/ui_apps.go`, `partition.go` | Legacy Tooling handlers turn tab strings back into minimal items and call the shared application contract. | Inventory reachable compatibility routes; supported navigation mutations delegate to the new owner. Unsupported rich creation must not falsely succeed. |
| Core `backend/internal/application/services/application_navigation_contract.go`, `nexuscrm-metadata/metadata_apps.go` | Full application update merges navigation under a process-local mutex, then invokes the broad app repository update. | Navigation-only database transaction and cross-process revision check; no process mutex as concurrency authority. |
| Core `nexuscrm-persistence/repo_app.go` | Broad update writes presentation, owner/default fields, navigation and utility data, and ensures canonical visibility rows. | Dedicated navigation update cannot call visibility/default seeding or overwrite unrelated app fields. |
| Core `nexuscrm-kernel/pkg/schemacatalog/system_tables.json` | `_System_App.NavigationItems` is a semantic field of `platform.permission-state.app-navigation-catalog.v1`. | Reclassify presentation membership after separating actual permission facts; regenerate owning guards. Do not bypass security coordination with a raw writer. |
| L1 `appservices/sfcrm/ui_apps.go` | Actual AppService `/ui-api/apps` decodes navigation from immutable `AppMetadataViewV3.EffectiveComponents`, then filters application and tab visibility. | Resolve current Runtime navigation; retain admitted app context and permission enforcement, not Catalog navigation content. |
| Core `frontend/src/host-sdk/contexts/AppContext.tsx` | Shared `['apps']` query has five-minute freshness; refresh helper swallows errors and returns an empty array. | Explicit confirmed cache refresh for saves, race protection, and distinguish refresh failure from an empty app list. |
| L1 `.../components/shell/Sidebar.tsx` | Empty current navigation can fall through to another application's navigation. | Select by app identity; preserve empty navigation. |
| Controlplane `catalogreconcile/metadata.go`, `effective_application_navigation.go` | Publication reads live `GetAllApps` and embeds navigation in effective Catalog content. | Stop treating administrator navigation as release content; any retained historical component carries installation/template semantics only. |
| Controlplane `catalogapp/effective_metadata.go` | Existing complete-view contracts require the application-navigation component. | Preserve old immutable contract compatibility explicitly; new serving must not fall back to that component. Do not delete a required old envelope blindly. |
| Core `nexuscrm-persistence/perm_app_tab.go`; Controlplane `navigation_visibility_ssot.go`, `operations.go` | Permission evaluation reads canonical permission tables; profile initialization/legacy repair derives default grants from navigation. | Preserve permission evaluation. Bound legacy backfill to migration and prevent normal edits/redeployments from manufacturing grants. |
| L2 `scripts/apply_runtime_app_shell.py` and deployment callers | Upsert replaces all navigation, then asserts exact equality with release configuration. | Generic initialization through the owning mechanism; reruns verify preservation, not template equality. |

Additional correctness facts: page permission identity currently falls back to
the editable label; admin loads prefer the permission-filtered UI Apps document.
Renaming must not change permission identity, and a full-list save must not delete
items merely hidden from the editing administrator's personal sidebar.

## Decisions

### 1. One navigation owner, independent of Catalog

Use the existing Runtime `_System_App.NavigationItems` current document rather
than introducing a second authoritative navigation store or an override read
chain. Add an application-scoped monotonic navigation revision and explicit
initialization state in the owning schema. The revision covers only navigation;
changing app description, Layout, Catalog, or permissions must not invalidate an
otherwise current navigation draft.

All supported D3/AppService navigation writers must go through this owner,
including app creation's initial navigation and initialization. Retire superseded
Classic navigation implementations; do not build a new compatibility adapter
solely because the historical crm-server executable still compiles.
Unrelated whole-AppConfig saves must no longer rewrite stale NavigationItems.
Do not enable the new editor against a fleet that still has competing old writers.
No navigation cache in the initial server implementation.

L0 owns the generic document, transaction, revision, read, and mutation ports.
L1 maps CRM navigation types and registered routes into neutral references and
owns the Salesforce wire projection. Do not add Home/dashboard product route
branches to L0 or let an editable permission-key string grant visibility.

The document preserves stable item IDs, order, type/target, label, icon, URL and
resource references. Display labels are never permission keys. Existing modeled
tab/resource identity remains the authorization identity; L1 derives it from
validated targets. Legacy ambiguous identities are resolved during migration,
not guessed from a label on every read.

### 2. Explicit synchronous contracts

Introduce neutral Runtime ports for authorized full-document administration,
synchronous compare-and-save, and caller-effective navigation resolution. Names
are finalized with contract review; they are separate from Layout v2.

Administration read returns the whole configured document plus its navigation
revision for an authorized administrator. Serving returns the same stored facts
filtered for the caller. These are different authorized projections of one
document, not two sources of truth. An app outside the installed/tenant scope
cannot be enumerated or edited by supplying its ID.

Save includes app identity, expected navigation revision, complete ordered items,
and an idempotency key for the frozen request. In one bounded transaction:

1. authenticate/admit the caller and check the navigation administration right
   (`CustomizeApplication`, with existing installed-app/tenant boundaries);
2. lock the app navigation aggregate and check same-key request identity;
3. reject a stale revision or invalid target/document without changing state;
4. commit changed navigation, its revision, audit, and a synchronous deduplication
   receipt together; a semantic no-op leaves revision/content unchanged;
5. return the committed document/revision only after commit.

A deduplication receipt is operation evidence, never a navigation serving source
or a publication job. Use a bounded domain-owned receipt if no suitable generic
transactional facility exists; do not borrow a file/approval-specific journal.
Same-key different-content requests are conflicts. Retries reauthorize the caller.
No submit/status protocol, asynchronous worker, polling, or background repair is
required to complete a save.

Navigation receipts share the application's lifetime. The new receipt table's
`ApplicationId -> _System_App(Id) ON DELETE CASCADE` relation owns cleanup for
both metadata and generic record deletion, atomically with the application row.
Save locks the application before writing its receipt; deletion takes that same
row lock. Recreating an application ID cannot replay the deleted application's
receipts. Setup audit history is retained. Startup checks the cascade along with
the receipt primary key; save does not add a lifecycle lookup or generation.

L1 exposes the native navigation editor endpoint; keep the existing UI Apps wire
shape. Do not stuff unsupported rich properties into Salesforce `Metadata.tabs`.
Determine supported Tooling surfaces from the current D3 release and live
consumers. Any retained navigation surface uses the same domain owner and follows
the v63 contract. Historical Classic-only routes do not require a new adapter.
Native Runtime authoring does not replace a direct Salesforce connection's API.

### 3. Separate permissions from presentation

The actual permission evaluator already reads Profile/PermissionSet tab and app
visibility rows. Keep those as the owners of grants. Adding a menu item grants
no permission, and removing one revokes no permission to its target resource.
Unauthorized callers cannot mutate; unauthorized targets remain filtered and
their underlying endpoints continue enforcing access.

Remove NavigationItems from the security-change classification only together
with the relevant consumer/writer changes and tests. Retain app lifecycle and
real grant-change coordination. Navigation save must not call
`ensureCanonicalAppVisibilityRows`. Revisit profile initialization and recurring
legacy repair so a later operation cannot convert edited menus into new grants.
Do not extend this work into general permission redesign.

Catalog still admits installed applications and executable routes/capabilities.
Any current Catalog schema/target checks are implementation coupling, not the
owner of editable object/field identity; follow the
[configuration lifecycle boundary](configuration-and-execution-lifecycle.md).
Catalog admission is read-only context for navigation;
navigation revision is never compared to CatalogRevision. The save and its
immediate readback must work while Catalog publication is unavailable and the
already-admitted release stays unchanged.

### 4. Initialization and upgrade

Choose seed-once semantics: release templates initialize missing navigation;
every initialized current document is preserved thereafter, even if it has never
been customized or is explicitly empty. This avoids guessing customization from
template equality. Template updates do not append/reorder items automatically.
A reset-to-template product feature is not required for #850.

Controlplane owns the generic initialization mechanism; L2 supplies its template
as installation input. It is not allowed to write the current document directly.
Initialization locks/checks the same aggregate as editing; a repeated or racing
install cannot overwrite a committed administrator edit.

Before cutover, inventory each existing app's database document and old served
snapshot. Preserve valid current database configuration, including saved admin
edits; report differences instead of silently replacing it with Catalog. Empty
and absent are distinct. Ambiguous/malformed legacy documents require explicit
migration diagnostics before activation, not a permanent fallback or data loss.

Catalog publication and drift producers must stop sampling live administrator
navigation as immutable release truth. Retained component envelopes for older
consumers stay version-compatible and template-scoped; the new sidebar never
uses their navigation. Test a later unrelated Catalog publication as well as a
normal deployment, not only the initial save.

### 5. Editing experience

- Navigation has one draft and an explicit **Save navigation** action. Add,
  remove, rename, target changes and drag reorder only change that draft.
  Cancel discards it. Separate this state from unrelated App Settings saves.
- Page/web items open a real navigation-properties panel. Standard-page picker
  selects the actual registered page; no placeholder Home web link. Object and
  dashboard targets retain their canonical resource reference.
- Show when a configured item is hidden from the current caller by permissions;
  saving it does not imply granting access. Keep that item in the administration
  document. Explicit administrator labels/icons survive enrichment; derive a
  default only when no override was authored.
- Route editing selects existing supported destinations; it does not create
  page implementations or runtime route handlers. Invalid/deleted targets get
  clear validation, not a silent redirect to `/`. External URLs use the existing
  safe URL policy (no executable schemes).
- Show validation errors before submission and authoritative server errors
  without losing the draft. Do not initialize a blank editable configuration
  after a failed load.
- A successful commit updates the draft baseline. Refresh the shared host apps
  query and the permission-filtered sidebar immediately, without browser reload
  or re-login. Cancel/supersede older in-flight reads so they cannot restore an
  old menu. Do not put the unfiltered admin document into a user-visible cache.
- If commit succeeds but refresh fails, say that the save succeeded and offer a
  refresh retry; do not invite another write. An uncertain commit preserves the
  original request identity and performs bounded confirmation/retry.
- One in-flight save per editor. Two administrators/tabs get a clear stale-edit
  conflict, with the draft preserved and a way to reload/compare. No silent
  last-writer-wins overwrite. Late results for app A cannot alter app B's draft.
- Same-browser tabs receive a scoped invalidation notification; on focus,
  reconnect and app entry they refresh. Other sessions get current state on the
  next read. Instant push to every idle browser is not required and must not
  become a prerequisite for save correctness.
- Empty navigation stays empty. Do not substitute another app's menu or Home.
  Keep authorized Setup/application-switching access usable. Removing a link
  does not close the user's current page or revoke its underlying permission.

## Delivery sequence and affected repositories

1. Freeze neutral document/permission identity, revision/error/idempotency
   semantics, migration rules and the walking-slice fixtures. Update the
   current-tree consumer map if the other Layout work changes adjacent files.
2. Contract + wire: publish the neutral navigation capability and mappings.
   API Host: delegate identity and add transport-only client/policy support.
3. Core: navigation transaction/read owner, schema/revision, permission-boundary
   changes, compatibility adapters, host query refresh, and real-database proof.
4. Controlplane + L2: initial template application, preservation assertions,
   producer/repair cutover, and migration proof. No tenant payload in Core.
5. L1: new endpoints, actual UI Apps Runtime read, lossless projection, editor,
   page picker, empty sidebar behavior, and capability declarations.
6. Run the full local release-tuple walking slice before environment deployment;
   release/pin each owner through its existing workflow. Do not patch immutable
   existing release artifacts or enable UI ahead of its runtime capability.

This is a coordinated cutover, not permission to leave both writers active after
landing. The one-time software delivery is distinct from subsequent administrator
saves: none of those saves asks for another release or service restart.

## Acceptance and verification matrix

| Boundary | Required proof |
| --- | --- |
| Original symptom | Remove Home and Save; it disappears from the active sidebar in the same browser. The next independent API request returns no Home. |
| Rich round trip | Add/edit web and registered-page targets, labels/icons and dashboard references; save/read/reopen/click preserve exact intended values. |
| Standard picker | Selecting a standard page adds that page, not a Home web placeholder. |
| Empty state | Remove every item; both read and UI retain `[]`, even with another visible app. Setup remains reachable for an authorized admin. |
| Shared authority | App Studio reads complete authorized configuration; shell reads its filtered projection; saving does not delete personally hidden items. |
| Permissions | Missing CustomizeApplication, other tenant/app, hidden/default-off tabs, additive grants, renamed page labels and changed targets preserve the existing access semantics. No new grants from adding a menu item. |
| Catalog independence | Freeze CatalogRevision, SecurityEpoch, SchemaGeneration, HandlerBindings, compatibility receipts and AppInstallation; disable publication/reconcile writes. Repeated navigation saves and reads pass with these facts unchanged. |
| Real processes | At least two Runtime instances and separate L1 service over the real authenticated transport; write through A, read through B. No service restart or worker completion required. |
| Concurrency | Two clients share a base revision: one write succeeds, the stale write conflicts without loss. Dragging generates no network writes before Save. |
| Failure windows | Failure before commit leaves everything unchanged; response loss after commit can be confirmed/retried with the same key; same-key different-content conflicts. Revoked callers cannot replay a write. |
| Browser races | Old in-flight reads, save A followed by navigation to B, refresh failure, two tabs and reconnect do not restore stale menus or corrupt drafts. |
| Install/lifecycle | Fresh installation receives the selected template; repeated initialization, new template versions, normal upgrades, redeployments and a later unrelated Catalog publication preserve current navigation and its empty state. |
| Migration | Existing valid database edits and item identities survive; snapshot divergence is reported; malformed input is not overwritten or silently defaulted. |
| Side-effect isolation | Navigation-only edits leave app settings, utility items, actual permission rows, Layout revisions/documents, and release state unchanged. |
| Bounds | Maximum item count/document size/target length is explicit; invalid/oversized inputs are rejected before mutation. Reads are bounded/indexed; no per-item database query loop. |
| Old paths | No active direct deployment overwrite, lossy authoring adapter, legacy competing writer, Catalog-navigation fallback, or automatic permission seeding remains on the normal edit path. |

Tier 1: contract/codec tests for lossless targets, empty arrays and errors.
Tier 2: real TiDB transaction, permission, concurrency, initialization and failure
tests using canonical schema. Tier 3: complete product UI through actual L1,
private Runtime transport and two Runtime instances. Tier 4: frozen-candidate
owner suites/builds and existing boundary gates. Required integration fixtures
must fail if their declared environment is absent, not silently skip.

Do not use mocked SQL sequences as proof of concurrency, crash recovery or
upgrade preservation. Persist bounded test logs and report decisive failures.
No implementation or new end-to-end verification was performed for this plan.


## Implementation evidence — follow-up verification

The architecture was reopened against current code before extending implementation.
Two existing mutation paths still need retirement: `MetadataRepository` broad
application updates automatically ensure tab grants, and Profile initialization
reads live navigation to derive grants. The effective tab read itself uses the
canonical Profile/PermissionSet rows and does not need navigation membership.
This confirms that navigation must stop being an implicit grant authoring source;
merely bypassing the Permission State writer is not an acceptable final cutover.

A further transaction defect was found during implementation: the new repository
used its SQL transaction only for pessimistic writes, while the existing selected
permission projection opened its own read transaction. Navigation reads now reuse
the caller transaction; the existing permission projection has a transaction-bound
entry point that reuses the same permission owner and selection. Tests assert both
read-your-writes inside the transaction and absence of uncommitted data outside it.

Implemented and checked so far:

- Navigation Runtime admission binds exact org, installation, workload and frontend
  application owner. Read/Resolve/Save are synchronous and use current documents.
- Save uses the existing transaction-current CustomizeApplication authorization
  policy, reauthorizes replays, and commits navigation and operation receipts together.
- The native L1 endpoint is `/services/data/v63.0/app-navigation/{applicationId}`:
  GET returns the complete administrative document; PUT requires revision,
  idempotency key and an explicit item list. Missing/null items fail validation.
- UI Apps resolves navigation from Runtime. A frozen-Catalog test changes the live
  navigation response and observes the new menu, then an explicit empty menu,
  without changing the Catalog view. Catalog is used only for application shell facts.
- The historical application component now carries shell definitions and an empty
  navigation array for envelope compatibility. The producer does not read mutable
  NavigationItems; tests show navigation changes cannot change its content.
  This supersedes the earlier tentative option of retaining templates in that field.
- Semantic response validation rejects Catalog/schema/engine publication receipts
  for navigation. The navigation revision is its own consistency marker.
- Strict host refresh preserves the current query data on a failed refresh instead
  of unmounting the editor through a query error. One test first reproduced the
  existing crash, then passed after the correction. Refresh results cannot overwrite
  a newer refresh or a replaced query cache entry.

Evidence limits:

- TiDB tests use independent transaction managers/connections within one test process;
  the Runtime RPC test uses real interceptors and a fixture engine. They are separate
  proofs, not yet a complete two-Runtime-process database-backed product test.
- Full App Studio draft/save/cancel, page target validation/editor, cross-tab refresh,
  the legacy writer cutover, explicit permission initialization, migration and L2
  upgrade preservation remain unfinished. No deployment or completed-fix claim.
- Local go.work overrides are development-only, not release pins.

## Navigation implementation continuation (2026-09-05)
- App Studio now reads the full native document and keeps a separate navigation draft. Reorder/add/remove no longer autosave. Save uses revision + frozen idempotency request; uncertain responses freeze edits and preserve the same retry. Cancel restores baseline; settings save omits navigation. Confirmed saves refresh host menu with distinct refresh-failed/retry UI. Late save/load responses do not overwrite a different app. Page/web selection now opens an editor; standard page adds use the selected registered page.
- Frontend evidence: L1 package build passed after first editor implementation; five AppStudio behavior tests pass. Host AppContext now supports cross-tab notifications and focus refresh with the existing success-only cache publication; ten host tests pass. New page dropdown / generated validation projection have not yet had their final package build. Browser verification remains pending.
- Controlplane applicationnavigation package + catalog-reconcile application-navigation subcommand own seed-once initialization. The same persistence owner now requires the observed raw source digest for revision-zero writes. Canonical existing [] is preserved; legacy data requires explicit source-bound mapping; stale mappings fail. Real TiDB package tests passed, including new seed, preserved empty, explicit legacy mapping, normal edit then upgrade, assert-only, and stale source. New physical absence is JSON null (the column is NOT NULL), while [] means a deliberate value.
- L2 app-shell config is now a neutral template with explicit tabPermissions and legacyPageTargets. Shell SQL no longer upserts NavigationItems. The script calls selected Controlplane binary/image and verifies current revision; it accepts newer concurrent revisions. D3 app-shell evidence is v2 with navigationRevision; current IDs are no longer compared against template IDs. Seven L2 unit tests pass. Actual full app-shell process + image run remains pending.
- Removed menu-derived tab grants from MetadataRepository.ensureCanonicalAppVisibilityRows and PermissionRepository profile initialization. The App security declaration now covers FrontendAppId (same historical surface ID); navigation Store/Initialize are registered with the existing direct-writer semantic guard. Generated inventory was inspected (+2 navigation writers, -1 profile tab seeder, app defaults no tab grant). Expected counts/digests updated; final inventory tests pending.
- Persistence focused batch ran real navigation tests but failed three pre-existing structural cases for RecordType writers: the test expects an inline writer ID while these current source functions use file-level constants. The affected RecordType source is unchanged; do not treat the full batch as green. Need isolated baseline proof/triage before final suite.
- L1 native target validation added a generated page-ID/path projection from frontend route policy, and resource symbol checks through metadata-admin read. NOT YET verified; review its interaction with replay and full hidden-item saves before retaining. No capability/release wiring added yet for these dependencies.
- STILL NOT SHIP READY: competing broad App writes/read projections, legacy Tooling routing, schema create/delete automatic navigation mutations, current capability declarations/release pins/owner config, source-vs-Catalog migration inventory, two real Runtime instances with real permission policy, browser QA, and full relevant checks remain unfinished. No deployment or publication performed.

## Release and Runtime evidence continuation
- Added candidate 0.72.0 AppRelease capability declarations: native navigation handler selects runtime.application-navigation.v1; UI Apps declares the same dependency. Historical release sets remain unchanged. Full crm-app-release tests passed (74.5s) before removing an unnecessary additional metadata-admin dependency; focused final declaration test still needs rerun after that removal. No release artifact or version pin was published.
- L2 recognizes navigation as an explicit workload-only App binding and delegated session capability. The selected workload must carry application_owner_id from the checked-in app-shell frontendAppId. Older BOM admission closure remains valid; optional navigation grant must have the exact known workload, and immutable AppPackage closure checks prove the selected release requirements. Sixteen application compatibility tests pass, including unknown workload rejection.
- Resource reference validation moved from L1 preflight into Runtime after idempotency replay lookup. New resource types must resolve through MetadataRepository; existing resource targets may be retained after deletion. This avoids blocking full-document edits because an existing hidden target is no longer available. L1 retains generated page identity/path validation and its supported resource-record shape. Registered-page validation across incompatible page-registry upgrades still needs a deliberate compatibility disposition.
- MetadataRepository.GetSchemaByAPIName and GetFieldsForObject now use the transaction-aware executor. New real TiDB snapshot test is pending final result in logs/navigation-schema-snapshot.log.
- Added backend runtime_api_integration test using real permission repositories, actual gRPC servers and two independent Runtime engines, with no Catalog navigation source. It passes concurrent real changes (one winner), cross-instance read/replay, full admin versus visibility-filtered resolve, unchanged Permission State revision and SecurityEpoch, revoked CustomizeApplication blocking replay and admin read, reauthorization followed by explicit empty save, and fresh Runtime reading that empty value. It uses one shared TiDB pool and one durable security-admission fixture for server instances, not separate OS processes/replica leases. Log: navigation-runtime-tidb.log, final 1.33s.
- Runtime target validation focused tests and L1 native hidden-document/page-validation tests pass. Latest frontend app package build, including generated page projection check, passes (logs/navigation-l1-frontend-final.log).
- Current-tree audit found a real legacy executable at backend/distributions/cmd/crm-server/main.go (crm build tag), not just dead routes. Its Tooling contract still writes through MetadataService; schema-create/delete also mutate navigation. No change to those paths in this continuation. Asked user asynchronously whether that old monolith must also be migrated or be fenced after data migration; no reply yet. Keep this scope question distinct from the already authorized implementation.
- Remaining blockers include legacy writer/read cutover, full L2 executable initialization test, exact frontend/browser and AppService-to-Runtime walking slice, release artifact/pins and selected candidate wiring, original RecordType inventory test triage. No deployment, publication, PR, or commit.

## Local L2 process proof and legacy write fence
- The metadata/schema caller snapshot TiDB test passed (navigation-schema-snapshot.log).
- Built the current Controlplane executable and ran the actual L2 app-shell Python process against an isolated TiDB database using canonical Core App/receipt tables. First initialization, preservation of an edited URL including query/fragment, preservation of explicit empty navigation, assert-only, and source-bound legacy page migration all passed. The reusable local harness is acceptance/app_shell.go; log navigation-l2-process.log. This proves the executable adapter, not an immutable image/BOM or full deployment.
- The original HEAD structural test, supplied via a Go overlay, reproduces the three RecordType writer-ID failures against unchanged RecordType functions. This is an existing scanner limitation for file-level constants. No RecordType production or scanner code was changed. Log navigation-recordtype-baseline.log.
- Final navigation security inventory/frozen digest tests passed (navigation-security-inventory-final.log).
- MetadataService now carries navigation PATCH omission as nil to persistence, so an unrelated App settings write cannot overwrite a stale navigation snapshot. The broad repository writer omits nil navigation and locks/rejects legacy navigation writes after NavigationRevision becomes nonzero. Validation errors remain validation errors at the metadata boundary. Real TiDB rich-target/version preservation and explicit-empty legacy rejection passed; relevant MetadataService tests passed (navigation-legacy-write-fence.log, navigation-app-shell-metadata.log).
- This fence is data protection, not completed compatibility: legacy read projection, Tooling adaptation, owner rebinding, and schema navigation side effects still need a coherent cutover. No publication, PR, commit, deployment, or completed-feature claim.

## Scope correction — current production versus historical executable (2026-09-06)
User clarified that the current production feature is ineffective and questioned supporting an earlier historical entrypoint. The latest successful Prod workflow found is D3 run 33965516661 (2026-09-05; https://github.com/pingcap-inc/nexuscrm-pingcap-pack/actions/runs/33965516661). The L2 production runbook specifies serviceified Runtime API + SFCRM AppService and explicitly retires Classic release workflows. This is deployment-record evidence; no live-host readback was performed in this turn.
Current delivery targets that D3/AppService path. A compilable crm-server plus CI checks is not sufficient evidence that Classic navigation must receive a new compatibility implementation. Remove superseded navigation readers/writers and their side effects after deriving live consumers; preserve existing data with the one-time migration. Do not expand this navigation issue into retirement of all unrelated monolith features. The old scope question is no longer a user-input blocker. No old code has yet been deleted on this basis.

## Authorized monolith retirement and navigation ownership closure (2026-09-06)
User explicitly confirmed there will be no future monolithic version and authorized deleting it. This supersedes the previous narrower retirement note.
- Deleted Core's app-blind and CRM monolith mains, RunVersioned startup composition, standby-only implementation, old image/entrypoint, and the obsolete image-smoke CI job. Preserved the source-backed setup gate and its job configuration; YAML unique keys, step IDs, and job dependencies were verified.
- Deleted startup-only tests and the old image entrypoint tests; preserved independently useful library tests. Updated executable closure checks to supported Runtime processes and added a tombstone guard against restoring runnable monolith surfaces. Updated active README startup instructions.
- Deleted the old ApplicationNavigationContract implementation and its automatic ServiceManager assembly. The old injectable dialect port remains for conformance fixtures, with no production implementation or default fallback. Removed schema-create/delete's implicit navigation writes; schema authoring no longer owns navigation mutations.
- Current Runtime/Worker/Integration and default setup compile passed. Retained CRM setup binary built successfully against this local multi-repo candidate using temporary module replacements, which were restored afterward (no publication/pin claim). Focused boundary checks passed. Retained serverruntime full suite exposed only an inventory anchor to the deleted startup; that inventory was corrected and its isolated test passed. No need to rerun unaffected behavioral tests.
- Protected initialized navigation's FrontendAppId against broad shell-update rebinding. L2 shell upsert preserves the initialized owner; mismatched templates fail during navigation initialization/assertion. Real TiDB persistence tests and actual L2 script -> Controlplane executable -> TiDB walking slice passed for changed/case-only/empty owner rejection, shell save preservation, edited and empty navigation preservation, explicit legacy migration, and rejected template owner rebind. Logs: monolith-retirement-focused.log (initial failures), monolith-retirement-boundary.log (pass), monolith-retained-setup-build.log (pass), monolith-retained-assembly-tests.log (stale inventory failure), monolith-navigation-removal.log (inventory + contract pass), navigation-legacy-removal-final.log (pass), navigation-owner-fence.log (pass), navigation-l2-owner-process.log (pass).
- L2 still contains historical Classic deploy helpers/workflows that reference retired upstream commands. Current D3 uses some shared modules from that tree, so do not blindly delete those modules. The runnable upstream monolith is gone; comprehensive L2 Classic helper retirement is not yet done.
- Remaining current-navigation delivery work: browser verification and complete AppService HTTP -> real Runtime/TiDB walking slice; final focused release-declaration test; final relevant checks and release candidate/pin integration. No commit, PR, tag publication, image publication, or deployment. Do not claim the complete feature is shipped.


## AppService and browser acceptance (2026-09-06)
- Added a separately compiled SFCRM consumer test that exercises the actual AppService HTTP navigation and UI Apps handlers against real Runtime gRPC servers and TiDB. Full hidden-item editing, exact retry, stale-revision rejection, cross-instance reads, visibility filtering, and explicit empty navigation passed. The adapter uses generated Runtime mapping clients with signed test delegation; production identity exchange is covered separately, not by this harness.
- Browser acceptance mounted the actual AppStudio, navigation sidebar/picker/editor, host AppProvider, and native API client against that live stack. Verified explicit Save, URL query/fragment preservation, renamed menu propagation, conflict preservation, and empty navigation updating both browser tabs. The registered page picker opened the actual page editor. This harness stubs unrelated object/dashboard editors and does not prove the full production shell or deployment image.
- Found and fixed Cancel leaving an empty editor after discarding a new item. The regression test passes; all seven AppStudio tests pass. Final L1 frontend build/projection check and host production build pass. Generated host version.json was restored.
- Final contract navigation, API-host runtimegrpc, SFCRM AppService (including main package), Controlplane CLI, and candidate release-dependency test suites pass. Controlplane applicationnavigation itself currently has no package tests; its real executable-to-TiDB behavior was proved by the earlier L2 process harness. All six repository diffs pass whitespace checks.
- Browser/runtime acceptance log: logs/navigation-browser-runtime-final.log (PASS, 236.55s including interactive wait). Backend fixtures and the temporary browser server were stopped. Failed exploratory fixture runs remain in separate logs; their security-lease/test-discovery issues were corrected before this passing run.
- Local evidence is not immutable BOM/image proof. Published dependency pins, release artifacts, complete release-candidate integration, and comprehensive retirement of remaining L2 Classic helpers are still outstanding. The L1 page registry's compatibility policy across a future removal/path change remains a release-design question; this run only proves the current page projection. No commit, PR, tag/image publication, or deployment was performed.


## Page upgrade compatibility and L2 launch retirement closure (2026-09-06)

The page-upgrade concern is resolved at the ownership boundary: navigation stores
only a stable page ID. Removed the duplicated path from the new, unpublished
semantic/wire contract, mappings, editor DTO, templates, and migration output.
The generated L1 page projection now governs serving availability, never Save or
retry. Unavailable page references remain in the full administrator document;
the editor explains their state and allows rename/replacement/removal. The
Sidebar no longer silently routes unavailable page/dashboard targets to Home.

Browser acceptance now mounts the actual Sidebar as well as AppStudio. Against
the actual AppService HTTP handlers, Runtime gRPC, and TiDB, an unavailable page
was renamed and saved, then replaced with Chatter and saved. Reload confirmed
that the stable target and label persisted. The actual sidebar retained only
visible valid entries. An initial browser-harness error was a missing
PermissionProvider when mounting the real Sidebar; the harness was corrected,
and the final Runtime/browser run passed (127.527s including interaction).
This remains a local harness with unrelated editors stubbed, not a release-image
or complete production-shell proof. The browser/server fixtures were stopped.

L2 retirement removed the Classic CI deployment workflow and adapter, its now
unused TiDB bootstrap helper, EXP/Staging monolithic orchestration bodies, and
both copies of backend runner/start/install helpers (30 named callable bodies).
The old offline/staging CLI invocations fail before reading environment or
mutating the host; their modules retain the actual D3 consumers for initialization,
credentials, database maintenance, and predecessor quiescence. Twenty-two obsolete
Seed callsite declarations and their exclusively owned invocations were removed
with their code. All six Seed profiles still validate. Current D3 initialization
and baseline/credential/reference-data owners are preserved. No remote host or
production data was changed by this retirement.

Final evidence (full logs under this worktree root's `logs/`):
- Contract `go test ./... -count=1 -timeout=180s`: pass (`final-contract.log`).
- Wire `go test ./mapping -count=1 -timeout=120s`: pass (`pages-wire.log`).
- API-host `go test ./runtimegrpc -count=1 -timeout=120s`: pass (`pages-api-host.log`).
- SFCRM `go test ./... -count=1 -timeout=120s`: pass (`pages-appservice.log`).
- Core `go test ./internal/runtimeassembly -count=1 -timeout=180s`: pass (`final-assembly.log`).
- Real Runtime integration `go test -tags runtime_api_integration ./internal/runtimeapi -run '^TestRuntimeNavigationRealTiDBConcurrentAuthorizationAndReplay$' -count=1 -v -timeout=120s`, with TiDB at 127.0.0.1:4001 and the separately compiled SFCRM consumer: pass (`pages-http-runtime-tidb.log`). Includes unavailable-page exact HTTP replay and cross-Runtime full read.
- Persistence `go test . -run '^TestApplication(Navigation|Definitions)' -count=1 -timeout=120s -v`, with real TiDB required: all four pass (`pages-persistence-tidb.log`).
- Controlplane `go test -tags integration ./applicationnavigation -count=1 -v -timeout=120s`, with TiDB: pass (`pages-controlplane-tidb.log`).
- Actual L2 script -> rebuilt Controlplane executable -> TiDB: seed, edited preservation, empty preservation, assert-only, owner-rebind rejection, and legacy migration all pass (`pages-l2-process.log`).
- Eight AppStudio tests pass (`pages-studio.log`); 26 Sidebar/API adapter tests pass (`pages-sidebar-final.log`); final L1 package build passes (`final-pages-build.log`). The adapter expectations now enforce no implicit Home route for an unresolved frontend owner.
- L2 combined retirement/D3/app-shell/compatibility/Seed batch: 89 tests, 86 initially passed. The three Prod-retirement failures were macOS `/var` versus `/private/var` temporary-path aliases in unchanged production/test logic. All three passed in isolation using a canonical TMPDIR (`l2-retirement-prod-canonical-tmp.log`); no production workaround added. Final CLI retirement tests pass (`l2-retirement-final-cli.log`).
- All six `plan_deploy_seed.py` profiles pass (`l2-retirement-seed-plans.log`).
- All six repository diffs pass `git diff --check`.

Current scope remains installed-application navigation. AppStudio already disables
navigation editing while creating an application; current D3 does not provide the
application-creation write API. This task does not introduce a second application
lifecycle owner to fill that separate gap.

Remaining before a mergeable release: publish the new neutral contract/wire and
host/Controlplane dependencies in order, update real downstream pins, produce one
immutable candidate BOM/images, and run the complete local D3 acceptance against
that exact candidate before any environment deployment. No commit, PR, tag/image
publication, CI dispatch, or deployment has been performed.


## Release preparation and monolith Layout removal (2026-09-06)

The five implementation branches now have local commits and incorporate current
upstream changes. Layout 0.72.0 was independently released upstream, so the
navigation AppRelease candidate is 0.73.0 and inherits that published definition.
This does not retain a monolithic serving fallback. The latest user instruction
explicitly retires monolithic Layout development: removed the old dialect Layout
editing handlers, stored-editor handler, four residual approval-layout write
routes, their exclusive tests, nine ServiceManager writer adapters, and the unused
LayoutMetadataAuthoring interface. Shared rendering test helpers were relocated.
Current Runtime Layout code is an upstream service implementation, outside this
cleanup. Core cleanup commit 7feb800c7 and L1 cleanup commit ed393c2 are local.

Verification: retired-layout-core.log (ServiceManager contract and retirement
guards) and retired-layout-dialect.log (UI API, partition, Layout read and v63
tests) pass. The dialect check used an isolated workspace to resolve this exact
Core shared module; an initial missing sibling-path failure was configuration,
and one shared test helper was moved out of the removed editor-only test file.

Merged-candidate verification also passes: core-merged.log, l1-merged.log,
release-builder-merged.log, release-l2-merged.log (six Seed profiles and 22 tests),
merged-real-runtime.log (rebuilt external AppService consumer, real two-Runtime
gRPC/TiDB/HTTP slice), merged-controlplane.log (real initialization plus Catalog
and CLI tests), and merged-l1-build.log (frontend package build).

Semantic-only release PR: https://github.com/pingcap-inc/nexuscrm-contract/pull/228.
Local full scripts/check.sh and Contract CI passed. Review initially rejected
aggregate response allocation, Unicode format characters, and mixed case
identity rules. The candidate now preflights the aggregate raw response budget
before text/URL parsing and serialization, rejects Unicode format characters,
and consistently uses exact case-sensitive opaque IDs. Regression tests cover
a compact input that would previously serialize over 128 MiB, format characters
on each text surface, and case-sensitive correlation/deduplication. Full local
release gate passes again (release-contract-review-fix-gate.log).

A separate wire release worktree is prepared. Its RPC message names now match
the repository's protobuf naming rules; a navigation compatibility baseline was
added. These bindings remain unpublished. No semantic/wire tags, images, BOM,
or environment deployments have been published. Published pins and full local
D3 candidate proof are still pending the dependency release sequence.


## Published dependency and schema validation update (2026-09-06)

This update supersedes the earlier unpublished-release status above. Contract
v0.6.9, wire-go/v0.3.6, Controlplane v0.2.119, and API-host v0.38.18 are published.
Core and AppService now consume those released versions. L1 seals AppRelease
0.73.0. Complete local D3 candidate composition remains pending; these owner-level
results do not claim environment readiness.

Normal L2 initialization no longer converts legacy navigation. Its real process
proof now requires legacy-shaped input to be rejected without changing records,
alongside initialize, preserve edited, preserve explicit empty, assert-only and
owner-rebind rejection. The earlier migration result is historical, not the
current deployment behavior.

Full backend verification exposed a navigation catalog error: the internal
receipt lacked LastModifiedDate and used an unparsed indexes property. The
canonical definition now declares both universal temporal fields, a named short
index, system_core ownership, and BLOCKED generic read/write permissions. The
Runtime startup schema check includes LastModifiedDate. Catalog tests enforce
universal temporal fields and the receipt's typed declaration/access boundary;
real TiDB creation and transaction tests pass. The rebuilt Runtime plus external
AppService walking slice also passes (navigation-schema-owner.log and
navigation-schema-real-runtime.log in the acceptance evidence directory).

The same full run exposed an existing OAuth integration harness panic before
bootstrap: setupOAuthPublicPKCERuntime calls InitL0 without the mandatory
BootstrapStateStore seam injection. An isolated unchanged main checkout at
71beca1ce, with its original Controlplane v0.2.117, reproduces the identical
operations.go:1551 panic (oauth-baseline.log). This is a separate test composition
issue, not a navigation regression; the full backend suite is not reported green.


The final Controlplane schema pin is v0.2.119 (merged source 10b8d25),
selecting Core 4603ea669 for all pinned Core modules. Its real TiDB navigation
integration and independent full build pass. All standalone Core module graphs
now explicitly select Contract v0.6.9; the workspace/private release-pin guards
and the complete backend/shared compile pass.

The approval source-pack fixture failure is also independently reproduced on
unchanged Core main 71beca1ce with the same explicit official app-packs root:
InitL0 succeeds, then InstallPackFromSource rejects custom_permission and the
missing product-dialect/salesforce-v63 host primitive. Evidence:
approval-baseline-with-pack.log. Together with the OAuth reproduction above,
these explain why the legacy full backend suite cannot be claimed green while
the current navigation owner and cross-process paths pass.

### Review closure: audit, settings ownership, and launch documentation

Navigation changes now write the existing SetupAuditTrail through the same
transaction as the document, revision, and deduplication receipt. Audit failure
rolls the entire save back; unchanged saves and replays add no audit entry.
The audit records the actor, application/owner, request identity, and before/after
documents. Startup requires the canonical audit table; no second audit store or
new schema is introduced. Real TiDB owner tests exercised failed audit INSERT,
rollback, retry, concurrency, no-op and replay. The Runtime integration test
also executed its external L1 AppService consumer and passed.

The shared L1 Tooling settings adapter no longer serializes navigation, including
resolved full-application projections and empty lists from app creation. Runtime
owns installed-application navigation writes. Current bootstrap documentation
now points to the standalone L0 deployer and separate L1 composition; historical
monolith evidence does not establish public greenfield readiness.


### Post-L1 merge candidate (2026-09-06)

L1 #851 is merged at `4bab822f41891d8b5c5519f01486895c166c029f`. Core
incorporates main through `da3294db8` and pins that L1 build source. Private
dependencies retain Contract v0.6.10, wire v0.3.7 and Controlplane v0.2.120 from
main, together with the navigation-capable API host v0.38.18.

Verification after the merge: backend/shared compile; workspace private-pin
guard; CRM appsource lineage/protocol checks; Runtime admission/target tests;
real TiDB navigation persistence; and the two-Runtime, separately compiled L1
AppService authorization/concurrent-save/exact-replay walking slice all pass.
This is not a new claim that every backend test passes. Complete immutable-image
D3 composition remains a release prerequisite.
