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:
- the area a node type names with
WithDefaultArea, and the renderer registered for it; - a node type that names no default area lands on the framework's own
Overview, so a bareWithView(MeshNodeLayoutAreas.OverviewArea, …)replaces its landing page exactly as an own-area registration does.Space,Skill,Document,Mailing,ExerciseandDeckare 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."
Related
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).