# How Canonical Application States in `templates/states.yml` Ensure Consistent Status Tracking

> Discover how canonical application states in templates/states.yml provide a single source of truth for consistent status tracking at merge time. Learn more.

- Repository: [Santiago Fernández de Valderrama/career-ops](https://github.com/santifer/career-ops)
- Tags: internals
- Published: 2026-06-10

---

**The [`templates/states.yml`](https://github.com/santifer/career-ops/blob/main/templates/states.yml) file serves as the single source of truth for all application statuses, with `merge-tracker.mjs` enforcing these definitions at merge time to prevent invalid values from polluting the tracker.**

In the `santifer/career-ops` repository, maintaining data integrity across multilingual job applications requires a centralized schema. The **canonical application states** defined in [`templates/states.yml`](https://github.com/santifer/career-ops/blob/main/templates/states.yml) standardize how every status—from "Applied" to "Evaluated"—is written, validated, and rendered across both the tracker and the dashboard.

## The Single Source of Truth

[`templates/states.yml`](https://github.com/santifer/career-ops/blob/main/templates/states.yml) defines every allowable state exactly once, establishing a contract that both the data pipeline and the user interface must honor. Each state entry includes a unique identifier, a human-readable label, language-specific aliases, and a descriptive explanation.

The header comment on lines 1–4 explicitly mandates that both the writer (Career-Ops scripts) and the dashboard must use these exact values. This prevents drift between what gets stored in [`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md) and what the user interface expects to display.

```yaml

# Example structure from templates/states.yml

- id: applied
  label: Applied
  aliases: [aplicado, candidatado]
  description: Application submitted, awaiting response

```

## Validation Pipeline in merge-tracker.mjs

When new entries are merged into [`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md), the `merge-tracker.mjs` script executes a strict validation sequence through the `validateStatus()` function.

### Canonical State Matching

The validator first normalizes the raw input by stripping markdown bold markers and stray dates. It then compares the cleaned value against the `CANONICAL_STATES` constant defined on lines 52–54. Direct matches pass through unchanged.

### Alias Resolution

If the value does not match a canonical label directly, the function checks the alias map on lines 65–74. This map mirrors the `aliases` field in [`states.yml`](https://github.com/santifer/career-ops/blob/main/states.yml), allowing inputs like "aplicado" to resolve to the canonical "Applied" state.

### Fallback Handling

Any value that fails both checks triggers a warning log on line 82 and is coerced to **Evaluated** (lines 82–84). This prevents rogue strings from entering the tracker while preserving the data row.

## Practical Examples

The following TSV patterns demonstrate how the validation layer handles different input scenarios:

**Example 1: Direct canonical match**

```tsv
1	2026-06-10	Acme Corp	Senior Data Engineer	Applied	4.2/5	✅	reports/001-acme-2026-06-10.md	First contact

```

`validateStatus('Applied')` finds an exact match in `CANONICAL_STATES` and stores the value unchanged.

**Example 2: Spanish alias resolution**

```tsv
2	2026-06-11	TechCo	Backend Engineer	aplicado	3.8/5	❌	reports/002-techco-2026-06-11.md	Skipped after interview

```

The function locates "aplicado" in the alias map (lines 66–68) and converts it to **Applied** before writing the row.

**Example 3: Invalid status fallback**

```tsv
3	2026-06-12	StartupX	Full-Stack Engineer	unknown_status	4.0/5	✅	reports/003-startupx-2026-06-12.md	-

```

The validator rejects `unknown_status`, logs a warning, and substitutes **Evaluated** to maintain schema integrity.

## Bidirectional Safety

The architecture ensures consistency across both data ingress and egress.

- **Writer side**: All scripts that generate tracker rows—including `batch/tracker-additions/*.tsv` handlers—must supply a `status` field matching either a canonical label or a supported alias. The validation step in `merge-tracker.mjs` guarantees only these values reach [`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md).

- **Reader side**: The dashboard components (located in `src/dashboard/…`) load [`templates/states.yml`](https://github.com/santifer/career-ops/blob/main/templates/states.yml) directly to render status filters, color coding, and statistical aggregations. Because the UI and the pipeline share the same YAML file, any modification to a label or alias automatically propagates to both systems without code changes.

## Extending States Without Breaking Changes

Adding a new application state requires only two steps: appending a new `- id:` block to [`templates/states.yml`](https://github.com/santifer/career-ops/blob/main/templates/states.yml) and optionally extending the alias map in `merge-tracker.mjs`. The validation logic immediately accepts the new label, and the dashboard instantly recognizes it, eliminating deployment coordination between the data pipeline and the frontend.

## Summary

- **[`templates/states.yml`](https://github.com/santifer/career-ops/blob/main/templates/states.yml)** defines the complete, version-controlled set of allowable application statuses and their multilingual aliases.
- **`merge-tracker.mjs`** enforces these definitions through the `validateStatus()` function, normalizing inputs and coercing invalid values to **Evaluated**.
- **Bidirectional loading** ensures the dashboard and the tracker remain synchronized by reading from the same YAML source.
- **Alias support** permits natural language inputs while maintaining canonical data storage.

## Frequently Asked Questions

### What happens if I submit a status that is not defined in states.yml?

The `validateStatus()` function in `merge-tracker.mjs` logs a warning and coerces the value to **Evaluated** (lines 82–84). This prevents schema corruption while preserving the application record.

### How does the dashboard stay synchronized with the tracker data?

The dashboard UI loads [`templates/states.yml`](https://github.com/santifer/career-ops/blob/main/templates/states.yml) directly to generate filter options and color mappings. Because both the writer (merge script) and reader (dashboard) consume the same file, changes to state definitions apply instantly to both systems.

### Can I add custom aliases for existing states without modifying the code?

Yes. Add new aliases to the `aliases` array in [`templates/states.yml`](https://github.com/santifer/career-ops/blob/main/templates/states.yml). For the validation pipeline to recognize them immediately, you may also need to update the alias map in `merge-tracker.mjs` (lines 65–74), though the YAML change alone suffices for dashboard rendering.

### Where is the validation logic implemented in the codebase?

The primary validation logic resides in `merge-tracker.mjs`, specifically within the `validateStatus()` function (lines 52–84). This function references the `CANONICAL_STATES` constant and the alias map to normalize and verify all incoming status values.