# Lifecycle Route Contract

**UNOFFICIAL/EXPERIMENTAL.** Schema and derivation rules for
[routes.csv](./routes.csv) and [routes.json](./routes.json), generated by
`scripts/generate-lifecycle-routes.mjs`. Both the data and this contract are
generated from the same constants, so they cannot drift.

## Purpose

For every hook or hook-like lifecycle behavior, make machine-readable: the
route, who executes it, the default, the named alternatives and their
requirements, whether a human and an agent can pick an alternative, the
evidence or next action, and whether it is safe to show as automatic.

## Columns

| Column | Meaning |
| --- | --- |
| `chart` / `version` | Source chart and version. |
| `base_or_variant` | Specific base when a selected receipt proves an exact route; empty means the chart-level route used by bases without an exact receipt. |
| `quirk_class` | The behavior class, derived from `route_name` (see table). |
| `hook_phases` | Helm hook phases recorded by the source scan. |
| `source_disposition` | The raw disposition word from the source data, kept for traceability. |
| `disposition` | Published word: `observed`, `routed`, `per-target`, `refused`, or `todo`. |
| `route_name` | The selected/current route for this behavior. |
| `default_route` | The default route among the alternatives (equals `route_name`). |
| `alternatives` | Named off-ramp routes for this quirk class. |
| `route_requirements` | What the current route needs (target facts, render conditions). |
| `execution_mode` | Who performs it: `product-executes`, `user-executes`, `target-owned`, or `not-yet-executable`. |
| `human_offramp` | How a human picks an alternative (`recipe-base-or-values` or `none`). |
| `agent_offramp` | How an agent picks an alternative (`routes-json` or `none`). |
| `live_status` | `observed`, `blocked`, `none`, or `candidate-route-plan`. |
| `safe_as_automatic` | `yes` only when `execution_mode` is `product-executes` and evidence proves it. |
| `evidence_or_next_action` | Receipt path when observed, otherwise the next action. |

## Invariants

1. **No silent automation.** `safe_as_automatic = yes` requires
   `execution_mode = product-executes` plus evidence. No row qualifies today,
   so the column is uniformly `no`. This is deliberate.
2. **`routed` names an executor.** A `routed` row always records an
   `execution_mode`; `candidate-route`/`recipe-needed` only map to a real
   disposition once a route and executor are named.
3. **`refused` is final; `todo` is unfinished.** `refused` is a deliberate
   decision not to run a behavior automatically. `todo` (from
   `recipe-needed`/`candidate-route-plan`) means no recipe/route exists yet and
   a next action is named. They are never merged.
4. **Generated, not authored.** Regenerate with `npm run lifecycle:routes`;
   verify byte-for-byte with `npm run lifecycle:routes:verify`.

## Source Disposition Mapping

The source data uses more than one vocabulary. This is the published join.

| Source value | Published disposition |
| --- | --- |
| `observed` | `observed` |
| `routed` | `routed` |
| `per-target` | `per-target` |
| `refused` | `refused` |
| `recipe-needed` | `todo` |
| `candidate-route` | `routed` |
| `candidate-route-plan` | `todo` |
| `selected-route-receipt` | `observed` |

## Route → Quirk Class

| route_name | quirk_class |
| --- | --- |
| `preflight-or-presync` | `hook-phase` |
| `preflight-or-presync-crd-apply` | `crd-install` |
| `postsync-check-or-observation` | `hook-phase` |
| `upgrade-action-with-receipt` | `hook-phase` |
| `explicit-managed-action` | `hook-phase` |
| `argocd-or-flux-lifecycle-hook` | `hook-phase` |
| `target-class-preflight-and-upgrade-action` | `per-target-hook` |
| `recipe-time-lifecycle-verification` | `hook-phase` |
| `explicit-test-check` | `hook-test` |
| `webhook-readiness-observation` | `webhook-readiness` |
| `target-facts-or-preflight` | `target-facts` |
| `preserve-ordering` | `hook-weight-ordering` |
| `preserve-cleanup-policy` | `hook-delete-policy` |
| `delete-cleanup-policy` | `hook-delete-policy` |
| `explicit-delete-cleanup-action` | `hook-delete-policy` |
| `explicit-post-install-check` | `hook-test` |
| `postsync-readiness-observation` | `webhook-readiness` |
| `self-contained-crd-base` | `crd-install` |

## Route → Execution Mode (base)

Before the `todo`/`refused` override, which forces `not-yet-executable`.
No route maps to `product-executes`.

| route_name | execution_mode |
| --- | --- |
| `preflight-or-presync` | `user-executes` |
| `preflight-or-presync-crd-apply` | `user-executes` |
| `postsync-check-or-observation` | `user-executes` |
| `upgrade-action-with-receipt` | `user-executes` |
| `explicit-managed-action` | `not-yet-executable` |
| `argocd-or-flux-lifecycle-hook` | `target-owned` |
| `target-class-preflight-and-upgrade-action` | `user-executes` |
| `recipe-time-lifecycle-verification` | `user-executes` |
| `explicit-test-check` | `user-executes` |
| `webhook-readiness-observation` | `target-owned` |
| `target-facts-or-preflight` | `user-executes` |
| `preserve-ordering` | `target-owned` |
| `preserve-cleanup-policy` | `target-owned` |
| `delete-cleanup-policy` | `target-owned` |
| `explicit-delete-cleanup-action` | `user-executes` |
| `explicit-post-install-check` | `user-executes` |
| `postsync-readiness-observation` | `user-executes` |
| `self-contained-crd-base` | `target-owned` |

## Quirk Class → Alternatives (off-ramps)

The off-ramp routes a user or agent can choose instead of the default, and what
each needs. Grounded in
[../../docs/user/hook-lifecycle-strategy.md](../../docs/user/hook-lifecycle-strategy.md)
and [../../docs/planning/where-does-my-hook-go.md](../../docs/planning/where-does-my-hook-go.md).

| quirk_class | alternative route | requirement |
| --- | --- | --- |
| `hook-phase` | `argocd-or-flux-lifecycle-hook` | a GitOps controller (Argo CD/Flux) that runs sync hooks or sync waves |
| `hook-phase` | `target-facts-or-preflight` | the prerequisite supplied as a target fact or preflight before apply |
| `hook-phase` | `refuse` | accept that the catalog will not run this automatically and do it manually |
| `crd-install` | `self-contained-crd-base` | a preset config that renders the required CRDs as ordinary objects before the workloads |
| `crd-install` | `target-owned-crds` | the cluster or an operator owns CRD install and upgrade out of band |
| `crd-install` | `refuse` | use a no-crds base and install/upgrade the CRDs yourself |
| `hook-delete-policy` | `delete-on-uninstall` | accept deletion of the hook-managed object on uninstall |
| `hook-delete-policy` | `refuse` | manage cleanup manually and keep the object |
| `hook-weight-ordering` | `argocd-sync-waves` | a GitOps controller that maps hook weights to sync waves |
| `hook-weight-ordering` | `apply-order` | an apply step that preserves the documented order |
| `hook-test` | `ci-check` | run the test as a CI or post-apply check instead of a Helm test hook |
| `hook-test` | `skip-test` | accept that the test is not run automatically |
| `webhook-readiness` | `postsync-check-or-observation` | a post-apply readiness check you run or observe |
| `target-facts` | `preflight-or-presync` | a preflight step that establishes the fact before apply |
| `target-facts` | `refuse` | supply the fact manually and accept no preflight |
| `per-target-hook` | `per-target-scope-split` | named target classes, each with its own route decision |

## Off-ramps Today

The human off-ramp is recipe base/values selection. The agent off-ramp is
[routes.json](./routes.json): a stable enumeration of routes, defaults,
alternatives, and requirements an agent can read and act on. Selecting an
alternative is a recipe-level change today; this contract makes the choice
legible to both readers. Neither off-ramp implies the product executes the
route — see invariant 1.
