Search Console ownership is proved by a file at the site root, not by the meta tag
Affects: public/google852f2393ed012f13.html, app/layout.tsx, app/robots.ts, .env.example
Decision. https://www.novelsystems.ca/ is a URL-prefix property in Google Search Console, on the Google account signed into this workstation, and ownership is proved by the HTML file method: public/google852f2393ed012f13.html, which contains one line and is served at the site root. Verification was confirmed on 7 August 2026 — the property shows "Ownership verified", method "HTML file". The meta-tag path stays wired in app/layout.tsx and documented in .env.example as a second, currently-unset method.
Why not the tag. The tag renders from NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION, which is a build-time value and therefore lives in Vercel's project environment. Setting it means a Vercel dashboard session, and the dashboard was signed out in the browser available here. The trade on offer was: ask the owner for a password so that a token which is public by construction — it ships in the HTML of every page — can be placed in a private console. That is a bad trade. The file method needs no dashboard, no environment variable and no rebuild coupling: the proof is a static asset that deploys with the commit, and it is the method Google itself labels recommended. The brief asked for the tag; this is a deliberate departure from it, recorded here rather than left as a silent substitution.
Why both are kept. A property can hold several verification methods at once, and Google re-checks on its own schedule. Two configured methods means one can fail — a file deleted in a public/ cleanup, an environment variable dropped in a project migration — without the property going dark. The tag costs a conditional spread in app/layout.tsx that omits the key entirely when the variable is unset, so an unconfigured fallback ships nothing.
Sitemap. /sitemap.xml was submitted against the property and reads back Status Success, 128 discovered pages. That number reconciles exactly: 28 hand-declared entries in PUBLIC_ROUTES plus one per decision from allDecisions(), of which there were 100 at submission. scripts/check-links.mjs reports only the 28 because it counts path: declarations in config/routes.ts; the derived half cannot drift by construction, so there is nothing there for a guard to check. A future reader comparing 28 against 128 and concluding the sitemap is broken is reading two different things.
Still open. The apex novelsystems.ca carries a stale google-site-verification TXT record — see D-033 — belonging to an account nobody here has established access to. If that account is reachable it is worth reusing for a Domain property, which covers every subdomain and both schemes at once and would make this URL-prefix property redundant; if it is not, the record should be deleted as debris. Separately, app/robots.ts advertises a sitemap on the docs host, and a cross-host sitemap is only honoured when both hosts are verified to the same owner. The docs host has no property, so that line is currently read and ignored.
What would make this wrong. Deleting public/google852f2393ed012f13.html un-verifies the property at Google's next check — this is the single most likely way for this to break, and nothing in the build guards it, which is why the file carries a comment saying so and why the tag exists as a fallback. If a Domain property is ever created off the apex TXT, this entry is superseded and the URL-prefix property should be removed rather than left to accumulate a second set of stale reports.