What a node landing page says about provenance

A node type that registers its own landing page replaces the framework's shared Overview renderer. Until the mechanism described here, the Type · Created · Updated line rode on that one renderer, so replacing it dropped the line — and nothing recorded the loss. An omission somebody had weighed (a Thread answers who-and-when per message; an Activity reports its own run) and one nobody had thought about were the same thing from outside: an absence.

Core inverted the default (MeshNodeLayoutAreas.WithNodePage, NodePageProvenance): a landing page registered without an opinion ships with the line, and the two other answers are declared rather than inferred. This repository is where the answers had to be given — 96 of the 97 landing registrations here were bare WithView, and core's guard cannot see a single one of them, because most live in in-mesh Source/*.cs and in NodeType configuration lambdas (C# inside a JSON string) that compile at RUNTIME in the portal.

The three answers

// The framework composes the provenance line above your page. No opinion required — this is the
// right answer for anything whose subject is a stored record, a document or a configuration.
layout.WithNodePage(ContentArea, Content)

// The page already draws the standard header — it calls MeshNodeLayoutAreas.BuildHeader itself.
// The framework adds nothing: two provenance lines is a worse page than one.
layout.WithNodePage(OverviewArea, Overview, NodePageProvenance.RenderedByThePage)

// The page carries none, deliberately. The reason is MANDATORY — Declined("") throws.
layout.WithNodePage(BoardArea, Board, NodePageProvenance.Declined(
    "this page is a board over other people's records; each row carries its own provenance, and "
    + "the board node's created/updated describes when the board itself was provisioned."))

What counts as a landing page — and the shape that is easy to miss

There are two, and a walk that knows only the first prints complete coverage over the pages the census names first:

  1. the area a node type names with WithDefaultArea, and the renderer registered for it;
  2. a node type that names no default area lands on the framework's own Overview, so a bare WithView(MeshNodeLayoutAreas.OverviewArea, …) replaces its landing page exactly as an own-area registration does. Space, Skill, Document, Mailing, Exercise and Deck are all this shape.

An Overview registration in a body that names a different default area is not that node's landing page — the reader lands elsewhere, and that area carries its own verdict. Four of those exist (Thread, ThreadSupervisor, Hosting/Deployment, Code) and each is listed in the gate's OVERVIEW_NOT_THE_LANDING with the reason, because a skip with no sentence beside it is indistinguishable from a site nobody thought about.

The census

Measured by scripts/check-node-page-provenance.py, which is also the gate — the number in any report about this comes from that script, never from a sentence someone typed:

node landing pages: 99 (98 record a verdict, 0 do not, 1 not resolvable by this walk)

The one unresolved site is the Northwind sample hub's LayoutAreas default — the framework's own area-catalogue area, supplied by MeshWeaver.Layout. No page here registers it, so there is no landing renderer to record a verdict for. It is named in the gate's EXPECTED_UNRESOLVED, with that reason, precisely so "the walk could not see it" never reads as "somebody decided".

How each family was decided

The page draws the line itself — detected mechanically, never by opinion: the registration file composes MeshNodeLayoutAreas.BuildHeader. Ten pages: Approvals/Approval, Governance/Activity, Governance/Standard, Signature/DeepSignCredential, Signature/SignatureRequest, Signature/SkribbleCredential, Notification, Release, AccessAssignment, GroupMembership.

An eleventh was MADE true rather than detected: Space. The framework strip cannot be used there, because the Space Overview skips its header on TWO conditions — the node's own ExcludeFromContext opt-out, which ComposeProvenance honours, and ?showHeader=false on the REFERENCE, which it knows nothing about and which the landing Space and every page under it carry. Composing above the page re-added chrome to the one page built to have none (SpaceOverviewBodyTest failed 2 of 224 saying exactly that). So Space's own header now carries MeshNodeLayoutAreas.BuildMetaRow — the first call site of that member in this repository, and the case its own documentation describes — and inside the header it rides both opt-outs for free, because the header is what they skip.

The framework composes it — the default, and the answer for every page whose subject is a stored record, a document or a piece of configuration, where who wrote this and when is a question a reader actually asks. The whole Hosting/ record family, Store/Plugin, Essentials/*, Collaboration/Review, Feedback/Feedback, Feedback/Digest, Feedback/EvalCase, Cornerstone/*, AzureCostManagement/*, the authored Edu/ content pages (Lesson, Module, Workbook, Quiz, AnswerSheet, LearningJourney), RolePlay/ Story / Scenery / Character, Publish/Slide, Agent, ModelProvider, Code, Group, MeshDataSource, Comment, DataModelExplorer, MyAi/Panel, Google/Account, and the inherited-default pages Skill, Document, Mailing, Exercise and Deck. 50 in total.

It declines, with a reason — five shapes, each stated once and applied to the family:

shape why pages
the page answers who-and-when better in its own terms an Activity reports the run's own start and end; a thread answers per message; a message renders its own author beside its text; an API token page reports the token's own created / expires / last-used Activity, Thread, ThreadMessage, ApiToken
an input surface, not a document there is no authored content on it whose provenance a reader asks about ThreadComposer
a board over other nodes each row carries its own provenance; the board node's created/updated says when the board was provisioned Approvals/Desk, Signature/Desk, Essentials/Mailbox, Store/Catalog, Hosting/Issue, Hosting/FleetConsole, Chess/History, Edu/CourseCatalog, Edu/CourseInvite, ThreadSupervisor, RemoteControl/Console
a live interactive surface the subject is the session in front of the reader, not the node Chess/GambitHunt, RolePlay/Playroom, RemoteControl/Screen, and the three simulations (DoublePendulum, ThreeBody, FractalStars)
a gallery, or a live reading of an external service the node's created/updated describes when the sample content was last edited, or when the connector was configured — neither is what the reader is looking at the ten */Gallery demo types; AppleWeather, AppleMaps/Maps, ICloud/Calendar, AppleMusic/Music, HomeAssistant/Home

37 decline in total.

🚨 A decline is one word to change, AND a wrong reason is worse than the absence it replaced. That is the point of writing it down: a reason someone disagrees with is a conversation, where an absence was not even visible — but a reason that is FALSE of its page is a claim the next reader trusts and nobody checked. Three of the first drafts were exactly that and were caught on review: Chess/Game and Chess/GambitHunt both claimed "the move list is the record of when the game started and when it last moved" over boards that render SAN move numbers on types storing no instants (Chess/Game is now framework-supplied, because the node's own instants are then the only timing a reader has), and RemoteControl/Console carried the Screen page's live-mirror reason although Start is a launcher that mirrors nothing. Check the page, not the family, before you reuse a family's sentence.

The gate

scripts/check-node-page-provenance.py walks every WithDefaultArea(...) in the repository — .cs files and the configuration lambdas inside NodeType JSON alike — resolves the renderer registered for that area (in the same body, or through the Add*LayoutAreas() extension the lambda calls), and fails on a bare WithView. A default area it cannot resolve to a registration is a failure too, unless it is named in EXPECTED_UNRESOLVED with a reason: a site the walk did not check must never be counted as a site that passed.

--self-test asserts all FOUR combinations (own default area / inherited framework default × bare WithView / WithNodePage) against trees built for it, so a walk whose subject moved and whose roots did not cannot print the same green tick. --census prints the table and never fails.

Controls, both sides, at the time of the sweep: on the unfixed tree the gate exits 1 with "96 landing page(s) record no provenance verdict"; on the swept tree it exits 0 with "every node landing page records a provenance verdict."

Core's page — why the default was inverted rather than the call sites patched, the per-type decisions core made for its own four, and both of the mechanism's blind spots: Doc/Architecture/NodePageProvenance (get Doc/Architecture/NodePageProvenance).

Reconnecting…
The connection to the server was interrupted. Trying to restore it…
Trying again…
The connection could not be restored. Reloading the page…
The server was updated. Reloading the page to pick up the latest version.