Case 404-PAP: the hardcoded drift

How a lost afternoon under an anti-PAP drift was fixed in 8 lines — and turned into a system

Jesús Pérez
Adding a content type that 'should have been just NCL' cost ~88 minutes, 8 builds and 8 false leads: a hardcoded match shadowed the routes registry (anti-PAP). The fix was 8 lines. But what the case left behind — a howto, a mode, a proof, a series — is the real evidence of what ontoref is for.
Case 404-PAP: the hardcoded drift

🕵️ Show the full case file → 📋 Session protocol →

Case file · Code Homicide Dept.

It was a quiet afternoon. Then <span class='mono'>/recursos</span> walked in and showed nothing. Nothing at all. And I, naive, thought it would be quick.

Case No. 404-PAPClassification: ANTI-PAPStatus: SOLVED
Show glossary
PAP
Project's Architecture Principles — the rules and patterns holding the project's architecture up: the single source of truth all code must respect.
anti-PAP
Code written against those rules: a stub that re-lists by hand what routes.ncl already declares, duplicating and contradicting the source of truth.

The protocol to declare, version and verify this → ontoref.dev

The double ledger — what it cost, and what it left

A case isn’t judged only by what it costs. It’s judged by what it leaves. From a hope-less low —eight false leads, six fingerprints, the whole afternoon— we found our bearings, and everything that was travelling on the same journey clicked into place at once:

What the crime cost

  • Pure origin hunt (framework only) ~88 <i>min</i>
  • Release builds launched 8
  • Server restarts ~15+
  • Build fingerprints coexisting 6
  • Files of <i>someone else's</i> code read (2 repos) ~20+
  • Full <span class='mono'>cargo clean</span> runs (867&nbsp;MB gone) 2
  • False leads before the right one 8
  • Size of the final fix 8 <i>lines</i>

What the case left

  • Registry-driven fix PAP-compliant
  • Knowledge content-kind-howto
  • Mechanism generate-case-file mode
  • Evidence proof (positioning)
  • Series + hub /case-files
  • Framework unblocked

That’s the credible depth: the afternoon of pain didn’t end in a patch — it ended in a declared system. Ontology, modes and positioning weren’t “added” — they were travelling together, and the case made them converge.

The point of abandonment — what the table doesn't show

There’s a point in every drift that no cost table records: the one where you stop feeling fear and start wanting to quit. Bugs become ghosts —they appear, vanish, come back with a different face—; everything stalls and you don’t know why; there’s no urgency left, only fatigue. “It’s too much. It can’t be solved.” That’s the real moment, the one not measured in builds — the one that kills projects in silence.

With ontoref that point shouldn’t arrive. Not because there are no bugs, but because it doesn’t leave you alone with them: the dao (ondaod) forces you to name the tension instead of collapsing or abandoning it; the modes hand you the next step when you no longer know what it is; the ADRs close what’s already decided so you don’t re-litigate it at two in the morning. The drift doesn’t sink you because the map holds you up.

The suspects — the false leads

The content in site/r“I wasn't in sync.”minor accomplice
filename ≠ id“The body came back empty, but I'm not the 404 guy.”other crime
The {# … #} comment“I just slipped in as text. Innocent.”solid alibi
Staleness & fingerprints“With 6 fingerprints, how could you not doubt the binary?”false lead
SITE_PUBLIC_PATH / symlink“The dir didn't exist. Symlink for nothing.”false lead
generated.rsvec!["content"]“I'm the fallback. I don't even run.”decoy
RBAC“A deny from me is a 302, darling. A 404 isn't my job.”302 alibi
build_page_generator“That's Leptos. In htmx-ssr I don't even show up.”wrong jurisdiction

The weapon — The weapon · a hardcoded match (pre-existing, anti-PAP)

let kind = match base {
    "blog" => Some("blog"),
    "activities" | "actividades" => Some("activities"),
    // … every kind by hand … except resources …
    _ => None,   // ← /recursos landed here → 404
};

Route registered. Content-type enabled. The API returned the items. The content, present. And still, 404 — because a stub decided the truth against the source of truth.

The turn — PAP-compliant · adding a kind is just NCL again

let kind = load_routes_config().routes.iter().find_map(|r| {
    if !r.enabled { return None; }
    let seg = r.path.trim_start_matches('/').split('/').next().unwrap_or("");
    (seg == base).then(|| r.content_type.clone()).flatten()
});

/recursos, /proyectos, /domains — all of them, bilingual aliases included — resolved from the single source. Adding a kind is just NCL again.

The verdict

We didn’t go to the beach because we chose badly: we went because the map had an unmarked drift. The four things ontoref instruments are the four that were missing:

Hardcoded stub shadowing the registryPAP + anti-patterns → grep-able
Knowledge lost, rediscovered through painqa/howto → content-kind-howto
Building on the invalid without knowingreview against invariants
Assuming the unassumable, believing the unbelievabledeclared state → 3 levels

Case law — what enforces the lesson today

  • The anti-pattern this case promised — the hand-written list that shadows the registry — finally exists as a typed constraint, and it was SEEN TO FAIL against the tree before anything was fixedadr:002/hardcoded-kind-list-shadows-registry
  • And the eight lines went home: rustelo's two templates resolve the kind from the registry, not from a hand-written listcontract:templates/*/crates/server/src/shell/htmx.rs#render_content_or_grid
  • That match arms are an optimisation and not the source of truth — queryable instead of rediscovered the hard wayqa:rustelo-static-page-howto

Declared debt: One thing remains, and it is what decides whether the ones above bite at all: nothing runs the constraint. Rustelo's justfile calls no ADR validator — 23 constraints across 8 ADRs, zero run by recipe or CI — so the rule that now forbids this defect is as written, and as silent, as ADR-002 was for months. A just recipe in rustelo's chain that runs them and reports per constraint would settle it. And two more come with it, ownerless: the instance's cure is still uncommitted (git status M), and templates/ is unversioned — a git clean takes the backport and nobody finds out. Case 8/0 tells this whole story.

A ten-line drift, unmarked, cost us an afternoon to escape — and was fixed in eight. That differential is the ROI. Ontoref is the map’s signposting.

From the project vocabulary (4)
Ondaod
Ontoref Dao Discipline.
PAP
Project's Architecture Principles.
anti-PAP
Approach that violates the project's PAP outright.
ontoref
The protocol itself: a typed, queryable surface on which a project declares WHAT IT IS (ontology) and HOW IT ACTS (reflection), so a claim about the project can be contradicted by a machine and not only by a reader.

The project's full vocabulary →

Was this useful? Rate it
Got something to add? Tell me what you think, what you'd suggest, or whether we should keep exploring this topic.
· reads

We use cookies to help this site function, understand service usage, and support marketing efforts. Cookie Policy for more info.