Skip to content

Blog ·

A date in the future is not a schedule

Two dozen live articles claimed they had not been published yet. The fix was not a scheduler; it was a build that refuses a date it cannot possibly know.

By Provibr

Every article on this site carries a publication date in its front matter. It shows up under the title, it goes into the structured data a search engine reads, and it is the only date the sitemap ever prints. It is supposed to be a fact.

For a while, twenty four of them were not. They carried dates spread over the following six days, because whoever wrote them had a publishing rhythm in mind and put it in the file. Nine languages each, so two hundred and sixteen files. Every one of those pages was live, crawlable and indexed, and every one of them told a crawler in machine readable form that it had been published on a day that had not happened yet.

Nothing was red. The build was green, the tests were green, the pages rendered. The only thing that could see the contradiction was a machine reading the page, and machines do not file bug reports.

What the history said

The first thing to establish was which of the two dates was true. That is one command: ask git when each file was first added. All twenty four were added on the same day, in three commits, by three rounds of the same night's work. There was no six day rollout. The spread was an intention that had been written into a field reserved for observations.

That distinction is the whole article. A field that records what happened cannot also express what you plan to happen, because a reader has no way to tell the two apart, and neither does a crawler.

Why we did not build the scheduler

The obvious repair is to make the dates true by making the site respect them: hide an article until its date arrives. It is the wrong repair here for two independent reasons, and it is worth spelling both out because the first one is easy to miss.

This site is statically generated. Pages are rendered once, during the build, and served as files. A gate on the date is therefore evaluated at build time and never again. Nothing appears at midnight; it appears when somebody builds. So a date gate does not produce a publication schedule, it produces a set of pages that are missing until an unrelated deployment happens to run.

The second reason is worse. Those two hundred and sixteen URLs were already crawled. Hiding them until their nominal date arrives means serving 404 for up to six days on pages a search engine has already seen and indexed. A 404 on a previously indexed URL is not "come back later", it is a removal signal, and the recovery from it costs far more than the untruth being fixed.

So the dates were corrected to the day the work actually landed, touching only that line in each of the two hundred and sixteen files, and the ability to write a future date was removed instead.

The gate is at the source, and it has no escape hatch

The content build now refuses any front matter date later than the day it is building on. Not a warning: a failed build with the file name and both dates in the message.

Two small decisions inside that gate are worth naming. It compares against today in the project's own time zone rather than UTC, because an article published here at half past midnight carries the local date, and a UTC comparison would reject a file on its own publication day. And there is deliberately no override: no environment variable, no flag. An escape hatch on a rule like this gets used once for a good reason and then permanently, and a schedule belongs in a task list, not in the front matter of a file that is already committed.

There is a second gate downstream, in the registry that assembles the published articles, which drops any article whose publication date is still in the future. That one is a safety net for the paths that do not go through the build: a file edited by hand, a merge that mixes two generations, a restored backup. It gates only the publication date and not the modification date, because a typo in a modification date should not make an already indexed page disappear.

And that second gate is evaluated once, when the module loads during the build. That is stated in a comment next to it, because a reader who assumes otherwise will eventually write the scheduler this section argues against.

A date you cannot derive is better left out

The same round had to decide what date, if any, to attach to the documentation pages, which have no front matter at all. Three candidates presented themselves, and all three are untrue.

CandidateWhy it is wrong
The git date of the page componentChanges when a class name changes, and does not change when the text moves into a translation file
The git date of the translation catalogueOne file holds thousands of unrelated strings, so it changes constantly for reasons that have nothing to do with this page
The build dateSays every page was written today, every day

So the documentation pages carry no publication date in their structured data. Nothing is better than a plausible number, because a plausible number is the one nobody checks.

The same reasoning already governs the sitemap. Fixed routes carry no last modified date at all, and the reason is not laziness: their content is source code and their text lives in nine translation catalogues, so any date we could compute would follow something other than what the reader sees. A search engine that fetches a "changed" page three times and finds it unchanged learns to ignore the field, and then the field is worse than absent for the pages where it is real. Articles do carry one, because it sits in the front matter next to the title, where the person rewriting the piece will actually see it.

After this work the coverage did not change at all: the article URLs carry a modification date, everything else carries none, exactly as before. What changed is that two hundred and sixteen of those dates had been in the future, and now none of them are.

One more contradiction, resolved by identity

While auditing the structured data, a smaller version of the same problem turned up: on some pages the author and the publisher were two separate entities with no relationship declared between them. Read literally, those pages claimed the article had been written by one organisation and published by another. Both now point at the same identifier. Nobody would have complained, and it was still a statement we were making that was not true.

What we would write down

  1. Never put an intention in a field that records an observation. A reader cannot tell them apart, and neither can a machine.
  2. Check the claim against the history. One command over the file history settled which of two dates was the real one, and it disagreed with all twenty four files.
  3. A date gate on a statically generated site is not a schedule. Nothing appears at midnight; it appears at the next build.
  4. Do not hide URLs that are already indexed. A 404 on a crawled page is a removal signal, and the cure costs more than the disease.
  5. Put the gate where the mistake is made, fail the build, and do not add an override. Add a second gate downstream for the paths that skip the first.
  6. Leave out a date you cannot derive honestly. Three plausible sources, all of them wrong, means the honest output is nothing.

For clarity about what this is: this is housekeeping on a public website, not a product feature, and it changed no behaviour anybody pays for. Some of it is unproven. The structured data was validated against its own schema and by reading it, not by a real crawler or a real link preview. Nothing here was measured against a search engine's index, because that measurement takes weeks and nobody should wait for it before removing an untruth.

From the site work on Provibr. What it costs to keep sixteen thousand strings correct in nine languages is in sixteen thousand keys, nine languages, and how a field number collision in a protocol stayed hidden is in two rounds, one field number.

Start with one host and one agent

Create an account, install the agent with a single command and connect your first Proxmox host. The trial runs for 14 days and asks for no payment details.