Commit Graph
26 Commits
Author SHA1 Message Date
jakman85 3b97f5f058 Merge fix/split-horizon-dns: in-cluster split-horizon DNS (R-DNS-01/03) — unblocks SSO 2026-08-24 22:29:59 -05:00
jakman85andClaude Opus 4.8 8b199a6780 test(dns): internal split-horizon resolution conformance (R-DNS-01/R-DNS-03)
Add dns-internal-resolution (L2 behavioral): an in-cluster Job resolves
keycloak.home.keithjackson.dev via the default cluster resolver and asserts the answer
is the in-cluster Gateway VIP (10.73.0.24x), not a public/off-cluster hairpin. Complements
dns-exposure (R-DNS-02, external record). coverage.py now maps R-DNS-01/R-DNS-03 to this
test (covered); chainsaw v0.2.12 lint clean; strict hygiene gate green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 22:28:53 -05:00
jakman85 186ff23098 Merge feature/servicedefinition-renderer: ServiceDefinition renderer T-0.3 (R-GITOPS-02) + L1 render tests green 2026-08-24 22:23:25 -05:00
jakman85andClaude Opus 4.8 a79f24f75e Add container-workload-render L1 test (R-PLAT-02)
The renderer's PaaS path (kind: container) had no verifying conformance test — the
R-PLAT-02 token appears only in renderer comments, so a change touching catalog/
surfaced it as an uncovered touched-requirement (blocking the merge gate). Adds the
container render analog of vm-lifecycle (R-PLAT-03): asserts a kind: container
ServiceDefinition renders a Deployment (running the declared image) + a Service,
with the egress-class/auth-mode labels admission requires. Offline render-check.sh,
wrapped by a chainsaw Test for in-cluster runs.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 22:22:04 -05:00
jakman85andClaude Opus 4.8 4a017c590e Wire L1 render conformance tests to the renderer (R-NET-01, R-NET-05/06/07)
Implements the two L1 rendering tests that were failing scaffolds:
- egress-class-render (R-NET-05/06/07): renders vpn + internet fixtures, diffs each
  against a committed golden, asserts the vpn steering label is present on vpn and
  absent on internet (fail-closed), and that flipping the class changes ONLY the
  egress path (R-NET-07). This fixes the --requirements R-NET-05 merge gate.
- default-deny-baseline-render (R-NET-01): asserts every render carries the
  namespaced default-deny baseline, shaped deny-all + both directions.

Each test carries a pure-offline render-check.sh (renderer + golden diff, no
cluster). run.sh runs L1 render checks offline and emits a REAL pass/fail JUnit
(docs/14: L1 rendering assertions run in CI without a cluster) instead of skipping
L1 with the cluster-gated layers; the chainsaw-test.yaml wrappers invoke the same
render-check.sh so an in-cluster run executes the identical assertion.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 22:19:08 -05:00
jakman85andClaude Opus 4.8 cbe5fab040 test(updates): complete hands-off-update L3 conformance (R-UPD-01/02/03)
Runnable, self-skipping (green until image.toolkit CRDs exist). Proves: an ImagePolicy
auto-advances to the newest STABLE tag on schedule with no edit and never a pre-release
(R-UPD-01/02, via podinfo ImageRepository/ImagePolicy status); a deliberately bad update
is health-gated and AUTO-ROLLS-BACK to the last-good revision while the old ReplicaSet
keeps serving, and the UpdateHealthGateFailed alert is wired (R-UPD-02/03); and an
exact-range (pinned) policy does not advance past its pin (R-UPD-03). Hermetic to the
chainsaw per-test namespace; block-style contains() checks per chainsaw v0.2.12.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 22:15:03 -05:00
jakman85 b95b540468 Merge feature/keycloak-auth-stack: Phase 6 Keycloak IdP + oauth2-proxy + CNPG (R-IDP-01..06, R-OBS)
# Conflicts:
#	platform-config/kustomization.yaml
#	platform/kustomization.yaml
2026-08-24 21:15:46 -05:00
jakman85 4bebd4b6d2 keycloak: coverage tests + dashboard + reword cross-cutting tags 2026-08-24 21:07:27 -05:00
jakman85 9da209c71e Merge branch 'main' into feature/kubevirt-iaas
# Conflicts:
#	conformance/run.sh
2026-08-24 21:01:29 -05:00
jakman85 0cb25c12e3 Merge branch 'main' into feature/keycloak-auth-stack 2026-08-24 20:59:51 -05:00
jakman85andClaude Opus 4.8 f5f52b0b8c chore(harness): adopt spec pin b633b7f + exempt R-ENV-02/R-GITOPS-01 coverage gaps
Adopt the architect's spec advance (ADR-0013 amendment + ADR-0015 egress
steering/invariants) into ci/SPEC_PIN; the impl main pin was stale (a7fc19f) so the
R-TEST-06 spec-immutability gate was red on main. Verified the a7fc19f..b633b7f range
is spec-only architect commits.

Add R-ENV-02 and R-GITOPS-01 to conformance/run.sh DOCUMENTED_GAPS (same precedent as
R-STATE-02): the completeness gate's coarse path->requirement matcher keys ci/* and
clusters/* (and doc-header tokens) to these unscaffolded, other-workstream requirements,
false-blocking any infra/overlay/CI edit. Documented + reviewable, not silent. Logged a
WS-TEST follow-up to fix the matcher so real requirements need not be exempted.

Serves: R-TEST-06. PM/Integration-Release governance change.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 20:57:26 -05:00
jakman85 ce83c6af3b WIP keycloak stack (gate probe) 2026-08-24 19:52:25 -05:00
jakman85andClaude Opus 4.8 dbffb8fab8 Phase 5: KubeVirt + CDI IaaS proof (R-PLAT-03/04, R-STATE-01, R-NET-01)
- platform/kubevirt: pinned KubeVirt v1.9.0 + CDI v1.66.0 operators (remote bases);
  KubeVirt/CDI CRs tuned for Longhorn; useEmulation=true (prefer real KVM, emulate fallback).
- Controllers/configs split (F-MON1): CRs + ServiceMonitors + alerts via platform-config.
- Observability: ServiceMonitors, KubeVirt platform-health alerts (R-OBS-04), VM dashboard.
- catalog/vm-demo.yaml (kind: vm) + rendered VirtualMachine/DataVolume fixture.
- Conformance: vm-lifecycle (R-PLAT-03/04), vm-data-survival (R-STATE-01, L3),
  vm-default-deny (R-NET-01) — self-skipping until KubeVirt+longhorn exist.
- ci/SPEC_PIN: adopt accepted spec b633b7f (ADR-0015 egress work already merged).
- conformance/run.sh: document R-ENV-02/R-GITOPS-01 coverage gaps (WS-GITOPS/INFRA-owned).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 19:51:30 -05:00
jakman85andClaude Opus 4.8 4c21f9f6e3 conformance: fix invalid test-level spec.finally for chainsaw v0.2.12
chainsaw v0.2.12 has no test-level `spec.finally` (finally exists only on a
step), and a step-level `finally` accepts only catch-style ops (script/command/
delete/wait), NOT `apply`/`assert`. Both tests failed to parse ("unknown field").

- egress-class: drop the redundant test-level `spec.finally` delete-pods block —
  chainsaw auto-deletes applied fixtures on cleanup (skipDelete: false). The
  valid step-level `finally` that restores the egress-class label is kept.
- vpn-killswitch: move the SAFETY-CRITICAL self-healing tunnel restart out of the
  invalid `spec.finally` and into the STEP-level `finally` of the fault step
  `when-force-tunnel-down-then-no-leak`, implemented as a `script` (finally can't
  use apply/assert) that `kubectl apply`s the privileged node-pinned restart Job
  (name `tunnel-restart`, distinct from `tunnel-stop`) and `kubectl wait`s for it
  to complete — the equivalent of `assert succeeded:1`, blocking teardown until
  the host confirms the tunnel is up. This finally runs even if the no-leak /
  internet-unaffected asserts in the same step's `try` fail, so no path leaves the
  gateway tunnel down. The recovery step is now poll-only (restart already
  happened in the prior step's finally).

Validation with the pinned binary (~/.local/bin/chainsaw v0.2.12):
`chainsaw lint test --file ...` => "The document is valid" for both; `chainsaw
test ... --no-cluster` loads both past the parse stage (no "unknown field"), then
fails only at runtime for lack of a cluster (expected). yamllint + run.sh
--strict-coverage clean (RC=0). No live apply / fault injection, no spec edits.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 14:09:28 -05:00
jakman85andClaude Opus 4.8 0e69c63b55 conformance: complete vpn-killswitch L3 test + ADR-0015 RBAC/fixtures
Turn the vpn-killswitch scaffold into a runnable SAFETY-CRITICAL L3 test for
R-NET-06 under ADR-0015 (node-level host OpenVPN tunnel steered by Cilium Egress
Gateway with a host nftables kill-switch), replacing the obsolete in-cluster
gluetun design.

- fixtures/vpn-and-internet-pods.yaml: two egress-class probe pods + documented
  RBAC/PodSecurity/namespace requirements for node-level fault injection.
- vpn-killswitch/chainsaw-test.yaml: GIVEN healthy tunnel (vpn egress != WAN);
  WHEN the host `vpn-egress-tunnel` systemd unit is STOPPED via a privileged,
  hostPID, node-pinned nsenter Job THEN vpn-class egress is blackholed (curl
  fails, never returns the WAN IP = no leak) while internet-class is unaffected;
  WHEN restarted THEN vpn egress recovers. spec-level `finally` ALWAYS runs an
  idempotent start Job (self-healing) so a mid-test failure never leaves the
  gateway tunnel down.
- incluster/rbac.yaml: grant the runner least-privilege for the delivered tests
  (namespaces create/delete/patch for the PSA relabel, pods/services/configmaps/
  pods-exec/pods-log, batch/jobs) and replace the gluetun-scale rule with the
  ADR-0015 host-systemd mechanism note (privilege via PodSecurity, not RBAC).

Offline validation: python yaml parse (all docs), `run.sh --lint` (structural),
`run.sh --coverage-only --strict-coverage` (no tag/annotation drift; R-NET-05/06/
07 covered, R-NET-06 has its L3 test), yamllint. No live apply, no cluster run.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 13:58:40 -05:00
jakman85andClaude Opus 4.8 e7a567db0b WIP conformance(egress-class): ADR-0015 assertions (partial — killswitch test TODO)
Session-limit interrupted the WS-TEST agent. egress-class test updated; vpn-killswitch
test + host fault-injection fixture still outstanding. Preserved for review/continuation.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 13:12:07 -05:00
jakman85andClaude Opus 4.8 34f41a0fba phase3(vpn): egress-class conformance fixtures + steering design finding
Author the internet/vpn egress-class fixture pods for the egress-class + vpn-killswitch
conformance tests. Record the datapath finding: ADR-0013's preferred steering (Cilium
egress gateway -> gluetun tun0) is NOT viable because gluetun must run hostNetwork to
put tun0 on the node, and gluetun then redirects the NODE's default route through the
VPN (breaks k3s/etcd). Escalated: remaining viable path is option 2 (gateway-pod
routing). No steering datapath committed until the mechanism is chosen + validated.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 00:26:26 -05:00
jakman85andClaude Opus 4.8 1bf8e3bfc4 feat(conformance): implement L3 default-deny-failclosed (R-NET-01/02, fault injection)
Was a scaffold (fixture TODO, fault-injection a no-op echo). Implemented against the
live datapath: fixtures/allowed-pair.yaml = default-deny baseline + source + dest(:8080)
+ the sole explicit allow. The test verifies source->dest works (ALLOWED_OK), then
REVOKES both halves of the allow (real fault: kubectl delete networkpolicy), and asserts
connectivity fails CLOSED (FAIL_CLOSED_OK) while DNS still resolves (DNS_OK). Proves
default-deny is datapath-enforced and fails closed on revocation. DNS check asserts the
resolved answer, since busybox nslookup exits non-zero on a successful forward lookup.
PASSES (74.8s).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-17 17:42:34 -05:00
jakman85andClaude Opus 4.8 70c0701d6f fix(conformance): block-style chainsaw checks (flow-map broke on contains comma)
My previous commit converted =~ to contains() but kept flow style
'check: { (contains($stdout, 'X')): true }' — the comma inside contains() is a
YAML flow-map separator, so 9 tests failed yamllint ('expected , or }'). Convert
those checks to block style (matching data-survival), where a comma in the key
scalar is fine. Gate PASS; data-survival still green.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-17 17:02:05 -05:00
jakman85andClaude Opus 4.8 e0c5c58710 fix(conformance): chainsaw checks use contains(), not unsupported =~
Every chainsaw `check` used the regex operator `=~` (`($stdout =~ 'X'): true`),
which chainsaw's expression engine rejects ('SyntaxError: Unknown char: ~') — so
EVERY such test failed on the assertion even when the behavior passed. Proven with
data-survival (R-STATE-01): the Longhorn canary genuinely SURVIVED pod destruction
(DATA_SURVIVED_OK) but the test FAILED on the `=~` syntax. Converted all 10 tests to
JMESPath `contains($stdout, 'X')` (data-survival's alternation → contains||contains).
data-survival now PASSES; the other 9 (later-phase components) are syntactically fixed
for when their phases deploy.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-17 16:58:38 -05:00
jakman85andClaude Opus 4.8 20de78f5eb feat(conformance): author R-TLS + R-DNS L2 tests; fix ExternalDNS zone filter
Phase-2 exit tests (were Phase-0 scaffolds), authored against the live platform:
- trusted-tls (R-TLS-01/03): in-cluster probe dials the gateway Service (env-agnostic
  via --connect-to) with wildcard SNI and verifies the served cert CHAINS TO THE LE
  STAGING ROOT (operator decision A; dev uses LE-staging per ADR-0006). Fixture embeds
  the '(STAGING) Pretend Pear X1' root. Probes public + internal-only hostnames (R-TLS-03).
  PASSES.
- dns-exposure (R-DNS-02): a LoadBalancer Service with an external-dns hostname; polls
  public DNS until ExternalDNS auto-publishes the record; teardown + policy=sync removes it.

ExternalDNS fix (root cause of the DNS test): domain-filter was the platform SUBDOMAIN
home.keithjackson.dev, which filters OUT the actual Cloudflare zone keithjackson.dev
('no hosted zone matching record DNS Name' — record generated but skipped). Add a
DNS_ZONE cluster-var (keithjackson.dev) and set domainFilters to it. policy=sync +
txt-registry still only manage ExternalDNS-owned records (the pre-existing wildcard is
untouched). Resolves the file's WS-NET R-DNS-02 TODO.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-17 08:24:51 -05:00
jakman85andClaude Opus 4.8 416e16bdbf fix(conformance): pass absolute --report-path so run.sh is CWD-robust
run.sh exited non-zero (breaking the L2 gate) when invoked from the repo root:
chainsaw's config report.path is relative (_artifacts), so it resolved against
$PWD and failed to write ('open _artifacts/chainsaw-report.xml: no such file')
even though all tests passed. Pass --report-path ${ARTIFACTS} (absolute) +
mkdir -p first. Verified: R-NET-01 from repo root now exits 0 with report written.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-17 07:40:03 -05:00
jakman85andClaude Opus 4.8 e20f17b896 K2 DONE: L2 network behavior tests pass against the live cluster
R-NET-01 default-deny + R-NET-02 deny-undeclared PASS via run.sh (JUnit+coverage,
exit 0). Fixed 3 real bugs found by running them:
- chainsaw strict list-matching: assert scalar 'phase: Running' not a conditions[] element.
- default-deny blocks the kubelet readiness probe -> drop probes (dest pods just need to run).
- chainsaw report path ../_artifacts resolved to repo-root -> fix to _artifacts (was exit 1).
- swap agnhost->busybox httpd listeners (cached everywhere; cache-timing-proof).
Logged the kubelet-probe finding for WS-NET's T-2.2 default-deny baseline design.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-16 15:46:00 -05:00
jakman85andClaude Opus 4.8 cc89b4b830 Autonomous session: Phase-2 platform authoring + Flux bootstrap progress
Author-only Phase-2 components (platform suspended; cluster untouched):
- platform/longhorn: 3-replica SC + bulk external-mount indirection (R-STATE)
- platform/monitoring: kube-prometheus-stack + Loki + Alloy (dev-trimmed) + 8 alert rules (R-OBS)
- platform/cilium: parity fix (k8sServiceHost=10.73.0.11 + masquerade) safe to adopt Phase 2
- conformance: partial L2 tests (worker hit session limit; incomplete — see DECISIONS-LOG K2)
- bootstrap/gitea: fix rootless-podman perms (host bind-mount -> named volume)

Flux installed on the cluster; host Gitea up + repo pushed; GitRepository BLOCKED by
host firewalld (VM->10.73.0.1:3000) — needs one operator sudo command (DECISIONS-LOG).
Known issue K1: cluster-vars substitution-var case mismatch (fix before un-suspend).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-16 14:32:47 -05:00
jakman85andClaude Opus 4.8 9d8e03892f T-0.4 Chainsaw conformance harness + coverage + behavior scaffolds (R-TEST-01..06)
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-16 00:08:32 -05:00
jakman85andClaude Opus 4.8 253b6600f4 Initial skeleton: home-network-k3s implementation repo
Phase 0 foundation shell (read-only spec at ../home-network-k3s). Top-level
tree + rules; workstream agents fill their subtrees on feature branches.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-15 23:41:43 -05:00