Skip to main content
← Back to list
01Issue
FeatureOpenSwamp Club
AssigneesNone

Relationships

#1255 Reconcile collective-token CLI activity to members (attribute → bonus → stamp → even-split)

Opened by keeb · 7/18/2026

Problem

swamp-club has exactly one identity-reconciliation path: an anonymous ghost device → a person, via bindIfUnclaimed on whoami, gated to personal API-key auth (routes/api/whoami.ts:56-61).

Collective API tokens (swamp_org_*, collective_apikey) authenticate device activity as an org, but take a separate middleware path that sets session: null and no username (routes/_middleware.ts:344-383). As a result:

  • The whoami binding never fires for them — the GET handler short-circuits into the collective branch before handleGetWhoami (whoami.ts:84-100).
  • identity_map has no collective dimension — it is {username, distinct_ids[]}, person-keyed only.
  • At telemetry ingest, server.ts:236-254 resolves usernames only for personal keys, so a collective-token cli_invocation resolves to no username and is scored as an anonymous ghost — value that accrues to nobody.

Net: all non-publish collective-token device activity (cli_invocation, presence, days_active) is orphaned. Only extension_published currently distributes to members. This is a meaningful blind spot — e.g. HiveMQ's ASDLC factory (~320+ pod device-ids operating under @hivemq) generates large CLI volume that reconciles to nothing, and it is invisible to any identity_map-based "claimed vs anonymous" analysis.

The mechanism (decided)

CLI events attributed to a collective receive a bonus, get stamped, and are evenly distributed to the collective's current members.

This is already how extension publishing works — the pattern is proven and just needs generalizing from extension_published to cli_invocation:

  • routes/api/v1/extensions/confirm.ts:146-197 emits with distinct_id="collective:<id>", username="@<slug>", and a frozen member_user_ids / member_usernames snapshot.
  • services/telemetry/lib/publish-distribution.sql:33-101 fans a 1.5× pool evenly across the snapshot into per-member collective_distribution grants; the collective:<id> pseudo-identity keeps only the MV floor (500) as inert provenance.
  • This also settles the "current members" semantics: frozen-at-stamp (the snapshot is frozen at emit time), not read-time.

Plan — four steps, mapped to existing machinery

  1. Attribute — server-side, at ingest (no CLI change). The /ingest endpoint already extracts and verifies the request's API credential and applies it to the whole batch (server.ts:236-254); it just calls the personal resolver. Extend it: on a personal-key miss, try verifyCollectiveApiKey. For a swamp_org_* token, stamp the batch's events with a collective marker (@slug label + collective_id) symmetric to how personal keys stamp username. Preserve the real device distinct_id (keeps provenance; lets the device still bind to a person later). This is the linchpin and is ~20 lines symmetric to code already present.
  2. "Current members" dimension. New swamp.collective_members ClickHouse dimension synced from the Mongo member collection — a direct analog of services/telemetry/lib/identity-map-sync.ts.
  3. Bonus + stamp + even-split. New cli-collective-distribution.sql modeled on publish-distribution.sql: take collective-attributed successful cli_invocation activity, apply the bonus, fan evenly to the current members as immutable collective_distribution grants (reuse the existing grant type + @slug rendering), stamped once / idempotent, member snapshot frozen at projection-stamp time.
  4. Stop ghosting. Exclude collective-attributed distinct_ids from the ghost gate / read filter (lib/infrastructure/clickhouse/clickhouse-score-reads.ts:72,184-188) so they route to the collective/members and don't also surface as anonymous ghosts. Mirrors publish-topup.sql:54-55 excluding collective:<id>.

Key engineering delta from the publish template

Frequency. Publishes are ~1.3K/week; CLI is ~1.15M/week. A fixed per-event pool per invocation would explode the grant table and unbound the bonus. The projector must roll up to (collective, day) first, then split, and the bonus must be a rate, not a fixed per-event pool.

Open decisions

  1. Bonus shape/size — the only genuinely undecided piece. Publish uses fixed pools (75k first / 7.5k update) × 1.5. For CLI, propose either a multiplier on the collective's minted daily activity grant (e.g. 1.25×) or a fixed per-active-day pool per collective (then split), so the bonus can't scale with raw invocation volume.
  2. distinct_id handling — preserve device id + stamp collective label (recommended, keeps provenance) vs re-key to collective:<id> like publish.
  3. Backfill — go-forward only (past CLI events carry no token marker, so they are unattributable without the deanon workflowContext-namespace hack), or a coarse one-time backfill.
  4. Abuse bound — even-split to current members is sybil-able (add sock-puppet members to farm future bonuses); frozen-at-stamp caps retroactive gaming. Cap eligible members, or accept and monitor?

Alignment note

Bonusing collective CLI usage incentivizes teams to route work through collective tokens — exactly the paying (Team/Business) shape. Reputation ≠ seats, so this stays consistent with "agents/CI never count" for pricing.

02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED

Open

7/18/2026, 5:32:51 PM

No activity in this phase yet.

03Sludge Pulse

Sign in to post a ripple.