82 lines
3.3 KiB
Markdown
82 lines
3.3 KiB
Markdown
---
|
||
type: architecture
|
||
title: Anniversary Matching
|
||
description: Rules and file boundaries used to discover Jekyll posts whose publication anniversary is exactly ten years before the current date.
|
||
tags: [matching, jekyll, anniversary, posts]
|
||
timestamp: 2026-08-04T18:35:00Z
|
||
---
|
||
|
||
# Purpose
|
||
|
||
`tenbackward.matching` scans the synchronized blog at
|
||
`<blog_dir>/_posts/blog` and returns posts whose date is exactly ten years
|
||
before the current date. The current date is evaluated in the configured
|
||
IANA timezone, defaulting to `Europe/Berlin` in the matcher.
|
||
|
||
# Discovery Rules
|
||
|
||
1. Walk every Markdown file below the configured post root recursively.
|
||
2. Parse YAML front matter when present.
|
||
3. Require a filename stem shaped as `YYYY-MM-DD-slug` with a valid calendar
|
||
date and non-empty slug.
|
||
4. Prefer a valid front matter `date`; otherwise use the date in the filename.
|
||
5. Match the post year to `today.year - 10` and the month/day to today.
|
||
6. Skip posts with `published: false`.
|
||
7. Return a `MatchedPost` containing the relative path, title, date, and
|
||
canonical site URL. If front matter has no title, use the filename slug.
|
||
|
||
# Leap-Day Behavior
|
||
|
||
A February 29 post matches February 29 when the current year is a leap year.
|
||
In a non-leap year it matches March 1, but only when the post's year is
|
||
exactly ten years before the current year (`today.year − 10`). Because
|
||
`today.year − 10` is never itself a leap year whenever today is a leap year
|
||
(`10 mod 4 == 2`), the Feb 29 ↔ Feb 29 branch is reached independently of
|
||
the year-equality check: any Feb 29 post from a prior leap year is a
|
||
candidate, and its canonical URL keeps the original `YYYY/02/29/<slug>/`
|
||
path.
|
||
|
||
# Output and Integration
|
||
|
||
`find_anniversary_matches()` returns `MatchedPost` values for callers that
|
||
need metadata. `iter_anniversary_paths()` yields only relative paths; the
|
||
pipeline uses those paths as deduplication identifiers in the
|
||
[system state store](/architecture/system-overview.md) and persists new IDs in
|
||
`posted.json`.
|
||
|
||
Unreadable files, malformed front matter, invalid filenames, unpublished
|
||
posts, and unexpected per-file errors are skipped with structured warning
|
||
events rather than aborting the full scan.
|
||
|
||
# Key Files
|
||
|
||
| Path | Responsibility |
|
||
|---|---|
|
||
| `/repo/src/tenbackward/matching.py` | Date parsing, Jekyll front matter handling, anniversary rules, URL construction, and recursive scanning. |
|
||
| `/repo/src/tenbackward/main.py` | Supplies the blog post root and site URL, then applies state deduplication. |
|
||
| `/repo/tests/test_matching.py` | Covers filename/front matter parsing, date matching, unpublished posts, malformed files, URL output, and leap-day behavior. |
|
||
| `/repo/src/tenbackward/blog.py` | Ensures the source repository is cloned or fast-forwarded before matching. |
|
||
|
||
# Examples
|
||
|
||
## Matching post
|
||
|
||
On 2026-08-04, a file named
|
||
`_posts/blog/2016/2016-08-04-release.md` is eligible. Its identifier is
|
||
`2016/2016-08-04-release.md` and its generated URL is:
|
||
|
||
```text
|
||
https://example.com/2016/08/04/release/
|
||
```
|
||
|
||
## Skipped post
|
||
|
||
A file with `published: false`, an invalid date, or a non-matching
|
||
month/day is not yielded and does not enter `posted.json`.
|
||
|
||
# Related
|
||
|
||
* [Pipeline Runner](/architecture/pipeline-runner.md)
|
||
* [System Architecture](/architecture/system-overview.md)
|
||
* [Daily Run Guide](/guides/daily-run.md)
|