Skip to content

Decide which conditions on the settled decisions change the model #367

Closed

Description

@ExtraToast

Question

Which conditions on the settled decisions change the model, and how?

Find the claims behind the settled decisions that no source supports found 21 claims that hold only with a condition. The evidence is in docs/research/unsupported-claims.md, by claim id. Most are render details the rewrite simply carries. These need a choice first:

  • Spread and parked (Decide how the model meets soft scheduling, spread, anti-affinity and parked replicas): with replicas equal to eligible nodes, a surging rollout under across: node leaves the new pod Pending (C21); unparking a scheduled Process starts one missed run at once, and parking does not stop a running Job (C22); behind one VIP only the announcing node's replica serves (C18).
  • Address pools and node agents (Decide how the foundation's HelmReleases are rendered and owned): a pool must name the nodes on its segment, and a Process that uses it must be placed there (C19); the GPU agents need host driver, toolkit and CDI state that the render does not install (C25, C26); loki-canary is a sixth per-node workload no catalog entry covers (C29).
  • Certificates and secrets (same decision): a Traefik that holds an ACME resolver is capped at one replica (C2) and shares Let's Encrypt's weekly allowance (C3); a static secret change reaches a workload only after refreshAfter (C10); 14 pods request the injected Vault token, which VSO does not deliver (C12).
  • Handover order (Order the Project handovers and what the spec must settle before each): removing a HelmRelease deletes the CRDs its chart templates unless it is suspended first or carries the keep policy (C49).
  • Derivations that will not reproduce live (Decide how the model meets the live needs no chapter covers): the priority class rule changes about eleven workloads (C32); a GPU request is one of four time-sliced shares (C30); egress: internet must also exclude the tailnet range (C36); the RabbitMQ lease lifetime is one setting per mount (C9).

For each, decide whether the model gains a field, a refusal or a derived value, or whether the condition is accepted and written down as a render detail or a runbook check. The remaining conditions (C6, C34, C44, C45, C47) are rewrite inputs unless this ticket finds otherwise.

Activity

  1. added
    type: docsDocumentation-only change.
    area: deployhomelab-deploy, deploy-v2, manifests, and rollout flow.
    wayfinder:grillingWayfinder ticket resolved by conversation (HITL)
    on Oct 12, 2026
  2. ExtraToast commented on Oct 12, 2026

    @ExtraToast
    ContributorAuthor

    Decide what each settled decision does about the claim under it that failed leaves four things for this ticket. Both tier proxies now hold an ACME resolver for the same names, so the one-replica limit (C2) and the shared allowance (C3) apply to two instances. A pool-kind L4 Address already names its site, which is where C19 starts; a node-kind one renders hostPort. The Node Agent catalog stays host-privileged only, so loki-canary (C29) cannot enter it. The RabbitMQ lease lifetime per mount (C9) is read by the rotation drill.

  3. self-assigned this
    on Oct 12, 2026
  4. ExtraToast commented on Oct 12, 2026

    @ExtraToast
    ContributorAuthor

    Resolution

    Settled with the maintainer on 2026-10-12, under Re-state the goals and non-goals against the scale premise: the compiler derives everything procedural, and a refusal stays only where its hazard is real at this scale. Input: docs/research/unsupported-claims.md, by claim id, fleet-infra at 4e4a9bb, and two drills run for this ticket.

    Of the sixteen conditions, one adds a refusal, one adds a Platform-authored list, seven add or fix a derived value, and seven are accepted and written down. No Process gains a field.

    The drills behind C21 and C22

    Both ran on the live cluster (k3s v1.35.2+k3s1) on 2026-10-12, in a throwaway namespace that was deleted afterwards. Every pod was busybox:1.36 as user 1000 with all capabilities dropped.

    C21. One Deployment of two replicas, selected onto enschede-gtx-960m-1 and enschede-t1000-1, with the required anti-affinity term on the hostname and a rolling update of surge 1 and unavailability 0, which is the strategy a blue-green Process renders.

    • Without the key, a template change left the new pod Pending for the 60 seconds watched, with 0/6 nodes are available: 2 node(s) didn't match pod anti-affinity rules, 4 node(s) didn't match Pod's node affinity/selector. The rollout did not progress.
    • With matchLabelKeys: [pod-template-hash] on the term, the same change rolled out in 4 seconds. The two new pods ran one on each node. The API server stored the hash inside the term's selector (pod-template-hash In [5c978c9c59]), so the scoping is part of the pod and needs nothing from the controller.

    C21 is therefore settled by test for a plain Deployment. A promotion by Flagger was not part of it.

    C22. Three CronJobs on a one-minute schedule with concurrencyPolicy: Forbid: one with no deadline, one with startingDeadlineSeconds: 20 and one with 300. All three ran at 10:59:00Z, were suspended at 10:59:54Z across three slots, and were resumed at 11:02:41Z, 41 seconds after a slot.

    • No deadline: a Job for the 11:02:00Z slot was created at 11:02:41Z, at once.
    • Deadline 20: no Job until the 11:03:00Z slot.
    • Deadline 300: a Job for the 11:02:00Z slot was created at 11:02:45Z, at once.

    So a deadline skips a missed run only when the slot is older than the deadline. Under Forbid, the two catch-up Jobs were still active at 11:03:00Z and that slot was skipped for both (JobAlreadyActive); their pods ran on a node that took minutes to start them.

    Spread and parked

    1. C21: the across: node term is scoped to one revision. The required anti-affinity term carries matchLabelKeys: [pod-template-hash], derived, on every Process with across: node. A surging rollout can then place its new pod when replicas equal eligible nodes. The cost is stated in chapter 18: while a rollout runs, an old and a new pod may share a node, so the one-per-node guarantee holds within a revision and not across two. The key must be on the term from the first render, because the term is checked from both sides: a running pod whose term lacks the key still excludes a new pod. No live workload carries a required term, so no handover meets that case.
    2. C22: a scheduled Process derives a start deadline of 300 seconds. Every rendered CronJob carries startingDeadlineSeconds: 300. A run starts within 300 seconds of its slot or is skipped, whatever delayed it. Unparking therefore starts a run at once only when a slot fell within the last 300 seconds, as the drill showed, and otherwise waits for the next slot: it behaves as if it had happened up to five minutes earlier. The maintainer kept 300 seconds with that known, because a shorter value skips runs over a control-plane restart. Parking stops new runs and nothing else: a run in flight finishes. Chapter 11 states both.
    3. C18: across: node behind a pool address is failover, not load sharing. No model change. Only the replica on the announcing node serves, and the other is a standby. Chapter 12 states it. adguard-ha stays one Process behind one address, as decided. The failover gap is measured before the utility Project hands over, and the question reopens with that number if the gap is too long for DNS.
    4. C19: a pool's site decides who announces it and who may use it. A pool-kind L4 Address already names its site. From that site the render derives three things: the advertisement's node selector; l4-speaker only on the nodes of a site that has a pool; and a hard site pin on every Process that exposes on the address, a tier's proxy included. A placement term that contradicts the pin is E_PLACEMENT_UNSATISFIABLE. This is the shape of the node dataset pin.

    Node agents and GPUs

    1. C25, C26: a gpus entry asserts that the host is prepared. No model change. Chapter 15 states that a GPU fact asserts the driver, the container toolkit and the CDI specification on that host. nix-config owns that state until the generator chapter 15 describes exists. The node Project's handover checks that each GPU node advertises nvidia.com/gpu.
    2. C30: the share count is fixed in the gpu-device catalog entry. The entry fixes four time-sliced shares per GPU, beside its image and its privileges. Chapter 18 states what a gpu requirement grants: one share, with no memory or fault isolation, of which gpu-metrics holds one on every GPU node. There is no capacity refusal, because eligibility is not a capacity plan (chapter 40).
    3. C29: loki-canary is not rendered. Nothing in fleet-infra names it; the Loki chart enables it by default. Logs are tailed through the Kubernetes API and not per node, so a canary on every node tests nothing per node. No per-node replica concept enters the model. The observability handover diff records it as an explained removal.

    Certificates and secrets

    1. C2: the proxy of an acme tier may not declare more than one replica. This is a new refusal, on an authored hazard: a second instance issues the same names and draws on the same allowance. An edge proxy is delivery machinery and already derives a rolling switchover that never runs two copies, so an authored replicas is the only way to break the limit. The code is named in chapter 12's pull request.
    2. C3: the shared Let's Encrypt allowance is accepted. No model change. The ACME claim stays reconstructible. Chapter 12 and the runbook state the fact: both tiers issue the same wildcard, so five lost claims within seven days, counted across both, exhaust the allowance.
    3. C10: every static secret polls at one derived interval of 10 minutes. Nothing is authored. Chapter 17 states that an edit to a static secret reaches its consumers within that time and then restarts them. The render sets hmacSecretData, which the restart needs. The three live intervals (ten minutes, one hour, one day) become one.
    4. C12: a Vault token is not delivered. No delivery kind is added for it. An Application that talks to Vault itself takes the self path and logs in with its service account. The estate already moved its Spring services to that after an injected token died 32 days into a pod's life. No manifest in fleet-infra and no application repository reads the token file today. Each of the fourteen pod templates that request the token gets one handover check: nothing in its image reads /vault/secrets/token.

    Handover

    1. C49: a HelmRelease is always suspended, then deleted. One rule for all 19 releases, in the handover runbook. helm-controller skips the uninstall for a suspended release, so every object stays, CRDs and their objects included. The render takes over what it emits, and the handover diff lists what is left to delete by hand. No audit of which charts template their CRDs is needed. A chart's CRDs still pass to the bootstrap set before its HelmRelease is removed.

    Derivations that do not reproduce live

    1. C32: the Platform document names the Applications of the critical class. The rule gains one clause: the critical class goes to the foundation's Applications and to the Applications the Platform document names. Priority is contended, so the Platform holds the list (0004), and each name links to a declared Application as a machinery name does. The estate names auth-api, agents-api and knowledge-api, which keep the class they have. The data class stays derived from an engine with a backup. Seven knowledge-platform workloads therefore lose the data class. The handover diffs explain those and every other difference the rule makes, Vault's among them. Nothing is authored on a Process.
    2. C36: egress: internet means public IPv4. The render allows 0.0.0.0/0 except a fixed set: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 100.64.0.0/10 (the tailnet), 169.254.0.0/16 and 127.0.0.0/8. No range is authored, so a new LAN or a changed pod range cannot open a hole, and the cluster and LAN ranges are no longer Platform facts this rule reads. Chapter 16 states two limits: IPv6 is not allowed, and traffic to a pod's own node is always allowed.
    3. C9: the lease lifetime is a fact of the rabbitmq engine. The engine's catalog entry carries one lifetime, and the render writes the mount's lease configuration from it. A grant carries no lifetime, each consumer still gets its own role, and the bootstrap's role parameters that the API does not define are dropped. Consumers restart when the credential is replaced at its maximum, as tribelt does daily for Postgres. Drill a RabbitMQ dynamic credential rotation under a connected consumer confirms the value before chapter 17 is rewritten.

    The five that stay rewrite inputs

    1. Each has one correct answer, and the chapter or pull request that touches it carries it:
      • C6. The metallb.io CRDs that join the bootstrap set carry a conversion webhook stanza that nothing calls while no BGPPeer exists. The foundation step decides whether to strip it.
      • C34. jellyfin's root init container owns /config, its own state on a host path. That directory becomes a claim at the media handover, with its data copied, and the derived volume group then applies. The media directories are node datasets and are not touched by this.
      • C44. The grafana adapter renders the datasources mapping for a vendored dashboard (chapter 19).
      • C45. The rendered Alertmanager sets alertmanagerConfigMatcherStrategy.type: None (chapter 19).
      • C47. The contract pull request puts the Release-As: 1.0.0 footer last in the squash commit's body.

    Consequences for other tickets

    Record

    This answer is recorded here, on the ticket. Nothing is written into spec/v1, CONTEXT.md or the register now. The chapter pull requests carry it:

    • Chapter 11 (Processes): the start deadline and what parking does to a run in flight.
    • Chapter 12 (Exposure): the one-replica refusal, the shared allowance, the pool's site and what it derives, and failover behind a pool address.
    • Chapters 14 and 15: the share count in the gpu-device entry, the critical-class list, the engine's lease lifetime, and what a gpus entry asserts.
    • Chapter 16 (Dependencies): the fixed non-public set.
    • Chapter 17 (Secrets and access): the refresh interval, and that no token is delivered.
    • Chapter 18 (Placement): the revision key on the across: node term, the priority rule, and the GPU share.
    • docs/runbooks/: suspend, then delete; the three handover checks.
    • No new ADR, and no new term for CONTEXT.md.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area: deployhomelab-deploy, deploy-v2, manifests, and rollout flow.type: docsDocumentation-only change.wayfinder:grillingWayfinder ticket resolved by conversation (HITL)

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions