feat(e2e): let the bootstrap profile assert what an action recorded

limitations.md recorded the bootstrap profile's emission gap as "empty as it stands … latent rather than active". It is neither: a large majority of that profile's declared rows carry an emission cell. So most bootstrap rows proved what an action did and said nothing about what it recorded.

Precisely: 85 of that profile's 143 declared rows carry an emission cell, measured at 58b3999ea. The limitations.md entry this change deletes carried a different figure, 87 of 145, counting scenario constants referenced under each profile's directory rather than declared rows. That figure leaves the tree with the entry, so only this one survives; it is recorded here with its commit so a reader who re-runs the count can tell a stale figure from a different question.

Forwards the collector for every profile and adds an Emissions assertion to the hosted fixture. This retires the bootstrap profile's "runs against any reachable deployment with no cluster access" property by decision rather than by erosion, and every doc and comment resting on it is updated.

The scope is not a copy of authz's. Each bootstrap fixture seeds its own namespace, where every authz fixture shares e2e-shared, so EmissionScope sets the namespace alone. Copying authz's repository scope would have been wrong in a way nothing fails on: a fixture composes further repositories, whose events carry those ids. Proved on a rig — after adding a second repository the namespace-scoped read awaited 2 repository_created events and found both.

Rig-confirmed that the anchored gate does not withhold: SeedNamespace sends EntityType: "organization", which is what usagedata.EntityTypeOrganization requires. Had bootstrap anchored to anything else this would have enabled nothing.

No emission assertions are retrofitted onto the affected rows — this makes them possible. Deletes the lifted limitations.md entry.

Where the methods live

Emissions and EmissionScope are declared on hosted.Fixture in e2e/internal/hosted, not in e2e/bootstrap. That package's Fixture is an alias for hosted.Fixture, and Go rejects a method on a non-local type, so e2e/bootstrap could not declare them. It reaches them through the alias, so the call shape in e2e/README.md is the profile's own — but the owner is hosted, and a search for the definition should look there. hosted is also where the promotion's own comment says shared behavior belongs — it exists "so a second profile over this same environment can hold the same type" — so the coming instance profile inherits them.

The file carries the e2e build tag rather than joining hosted.go, because these take a *testing.T and an untagged file would put testing into that package's ordinary build. Verified: go list reports hosted.go alone untagged and [emissions.go hosted.go] under -tags=e2e.

A missing collector is now fatal, not a skip

Hoisting the forward puts start_forward ... || exit 1 for svc/snowplow-micro on every profile's path, including the instance profile that is not this MR's. Before, a non-authz run left AR_E2E_MICRO_URL unset and every emission assertion skipped with a reason.

This is deliberate, not overlooked. A skipped emission assertion is indistinguishable from a passing one, which is the failure mode this MR exists to remove — leaving it in place would make "grep the report for ErrNotConfigured before believing a green run" the permanent price of the feature. The blast radius is small for a checkable reason: snowplow-micro is a base-environment deployer, so any profile over this environment already has it, and the hard failure fires only where the base rig is broken.

What is genuinely lost, stated plainly: you can no longer run a non-authz profile against a deployment that has no collector at all and let the emission rows skip. AR_E2E_MICRO_URL still skips the forward, but it means "point at a collector", not "run without one".

Preflight fix, pre-existing

require_commands is now unconditional (caproni go). It was previously conditional on the profile, with a comment claiming "the bootstrap profile can run against any reachable deployment with no cluster access at all". That claim was already false on the target before this MR: the docker forward sits outside every profile guard, calls start_forward — which runs caproni kubectl port-forward — and ends || exit 1. So a bootstrap run on a machine without caproni cleared preflight and then died on a bare command-not-found, which is the failure require_commands exists to prevent. Flagging it as a pre-existing defect this change repairs, not damage introduced here.

Rig evidence: both hoists live together

This change hoists the collector forward out of the authz-only branch. test(e2e): assert the metric families a deployment must expose independently hoisted the probe-listener forward out of the same branch, and the two changes conflict textually while being entirely compatible. Resolved to both at top level, and the bootstrap job on this head proves both run for a profile that could have had neither before:

==> Port-forwarding artifact-registry/svc/snowplow-micro to 127.0.0.1:9091
    forward ready
==> Port-forwarding artifact-registry/deploy/artifact-registry to 127.0.0.1:9092
    forward ready
==> Port-forwarding artifact-registry/svc/artifact-registry to 0.0.0.0:8090
    forward ready

The collector, the probe listener and the registry service, all in a bootstrap run. The failure mode this avoids is worth naming, because nothing would have reported it: resolving the conflict the other way re-nests the probe-listener forward under if [[ ${PROFILE} == authz ]], which reverts the metrics change. Its own rows keep passing, because authz still forwards it, and no bootstrap row asserts on metrics yet — so the revert is green and silent.

What is verified, and on which commit

All three profiles are green on 2968f27c1, the head this MR ships, on its own /merge pipeline 2838840556:

Profile Total Passed Failed Skipped
test:e2e:caproni:bootstrap 325 325 0 0
test:e2e:caproni:authz 66 60 0 6
test:e2e:caproni:instance 41 41 0 0

The instance row matters beyond coverage: that profile inherits the now-fatal collector forward described above, and it runs green with the hoist in place.

authz's six skips are the profile's pre-existing shared-organization strategy — TestFixtureIsIdempotent, TestFixturesAreIsolated, TestSiblingFixtureIsAnotherNamespace, TestNamespaceAnchorIsTheOrganization, TestNoOrganizationAccessHidesTheRepositoriesArea, TestFlagOffHidesTheRepositoriesArea. They skip with "this assertion needs one organization per fixture", are unrelated to this change, and are the same set before and after it.

The emission path was checked for vacuity rather than assumed: the JUnit report was searched for ErrNotConfigured, "not configured", AR_E2E_MICRO_URL and AR_E2E_METRICS_URL, and none appears in any skipped or failed case. A skipped emission assertion reports the same as a passing one, so that search is the only thing separating the two.

Drive-by: the Docker-in-Docker host resolution

5552c2647 fixes scripts/e2e/lib.sh, which resolved the DinD address with getent hosts docker | awk '{print $1}'. getent prints one line per address and that awk prints field one of every line, so a multi-homed docker service produced a newline-joined pair. caproni update-etc-hosts --ip then wrote an entry that reported success and resolved to nothing.

Measured on two bootstrap runs of this branch, both logging successfully updated /etc/hosts exactly once:

expected_ip no such host result
failing "172.18.0.3\n172.18.0.2" 342 320 of 322 failed
passing "172.18.0.2" 0 322 of 322 passed

The shell's own banner places the newline before caproni is invoked — it printed (172.18.0.3 with the closing parenthesis on the next line. The two pre-write WRN hostname needs updating lines and the success line both appear in passing runs, so neither is a signature; the multi-address value is.

All three call sites are fixed, and which one matters depends on the profile:

Profile Bring-up script Call site
bootstrap, instance scripts/e2e/caproni-up.sh caproni-up.sh
authz scripts/e2e/caproni-authz-up.sh lib.sh × 2

The two in lib.sh are the ones the failing profile never executes — bootstrap fails and bootstrap runs caproni-up.sh. e2e_upstream_host carries the expression too, and its comment states that it matches how the hosts update detects DinD, so leaving it would have made that parity claim false; there a joined value feeds a route lookup and an IPv4 check rather than a hosts write. grep -rn "getent hosts docker" --include='*.sh' is what enumerates the set; a line number does not.

Verified end to end on 2968f27c1: the banner prints (172.18.0.2) on one line, expected_ip is a single address, no such host is 0 where it was 101, and bootstrap passes 325/325 in 432s against 1443s when it was failing.

It is here rather than split out because it blocked this branch's own bootstrap run twice and the mechanism was established from this MR's traces. Not tested: scripts/e2e/ has no shell harness — the *_test.sh files under scripts/ci/ test their own checkers — and the expression has no seam reachable without a live multi-homed docker service, so the measurement above stands as the evidence.

Commit structure

The three commits are ordered so that no intermediate commit fails to build. The obvious split — add the methods to e2e/bootstrap, then move them to hosted — leaves a middle commit declaring a method on an alias, which does not compile and which would break git bisect for everyone downstream permanently. The hosted placement is therefore folded into the feature commit, and the third commit carries only the README ownership sentence.

Related to #1289

🤖 Generated with Claude Code

Edited by Suleimi Ahmed

Merge request reports

Loading
Loading