Files
tech-log-backend/docs/superpowers/plans/2026-07-30-redis-lab-strict-kubeconfig-renderer.md
T

8.8 KiB

Redis Lab Strict Kubeconfig Renderer Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Complete parent Task 11.1A by replacing mutation-by-mutation kubeconfig filtering with a pinned-K3s, strict block-grammar validator/renderer and passing an independent safety review.

Architecture: Freeze the already-reviewed lifecycle/ownership state machine as Task 11.1A-1. Move kubeconfig validation/rendering into one tracked AWK program, Task 11.1A-2. The program accepts only the exact single-cluster/context/user block grammar emitted by the pinned K3s slice, transforms only lab identity fields, and rejects every non-allowlisted structure before any lab kubectl command.

Tech Stack: Bash 5 strict mode, POSIX-compatible AWK features already used by the repository, the fake-command shell contract, Gradle 9, Java 21.

Global Constraints

  • Do not create a VM, run real Multipass/k3s/kubectl, inspect host inventory, or access the network.
  • Do not modify Task 11.1A-1 ownership, state, signal, lock, cleanup or fingerprint behavior.
  • Do not add yq, PyYAML, Ruby, Java YAML runtime, or another downloadable parser dependency.
  • The only accepted source grammar is the pinned K3s admin kubeconfig block-style shape defined in deep design §37.13.4.1.
  • preferences: {} is the only permitted flow collection.
  • Validation failure removes the destination, emits only redis-lab: lab kubeconfig invalid, and occurs before lab kubectl.
  • Preserve prior CREATED|RECONCILE state and delete only exact marker-proven current-run VMs.
  • Tests must show RED against the current implementation before production changes.
  • Human-only Git policy applies: do not stage, commit, amend or push.

Task 1: Extract a strict generated-kubeconfig renderer

Files:

  • Create: infra/redis-lab/lib/render-kubeconfig.awk
  • Modify: infra/redis-lab/bin/redis-lab
  • Modify: infra/redis-lab/test/redis-lab-contract.sh

Interfaces:

  • Consumes: awk -v address=<validated IPv4> -v target=ca-redis-lab -f <renderer> <source>.

  • Produces: rendered kubeconfig on stdout and exit 0, or no accepted output and non-zero exit.

  • Integration: render_lab_kubeconfig <source> <destination> <server-address> performs atomic temporary render, mode 0600, destination replacement only after renderer success.

  • Step 1: Add realistic positive and sibling-flow RED fixtures

    Change the fake valid kubeconfig to this complete credential-data shape, using canary values rather than real certificate material:

    apiVersion: v1
    clusters:
    - cluster:
        certificate-authority-data: preserve-default-ca-canary
        server: https://127.0.0.1:6443
      name: default
    contexts:
    - context:
        cluster: default
        namespace: team-default
        user: default
      name: default
    current-context: default
    kind: Config
    preferences: {}
    users:
    - name: default
      user:
        client-certificate-data: preserve-default-client-cert-canary
        client-key-data: preserve-default-client-key-canary
    

    Add separate public up variants containing, after their canonical item:

      cluster : {server: https://foreign.invalid:6443}
    

    and:

      context : {cluster: foreign, user: foreign}
    

    Each variant must assert failure, zero lab kubectl, three exact marker-proven deletes, removed rendered kubeconfig, and no forbidden fake invocation.

  • Step 2: Run the direct contract and verify RED

    Run:

    bash -n infra/redis-lab/bin/redis-lab infra/redis-lab/test/redis-lab-contract.sh
    bash infra/redis-lab/test/redis-lab-contract.sh
    

    Expected: syntax succeeds and the first new sibling-flow case fails because the current renderer unexpectedly accepts it.

  • Step 3: Implement the strict AWK state machine

    render-kubeconfig.awk must use an explicit state transition for every accepted line. It must not print from a catch-all rule. The accepted transition sequence is:

    apiVersion -> clusters -> cluster-item -> ca-data -> server -> cluster-name
    -> contexts -> context-item -> context-cluster -> optional-namespace -> context-user
    -> context-name -> current-context -> kind -> preferences -> users -> user-name
    -> user-body -> client-cert -> client-key -> EOF
    

    Exact identity transitions print these replacements:

    print "    server: https://" address ":6443"
    print "  name: " target
    print "    cluster: " target
    print "    user: " target
    print "current-context: " target
    print "- name: " target
    

    CA/client credential and namespace transitions print $0 unchanged. Any unmatched line sets invalid=1; END exits non-zero unless the final state is client-key, every required transition occurred once, the input had no tab/CR/YAML marker, and no trailing line exists.

  • Step 4: Integrate the renderer fail-closed

    Add:

    KUBECONFIG_RENDERER="${REPOSITORY_ROOT}/infra/redis-lab/lib/render-kubeconfig.awk"
    

    validate_static_contract must require a readable regular non-symlink renderer at that exact canonical path. Replace the inline AWK body with:

    local render_next="${destination_file}.next"
    rm -f -- "${render_next}"
    if ! awk -v address="${server_address}" -v target="${CONTEXT_NAME}" \
        -f "${KUBECONFIG_RENDERER}" "${source_file}" >"${render_next}"; then
        rm -f -- "${render_next}" "${destination_file}"
        fail 'lab kubeconfig invalid'
        return 1
    fi
    chmod 0600 -- "${render_next}"
    mv -f -- "${render_next}" "${destination_file}"
    

    Add the .next destination to symlink-child validation. Propagate rm, chmod and mv failures with the same sanitized error and without retaining a partially accepted destination.

  • Step 5: Run focused GREEN

    Run the direct contract again. Expected: redis-lab-contract: PASS, exit 0.

Task 2: Complete the mutation matrix and parent acceptance

Files:

  • Modify: infra/redis-lab/test/redis-lab-contract.sh
  • Modify: infra/redis-lab/README.md
  • Modify: docs/superpowers/plans/2026-07-29-redis-production-capability-completion.md
  • Modify: .superpowers/sdd/2026-07-29-redis-production-capability-completion/progress.md
  • Create: .superpowers/sdd/2026-07-29-redis-production-capability-completion/task-11-1a-2-brief.md
  • Create: .superpowers/sdd/2026-07-29-redis-production-capability-completion/task-11-1a-2-report.md

Interfaces:

  • Consumes: Task 1 strict renderer and existing lifecycle fake runtime.

  • Produces: parent Task 11.1A review package with no open Critical/Important finding.

  • Step 1: Add one mutation per grammar boundary

    Add table-driven fixture variants for missing, duplicate, reordered and unknown keys; whitespace before colon; quoted/tagged/explicit keys; anchor/alias/merge; unexpected {}/[]; tab, CRLF, ---/..., and trailing content. Every case must assert failure before lab kubectl, exact current-run cleanup and removed render output.

  • Step 2: Prove scalar preservation and exact transformation

    The positive case must assert:

    server: https://192.0.2.10:6443
    name/current-context: ca-redis-lab
    namespace: team-default
    preserve-default-ca-canary
    preserve-default-client-cert-canary
    preserve-default-client-key-canary
    

    It must also assert that no name: default, cluster: default, user: default, current-context: default or loopback server remains.

  • Step 3: Re-run the full fake-only verification

    Run:

    bash -n infra/redis-lab/bin/redis-lab infra/redis-lab/test/redis-lab-contract.sh
    bash infra/redis-lab/test/redis-lab-contract.sh
    cd src
    ./gradlew :adapter:outbound:cache-redis:redisLabContractTest --console=plain
    ./gradlew :adapter:outbound:cache-redis:test --console=plain
    ./gradlew :adapter:outbound:cache-redis:check --dry-run --console=plain
    

    Expected: direct PASS; both Gradle executions BUILD SUCCESSFUL; dry-run includes redisLabContractTest.

  • Step 4: Run an independent scoped review

    Reviewer acceptance:

    • strict renderer has no catch-all pass-through;
    • the valid pinned fixture reaches EOF exactly once;
    • every non-allowlisted structural line fails;
    • destination publication is atomic/fail-closed;
    • Task 11.1A-1 lifecycle code is unchanged except the renderer call and static path checks;
    • Critical 0, Important 0, both spec and quality PASS.
  • Step 5: Close the parent task

    Only after Step 4 passes, replace the ledger BLOCKED state with an additive resolution line:

    Task 11.1A-2: complete (human-only commit policy; strict renderer review clean)
    Task 11.1A: complete (11.1A-1 lifecycle + 11.1A-2 renderer; fake-only evidence)
    

    Do not claim live readiness, R2 or VM/k3s qualification.