Skip to content

Latest commit

 

History

History
216 lines (181 loc) · 74.2 KB

File metadata and controls

216 lines (181 loc) · 74.2 KB

Milestone 1 — package skeleton + HardenedHelmRelease + EksSubnetTagger

Parent runbook: docs/slo/completed/RUNBOOK-hulumi-k8s.md. Read the runbook's Global Execution Rules (especially Rule 0 — the Hulumi-K8s scope contract) + Global Entry Rules before starting.

Goal: After M1, a new workspace package @hulumi/k8s-baseline@1.0.0-pre.1 exists with two shippable components: HardenedHelmRelease (a thin wrapper on @pulumi/kubernetes helm.v3.Release enforcing required version, required repository, instance-name release default, opt-in Fargate-exclusion nodeAffinity, and chart-class-aware timeout) and EksSubnetTagger (writes the three conventional ALB-Controller-discovery tags via aws.ec2.Tag). Closes #38 (subnet tagging), the cross-cutting #44 (release-name default), and half-closes #42 (Fargate affinity arg lands on the wrapper; auto-detection across cluster shape stays an opt-in).

Context: The design record docs/slo/design/hulumi-k8s-surface.md commits the package layout (@hulumi/k8s-baseline separate package, not @hulumi/baseline/k8s subpath), the HardenedHelmRelease API shape, the stable-name + version-pin defaults, and the EksSubnetTagger shape. M1 ships that exact API surface, no more. The components mirror packages/baseline/src/aws/secure-bucket.ts's discipline (pulumi.ComponentResource, child registration via { parent: this }, assertValidTier not used here because tiers are not part of the K8s components' API at v1, tag triple hulumi:component/hulumi:tier?/hulumi:controls? only emitted on resources where it makes sense).

Important design rule: HardenedHelmRelease's release-name default is the ComponentResource instance name verbatim — not the chart name, not a derived value. The Pulumi default of "always add an 8-character random suffix" is the wrong default for IaC reproducibility (issue #44); reverting it is the load-bearing decision M1 ships. Override via explicit releaseName arg. Two HardenedHelmRelease instances with the same instance name in the same namespace WILL collide on helm install — Pulumi's diagnostic surfaces this clearly at preview time. This is a feature, not a bug: collisions surface at IaC review. The decision is irreversible without a pulumi replace (delete + create); the migration cookbook for adopting the new wrapper on existing suffixed releases lands in M5.

Refactor budget: Surgical addition only. New packages/k8s-baseline/ directory containing the package manifest, source, tests, and compatibility table. The only existing files modified are pnpm-workspace.yaml (the workspace already includes packages/* per the existing structure — verify, no edit needed if so), scripts/exact-pin-guard.mjs and scripts/cooling-off-diff.mjs (add @pulumi/kubernetes and @pulumi/eks to the pin-checked + cooling-off-checked lists), docs/slo/completed/RUNBOOK-hulumi-k8s.md Milestone Tracker (mark M1 in_progress → done), docs/ARCHITECTURE.md (one-paragraph addition describing the new package). No changes to any existing packages/baseline/, packages/policies/, or packages/drift/ source.

Contract Block

Field Value
Inputs (Component) new HardenedHelmRelease(name, args) where args: HardenedHelmReleaseArgs requires: chart: string, version: string (no latest, no missing — refuses construction), namespace: pulumi.Input<string>, repository: string (must start with https:// or oci:// — refuses local paths and helm repo lookups). Optional: releaseName?: string (defaults to component instance name verbatim), values?: pulumi.Inputs, daemonSet?: boolean (default false; when true, the wrapper injects an eks.amazonaws.com/compute-type NotIn fargate nodeAffinity into values.affinity if no affinity is already present), excludeFargate?: boolean (default true when daemonSet: true, otherwise no-op), waitTimeoutMs?: number (default 300_000; chart-class table provides higher defaults — Istio: 480_000, set in M2), chartClass?: "istio" | "default" (default "default"; M2 adds "istio" consumers). (Component) new EksSubnetTagger(name, args) where args: EksSubnetTaggerArgs requires: clusterName: string, ownership: "shared" | "owned". Optional: publicSubnetIds?: pulumi.Input<pulumi.Input<string>[]>, privateSubnetIds?: pulumi.Input<pulumi.Input<string>[]>. At least one of the two subnet lists must be supplied — refuses construction if both are absent.
Outputs (Component) HardenedHelmReleaseOutputs exposes releaseName: pulumi.Output<string>, namespace: pulumi.Output<string>, status: pulumi.Output<helm.v3.ReleaseStatus>, chartVersion: pulumi.Output<string> (echoes the input — useful for downstream dependsOn chains and version-skew assertions). (Component) EksSubnetTaggerOutputs exposes tagsApplied: pulumi.Output<{ subnetId: string; key: string; value: string }[]> listing every tag the component wrote.
Interfaces touched New stable surface: @hulumi/k8s-baseline#{HardenedHelmRelease,HardenedHelmReleaseArgs,HardenedHelmReleaseOutputs,HARDENED_HELM_RELEASE_COMPONENT_TYPE,EksSubnetTagger,EksSubnetTaggerArgs,EksSubnetTaggerOutputs,EKS_SUBNET_TAGGER_COMPONENT_TYPE}. Component types: "hulumi:k8s:HardenedHelmRelease", "hulumi:k8s:EksSubnetTagger". The package's first published version is 1.0.0-pre.1 in M1; 1.0.0 lands in M5 alongside the atomic four-package release.
Data classification Internal — the milestone provisions Helm releases and writes EC2 tags on subnets in a sandbox EKS cluster (or kind cluster) during integration tests (no PII, no customer data, no production secrets). HardenedHelmRelease and EksSubnetTagger are declarative IaC — they do not handle user data at runtime. Helm values arg can carry Confidential data (chart secrets); covered by Pulumi's standard pulumi.secret() discipline, not by this milestone's surface.
Proactive controls in play (a) C1 Define Security Requirements — this milestone's design record IS the security requirements record for the K8s variant; the runbook's Rule 0 scope contract pins the surface boundary. (b) C5 Validate All InputsHardenedHelmReleaseArgs.version is required and refused if missing or "latest"; repository is refused if it doesn't start with https:// or oci://; EksSubnetTaggerArgs refused if both subnet lists are absent. (c) @hulumi/baseline.aws.SecureBucket (existing precedent) — both new components mirror its ComponentResource discipline (child registration via { parent: this }, conditional tag emission, no implicit defaults that hide policy choices). (d) C10 Handle All Errors and Exceptions — components throw plain Error for input violations (matches existing AWS pattern); no detail-leakage paths. (e) C9 Implement Security Logging and MonitoringHardenedHelmRelease emits a pulumi.log.warn when the consumer pins to a chart version not in COMPATIBILITY.md; the warning surfaces in pulumi up output, recording the consumer's risk acceptance.
Abuse acceptance scenarios Six BDD rows in the table below cite tm-hulumi-k8s-abuse-N. Slug-keyed: tm-hulumi-k8s-abuse-version-omitted (HardenedHelmRelease refuses construction without version), tm-hulumi-k8s-abuse-version-floating (refuses "latest" and ^/~-prefixed semver — Helm chart versions are exact), tm-hulumi-k8s-abuse-untrusted-repo (refuses repository: "file://…" or non-http(s)/oci paths to prevent local-path supply-chain confusion), tm-hulumi-k8s-abuse-implicit-suffix (release name does NOT receive a random suffix; two same-name same-namespace components collide loudly), tm-hulumi-k8s-abuse-affinity-clobber (daemonSet: true injection refuses to silently overwrite a consumer-supplied affinity block; refuses construction with a clear error), tm-hulumi-k8s-abuse-subnet-empty (EksSubnetTagger refuses if both subnet lists are absent — silently writing no tags is the failure mode that motivated the issue). Threat-model rows trace to the design record's § Decision: HardenedHelmRelease wrapper + § Decision: EksSubnetTagger sections.
Files allowed to change New: packages/k8s-baseline/{package.json,tsconfig.json,tsconfig.build.json,vitest.config.ts,COMPATIBILITY.md}; packages/k8s-baseline/src/{index.ts,hardened-helm-release.ts,hardened-helm-release.args.ts,hardened-helm-release.outputs.ts,eks-subnet-tagger.ts,eks-subnet-tagger.args.ts,eks-subnet-tagger.outputs.ts,compatibility.ts}; packages/k8s-baseline/tests/{hardened-helm-release.test.ts,eks-subnet-tagger.test.ts,compatibility.test.ts}; packages/k8s-baseline/tests/integration/kind/hardened-helm-release.kind.test.ts; docs/slo/runbook-milestones/hulumi-k8s-m1.md (this file — Evidence Log only, in execution); docs/slo/lessons/hulumi-k8s-m1.md; docs/slo/completion/hulumi-k8s-m1.md; docs/components/hardened-helm-release.md (one-line stub); docs/components/eks-subnet-tagger.md (one-line stub). Modified: scripts/exact-pin-guard.mjs; scripts/cooling-off-diff.mjs; docs/slo/completed/RUNBOOK-hulumi-k8s.md Milestone Tracker; docs/ARCHITECTURE.md (one-paragraph K8s-package addition); pnpm-workspace.yaml IF packages/* is not already covered (verify first); .gitignore IF needed for kind-cluster artifacts. Files outside this milestone's allow-list — REFUSE TO TOUCH: any packages/baseline/src/, packages/policies/src/, packages/drift/src/, any existing skills/, any existing examples/, any other runbook-milestones file, package.json at repo root (workspace metadata only — never add deps there).
Files to read before changing anything docs/slo/completed/RUNBOOK-hulumi-k8s.md (Global Execution Rules + this milestone in full); docs/slo/design/hulumi-k8s-surface.md (entire doc); packages/baseline/src/aws/secure-bucket.ts (pattern precedent); packages/baseline/src/aws/secure-bucket.args.ts; packages/baseline/src/aws/secure-bucket.outputs.ts; packages/baseline/package.json (peer-dep + dev-dep style); packages/baseline/tsconfig.json, tsconfig.build.json, vitest.config.ts; packages/baseline/tests/secure-bucket.test.ts; scripts/exact-pin-guard.mjs; scripts/cooling-off-diff.mjs; docs/cookbooks/psa-baseline-istio-sidecar.md (the cookbook this surface eventually replaces).
New files allowed All "New" entries in Files allowed to change.
New dependencies allowed Runtime peer + dev: @pulumi/kubernetes@^4.x (exact-pin via integrity hash, mirroring @pulumi/aws discipline), @pulumi/eks@^3.x (peer dep only — used for EksSubnetTagger typing if/when the typed args reference EKS types; declared as optional peer with no runtime import to keep the install cost off non-EKS users; decide during implementation — if no typed reference is needed, do not add). Test-only dev: nothing additional in M1 (the kind integration test shells out to kind + kubectl binaries; both are dev-environment expectations, not npm deps). No other runtime deps.
Migration allowed no — additive only. New package; no migration of existing code.
Compatibility commitments HardenedHelmRelease, HardenedHelmReleaseArgs, HardenedHelmReleaseOutputs, HARDENED_HELM_RELEASE_COMPONENT_TYPE, EksSubnetTagger, EksSubnetTaggerArgs, EksSubnetTaggerOutputs, EKS_SUBNET_TAGGER_COMPONENT_TYPE are stable from M1 (no rename in v1.x). The chartClass enum is allowed to GROW (e.g., M2 adds "istio"); existing values do not change. The releaseName default (component instance name verbatim) is load-bearing and irreversible without pulumi replace — the migration cookbook lands in M5. Existing AWS + GitHub interfaces from Hulumi v1.x unchanged.
Forbidden shortcuts (a) NEVER wrap helm.v3.Release such that the wrapper silently injects a random suffix. The whole point of the wrapper is the suffix-free default; if the implementation ends up calling helm.v3.Release with no name set (which Pulumi suffixes by default), the wrapper has failed. (b) NEVER accept version: "latest" or any ^/~/>=-prefixed version. Helm chart versions are exact. The constructor refuses with a clear error. (c) NEVER silently overwrite a consumer-supplied affinity block when injecting Fargate-exclusion. If daemonSet: true AND values.affinity is already set by the consumer, refuse construction with Error("HardenedHelmRelease: cannot inject Fargate-exclusion affinity because values.affinity is already set; merge manually or set excludeFargate: false"). (d) NEVER call child_process.exec, eval, or shell-interpolate user input in component code — argv-based spawning only (existing rule, extended). (e) NEVER read the Helm chart from a file:// path or rely on the consumer's local helm repo list. The wrapper requires repository: string starting with https:// or oci://. (f) NEVER add @pulumi/kubernetes or @pulumi/eks without a 72h/24h cooling-off CI check — scripts/cooling-off-diff.mjs extension lands in this milestone. (g) NEVER import from packages/baseline/src/ other than the Tier re-export from the package's public API (@hulumi/baseline/aws) — and even that import is allowed only if Tier is genuinely needed in M1, which it currently is not. (h) NEVER allow EksSubnetTagger to write zero tags silently. If both publicSubnetIds and privateSubnetIds resolve to empty arrays at apply time, emit a pulumi.log.warn (the consumer's lists may legitimately be conditional on tier — log is the right surface; refuse only at construction time when both are absent at the type level).

Out of Scope / Must Not Do

  • No IstioFoundation — that's M2.
  • No AlbMeshedHttpEntrypoint — that's M3.
  • No KubernetesSecretFromAwsSecretsManager or RdsCredentialSecret — that's M4.
  • No GitHubAppCredential — that's M5.
  • No @pulumi/eks Cluster wrapper. EVER. (Rule 0.)
  • No Linkerd* / LinkerdFoundation / mesh-agnostic facade. The MeshFoundation ref shape lands in M2 with IstioFoundation as the concrete; nothing in M1 anticipates a second mesh.
  • No examples package (examples/k8s-helm-smoke/) — that's M5 launch-readiness work.
  • No threat-model skill scenarios for K8s — Hulumi K8s is intentionally NOT in the threat-model skill's scenario set at v1 (the scope contract precludes the kind of scenario shape the skill produces; revisit at v1.x if consumer demand emerges).
  • No CI integration of the kind test in weekly-integration.yml — that lands in M5 alongside the release. M1 ships the test file; CI gating is M5.
  • No license-boundary mappings (no cis-eks.ts, no cis-kubernetes.ts). The package ships zero mapping tables in M1; if they're added later, they go through the same IDs-only discipline as cis-aws.ts / cis-github.ts.

Pre-Flight

  1. Complete the Global Entry Rules in ../RUNBOOK-hulumi-k8s.md.
  2. No docs/slo/lessons/hulumi-k8s-m0.md exists. Skip "read prior lessons" with a note in the Evidence Log.
  3. Read the design record ../design/hulumi-k8s-surface.md end-to-end — every API decision in M1 is committed there.
  4. Read files listed in Files to read before changing anything.
  5. Copy the Evidence Log template into the milestone's Evidence Log section (already present below — clone the row shape).
  6. Re-state in working notes the four load-bearing constraints: (i) scope contract — no cluster topology, no IRSA, no mesh / ingress alternatives; (ii) HardenedHelmRelease's release-name default is the component instance name verbatim — the suffix default reversal is the whole point; (iii) version and repository are required, no latest, no file://; (iv) daemonSet: true opt-in injects Fargate-exclusion affinity but refuses to clobber a pre-set affinity block.
  7. Verify the kind binary is available locally for integration tests: kind version should print kind v0.x or higher; if absent, install via brew install kind (macOS) or [docs/integration-testing.md] guidance. The kind test skips with a clear message when kind is not on PATH.

Files Allowed To Change

File Planned Change
packages/k8s-baseline/package.json NEW: package manifest declaring @pulumi/kubernetes peer + dev deps at exact pin
packages/k8s-baseline/tsconfig.json NEW: extends repo tsconfig.base.json
packages/k8s-baseline/tsconfig.build.json NEW: emit-only build config
packages/k8s-baseline/vitest.config.ts NEW: standard vitest config (no node-pool overrides at v1 — revisit if dynamic-resource clash hits in M4)
packages/k8s-baseline/COMPATIBILITY.md NEW: tested-versions table (initially empty — entries land per chart introduction in M2/M3)
packages/k8s-baseline/src/index.ts NEW: re-exports the two component classes + types + component-type constants
packages/k8s-baseline/src/hardened-helm-release.ts NEW: HardenedHelmRelease extends pulumi.ComponentResource; mirrors secure-bucket.ts shape
packages/k8s-baseline/src/hardened-helm-release.args.ts NEW: HardenedHelmReleaseArgs type
packages/k8s-baseline/src/hardened-helm-release.outputs.ts NEW: HardenedHelmReleaseOutputs type
packages/k8s-baseline/src/eks-subnet-tagger.ts NEW: EksSubnetTagger extends pulumi.ComponentResource writing aws.ec2.Tag per subnet × tag
packages/k8s-baseline/src/eks-subnet-tagger.args.ts NEW: EksSubnetTaggerArgs type
packages/k8s-baseline/src/eks-subnet-tagger.outputs.ts NEW: EksSubnetTaggerOutputs type
packages/k8s-baseline/src/compatibility.ts NEW: typed const TESTED_VERSIONS mapping chart -> version[]; assertVersionTested(chart, version) function emitting pulumi.log.warn when not tested
packages/k8s-baseline/tests/hardened-helm-release.test.ts NEW: Vitest BDD covering happy path, invalid input (missing version, "latest", ^-prefixed, non-http(s)/oci repository, daemonSet+pre-set-affinity), abuse rows
packages/k8s-baseline/tests/eks-subnet-tagger.test.ts NEW: Vitest BDD covering happy path with both subnet lists, public-only, private-only, both-absent refusal, ownership semantics
packages/k8s-baseline/tests/compatibility.test.ts NEW: asserts assertVersionTested warns (not throws) on untested versions; asserts the table parses
packages/k8s-baseline/tests/integration/kind/hardened-helm-release.kind.test.ts NEW: kind-cluster gated test; installs a tiny chart (e.g., bitnami/nginx) via HardenedHelmRelease and asserts the K8s Service.metadata.name matches the wrapper's releaseName (no suffix)
scripts/exact-pin-guard.mjs MODIFY: extend the pin-checked dep list to include @pulumi/kubernetes (and @pulumi/eks if the optional peer is added)
scripts/cooling-off-diff.mjs MODIFY: extend the cooling-off-checked dep list to include @pulumi/kubernetes (and @pulumi/eks if added)
docs/slo/completed/RUNBOOK-hulumi-k8s.md Milestone Tracker MODIFY: M1 row → in_progress on start, done on exit
docs/slo/runbook-milestones/hulumi-k8s-m1.md MODIFY (during execution only): fill Evidence Log rows
docs/slo/lessons/hulumi-k8s-m1.md NEW (during exit): surprises, decisions, deltas-from-plan
docs/slo/completion/hulumi-k8s-m1.md NEW (during exit): changed files, tests added, docs updated
docs/components/hardened-helm-release.md NEW (one-line stub): "Helm release wrapper enforcing version + repository + stable name; full reference at M5."
docs/components/eks-subnet-tagger.md NEW (one-line stub): "ALB-Controller-discovery tag writer; full reference at M5."
docs/ARCHITECTURE.md MODIFY: append one paragraph describing the new @hulumi/k8s-baseline package and its package-layout rationale (link to design record)
pnpm-workspace.yaml VERIFY (modify only if needed): the existing pattern should already cover packages/* — if not, extend
.gitignore MODIFY (only if needed): add patterns for kind cluster state and Helm chart caches under test tempdirs

Step-by-Step

  1. Verify pnpm-workspace.yaml covers packages/*. Read repo root package.json workspace field — if it lists packages/* (currently lists skills/* and tests/*), extend it; otherwise document in Evidence Log.
  2. Scaffold packages/k8s-baseline/ mirroring packages/baseline/ shape: package.json, tsconfig.json (extends ../../tsconfig.base.json), tsconfig.build.json, vitest.config.ts, empty src/index.ts, empty tests/ dir, empty COMPATIBILITY.md. Run pnpm install from repo root — it should pick up the new workspace, install @pulumi/kubernetes peer + dev deps, and report success.
  3. Extend scripts/exact-pin-guard.mjs to include @pulumi/kubernetes (and @pulumi/eks if added) in PULUMI_PACKAGES. Extend scripts/cooling-off-diff.mjs likewise. Run both — expect green now that the new deps are pinned.
  4. Write packages/k8s-baseline/tests/hardened-helm-release.test.ts mock-runtime tests covering: happy path (valid args yield correct release name, namespace, version), refusal of missing version, refusal of "latest", refusal of ^1.0.0, refusal of non-http(s)/oci repository, refusal of daemonSet: true when values.affinity is already set, the affinity-injection happy path. Run — expect failures for "module not found".
  5. Implement HardenedHelmReleaseArgs (interface) + HardenedHelmReleaseOutputs + HardenedHelmRelease (pulumi.ComponentResource constructor that validates inputs, builds the helm.v3.Release with name: releaseName ?? componentInstanceName, version, repositoryOpts: { repo: repository }, values with optional Fargate-affinity injection, wait: true, timeout: waitTimeoutMs / 1000). Re-run — tests should pass.
  6. Write packages/k8s-baseline/tests/eks-subnet-tagger.test.ts mock-runtime tests covering: happy path (both subnet lists yield 3 tags per subnet), public-only path, private-only path, both-absent refusal, ownership=shared vs owned tag value. Implement EksSubnetTagger (constructor walks each subnet × tag-key, writes aws.ec2.Tag with { parent: this }). Re-run — tests pass.
  7. Write packages/k8s-baseline/src/compatibility.ts with empty TESTED_VERSIONS const and assertVersionTested(chart, version) that emits pulumi.log.warn if the chart isn't in the table OR the version is not in the chart's tested list. Wire into HardenedHelmRelease constructor. Write packages/k8s-baseline/tests/compatibility.test.ts asserting the warn-not-throw semantic (using a pulumi.log spy).
  8. Wire packages/k8s-baseline/src/index.ts re-exports. Run pnpm --filter @hulumi/k8s-baseline build && test && typecheck && lint — green.
  9. Write the kind integration test at packages/k8s-baseline/tests/integration/kind/hardened-helm-release.kind.test.ts: gated on kind binary present + KIND_E2E=1 env var; creates a uniquely-named kind cluster, installs bitnami/nginx via HardenedHelmRelease(name="hulumi-test-nginx", ...), asserts via kubectl get release that the release name is exactly hulumi-test-nginx (no suffix), tears down. Run locally if kind is installed, otherwise document the skip.
  10. Run the full repo test suite: pnpm install --frozen-lockfile && pnpm -r build && pnpm -r test && pnpm -r typecheck && pnpm -r lint && pnpm run lint:license-boundary && pnpm run lint:exact-pin-guard. All green. Update Milestone Tracker, write lessons + completion files.

BDD Acceptance Scenarios

Feature: @hulumi/k8s-baseline.HardenedHelmRelease enforces version + repository + stable name; EksSubnetTagger writes the three ALB-discovery tags

Scenario Category Given When Then Threat-model row Control
Happy path — HardenedHelmRelease with valid args happy path mock-runtime; chart: "nginx", version: "15.4.4", namespace: "default", repository: "https://charts.bitnami.com/bitnami" new HardenedHelmRelease("my-nginx", args) is constructed child helm.v3.Release is registered with name: "my-nginx" (NOT "my-nginx-<8charsuffix>"), version: "15.4.4", chart: "nginx", repositoryOpts.repo: "https://charts.bitnami.com/bitnami", wait: true, timeout: 300; outputs expose releaseName: "my-nginx", chartVersion: "15.4.4" n/a (happy path) n/a
Happy path — explicit releaseName overrides instance name happy path mock-runtime; same as above but releaseName: "my-named" new HardenedHelmRelease("instance-name", args) is constructed child helm.v3.Release is registered with name: "my-named"; outputs expose releaseName: "my-named" n/a (happy path) n/a
Happy path — daemonSet: true injects Fargate-exclusion affinity happy path mock-runtime; valid args + daemonSet: true, no values.affinity pre-set new HardenedHelmRelease(...) is constructed child helm.v3.Release.values.affinity contains nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0] of { key: "eks.amazonaws.com/compute-type", operator: "NotIn", values: ["fargate"] } n/a (happy path) n/a
Happy path — EksSubnetTagger with both subnet lists happy path mock-runtime; clusterName: "test-cluster", publicSubnetIds: ["subnet-aaa", "subnet-bbb"], privateSubnetIds: ["subnet-ccc"], ownership: "shared" new EksSubnetTagger(...) is constructed 3 child aws.ec2.Tag resources registered for subnet-aaa (kubernetes.io/role/elb=1, kubernetes.io/cluster/test-cluster=shared); 3 for subnet-bbb; 3 for subnet-ccc (kubernetes.io/role/internal-elb=1, kubernetes.io/cluster/test-cluster=shared); tagsApplied output lists all 9 n/a (happy path) n/a
Happy path — EksSubnetTagger private-only happy path mock-runtime; only privateSubnetIds new EksSubnetTagger(...) is constructed child aws.ec2.Tag resources for internal-elb + cluster ownership only; no kubernetes.io/role/elb tags n/a (happy path) n/a
Happy path — ownership: "owned" writes =owned value happy path mock-runtime; ownership: "owned" with public + private subnets new EksSubnetTagger(...) is constructed every kubernetes.io/cluster/<name> tag has value: "owned" (not "shared") n/a (happy path) n/a
Invalid input — missing version on HardenedHelmRelease invalid input mock-runtime; args without version new HardenedHelmRelease(...) is constructed constructor throws Error('HardenedHelmRelease: version is required and must be an exact chart version (no "latest", no semver ranges)'); no child resources registered n/a (invalid input) C5 input validation
Invalid input — empty subnet lists on EksSubnetTagger invalid input mock-runtime; both publicSubnetIds and privateSubnetIds absent new EksSubnetTagger(...) is constructed constructor throws Error('EksSubnetTagger: at least one of publicSubnetIds or privateSubnetIds must be provided'); no child resources registered tm-hulumi-k8s-abuse-subnet-empty C5 input validation
Empty state — EksSubnetTagger with empty arrays at apply time empty state mock-runtime; publicSubnetIds: [], privateSubnetIds: [] (both supplied as empty) new EksSubnetTagger(...) is constructed constructor succeeds (the empty array is a valid runtime state — e.g., consumer's tier disables public subnets); zero child resources registered; pulumi.log.warn emitted with text containing "EksSubnetTagger" and "no tags written" n/a C9 logging
Dependency failure — helm.v3.Release provider unconfigured partial failure mock-runtime where the implicit kubernetes.Provider lacks credentials new HardenedHelmRelease(...) is constructed mock-runtime test passes (no real K8s API call); kind integration test gracefully skips when kind binary is absent or KIND_E2E unset n/a env-var-gating in test setup
Abuse case — version omitted refused abuse case mock-runtime; args cast through as any to bypass TS, no version field new HardenedHelmRelease(...) is constructed constructor throws the missing-version error from row "Invalid input — missing version"; no child resources registered tm-hulumi-k8s-abuse-version-omitted C5 + Forbidden shortcut (b)
Abuse case — "latest" refused abuse case mock-runtime; version: "latest" new HardenedHelmRelease(...) is constructed constructor throws Error mentioning "latest"; no child resources registered tm-hulumi-k8s-abuse-version-floating C5 + Forbidden shortcut (b)
Abuse case — ^1.0.0 refused abuse case mock-runtime; version: "^1.0.0" new HardenedHelmRelease(...) is constructed constructor throws Error mentioning "exact"; no child resources registered tm-hulumi-k8s-abuse-version-floating C5 + Forbidden shortcut (b)
Abuse case — repository: "file:///tmp/charts/nginx" refused abuse case mock-runtime; valid version + repository: "file:///tmp/charts/nginx" new HardenedHelmRelease(...) is constructed constructor throws Error mentioning "must start with https:// or oci://"; no child resources registered tm-hulumi-k8s-abuse-untrusted-repo C5 + Forbidden shortcut (e)
Abuse case — daemonSet: true with pre-set affinity refused abuse case mock-runtime; valid args + daemonSet: true + values.affinity: { ... } already set new HardenedHelmRelease(...) is constructed constructor throws Error mentioning "cannot inject Fargate-exclusion affinity because values.affinity is already set"; no child resources registered tm-hulumi-k8s-abuse-affinity-clobber Forbidden shortcut (c)
Abuse case — release name does NOT receive a random suffix abuse case mock-runtime; valid args; instance name "my-nginx" new HardenedHelmRelease("my-nginx", args) is constructed child helm.v3.Release.name is exactly "my-nginx" (regex ^my-nginx$); does NOT match ^my-nginx-[0-9a-f]{8}$ tm-hulumi-k8s-abuse-implicit-suffix Forbidden shortcut (a)
Schema / compatibility — HardenedHelmReleaseArgs shape lock schema / compatibility packages/k8s-baseline/src/hardened-helm-release.args.ts exists tests/skill-bdd/k8s-baseline-args.test.ts (added in this milestone if absent) runs HardenedHelmReleaseArgs exports the documented fields with the documented types; chart, version, namespace, repository are required; releaseName, values, daemonSet, excludeFargate, waitTimeoutMs, chartClass are optional; no field is renamed or removed in v1.x n/a type-layer schema lock
Compatibility — untested chart version emits pulumi.log.warn compatibility mock-runtime; chart: "nginx", version: "0.0.0-untested" (NOT in TESTED_VERSIONS) new HardenedHelmRelease(...) is constructed pulumi.log.warn is called with text containing the chart + version + a pointer to COMPATIBILITY.md; no Error thrown; child resource still registered n/a C9 logging + Rule 8

Regression Tests

  • All AWS BDD scenarios in packages/baseline/tests/ continue to pass (no rename, no behavioral change).
  • All GitHub BDD scenarios in packages/baseline/tests/github/ continue to pass.
  • pnpm run lint:license-boundary continues to pass on the existing surface.
  • pnpm run lint:exact-pin-guard passes on the extended dep list (now incl. @pulumi/kubernetes).
  • Skill SKILL.md continues to validate against the agentskills.io schema (no skill changes in M1).
  • packages/drift/tests/tla-alignment.test.ts continues to pass (this milestone does not touch verdict.ts or HulumiDrift.tla).
  • packages/baseline/tests/index.test.ts (or equivalent re-export verification) continues to pass.

Compatibility Checklist

  • HardenedHelmRelease, HardenedHelmReleaseArgs, HardenedHelmReleaseOutputs, HARDENED_HELM_RELEASE_COMPONENT_TYPE, EksSubnetTagger, EksSubnetTaggerArgs, EksSubnetTaggerOutputs, EKS_SUBNET_TAGGER_COMPONENT_TYPE documented in docs/components/ (one-line stubs adequate; full reference doc in M5).
  • @pulumi/kubernetes exact-pinned with integrity hash in packages/k8s-baseline/package.json.
  • pnpm install --frozen-lockfile && pnpm -r build && pnpm -r test && pnpm -r typecheck && pnpm -r lint && pnpm run lint:license-boundary && pnpm run lint:exact-pin-guard green on Node 20 LTS.
  • License header present on every new .ts source file.
  • DCO sign-off required on every commit (CI enforcement carries over).
  • All existing AWS + GitHub BDD scenarios continue to produce valid output unchanged.
  • No child_process.exec, eval, or shell-interpolation in any new file — tests/no-shell-exec.test.ts (existing) covers packages/*/src/; extend coverage to packages/k8s-baseline/src/ if not already glob-matched.
  • packages/k8s-baseline/COMPATIBILITY.md exists (initially empty — entries land per chart introduction in M2/M3).

E2E Runtime Validation

Files: packages/k8s-baseline/tests/integration/kind/hardened-helm-release.kind.test.ts.

E2E Test What It Proves Pass Criteria
installs_chart_against_kind_with_stable_release_name HardenedHelmRelease works end-to-end against a real cluster Test creates a kind cluster hulumi-k8s-m1-<test-id>; installs bitnami/nginx via new HardenedHelmRelease("hulumi-test-nginx", { chart: "nginx", version: "15.4.4", namespace: "default", repository: "https://charts.bitnami.com/bitnami" }); kubectl get release returns exactly one release named hulumi-test-nginx (no suffix); teardown deletes the cluster
release_name_collision_surfaces_as_helm_install_failure The collision-as-feature design intent is verified Two HardenedHelmRelease instances named "my-nginx" in the same namespace cause the second pulumi up to fail with a Helm cannot re-use a name that is still in use diagnostic; Pulumi diagnostic is clear at preview time
daemonSet_true_injects_fargate_affinity_in_rendered_manifest The Fargate-affinity injection actually reaches the rendered chart Install a chart known to contain a DaemonSet with daemonSet: true; kubectl get ds <name> -o yaml returns a node affinity matching the documented selector
pin_guard_catches_pulumi_kubernetes_drift scripts/exact-pin-guard.mjs covers the new dep Seeded fixture mutating @pulumi/kubernetes integrity hash → pnpm run lint:exact-pin-guard exits non-zero with file:line

Smoke Tests

  • pnpm install --frozen-lockfile && pnpm -r build && pnpm -r test && pnpm -r typecheck && pnpm -r lint && pnpm run lint:license-boundary && pnpm run lint:exact-pin-guard → all green.
  • (Optional, requires kind binary) KIND_E2E=1 pnpm --filter @hulumi/k8s-baseline test:integration:kind → integration test green; kind cluster hulumi-k8s-m1-* does not persist after the run.
  • In a Pulumi program importing @hulumi/k8s-baseline: new HardenedHelmRelease("foo", { ... no version ... } as any) causes pulumi preview to fail with the documented missing-version error.
  • In a Pulumi program: new EksSubnetTagger("foo", { clusterName: "x", ownership: "shared" } as any) causes pulumi preview to fail with the documented missing-subnets error.
  • git status shows no untracked test artifacts.
  • .gitignore covers any new generated files (e.g. kind cluster checkpoints under ~/.config/kind and ~/.cache/helm are user-side, not repo).

Evidence Log

Step Command / Check Expected Result Actual Result Pass/Fail Notes
Baseline tests pnpm -r build && pnpm -r test green pre-M1 filled during execution pending Captures pre-M1 baseline so any regression is attributable to M1
Workspace verification grep packages in package.json and pnpm-workspace.yaml one of them lists packages/* filled during execution pending Establishes whether the workspace already covers the new package
Package skeleton ls packages/k8s-baseline/ files present per Files Allowed To Change table filled during execution pending After scaffolding
Pin-guard extension pnpm run lint:exact-pin-guard OK with @pulumi/kubernetes listed filled during execution pending Confirms @pulumi/kubernetes integrity hash captured
Cooling-off-diff extension node scripts/cooling-off-diff.mjs --dry @pulumi/kubernetes in PULUMI_PACKAGES filled during execution pending Future bumps subject to cooling-off
BDD tests created pnpm --filter @hulumi/k8s-baseline test (pre-impl) fail with module-not-found / wrong-shape errors filled during execution pending Pre-implementation failure shape captured
HardenedHelmRelease impl filesystem source files present and re-exported from index.ts filled during execution pending After implementation
EksSubnetTagger impl filesystem source files present and re-exported from index.ts filled during execution pending After implementation
compatibility.ts impl pnpm --filter @hulumi/k8s-baseline test -- compatibility warn-not-throw asserted filled during execution pending After compatibility test passes
Mock-runtime BDD pnpm --filter @hulumi/k8s-baseline test all BDD rows pass filled during execution pending Every row from the BDD table covered
Build / typecheck / lint pnpm -r build && pnpm -r typecheck && pnpm -r lint green filled during execution pending All packages clean
Kind integration test KIND_E2E=1 pnpm --filter @hulumi/k8s-baseline test:integration:kind green or skipped filled during execution pending Skip cleanly if kind binary absent
License-boundary lint pnpm run lint:license-boundary OK filled during execution pending Existing lint scope unchanged in M1
Smoke — pnpm install --frozen-lockfile && pnpm -r build && pnpm -r test && pnpm -r typecheck && pnpm -r lint terminal all green filled during execution pending Final regression sweep
Test artifact cleanup git status --short only intentional new files + modified tracker filled during execution pending No transient test artifacts
.gitignore review existing .gitignore covers Node/pnpm/Vitest/TLA+ no change needed unless kind drops state in repo filled during execution pending Adjust if kind dumps cluster state in ./

Definition of Done

  • All BDD scenarios pass (mock-runtime always; kind integration when binary present).
  • All E2E runtime validation tests pass.
  • pnpm -r test green; pnpm -r typecheck green; pnpm -r lint green; pnpm run lint:license-boundary green; pnpm run lint:exact-pin-guard green.
  • Smoke tests checked off.
  • Compatibility checklist complete.
  • No forbidden shortcuts present.
  • git status clean.
  • .gitignore covers all new generated files.
  • All existing AWS + GitHub BDD scenarios produce valid output unchanged (regression-tested).
  • docs/slo/lessons/hulumi-k8s-m1.md written (incl. the workspace-extension decision and any kind-test surprises).
  • docs/slo/completion/hulumi-k8s-m1.md written.
  • Milestone Tracker in docs/slo/completed/RUNBOOK-hulumi-k8s.md updated to done.

Post-Flight

  • docs/slo/completed/RUNBOOK-hulumi-k8s.md Milestone Tracker → M1 done.
  • docs/slo/completed/RUNBOOK-hulumi-k8s.md Component Summary Table — verify M1 row matches what was actually shipped.
  • docs/components/hardened-helm-release.md — one-line stub if not present (full reference doc in M5).
  • docs/components/eks-subnet-tagger.md — one-line stub if not present (full reference doc in M5).
  • docs/ARCHITECTURE.md — one-paragraph description of the new @hulumi/k8s-baseline package and the package-layout rationale (link to design record).
  • docs/issue-candidates.md — strike #38 and #44 (filed → shipped); update the K8s table accordingly.

Notes

  • This milestone ships no IstioFoundation, no mesh entrypoint, no secret extraction, no GitHub App component — those are M2 / M3 / M4 / M5.
  • Prior-lessons coverage category does not apply (greenfield for the K8s variant).
  • The releaseName default (component instance name verbatim) is the single most important design decision in this milestone — it must be recorded in the lessons file as a deliberate reversal of the Pulumi default and as the load-bearing reason HardenedHelmRelease exists at all.
  • The kind integration test is gated on kind binary present + KIND_E2E=1 env var. CI in this milestone runs only mock-runtime BDD; the kind suite is exercised in M5's release readiness smoke + the weekly-integration workflow extension.