Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 34 additions & 35 deletions docs/adr/0004-semantic-plan-describes-intent-not-feasibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,59 +6,58 @@ Accepted

## Context

Deployah's plan was treated as a preview of the Kubernetes state a
deploy would produce. That mixed two questions: what Deployah intends
to do, and whether the API server, admission, quotas, RBAC, and
controllers would accept or mutate it.

Answering the second question pulled planning onto write dry-runs,
managedFields reconstruction, and partial results when prediction was
uncertain. A correct statement of intent then looked incomplete
whenever Kubernetes might later refuse the write.
A semantic plan can answer what the release declares, or whether
Kubernetes would accept the write. Those are different questions.
Admission, quotas, RBAC, webhooks, and field ownership decide the
second one. Pulling that into the plan turns a statement of intent
into a guess about a later deploy.

## Decision

The semantic plan describes Deployah's intended Kubernetes operations
if this deployment runs. It is not an execution-feasibility engine and
not a Kubernetes state prediction engine. A plan may be correct even
when the later deploy fails.
The semantic plan describes release intent. It is not an
execution-feasibility engine and not a prediction of the API server's
final state. A plan may be correct even when the later deploy fails.

Resource Changes compare Previous with Desired (ADR-0011). Drift
compares Previous with Live (ADR-0005). Live does not change a
Resource Change.

These concerns are outside semantic planning:
Rendering Desired uses Helm's client-side dry-run and release
history. Helm discovery during that render is valid. After that
render, classifying a Resource Change uses discovery only to resolve
scope. It does not GET Live objects, and it does not call Create,
Update, Patch, or Delete, including server-side or mutating dry-run
forms of those writes.

- admission controllers and validating or mutating webhooks
- quota availability and write RBAC
- server-side apply ownership conflicts and immutable-field rejection
- Kubernetes defaulting, controller reconciliation, and final generated
names
- the exact final API-server state
These concerns are outside the semantic plan:

A later prediction, preflight, or feasibility capability may cover some
of those. It is not part of this contract, including under a renamed
prediction abstraction.
- server-side apply and server dry-run prediction
- managedFields migration
- ownership and adoption feasibility
- admission, quota, RBAC, and conflict prediction
- pre-flight checks
- how Helm writes or deletes objects

Semantic planning is read-only toward the cluster. GET, discovery, and
REST mapping are allowed. CREATE, UPDATE, PATCH, DELETE, and any
dry-run form of those writes are not. Dry-run mutation is not part of
Previous, Live, or Desired.
A later pre-flight capability may cover some of those. It is not part
of this contract.

Required release and cluster state is ADR-0010. Resource consequences
are ADR-0011. API constructibility and migration are ADR-0013. Secret
presentation is ADR-0009. Deployah contract validation is ADR-0007.
Required release state is ADR-0010. API constructibility is ADR-0013.
Secret presentation is ADR-0009. Deployah contract validation is
ADR-0007. Ownership is ADR-0012.

## Consequences

### Positive

- Plan output stays a statement of intent, not a guess at API-server
success.
- Planning cannot depend on write dry-runs or managedFields
reconstruction.
- A Resource Change does not depend on Live or on a write dry-run.

### Negative

- Live replicas `4` and Desired replicas `3` may correctly show
`4 -> 3` even if another field manager owns the field and runtime
server-side apply fails.
- Previous replicas `3`, Desired replicas `5`, and Live replicas `4`
report a Resource Change of `3 -> 5`. Live `4` is Drift, not the
change.
- Desired replicas `100` remains valid intent even if admission later
rejects values above `20`.
- Operators who want "will this deploy work?" need a different
Expand Down
82 changes: 45 additions & 37 deletions docs/adr/0005-semantic-plan-uses-previous-live-and-desired.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,73 +12,81 @@ renders. Collapsing those into one "before -> after" hides whether a
difference is a config edit, cluster drift, or both.

Field-level Live noise also looks like intent if the plan treats
"absent from Desired" as a removal.
"absent from Desired" as a removal of Live state.

## Decision

Semantic planning uses three distinct states.

- Previous is the intent recorded by the Helm release baseline selected
by Deployah's release-preparation semantics.
- Previous is the intent recorded by the Helm release baseline
selected by Deployah's release-preparation semantics. On a fresh
install, Previous is empty.
- Live is the actual current Kubernetes state.
- Desired is the deterministic Kubernetes intent generated from the
current Deployah configuration and Helm render.

Those states answer different questions and are independent:
Those states answer different questions:

- Previous -> Desired: declarative / release intent change
- Previous -> Live: drift
- Live -> Desired: visible resource consequence when the semantic
plan includes the corresponding write (ADR-0011)
- Previous -> Desired: Resource Changes (ADR-0011)
- Previous -> Live: Drift

Do not infer a release change merely by comparing Live and Desired.
Ordinary drift alone is not a release change (ADR-0006). Whether a
requested deployment runs is ADR-0016.
Live -> Desired is not a semantic comparison. Do not infer a
Resource Change or Drift by comparing Live and Desired. Ordinary
drift alone is not a release change (ADR-0006). Whether a requested
deployment runs is ADR-0016.

Drift is intrinsic. If Previous and Live are available, Previous ->
Live is always evaluated. There is no opt-in drift mode. When there
is no drift, presentation may omit an empty Drift section.
Drift needs Previous. A fresh install has no Previous, so it reports
no Drift. Unexpected-resource drift applies only to an existing
release: a Live object with no Previous record. A field present only
in Live and absent from Previous and Desired does not automatically
become field drift. Fields declared in Previous that differ in Live
are drift regardless of who changed them. A field intentionally
absent from Desired that existed in Previous is a declarative
removal: a Resource Change, not Drift by itself.

A field present only in Live and absent from Previous and Desired does
not automatically become field drift. Fields declared in Previous that
differ in Live are drift regardless of who changed them. A field
intentionally absent from Desired that existed in Previous is a real
declarative removal.

Do not report API-server bookkeeping as resource changes or drift:
Do not report API-server bookkeeping as Resource Changes or drift:
status, uid, resourceVersion, generation, managedFields, creation
timestamps, and similar server-maintained metadata. This is not a
statement about Kubernetes managed-field ownership.

Previous absent, Live present, Desired present is resource-level
drift: the Live logical resource is unexpected relative to Previous.
That is distinct from field-level Live-only state on an expected
resource. Previous present and Live absent is missing-resource drift,
not synthetic field removals. Hook runtime artifacts are outside this
rule (ADR-0014). Resource consequences for those states are ADR-0011.

Resources with the same group, kind, effective namespace, and name are
the same logical resource. apiVersion is not part of that identity. An
A named resource's logical identity is group, kind, effective
namespace, and name. apiVersion is not part of that identity. An
apiVersion transition is not Delete plus Create. Reading the same
logical object through another served apiVersion must not produce
drift solely because `/apiVersion` differs. For a cluster-scoped
resource, effective namespace is empty. Duplicate Desired logical
identities fail planning.
drift solely because `/apiVersion` differs.

Effective namespace is part of identity. Snapshots keep the declared
content unmodified. For a namespaced resource, an omitted
`metadata.namespace` and an explicit release namespace are the same
effective namespace. For a cluster-scoped resource, effective
namespace is empty. A declared `metadata.namespace` on that object
stays in the snapshot. It is ordinary declared content: it is not
stripped, and it is not part of identity.

`metadata.generateName` without `metadata.name` has no logical
identity. It is a fallback declaration key for pairing Previous with
Desired: group, kind, effective namespace, and generateName. That key
cannot locate a Live object. Duplicate logical identities on one side
fail planning. Duplicate generateName keys on one side fail planning
because pairing is ambiguous. Setting both name and generateName
fails planning. Setting neither fails planning.

Labels and annotations in Previous or Desired are ordinary declared
fields. Comparison normalization is ADR-0015. Secret presentation is
ADR-0009.
ADR-0009. Hook runtime artifacts are outside Drift (ADR-0014).

## Consequences

### Positive

- Operators can tell a config edit from cluster drift on the same
object.
- A fresh install reports Resource Changes and no Drift.
- Live-only noise does not show up as Deployah removals.

### Negative

- Resource consequences and Drift must both be read.
- Declared-surface filtering can hide Live fields the operator cares
about until they appear in Previous or Desired.
- On an existing release, Resource Changes and Drift must both be
read.
- A generateName declaration cannot be matched to a Live object by
that key alone.
62 changes: 26 additions & 36 deletions docs/adr/0006-semantic-plan-follows-helm-lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,66 +7,56 @@ Accepted
## Context

Deployah ships a generated umbrella chart and extra manifests through
Helm. A parallel Kubernetes lifecycle (forced CRD upgrades, install-time
namespace creation on upgrade, planner-invented recreates) makes the
plan describe operations Helm would not attempt.
Helm. The plan should describe the release Helm records, not a
parallel Kubernetes lifecycle.

Deployah's Helm 4.3.0 install path sets `CreateNamespace` and
server-side apply. Helm then creates that implicit Namespace through
an apply PATCH. An existing target namespace can therefore change on
install.
Helm install creates the target namespace before the release, with
CreateNamespace. That namespace is an execution prerequisite. It is
not a release object.

## Decision

Deployah follows the lifecycle Helm actually performs. The semantic
plan uses Helm lifecycle semantics to describe release changes and
their resource and task consequences. The plan does not decide
their Resource Changes and task changes. The plan does not decide
whether a requested deployment runs (ADR-0016).

The plan reports a release change when the desired release differs
from the release Helm last recorded, including hook definitions.
Ordinary live drift, including a missing live object, is not by
itself a release change. A plan with no release change does not
invent resource consequences (ADR-0011). Drift stays independent
invent Resource Changes (ADR-0011). Drift stays independent
(ADR-0005). When Helm upgrades, unchanged preDeploy and postDeploy
hooks still run (ADR-0014).

CRD lifecycle is ADR-0008.

Namespace lifecycle comes from Helm install `CreateNamespace`, not a
chart Namespace manifest. Deployah enables that flag together with
server-side apply. Helm applies an implicit Namespace (name, plus a
`name` label) through the server-side apply Create path.
The target namespace is an execution prerequisite created by Helm
install, outside the release. It is never a Resource Change. Planning
does not read Live to decide whether that namespace exists. A missing
namespace is not a planning failure.

On install: missing Namespace is Create; existing Namespace with
differing declared implicit fields is Update; already matching is no
visible change. Live-only fields Helm did not declare must not become
removals (ADR-0005).
Neither the Desired render nor the Previous release baseline may
declare that target namespace. If either does, planning fails with a
Deployah contract error (ADR-0007). Deployah does not filter, drop,
or rewrite the stored release or the rendered manifest. Other
Namespace names are ordinary Resource Changes.

On a real upgrade Helm does not run CreateNamespace. Do not invent a
Namespace Create or Update from it. A missing release Namespace on
upgrade is not a planning failure. A later Helm or Kubernetes failure
is outside semantic planning (ADR-0004).

Raw or custom manifests must not define Namespace resources. That is a
Deployah contract violation (ADR-0007). Do not merge a raw Namespace
with the implicit Helm namespace operation.

A Desired resource with `metadata.generateName` is a Create that uses
that generateName. Do not fabricate the final runtime name. The
unknown final name does not make the semantic plan partial.
A resource with `metadata.generateName` and no `metadata.name` is
paired by its declaration key (ADR-0005). Do not fabricate the final
runtime name.

## Consequences

### Positive

- Planned release changes and their consequences follow Helm's
install, upgrade, and uninstall lifecycle, including server-side
apply on an install Create.
- Resource Changes follow the release Helm records.
- The target namespace stays an execution prerequisite, not a release
object.

### Negative

- Implicit Namespace fields can change on install through Helm
server-side apply. They do not change on upgrade.
- Raw Namespace manifests are rejected even when Kubernetes would
accept them.
- A chart or stored release that declares the target namespace fails
planning, even when Kubernetes would accept the object.
- An existing release that already claims the target namespace fails
planning. Deployah does not rewrite the stored manifest.
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ behavior ambiguous:
- duplicate logical resource identities (ADR-0005)
- a raw resource that collides with a resource Deployah generates or
manages
- raw Namespace resources; Namespace lifecycle belongs to Deployah's
Helm configuration (ADR-0006)
- a Namespace that names the release target, in the Desired render
or the Previous baseline, because that namespace is Helm install's
execution prerequisite (ADR-0006)
- mutually incompatible Deployah configuration, including combinations
the spec itself defines as exclusive (for example `replicas` with
`autoscaling.enabled`)
Expand Down
25 changes: 15 additions & 10 deletions docs/adr/0010-semantic-plan-requires-release-and-cluster-state.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,22 @@ Accepted

## Context

A semantic plan is built from Previous and Live (ADR-0005). Rendering
Desired YAML without those states can still validate a spec. Treating
that render as a semantic plan fabricates Drift, HelmAction, and
resource consequences.
A semantic plan is built from Previous and Desired (ADR-0005).
Rendering Desired YAML without the release baseline can still
validate a spec. Treating that render as a semantic plan fabricates
HelmAction and Resource Changes.

## Decision

A semantic plan requires enough release state to construct Previous
and enough cluster state to construct Live. Deployah does not support
offline semantic planning.
Resource Changes require enough release state to construct Previous,
and discovery to resolve scope (ADR-0013). Drift requires Live as
well. Deployah does not support offline semantic planning.

When that information is unavailable, the planner must not fabricate
Previous, Live, Drift, HelmAction, or resource consequences.
Previous, Drift, HelmAction, or Resource Changes.

A fresh install has an empty Previous. That is known release state,
not a missing baseline.

Cluster-independent validation and configuration resolution are
separate capabilities. They are not semantic planning.
Expand All @@ -27,10 +30,12 @@ separate capabilities. They are not semantic planning.

### Positive

- Plan output cannot invent a HelmAction or resource consequence from
- Plan output cannot invent a HelmAction or Resource Change from
Desired YAML alone.

### Negative

- Operators without release and cluster access cannot obtain a
- Operators without release access and discovery cannot obtain a
semantic plan.
- Drift also needs Live, so it cannot be reported from the release
baseline alone.
Loading
Loading