Skip to content

feat(countme): replace bluefin-countme with a daily projectbluefin-countme ping - #1205

Merged
castrojo merged 4 commits into
mainfrom
feat/projectbluefin-countme
Oct 1, 2026
Merged

castrojo merged 4 commits into
mainfrom
feat/projectbluefin-countme

Conversation

@castrojo

Copy link
Copy Markdown
Contributor

Replaces the weekly /metalink client (bluefin-countme) with projectbluefin-countme, reporting to the live countme.projectbluefin.io/v1/ping (projectbluefin/documentation#1347).

What it sends: at most once per 24h, {"image": "<image-name>/<image-flavor>:<stream>"}, e.g. dakota-nvidia/nvidia:testing. No hardware fields, no identifiers.

Scope: system_files/shared reaches every consumer, so the script exits before any network call unless image-info.json image-name starts with dakota or utah. Bluefin Classic and LTS never report (bats-tested).

Stream: booted ref tag from bootc status (stable/testing, else unknown); digest suffix stripped; never the baked image-tag. No booted bootc image ⇒ no report.

Timer/service: upstream eos-phone-home units (OnActiveSec=0, OnUnitInactiveSec=3h, AccuracySec=30m, ConditionDirectoryNotEmpty=/home, Type=simple). Deviations: After/Wants=network-online.target (upstream uses a NetworkManager dispatcher hook), StateDirectory= (upstream: tmpfiles), ConditionPathExists=/run/ostree-booted, enabled via preset + timers.target.wants (upstream: a .path unit). A failed send exits 0 and the next run retries.

Opt-out: systemctl mask --now projectbluefin-countme.timer. The previous opt-out files (/etc/projectbluefin/countme/disabled, /etc/bluefin-countme-opt-out, /etc/dakota-countme/disabled) still block the service, so nobody who opted out is opted back in.

Rollout notes:

  • Dakota picks this up only when elements/bluefin/common.bst moves past it; that bump also carries common#1121 (ChairLift rebrand), which Dakota's files/chairlift/install.sh currently rejects.
  • Utah picks it up with its next COMMON_IMAGE_SHA bump; its image-info.json has image-name: utah.
  • Unverified: that the Dakota/Utah live ISOs report no booted bootc image. ISO owners can check with bootc status --json in the live session.

Verification: new tests/test_projectbluefin_countme.bats 7/7 (registered in the Justfile); all 38 registered bats suites pass; pytest 115/115; just check passes; shellcheck and systemd-analyze verify clean; pre-commit hooks pass (skill catalog --check run with jsonschema installed). The client was also run in a Fedora container against the Worker on an empty local D1.

Assisted-by: Claude Opus 5.5 via pi

castrojo added a commit that referenced this pull request Sep 24, 2026
Homebrew no longer ships `tremor-runtime`
(`https://formulae.brew.sh/api/formula/tremor-runtime.json` → 404), so
"Validate Brewfiles" fails on every open PR at `cncf.Brewfile:265` (e.g.
runs for #1205 and `fix/ai-tools-align-agent-mode`). This removes the
entry and its duplicated `# Tremor` comment. No other file references
it.

Verification: `just check-brewfiles` no longer reports `tremor-runtime`;
`bats tests/test_validate_brewfiles.bats` passes.

Assisted-by: Claude Opus 5.5 via pi

Co-authored-by: Jorge Castro <jorge@projectbluefin.io>
@castrojo
castrojo enabled auto-merge September 25, 2026 03:02
@Danathar

Copy link
Copy Markdown
Contributor

I read the whole diff at 66f4ba5 and ran the new suite. bats tests/test_projectbluefin_countme.bats passes 7 of 7, test_suite_registration.bats passes, and shellcheck is clean. CI at 66f4ba5 is green (Build, Unit Tests, Validate PR, PR E2E). The script is much smaller and easier to reason about than the old one, and sending only image-name/flavor:stream is a good privacy trade. Three things before I'd approve:

1. The new service has none of the old sandboxing. bluefin-countme.service ran with ProtectSystem=strict, ProtectHome=yes, PrivateTmp=yes, NoNewPrivileges=yes, RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6 and SystemCallFilter=@system-service @mount. projectbluefin-countme.service drops all of them, so a root service that talks to the network on every Dakota and Utah machine now runs with no sandbox. The script only needs jq, stat, bootc status, curl and a write to its own StateDirectory, and the old set was already tuned so that bootc status works as root (that is why @mount is there). Was dropping them on purpose? If not, I think the old block can come back unchanged.

2. CI does not run the new tests. The Unit Tests workflow (run 36063166584) has no step for test_projectbluefin_countme.bats. The PR adds it to the Justfile test recipe, but unit-tests.yml lists its bats files one by one, so these 7 tests never gate a PR. One more step in unit-tests.yml would fix it.

3. It conflicts with #1225. Both PRs fix the same test_suite_registration.bats failure on main (it fails today because test_bluefin_countme.bats is not registered). #1225 registers the old suite, and this PR deletes it. Merging #1225 and then this PR onto main gives a Justfile conflict in the test recipe. If this one lands first, #1225 can drop its fix(ci): register test_bluefin_countme.bats commit (44cd9d3) and keep only its own test_escl_fixture.bats line.

Small one: image="$name/$(jq -r '."image-flavor"' "$info"):$tag" sends dakota/null:stable when image-flavor is missing. Using // "main" or // empty like the image-name line would avoid that.

@hanthor hanthor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few things before this can go in:

  • projectbluefin-countme.service: the hardening from the old unit is gone (ProtectSystem=strict, ProtectHome, PrivateTmp, NoNewPrivileges, RestrictAddressFamilies, SystemCallFilter). This is a root service that parses JSON and talks to the network, so please restore it. bootc status works under ProtectSystem=strict.
  • Scope: reporting widens from Dakota to Utah, and the script ships in shared/, so the image-name prefix check is the only gate. That's a telemetry/privacy change that needs explicit maintainer sign-off, and docs.projectbluefin.io/analytics should be updated at the same time.
  • projectbluefin-countme:24: if image-flavor is missing, the payload contains the literal null. Please default it.
  • The timer's Wants=network.target does nothing in a timer unit. Minor.
  • This deletes tests/test_bluefin_countme.bats, which #1249 adds to CI, and the replacement suite isn't registered in unit-tests.yml. Please coordinate with #1249.

@castrojo

Copy link
Copy Markdown
Contributor Author

Status update on CountMe ping migration:
Key points identified in review by @hanthor and @Danathar:

  1. Sandboxing: projectbluefin-countme.service drops the previous hardening directives (ProtectSystem=strict, ProtectHome=yes, PrivateTmp=yes, NoNewPrivileges=yes, SystemCallFilter). Since this service runs as root and touches the network, restoring the sandboxing is required.
  2. Missing flavor default: line 24 yields literal null if image-flavor is unset; needs a // "main" default.
  3. Test suite wiring: test_projectbluefin_countme.bats should be added as a step in .github/workflows/unit-tests.yml.
  4. Scope: expansion of telemetry to Utah requires maintainer agreement and documentation update on docs.projectbluefin.io/analytics.

Awaiting author updates on service hardening, null fallback, and unit-test registration.

@castrojo

Copy link
Copy Markdown
Contributor Author

@hanthor @Danathar Pushed commit 88e10f9 addressing the review findings:

  1. Restored full systemd sandboxing on projectbluefin-countme.service (ProtectSystem=strict, ProtectHome=yes, PrivateTmp=yes, NoNewPrivileges=yes, RestrictAddressFamilies, SystemCallFilter=@system-service @mount).
  2. Dropped Wants=network.target from projectbluefin-countme.timer.
  3. Defaulted image-flavor to "main" when missing or null in image-info.json.
  4. Registered test_projectbluefin_countme.bats in .github/workflows/unit-tests.yml.

All 7 suite tests plus registration checks pass. Ready for your review.

@hanthor hanthor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. The sandbox block is back and matches the old unit, image-flavor defaults to main, Wants=network.target is gone, and the new suite is in unit-tests.yml. All good.

Still open:

  • test is red. tests/test_suite_registration.bats fails because the Justfile test recipe still runs tests/test_bluefin_countme.bats (line 14 once merged with main; it came in with #1192 after your branch point). Please rebase and drop that line.
  • Coordinate with #1249: it adds the same deleted suite at unit-tests.yml:156-157.
  • Utah scope: I don't see a maintainer sign-off in the thread yet, and docs.projectbluefin.io/analytics still describes Dakota-only reporting. That needs an explicit OK plus the docs update before this merges.

@hanthor hanthor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for dropping the Justfile line in af2a8fb. Unfortunately #1225 went in right after and added bats tests/test_bluefin_countme.bats back (main Justfile:48), so this is conflicting again.

Still open:

  • Rebase on main and drop Justfile:48 again. #1249 is about to land with a Run bats (bluefin-countme) step (unit-tests.yml:163-164), so drop that step as well when you rebase.
  • Utah scope: this is a policy question, not a code problem. There's still no maintainer sign-off in the thread for widening reporting from Dakota to Utah, and docs.projectbluefin.io/analytics still says Dakota only. It stays open until a maintainer agrees here and the docs change is ready to go with it.

The code side is otherwise good.

@castrojo
castrojo force-pushed the feat/projectbluefin-countme branch from af2a8fb to 53537d6 Compare September 26, 2026 04:38
@hanthor

hanthor commented Sep 26, 2026

Copy link
Copy Markdown
Member

Rebased cleanly on main, and 53537d6 drops the stale test_bluefin_countme.bats registration. Unit tests, validate, both image builds and Trivy are green. That settles every code point from my review. The one thing left is the Utah scope, which needs a maintainer to sign off here, with the docs.projectbluefin.io/analytics update to go alongside it. I'll approve once that's settled.

@castrojo

Copy link
Copy Markdown
Contributor Author

Rebased onto main and cleared the test-registration conflict hanthor flagged:

Verified locally: tests/test_suite_registration.bats is green and all 7 tests in tests/test_projectbluefin_countme.bats pass. The code side is done.

Left open, exactly as hanthor noted: the Utah scope is a telemetry/privacy change, not a code fix. It needs an explicit maintainer sign-off in this thread plus the docs.projectbluefin.io/analytics update (Dakota-only -> Dakota+Utah) before this merges. I am the PR author, so I cannot provide that sign-off myself.

🐝 Hive Agent: contributor | SHA: cce2e80

@Danathar Danathar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-checked at 53537d6. All three points from my review are fixed: the service has the old sandboxing back line for line, unit-tests.yml now runs the new suite, and the Justfile conflict with #1225 is resolved. CI is green.

On the Utah scope: I'm fine with CountMe covering Utah as well as Dakota, since the ping only carries image-name/flavor:stream. Before this merges, please update docs.projectbluefin.io/analytics to say Dakota and Utah, so the public page matches what the images do.

@Danathar Danathar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-checked the one commit since my approval (53537d6..a5e0ef2). It adds ConditionPathExists=! checks for /etc/systemd/system/bluefin-countme.timer and .service, so a mask of the old units (what docs.projectbluefin.io/analytics tells people to run today) still stops the new service; systemd-analyze condition fails for a path symlinked to /dev/null and passes for a missing one. tests/test_suite_registration.bats and tests/test_projectbluefin_countme.bats pass (14/14), and the required checks (validate, test, both builds, Trivy) are green; the E2E run was still going when I looked. My earlier note still stands: the public analytics page still describes weekly metalink reporting, so please update it to match the daily Dakota and Utah ping before this merges.

@castrojo castrojo left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Author follow-up at a5e0ef2.

I re-checked hanthor's code points and all of them hold at head:

  • projectbluefin-countme.service:21-28 has the old sandbox block unchanged (PrivateTmp, ProtectSystem=strict, ProtectHome, NoNewPrivileges, RestrictAddressFamilies, SystemCallFilter=@system-service @mount).
  • projectbluefin-countme:24 falls back to main when image-flavor is missing.
  • Wants=network.target is gone.
  • tests/test_projectbluefin_countme.bats is registered in .github/workflows/unit-tests.yml:163-164 and Justfile:50.
  • Neither the Justfile nor unit-tests.yml still references the deleted test_bluefin_countme.bats. The branch point already includes #1249, and a trial merge with current main (12e3114) is clean and leaves no stale references.

One new fix in a5e0ef2. docs.projectbluefin.io/analytics tells users to opt out with systemctl mask --now bluefin-countme.timer bluefin-countme.service. The replacement units have new names, so those masks would stop applying and opted-out Dakota systems would start reporting again. projectbluefin-countme.service:12-14 now also skips when /etc/systemd/system/bluefin-countme.timer or .service exists. That path is where systemctl mask puts its /dev/null symlink. I checked the semantics with a transient systemd-run --user unit: with ConditionPathExists=! pointing at a /dev/null symlink the unit was skipped, and without the symlink it ran. docs/skills/image-registry.md:122-125 lists the preserved opt-outs.

Local checks: bats tests/test_projectbluefin_countme.bats tests/test_suite_registration.bats gives 14/14 ok. systemd-analyze verify on the unit reports only the host-missing /usr/libexec binary. At a5e0ef2 validate, both image builds, unit tests, Trivy and PR E2E are green.

Still open: the Utah scope, which is a policy question. Danathar has OK'd Dakota plus Utah. The docs.projectbluefin.io/analytics update has not been opened yet. It needs to say Dakota and Utah, describe the daily /v1/ping payload (image-name/flavor:stream, no cohort buckets), and give the new opt-out (systemctl mask --now projectbluefin-countme.timer). @hanthor, your re-review is the one still outstanding once that docs PR is up.

@castrojo

Copy link
Copy Markdown
Contributor Author

Here is where each of hanthor's review points (R5319615531, R5323541980, R5323665571) is fixed at head a5e0ef2:

Point Commit
Restore the old service sandbox (PrivateTmp, ProtectSystem=strict, ProtectHome, NoNewPrivileges, RestrictAddressFamilies, SystemCallFilter=@system-service @mount) 70e26cc
image-flavor missing produces a literal null; now defaults with // "main" 70e26cc
Drop Wants=network.target from the timer 70e26cc
Register test_projectbluefin_countme.bats in unit-tests.yml 70e26cc
Stale test_bluefin_countme.bats line in the Justfile (#1192/#1225) and the Run bats (bluefin-countme) step from #1249 53537d6
Not in the review, found afterwards: existing systemctl mask bluefin-countme.* opt-outs keep blocking the new service a5e0ef2

Required checks are green at a5e0ef2. Danathar approved at this head (R5327343284).

Still open: the Utah telemetry scope. This is a maintainer policy decision, not a code change. Once it's decided, the docs.projectbluefin.io/analytics update will be opened in projectbluefin/documentation and paired with this merge. That page still describes the weekly /metalink report and the old opt-out. @hanthor, please give your maintainer sign-off on Dakota plus Utah reporting, or say which way you want it, and re-review.

castrojo is the author, so castrojo's approval doesn't count. This still needs a second approval from someone other than castrojo.

@castrojo

Copy link
Copy Markdown
Contributor Author

The /analytics update requested in review is up: projectbluefin/documentation#1355. It covers Dakota + Utah scope, the daily /v1/ping payload (image-name/flavor:stream, no cohort buckets), and the systemctl mask --now projectbluefin-countme.timer opt-out, and notes that the old masks still apply. Please merge the two together. @hanthor, re-review please.

@castrojo

Copy link
Copy Markdown
Contributor Author

Status check at head a5e0ef2:
The companion documentation PR covering Utah scope and the opt-out mechanism has been submitted at projectbluefin/documentation#1355. Sandboxing, fallback handling, and test registrations are verified green in CI.
Blocked on maintainer sign-off from @hanthor to clear CHANGES_REQUESTED and merge alongside documentation#1355.

pull Bot pushed a commit to arteeh/documentation that referenced this pull request Sep 29, 2026
Pairs with projectbluefin/common#1205, which replaces the weekly
bluefin-countme metalink client with a daily /v1/ping from Dakota and
Utah images. States the payload, cadence, storage, and the new
opt-out; notes that masks of the previous units still apply.

Assisted-by: Claude Opus via GitHub Copilot
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@hivecommons-hive hivecommons-hive Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correctness

  • low — system_files/shared/usr/libexec/projectbluefin-countme:19 runs bootc status --json 2>/dev/null and treats any empty result as "no booted image" (:20 exit 0). If bootc status fails for another reason — e.g. under the unit's ProtectSystem=strict / SystemCallFilter=@system-service @mount sandbox (projectbluefin-countme.service:24-28), which the PR body says was not the environment the client was tested in — the failure is indistinguishable from a live ISO, nothing is logged, and the machine is silently never counted, every 3h, forever. The replaced script logged bootc status failed: … to the journal. Dropping the 2>/dev/null (stderr already goes to the journal) keeps the exit-0 behaviour but makes that state observable.

No findings from: security, intent-alignment, style, docs-currency.

Confidence: 5/5 (safe)

— hive: agent=reviewer backend=copilot model=claude-fable-5.1 copilot=1.0.88

Jorge Castro and others added 4 commits September 29, 2026 19:13
…untme ping

Dakota and Utah images now send at most one anonymous
{"image": "<image-name>/<image-flavor>:<stream>"} per day to
countme.projectbluefin.io/v1/ping. The stream comes from the booted bootc
ref; other images (Bluefin Classic, LTS) exit before any network call.
Timer and service follow upstream eos-phone-home; a failed send exits 0
and the next run retries. The previous opt-out files still block the
service; the documented opt-out is masking the timer.

Removes the weekly /metalink client (bluefin-countme) and its tests.

Assisted-by: Claude Opus 5.5 via pi
…unit tests

- restore systemd security hardening directives on projectbluefin-countme.service
- drop vestigial Wants=network.target on timer unit
- default image-flavor to 'main' when empty or null
- register test_projectbluefin_countme.bats in unit-tests.yml
docs.projectbluefin.io/analytics tells users to opt out with
`systemctl mask --now bluefin-countme.timer bluefin-countme.service`.
The replacement units have new names, so those masks no longer applied
and opted-out Dakota systems would start reporting again. Skip the
service when either old unit is masked in /etc/systemd/system.

Assisted-by: Claude via omp
Signed-off-by: castrojo <castrojo@users.noreply.github.com>
@castrojo
castrojo force-pushed the feat/projectbluefin-countme branch from a5e0ef2 to 138e16e Compare September 29, 2026 23:13
@castrojo
castrojo disabled auto-merge September 29, 2026 23:13

@hivecommons-hive hivecommons-hive Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read at head 138e16e. Ran tests/test_projectbluefin_countme.bats + test_suite_registration.bats locally: 14/14 pass.

correctness

  • low — projectbluefin-countme.timer:6 drops OnCalendar/Persistent for OnUnitInactiveSec=3h, while projectbluefin-countme.service:6-14 keeps Condition* gates. systemd only re-arms OnUnitInactiveSec from the service's inactive-enter timestamp; a start that is skipped by a failed condition never transitions state, so the timer goes elapsed and does not fire again until reboot (systemd/systemd#23696). The opt-out/ostree-booted conditions are static per boot so that's harmless, but ConditionDirectoryNotEmpty=/home (service:7) is not: a first boot with an empty /home (user created after first boot) skips the ping and never retries that boot. Undercount only, and eos-phone-home has the same gap, but worth knowing since the PR body says "the next run retries". If you want the retry guarantee, move the /home check into the script (exit 0) so the unit actually runs.
  • info (limit of review) — projectbluefin-countme:19 now makes bootc status --json the only stream source (old script preferred /run/ublue-os/booted-image), under ProtectSystem=strict + SystemCallFilter=@system-service @mount (service:24-28). The body says the client was exercised in a Fedora container, not under this sandbox on a booted Dakota/Utah; I could not verify it either. One journalctl -u projectbluefin-countme on a real deploy would close this.

No findings from: security, intent-alignment, style, docs-currency.

Confidence: 5/5 (safe)

— hive: agent=reviewer backend=copilot model=claude-fable-5.1 copilot=1.0.88

dtg01100 pushed a commit to dtg01100/documentation that referenced this pull request Sep 30, 2026
Pairs with projectbluefin/common#1205, which replaces the weekly
bluefin-countme metalink client with a daily /v1/ping from Dakota and
Utah images. States the payload, cadence, storage, and the new
opt-out; notes that masks of the previous units still apply.

Assisted-by: Claude Opus via GitHub Copilot
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@castrojo
castrojo merged commit 3dbc0e2 into main Oct 1, 2026
11 checks passed
@castrojo
castrojo deleted the feat/projectbluefin-countme branch October 1, 2026 04:52
dtg01100 pushed a commit to dtg01100/common that referenced this pull request Oct 3, 2026
…untme ping (projectbluefin#1205)

Replaces the weekly `/metalink` client (`bluefin-countme`) with
`projectbluefin-countme`, reporting to the live
`countme.projectbluefin.io/v1/ping` (projectbluefin/documentation#1347).

**What it sends:** at most once per 24h, `{"image":
"<image-name>/<image-flavor>:<stream>"}`, e.g.
`dakota-nvidia/nvidia:testing`. No hardware fields, no identifiers.

**Scope:** `system_files/shared` reaches every consumer, so the script
exits before any network call unless `image-info.json` `image-name`
starts with `dakota` or `utah`. Bluefin Classic and LTS never report
(bats-tested).

**Stream:** booted ref tag from `bootc status` (`stable`/`testing`, else
`unknown`); digest suffix stripped; never the baked `image-tag`. No
booted bootc image ⇒ no report.

**Timer/service:** upstream `eos-phone-home` units (`OnActiveSec=0`,
`OnUnitInactiveSec=3h`, `AccuracySec=30m`,
`ConditionDirectoryNotEmpty=/home`, `Type=simple`). Deviations:
`After/Wants=network-online.target` (upstream uses a NetworkManager
dispatcher hook), `StateDirectory=` (upstream: tmpfiles),
`ConditionPathExists=/run/ostree-booted`, enabled via preset +
`timers.target.wants` (upstream: a `.path` unit). A failed send exits 0
and the next run retries.

**Opt-out:** `systemctl mask --now projectbluefin-countme.timer`. The
previous opt-out files (`/etc/projectbluefin/countme/disabled`,
`/etc/bluefin-countme-opt-out`, `/etc/dakota-countme/disabled`) still
block the service, so nobody who opted out is opted back in.

**Rollout notes:**
- Dakota picks this up only when `elements/bluefin/common.bst` moves
past it; that bump also carries common#1121 (ChairLift rebrand), which
Dakota's `files/chairlift/install.sh` currently rejects.
- Utah picks it up with its next `COMMON_IMAGE_SHA` bump; its
`image-info.json` has `image-name: utah`.
- Unverified: that the Dakota/Utah live ISOs report no booted bootc
image. ISO owners can check with `bootc status --json` in the live
session.

**Verification:** new `tests/test_projectbluefin_countme.bats` 7/7
(registered in the Justfile); all 38 registered bats suites pass; pytest
115/115; `just check` passes; shellcheck and `systemd-analyze verify`
clean; `pre-commit` hooks pass (skill catalog `--check` run with
`jsonschema` installed). The client was also run in a Fedora container
against the Worker on an empty local D1.

Assisted-by: Claude Opus 5.5 via pi

---------

Signed-off-by: castrojo <castrojo@users.noreply.github.com>
Co-authored-by: Jorge Castro <jorge@projectbluefin.io>
Co-authored-by: castrojo <castrojo@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants