Skip to content

Latest commit

 

History

History
182 lines (147 loc) · 61.3 KB

File metadata and controls

182 lines (147 loc) · 61.3 KB

Milestone 5 — GitHubAppCredential + shipped scripts + atomic four-package release

Parent runbook: docs/slo/completed/RUNBOOK-hulumi-k8s.md. Read the runbook's Global Execution Rules + Global Entry Rules + M4's lessons before starting.

Goal: After M5, @hulumi/k8s-baseline.GitHubAppCredential ships as a pulumi.ComponentResource provisioning a Secrets Manager container + IAM read policy for a GitHub App's app_id + private-key PEM, plus two user-facing executable scripts in the package tarball: populate-github-app-secret.sh (writes the JSON {app_id, private_key} to the SM secret out-of-band) and mint-github-app-token.sh (signs a JWT with RS256, calls GET /repos/{owner}/{repo}/installation to discover the installation ID, then POST /app/installations/{id}/access_tokens with scoped permissions, prints the 1-hour installation token to stdout for use under BuildKit's --mount=type=secret). Closes #43. The K8s package's atomic v1.0.0 release lands alongside (@hulumi/baseline v1.2.0, @hulumi/policies v1.2.0, @hulumi/drift v1.2.0, @hulumi/k8s-baseline v1.0.0 — same day, same SLSA-L3 attestation chain).

Context: Build-time credential bundles are universal (every team with private deps re-implements them). The mint script is the non-trivial piece: the JWT shape, the installation-ID discovery, the scoped-token POST are all error-prone individually. The decision record commits the design: GitHubAppCredential is the focused-on-GitHub-App component (not over-generalized to "any OAuth credential"); the scripts are user-facing executables shipped in the npm tarball; the component itself just provisions infrastructure.

Important design rule: The component does NOT mint tokens itself. Tokens are minted at build time, inside docker build, via the BuildKit secret-mount pattern. Pulumi-up has no access to the build context; embedding the mint into a Pulumi resource would hide it from CI consumers who script around it. The component provisions and the scripts use — clean separation. The scripts are read-only at runtime: they read from the SM secret (via the IAM principal attached to the IAM read policy the component creates), they never write back. The populate.sh script is run once out-of-band by the consumer (not by Pulumi-up) — Pulumi-up creates an empty SM container and the consumer fills it.

Refactor budget: Surgical addition only. New files. Modifies packages/k8s-baseline/src/index.ts, packages/k8s-baseline/package.json (add scripts/ to the files array so the executables ship; bump version to 1.0.0), docs/slo/completed/RUNBOOK-hulumi-k8s.md Milestone Tracker, the cookbook index, README, AGENTS.md, why-hulumi.md, getting-started.md, ARCHITECTURE.md, CHANGELOG.md, the cooling-off scripts (no new deps but the four-package atomic release config), .github/workflows/release.yml if needed, and the docs/issue-candidates.md final strike.

Contract Block

Field Value
Inputs new GitHubAppCredential(name, args) where args: GitHubAppCredentialArgs requires: repos: string[] (refused if empty; allows ["*"] to mean all installation repos), permissions: Record<string, "read" | "write" | "admin"> (refused if empty), kmsKeyAlias: pulumi.Input<string> (KMS alias to encrypt the SM secret at rest; consumer can use accountFoundation.kmsKeyAliases.secrets). Optional: secretName?: string (defaults to the component instance name + -github-app), iamPrincipalArn?: pulumi.Input<string> (the IAM principal that gets secretsmanager:GetSecretValue — defaults to the Pulumi process's caller identity, but the consumer typically supplies the BuildKit IAM role ARN explicitly).
Outputs GitHubAppCredentialOutputs exposes secretArn: pulumi.Output<string>, iamReadPolicyArn: pulumi.Output<string>, populateScriptPath: string (filesystem path within node_modules/@hulumi/k8s-baseline/scripts/populate-github-app-secret.sh), mintScriptPath: string (path to mint-github-app-token.sh).
Interfaces touched New stable surface: @hulumi/k8s-baseline#{GitHubAppCredential,GitHubAppCredentialArgs,GitHubAppCredentialOutputs,GITHUB_APP_CREDENTIAL_COMPONENT_TYPE}. Component type: "hulumi:k8s:GitHubAppCredential". Two new shipped script artifacts at packages/k8s-baseline/scripts/{populate-github-app-secret.sh,mint-github-app-token.sh}.
Data classification Restricted — the milestone's load-bearing data flow is GitHub App private keys (RS256 signing material) and short-lived installation tokens. SM secret value transits Pulumi state ONLY at the consumer's choice (the typical flow is: pulumi up creates the empty container, the consumer runs populate.sh out-of-band — the PEM never touches Pulumi state). The mint script holds the PEM in memory only for the duration of the JWT signing call. Compliance frontmatter: compliance: [soc2, asvs] for any consumer importing this component.
Proactive controls in play (a) C1 Define Security Requirements — design record + the issue (#43) record the requirement: GitHub App credential bundle with shipped scripts. (b) C2 / C8 Data protection — KMS key alias is required (no default) so the consumer's KMS choice is explicit; the SM secret is at-rest encrypted with the consumer's CMK; the IAM read policy is scoped to the single ARN, never *. (c) C5 Validate All Inputsrepos/permissions non-empty; kmsKeyAlias non-empty; secretName validated like the M4 K8s name shape. (d) C7 Authorization — IAM read policy is scoped to one ARN, one principal. (e) C9 Implement Security Logging and Monitoringpopulate.sh writes a structured log line security_event.github_app_secret_populated (no value bytes) to stderr. (f) C10 Handle All Errors and Exceptions — the scripts use set -euo pipefail and trap to scrub the PEM from temp files on exit; component refuses construction with clear errors.
Abuse acceptance scenarios Five BDD rows in the table below cite tm-hulumi-k8s-abuse-N. Slug-keyed: tm-hulumi-k8s-abuse-iam-policy-wildcard (refuses if the consumer's secretName would resolve to a wildcard ARN), tm-hulumi-k8s-abuse-mint-token-leaks-pem (asserts mint.sh does not echo the PEM under any error path), tm-hulumi-k8s-abuse-script-not-shipped (asserts the scripts are present in the published tarball — caught by the npm pack && tar tf smoke), tm-hulumi-k8s-abuse-shellinjection-in-secretname (secretName is interpolated into AWS CLI calls in populate.sh; refuses metacharacters), tm-hulumi-k8s-abuse-token-exfil-via-stderr (asserts mint.sh writes the token to stdout only, never stderr — even on partial failure).
Files allowed to change New: packages/k8s-baseline/src/{github-app-credential.ts,github-app-credential.args.ts,github-app-credential.outputs.ts}; packages/k8s-baseline/scripts/{populate-github-app-secret.sh,mint-github-app-token.sh}; packages/k8s-baseline/tests/github-app-credential.test.ts; packages/k8s-baseline/tests/scripts/{populate.test.ts,mint.test.ts}; packages/k8s-baseline/tests/integration/release-tarball.test.ts (asserts npm pack includes the scripts); examples/k8s-helm-smoke/ (NEW — minimal Pulumi program importing HardenedHelmRelease); examples/k8s-mesh-bootstrap-smoke/ (NEW — full M2+M3+M4 bootstrap example); docs/cookbooks/k8s-helm-release-rename.md (NEW — migration); docs/cookbooks/eks-meshed-workload-bootstrap.md (NEW — full bootstrap); docs/cookbooks/github-app-private-deps-build.md (NEW — build-credential cookbook); docs/components/github-app-credential.md (NEW — full reference, since M5 is the launch milestone — also fill in the other K8s component reference docs to full reference); docs/slo/lessons/hulumi-k8s-m5.md; docs/slo/completion/hulumi-k8s-m5.md. Modified: packages/k8s-baseline/src/index.ts (re-export); packages/k8s-baseline/package.json (add scripts/ to files, bump version to 1.0.0, add chmod +x script for the bash files); docs/cookbooks/README.md (index 3 new cookbooks); docs/cookbooks/psa-baseline-istio-sidecar.md (replace hand-rolled snippets with IstioFoundation examples); README.md, AGENTS.md, docs/why-hulumi.md, docs/getting-started.md, docs/ARCHITECTURE.md, CHANGELOG.md; docs/components/{hardened-helm-release,eks-subnet-tagger,istio-foundation,alb-meshed-http-entrypoint,kubernetes-secret-from-asm,rds-credential-secret}.md (full reference docs); docs/slo/completed/RUNBOOK-hulumi-k8s.md Milestone Tracker + Doc Update Table fill-in; docs/issue-candidates.md (strike #43, sync with v1.2 release); .github/workflows/weekly-integration.yml (add kind matrix entry); .github/workflows/release.yml (atomic four-package release matrix); pnpm-workspace.yaml if needed for examples/*; .gitignore if needed. Files outside this milestone's allow-list — REFUSE TO TOUCH including any AWS / GitHub source under packages/baseline/src/{aws,github}/, any packages/policies/src/, any packages/drift/src/, M1-M4 K8s component source files (other than index.ts).
Files to read before changing anything docs/slo/completed/RUNBOOK-hulumi-k8s.md; docs/slo/design/hulumi-k8s-surface.md (§ Decision: GitHubAppCredential); GitHub Apps REST API docs (/repos/{owner}/{repo}/installation, /app/installations/{id}/access_tokens); the existing release workflow (.github/workflows/release.yml); docs/cookbooks/verify-provenance.md (the SLSA-L3 verify pattern that extends to the new package).
New files allowed All "New" entries above.
New dependencies allowed none runtime. Test-only: js-yaml if the cookbook needs to assert YAML examples render — decide during execution; default no.
Migration allowed no for the new K8s package. The Helm release-rename cookbook walks consumers through migrating their existing suffixed releases via pulumi replace; this is consumer-side migration documented in a cookbook, not a Hulumi-side migration.
Compatibility commitments GitHubAppCredential + args + outputs + type constant — stable from M5. The script names populate-github-app-secret.sh and mint-github-app-token.sh are stable; renaming requires consumer code changes. The four packages release atomically at v1.2.0/v1.0.0; future K8s-package patches can release independently of AWS/GitHub patches.
Forbidden shortcuts (a) NEVER mint tokens inside the Pulumi component. The component provisions; the scripts use. The split is load-bearing. (b) NEVER scope the IAM read policy to Resource: "*" — always to the single SM secret ARN. (c) NEVER echo the PEM or the minted token to stderr. Only stdout for the token; stderr is for security-event lines that contain no secret material. (d) NEVER ship populate.sh or mint.sh without set -euo pipefail and a trap that scrubs any temp files containing the PEM. (e) NEVER add the scripts to a bin/ field in package.json — they are not Hulumi-process commands; they are user-side artifacts the consumer invokes from CI. (f) NEVER publish the K8s package without the SLSA-L3 attestation pipeline that the existing three packages use. (g) NEVER ship the v1.2.0 release without the cooling-off CI gate green. (h) NEVER ship a default-no-KMS posture; kmsKeyAlias is required.

Out of Scope / Must Not Do

  • No GHCR / npm-private / Docker-Hub credential variants. GitHub App only at v1 (Rule 0). The cookbook cross-references future variants but ships none.
  • No automatic token rotation. Tokens are 1-hour by GitHub design; the consumer's CI re-mints per build.
  • No automatic PEM rotation. The consumer rotates the PEM via populate.sh re-run.
  • No alternative KMS key managers (Cloud HSM, external KMS). AWS KMS only.
  • No GitHub Enterprise Server (on-prem) endpoint; GHEC only — the API endpoints differ; revisit on demand.
  • No Pulumi-managed installation discovery (the mint script does this; embedding it Pulumi-side hides it from CI).

Pre-Flight

  1. Complete the Global Entry Rules.
  2. Read M4 lessons.
  3. Read GitHub's Apps REST API docs end-to-end. The JWT claims (iat, exp, iss), the installation discovery, the scoped POST — each is small but order-of-operations matters.
  4. Copy the Evidence Log.
  5. Re-state the load-bearing constraints: (i) component provisions; scripts use; (ii) IAM scoped to one ARN; (iii) PEM never on stderr; (iv) scripts are shipped via files: ["scripts/"] in package.json — verify the npm tarball contains them; (v) atomic four-package v1.2.0/v1.0.0 release.
  6. Verify npm pack --dry-run against the K8s package preview includes scripts/ files (smoke before write).

Files Allowed To Change

Listed comprehensively above in the Contract Block. The most important ones:

File Planned Change
packages/k8s-baseline/src/github-app-credential.ts NEW: ComponentResource provisioning SM container + IAM read policy
packages/k8s-baseline/scripts/populate-github-app-secret.sh NEW: bash script (set -euo pipefail, trap) writing JSON to SM
packages/k8s-baseline/scripts/mint-github-app-token.sh NEW: bash script (JWT RS256, installation discovery, scoped POST, stdout-only token)
packages/k8s-baseline/tests/scripts/{populate,mint}.test.ts NEW: tests asserting set -euo pipefail, no PEM-on-stderr, etc.
packages/k8s-baseline/tests/integration/release-tarball.test.ts NEW: npm pack && tar tf includes the two scripts under package/scripts/
examples/k8s-helm-smoke/ + examples/k8s-mesh-bootstrap-smoke/ NEW examples
3 NEW cookbooks release-rename + bootstrap + github-app-private-deps
6 component reference docs full references for all M1–M5 K8s components
packages/k8s-baseline/package.json bump to 1.0.0; add scripts/ to files; add chmod +x postinstall
.github/workflows/release.yml extend the four-package atomic release matrix
.github/workflows/weekly-integration.yml add kind matrix entry exercising M1–M4 components

Step-by-Step

  1. Implement populate-github-app-secret.sh and mint-github-app-token.sh as stand-alone bash scripts. Test them in isolation with the tests/scripts/ test files (using bash -n syntax check + shellcheck if available + happy-path with mocked AWS CLI + abuse rows).
  2. Write packages/k8s-baseline/tests/github-app-credential.test.ts BDD: happy path (component provisions one SM secret + one IAM policy with single-ARN resource), missing repos refused, missing permissions refused, missing kmsKeyAlias refused, secretName traversal refused. Run — fail for module-not-found.
  3. Implement GitHubAppCredentialArgs + GitHubAppCredentialOutputs + GitHubAppCredential. The constructor: validates inputs; creates aws.secretsmanager.Secret with kmsKeyId: kmsKeyAlias; creates aws.iam.Policy with Resource: <secret-arn> and Action: secretsmanager:GetSecretValue; if iamPrincipalArn is supplied, attaches via aws.iam.PolicyAttachment; computes populateScriptPath and mintScriptPath from require.resolve("@hulumi/k8s-baseline/scripts/populate-github-app-secret.sh") (the package ships them).
  4. Wire re-export. Run pnpm --filter @hulumi/k8s-baseline build && test && typecheck && lint — green.
  5. Write release-tarball.test.ts: runs npm pack --pack-destination /tmp from packages/k8s-baseline/, then tar tf /tmp/hulumi-k8s-baseline-1.0.0.tgz should include package/scripts/populate-github-app-secret.sh and package/scripts/mint-github-app-token.sh.
  6. Write the three cookbooks. Update the cookbook index.
  7. Write the 6 full component reference docs. Update docs/components/README.md.
  8. Update README.md, AGENTS.md, docs/why-hulumi.md, docs/getting-started.md, docs/ARCHITECTURE.md, CHANGELOG.md (v1.2.0 entry).
  9. Write examples/k8s-helm-smoke/ + examples/k8s-mesh-bootstrap-smoke/. Each is a runnable index.ts + package.json mirroring the existing examples/account-foundation-smoke/ shape.
  10. Extend .github/workflows/weekly-integration.yml with a kind matrix entry. Extend .github/workflows/release.yml to release four packages atomically with provenance attestations on all four.
  11. Run the full repo regression sweep + tarball test + cookbook lint. Update Tracker, Doc Update Table fill-in, lessons, completion. Strike #43 in docs/issue-candidates.md.

BDD Acceptance Scenarios

Feature: GitHubAppCredential provisions an SM secret + scoped IAM read policy; the shipped scripts populate / mint with redaction discipline; the npm tarball includes the scripts

Scenario Category Given When Then Threat-model row Control
Happy path — component happy path mock-runtime; repos: ["myorg/private-libs"], permissions: { contents: "read" }, kmsKeyAlias: "alias/secrets" construction one aws.secretsmanager.Secret registered with kmsKeyId: "alias/secrets"; one aws.iam.Policy with Statement[0].Resource = the secret ARN, Action: secretsmanager:GetSecretValue; outputs include secretArn, iamReadPolicyArn, populateScriptPath, mintScriptPath n/a n/a
Happy path — iamPrincipalArn supplied happy path mock-runtime; valid args + iamPrincipalArn: "arn:aws:iam::123:role/buildkit" construction one aws.iam.PolicyAttachment registered linking the policy to the principal n/a C7
Invalid input — empty repos invalid input mock-runtime; repos: [] construction constructor throws Error('GitHubAppCredential: repos must be non-empty') n/a C5
Invalid input — empty permissions invalid input mock-runtime; permissions: {} construction constructor throws n/a C5
Invalid input — missing kmsKeyAlias invalid input mock-runtime; args without kmsKeyAlias construction constructor throws — KMS is required, no default n/a C5 + Forbidden shortcut (h)
Invalid input — secretName traversal invalid input mock-runtime; secretName: "../etc/secret" construction constructor throws tm-hulumi-k8s-abuse-shellinjection-in-secretname C5
Empty state — repos: ["*"] allowed empty state mock-runtime; repos: ["*"] construction construction succeeds; no special handling required Hulumi-side (the * means "all installation repos" to GitHub's API — interpreted by the mint script, not by the component) n/a n/a
Dependency failure — KMS alias does not exist partial failure mock-runtime; kmsKeyAlias: "alias/does-not-exist" pulumi up against real AWS mock-runtime test passes; real pulumi up fails with KMS not-found error from AWS — the component does NOT pre-validate (consumer's responsibility) n/a C10
Abuse case — IAM policy resource is single ARN, not wildcard abuse case mock-runtime; valid args construction the rendered IAM policy has Statement[0].Resource = the exact secret ARN (string match against the SM resource's ARN output); Statement[0].Resource is NOT "*" tm-hulumi-k8s-abuse-iam-policy-wildcard Forbidden shortcut (b)
Abuse case — populate.sh is set -euo pipefail + trap-scrubs abuse case filesystem bash -n + grep first line after shebang is set -euo pipefail; trap line present that scrubs any temp file matching *.pem on EXIT n/a Forbidden shortcut (d)
Abuse case — mint.sh token-to-stdout-only abuse case filesystem + smoke run with stubbed AWS CLI run mint.sh against a stub the minted token (the ghs_xxx string) appears on stdout exactly once; appears on stderr ZERO times even on partial failure (e.g., AWS CLI throttle); error paths print security_event.mint_failed to stderr (no token bytes) tm-hulumi-k8s-abuse-token-exfil-via-stderr Forbidden shortcut (c)
Abuse case — mint.sh does not echo PEM abuse case filesystem grep static check no cat or echo of $PEM (or any var holding the PEM) anywhere in the script; the PEM is piped into openssl dgst via stdin, never to stdout/stderr tm-hulumi-k8s-abuse-mint-token-leaks-pem Forbidden shortcut (c)
Abuse case — scripts shipped in npm tarball abuse case local npm pack --pack-destination /tmp then tar tf the tarball includes package/scripts/populate-github-app-secret.sh and package/scripts/mint-github-app-token.sh tm-hulumi-k8s-abuse-script-not-shipped package.json files discipline
Compatibility — atomic four-package release schema / compatibility release tag v1.2.0 exists release workflow runs four npm packages publish (@hulumi/baseline@1.2.0, @hulumi/policies@1.2.0, @hulumi/drift@1.2.0, @hulumi/k8s-baseline@1.0.0); each with SLSA-L3 attestation; npm view <pkg>@1.2.0 dist-tags reports latest n/a release-pipeline lock

Regression Tests

  • All M1–M4 K8s BDD scenarios continue to pass.
  • All AWS + GitHub regression suites continue to pass.
  • pnpm run lint:license-boundary continues to pass on the existing surface.
  • pnpm run lint:exact-pin-guard continues to pass.
  • gh attestation verify --repo kerberosmansour/hulumi <tarball> for each of the four packages.

Compatibility Checklist

  • GitHubAppCredential + args + outputs + type constant exported.
  • populate-github-app-secret.sh and mint-github-app-token.sh shipped in the tarball (asserted by the integration test).
  • 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.
  • License header on every new source file.
  • Bash scripts begin with set -euo pipefail and a PEM-scrubbing trap.
  • All 6 K8s component reference docs filled in to full reference.
  • CHANGELOG.md v1.2.0 entry covers all four packages.
  • Atomic four-package release workflow tested in dry-run.
  • Cookbook index includes the three new cookbooks.

E2E Runtime Validation

| E2E Test | What It Proves | Pass Criteria | | ------------------------------------------------------------------- | --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | | provisions_sm_secret_and_scoped_policy_against_kind_or_localstack | Component provisions correctly | Test creates the component; assertions on rendered SM resource + IAM policy match the BDD happy-path row | | populate_script_writes_json_to_sm | Populate script works end-to-end with mocked AWS CLI | Run populate.sh <APP_ID> <PEM_PATH> against the mock; aws secretsmanager get-secret-value returns {"app_id":..., "private_key":...} | | mint_script_emits_one_hour_token_to_stdout | Mint script works end-to-end with stubbed GitHub API | Run mint.sh against stubbed gh api calls; stdout matches ghs_[A-Za-z0-9_]+; stderr contains the security-event line; no PEM bytes in any output | | release_tarball_contains_scripts | The shipping discipline holds | npm pack && tar tf | grep scripts/ returns 2 paths | | four_package_atomic_release_dry_run | The atomic release workflow doesn't accidentally publish 3-of-4 | act -j release-dry-run (or workflow-side dry-run gate) reports all 4 packages would publish in lockstep |

Smoke Tests

  • Full sweep green.
  • In a fresh checkout: npm pack from packages/k8s-baseline/ produces a tarball; tar tf includes the two scripts.
  • bash -n packages/k8s-baseline/scripts/*.sh syntax-check passes.
  • git status clean.

Evidence Log

Step Command / Check Expected Result Actual Result Pass/Fail Notes
Baseline pnpm -r build && pnpm -r test green pre-M5 filled during execution pending
Bash scripts written bash -n packages/k8s-baseline/scripts/*.sh OK filled during execution pending
Component impl filesystem source files present + re-exported filled during execution pending
Mock-runtime BDD pnpm --filter @hulumi/k8s-baseline test all rows pass filled during execution pending
Tarball test npm pack --dry-run from packages/k8s-baseline/ includes scripts/ filled during execution pending
Examples pnpm install in each examples/k8s-* dir OK filled during execution pending
Cookbooks filesystem + prettier 3 cookbooks present, formatted filled during execution pending
Reference docs filesystem 6 component reference docs filled filled during execution pending
Changelog grep v1.2.0 in CHANGELOG.md entry present filled during execution pending
Release workflow act -j release-dry-run or equivalent dry-run green filled during execution pending
Build / typecheck / lint pnpm -r build && pnpm -r typecheck && pnpm -r lint green filled during execution pending
Smoke full sweep green filled during execution pending

Definition of Done

  • All M1–M5 BDD scenarios pass.
  • Tarball integration test green.
  • All cookbooks + reference docs + examples present.
  • Atomic four-package release workflow dry-run green.
  • pnpm -r test && typecheck && lint && lint:license-boundary && lint:exact-pin-guard green.
  • git status clean.
  • All AWS + GitHub regression suites pass.
  • docs/slo/lessons/hulumi-k8s-m5.md + completion/hulumi-k8s-m5.md written.
  • Milestone Tracker → done.
  • docs/issue-candidates.md strikes #43.
  • README, AGENTS.md, getting-started.md, why-hulumi.md updated to surface the K8s variant.

Post-Flight

  • Tracker M5 → done; entire runbook complete.
  • Doc Update Table — final fill-in.
  • docs/issue-candidates.md strikes #43; the K8s table marks all 8 issues shipped.
  • CHANGELOG.md v1.2.0 entry covers all four packages.
  • After tag is cut, re-verify SLSA-L3 attestations on all four published tarballs via npm run release:verify-attestations.

Notes

  • The component-provisions / scripts-use split is the load-bearing decision; record in lessons.
  • The atomic four-package release is the launch event; if it splits (3-of-4 publish), users get inconsistent versions and the cooling-off + cross-package contracts break. The dry-run gate is the prevention.
  • Future K8s-package patches can release independently of AWS/GitHub patches once the v1.0.0 baseline is established.