diff --git a/.clue/id-ledger.yaml b/.clue/id-ledger.yaml index c0f684d5d..a9307ff2e 100644 --- a/.clue/id-ledger.yaml +++ b/.clue/id-ledger.yaml @@ -1,16 +1,18 @@ counters: - ADR: "46" + ADR: "47" AN: "1" ARCH: "26" C: "4" CAP: "17" - CH: "30" + CH: "31" G: "1" GTD: "1" IDR: "4" LOG: "1" P: "3" PDR: "12" + RBC: "4" + RCL: "11" TNP: "3" entries: - id: ADR-001 @@ -243,6 +245,11 @@ entries: state: live prefix: ADR component: "46" + - id: ADR-047 + kind: numeric + state: live + prefix: ADR + component: "47" - id: AN-001 kind: numeric state: live @@ -640,6 +647,17 @@ entries: - id: CH-030-tasks kind: opaque state: live + - id: CH-031 + kind: numeric + state: live + prefix: CH + component: "31" + - id: CH-031-open-questions + kind: opaque + state: live + - id: CH-031-tasks + kind: opaque + state: live - id: G-001 kind: numeric state: live @@ -750,6 +768,81 @@ entries: state: live prefix: PDR component: "12" + - id: RBC-001 + kind: numeric + state: live + prefix: RBC + component: "1" + - id: RBC-002 + kind: numeric + state: live + prefix: RBC + component: "2" + - id: RBC-003 + kind: numeric + state: live + prefix: RBC + component: "3" + - id: RBC-004 + kind: numeric + state: live + prefix: RBC + component: "4" + - id: RCL-001 + kind: numeric + state: live + prefix: RCL + component: "1" + - id: RCL-002 + kind: numeric + state: live + prefix: RCL + component: "2" + - id: RCL-003 + kind: numeric + state: retired + prefix: RCL + component: "3" + - id: RCL-004 + kind: numeric + state: live + prefix: RCL + component: "4" + - id: RCL-005 + kind: numeric + state: live + prefix: RCL + component: "5" + - id: RCL-006 + kind: numeric + state: live + prefix: RCL + component: "6" + - id: RCL-007 + kind: numeric + state: live + prefix: RCL + component: "7" + - id: RCL-008 + kind: numeric + state: live + prefix: RCL + component: "8" + - id: RCL-009 + kind: numeric + state: live + prefix: RCL + component: "9" + - id: RCL-010 + kind: numeric + state: live + prefix: RCL + component: "10" + - id: RCL-011 + kind: numeric + state: live + prefix: RCL + component: "11" - id: TNP-001 kind: numeric state: retired diff --git a/docs/capabilities/CAP-014-rumble-bot-catalog/criteria.md b/docs/capabilities/CAP-014-rumble-bot-catalog/criteria.md index ac4d1f844..16b85f280 100644 --- a/docs/capabilities/CAP-014-rumble-bot-catalog/criteria.md +++ b/docs/capabilities/CAP-014-rumble-bot-catalog/criteria.md @@ -35,4 +35,12 @@ Feature: rumble-bot-catalog — Rumble bot catalog When an unregistered account submits that name or an owner changes its source without increasing its version Then validation rejects the submission And an approved version increase supersedes the old active version without deleting its historical record + + @RBC-004 @draft + Scenario: A TwinDuel team is published with immutable member identities + Test-type: Integration + Given a valid TwinDuel team entry names exactly two active member bots + When the generated catalog is synchronized for ranked clients + Then the team entry carries both member identities in `teamMembers` while individual entries carry an empty list + And generation rejects a missing, inactive, unknown, or nested team member ``` diff --git a/docs/capabilities/CAP-014-rumble-bot-catalog/design.md b/docs/capabilities/CAP-014-rumble-bot-catalog/design.md index 6ac4fc9cd..65d60f45a 100644 --- a/docs/capabilities/CAP-014-rumble-bot-catalog/design.md +++ b/docs/capabilities/CAP-014-rumble-bot-catalog/design.md @@ -16,10 +16,10 @@ The external `rumble-bots` repository is the authoritative source of reviewed bo `bots/index.json` is generated after accepted changes and is never edited by contributors. Its top-level object contains a positive integer `schemaVersion`, an ISO-8601 `generatedAt` timestamp, the immutable generating Git `commit`, and a `bots` array. -Each bot entry contains `name`, `version`, `platform`, `path`, `sourceHash`, `owner`, `authors`, `addedAt`, and `status`. `sourceHash` uses the `sha256:` form and covers the source tree that clients run. Only `status: active` entries are eligible for matchmaking; `superseded`, `retired`, and `disqualified` entries remain historical metadata. +Each bot entry contains `name`, `version`, `platform`, `path`, `sourceHash`, `owner`, `authors`, `addedAt`, and `status`. It also contains `teamMembers`, an immutable ordered list of catalog identities written as `name version`; the list is empty for an individual bot and contains exactly two active individual identities for a TwinDuel team. A member identity may occur twice because Battle Runner preserves repeated team slots. `sourceHash` uses the `sha256:` form and covers the entry's source tree, while the member identities let clients obtain and verify every source tree the team executes. Only `status: active` entries are eligible for matchmaking; `superseded`, `retired`, and `disqualified` entries remain historical metadata. -The catalog schema is additive within a schema version: readers ignore fields they do not understand, while a breaking structural change requires a new `schemaVersion`. A client verifies the source-tree hash after obtaining the cataloged source and must reject a mismatch. +The catalog schema is additive within a schema version: readers ignore fields they do not understand, while a breaking structural change requires a new `schemaVersion`. Readers treat an absent `teamMembers` field as an empty list for compatibility. A client verifies every selected entry's source-tree hash after obtaining the cataloged source and must reject a mismatch. ## External evidence -The external `robocode-dev/rumble-bots` repository holds the implementation and focused integration tests for RBC-001 through RBC-003 at merged commit `c735e6ff4`. Its 4-test suite and `scripts/validate_bot.py --root . --owner flemming-n-larsen --smoke` check pass; during P-001/M-002, those tests will receive their RBC purpose tags and this criteria artifact can become active without changing the criterion meanings. +The external `robocode-dev/rumble-bots` repository holds the implementation and focused integration tests for RBC-001 through RBC-003 at merged commit `c735e6ff4`. [rumble-bots#3](https://github.com/robocode-dev/rumble-bots/pull/3) at `d58e6b5` adds RBC-004 positive and negative evidence for TwinDuel membership, including rejection of malformed catalog entries. Run `python -m unittest discover -s tests -v` and `scripts/validate_bot.py --root . --owner flemming-n-larsen --smoke` in that repository to reproduce the evidence. During P-001/M-002, those tests will receive their RBC purpose tags and this criteria artifact can become active without changing the criterion meanings. diff --git a/docs/capabilities/CAP-015-rumble-result-data/criteria.md b/docs/capabilities/CAP-015-rumble-result-data/criteria.md index 8c8d90311..4b4635a7b 100644 --- a/docs/capabilities/CAP-015-rumble-result-data/criteria.md +++ b/docs/capabilities/CAP-015-rumble-result-data/criteria.md @@ -43,4 +43,13 @@ Feature: rumble-result-data — Rumble result data When a visitor selects a ranked game type Then the dashboard requests its generated leaderboard projection and links each entry to its generated detail shard And it does not require a live application backend + + @RDA-005 @draft + Scenario: Result eligibility is derived from immutable catalog team membership + Test-type: Integration + Given a supported engine pin and a synchronized catalog containing active individual entries and TwinDuel teams with immutable `teamMembers` + When validation processes a ranked result and aggregation prepares matchmaking advice + Then `1v1` and `melee` admit only the pinned number of distinct active individual entries + And `twinduel` admits exactly two distinct active teams whose catalog members are active individuals, expand to the pinned participant count, and have disjoint member identities + And validation rejects a team in an individual game type, an individual in TwinDuel, or a result whose catalog membership, eligibility, or member disjointness is invalid ``` diff --git a/docs/capabilities/CAP-015-rumble-result-data/design.md b/docs/capabilities/CAP-015-rumble-result-data/design.md index 424c46c7f..2d6b12f74 100644 --- a/docs/capabilities/CAP-015-rumble-result-data/design.md +++ b/docs/capabilities/CAP-015-rumble-result-data/design.md @@ -16,6 +16,8 @@ The external `robocode-dev/rumble-data` repository owns result storage and proje `engine.json` pins a positive `behaviorVersion`, Tank Royale release and image references, and ranked settings for `1v1`, `twinduel`, and `melee`. `catalog.json` is the synchronized published bot catalog; only its active name-and-version entries can occur in ranked results. A contributor is admitted by a reviewed `clients/.json` registration that declares stable client IDs. +For every result, validation resolves each reported entry against the catalog before accepting or aggregating it. `1v1` and `melee` require the engine pin's exact number of distinct active individual entries, identified by an empty `teamMembers` list. `twinduel` requires exactly two distinct active team entries; each must have exactly two active individual catalog members, the teams' expanded members must equal the pinned participant count, and their member-identity sets must be disjoint. A team is ineligible for an individual game type, and an individual is ineligible for TwinDuel. This catalog lookup binds eligibility and membership without changing the result-envelope wire shape. + An issue-ops submission is one fenced JSON batch envelope with `schemaVersion`, `clientId`, `clientVersion`, and one to sixty result records. Each record includes a UUID `battleId`, completion time, matching nested client identity, behavior version, game type, pinned battle dimensions, and the complete Battle Runner participant result model. The validator checks the contract independently for every record, then normalizes valid records with the submitting account and payload hash. A content-addressed JSON file under `results/raw///` is the authoritative fact; issue bodies are transport, never state. The workflow publishes successful per-result receipts only after accepted facts reach the canonical repository, and an identical retry of an already retained result receives the same successful outcome. ## Projection and moderation contracts @@ -26,4 +28,4 @@ CI serializes ingestion and is the sole writer of facts and projections on `main ## External evidence -The external `robocode-dev/rumble-data` repository holds the implementation and focused suite for RDA-001 through RDA-004. [rumble-data#4](https://github.com/robocode-dev/rumble-data/pull/4) added every-ranked-type matchmaking advice and catalog-synchronization coverage and was accepted at merge commit `18e916e4a381c8108568d7ca77e3e14d88dd4583`; M-007 is complete. During P-001/M-002, the external tests will receive purpose tags and this criteria artifact can become active without changing the criterion meanings. +The external `robocode-dev/rumble-data` repository holds the implementation and focused suite for RDA-001 through RDA-004. [rumble-data#4](https://github.com/robocode-dev/rumble-data/pull/4) added every-ranked-type matchmaking advice and catalog-synchronization coverage and was accepted at merge commit `18e916e4a381c8108568d7ca77e3e14d88dd4583`; M-007 is complete. [rumble-data#7](https://github.com/robocode-dev/rumble-data/pull/7) at `83e71d4` adds RDA-005 positive and negative validation and advice-generation evidence, including TwinDuel member disjointness while preserving repeated member slots within a team. Run `python -m unittest discover -s tests -v` in that repository to reproduce the evidence. During P-001/M-002, the external-evidence reconciliation set must include RDA-001 through RDA-005 before this criteria artifact can become active without changing the criterion meanings. diff --git a/docs/capabilities/CAP-016-rumble-client/criteria.md b/docs/capabilities/CAP-016-rumble-client/criteria.md index c081159cd..ac8802e7f 100644 --- a/docs/capabilities/CAP-016-rumble-client/criteria.md +++ b/docs/capabilities/CAP-016-rumble-client/criteria.md @@ -28,7 +28,7 @@ Feature: rumble-client — Local ranked and practice battle client Then the client follows the canonical location and accepts one mutually consistent snapshot with supported schemas, an active registered client ID, an immutable catalog commit, and advice for every selected game type And it refuses ranked execution when a required document is missing, unsupported, inconsistent, or fails its declared source identity - @RCL-003 @draft + @RCL-003 @retired Scenario: Ranked selection turns published advice into a valid battle Test-type: Unit Given a validated snapshot contains active bots and matchmaking advice for `1v1`, `twinduel`, or `melee` @@ -36,6 +36,22 @@ Feature: rumble-client — Local ranked and practice battle client Then it selects the pinned number of distinct active participants, preferring advised pairings involving configured own bots before other high-priority advice And it treats advice as non-exclusive and can select a valid fallback battle when no advised pairing is available + @RCL-010 @draft + Scenario: Ranked selection turns published advice into a valid executable battle + Test-type: Unit + Given a validated snapshot contains active individual bots, active TwinDuel teams with two member slots backed by active individuals, and matchmaking advice + When the client selects a ranked `1v1`, `twinduel`, or `melee` battle with a recorded random seed + Then it selects the pinned number of distinct individual entries or two distinct team entries whose expanded members equal the pinned participant count + And it prefers advised pairings involving configured own entries before selecting a valid seeded fallback + + @RCL-011 @draft + Scenario: TwinDuel teams never face a shared member bot + Test-type: Unit + Given a validated snapshot contains active TwinDuel teams with immutable member identities + When the client selects a ranked TwinDuel battle + Then the two selected team entries have disjoint member identities + And it rejects selection when no two teams can satisfy that condition + @RCL-004 @draft Scenario: Ranked and practice modes cannot mix result state Test-type: Integration diff --git a/docs/capabilities/CAP-016-rumble-client/design.md b/docs/capabilities/CAP-016-rumble-client/design.md index a8657f0c4..68ff68bc2 100644 --- a/docs/capabilities/CAP-016-rumble-client/design.md +++ b/docs/capabilities/CAP-016-rumble-client/design.md @@ -18,7 +18,7 @@ One versioned configuration selects `ranked` or `practice`, the initial bot and A ranked session begins from the configured data repository's `wellknown/rumble.json` and follows its canonical location. From one data-repository revision it reads `engine.json`, `catalog.json`, `clients/.json`, and `matchmaking/matches_needed-.json` for each selected game type. The client rejects unknown schema versions, an unregistered client ID, unsupported game types, inconsistent projections, or a catalog whose declared source commit cannot identify the reviewed source in [CAP-014](../CAP-014-rumble-bot-catalog/README.md). -The accepted snapshot pins all ranked work in the session. Cached bot source is addressed by the catalog's immutable `sourceCommit` and each entry's `sourceHash`; a hash disagreement rejects the bot before execution. Matchmaking files are advice rather than reservations, so seeded selection may safely fall back to other active catalog participants while preserving the engine pin's participant count. +The accepted snapshot pins all ranked work in the session. Cached bot source is addressed by the catalog's immutable `sourceCommit` and each entry's `sourceHash`; a hash disagreement rejects the bot before execution. Individual entries have no team members. A TwinDuel team entry names exactly two active individual catalog identities in `teamMembers`, preserving repeated member slots, and cache preparation obtains and verifies the team entry plus each referenced member source tree. Matchmaking files are advice rather than reservations, so seeded selection may safely fall back to other eligible entries. `1v1` and `melee` select distinct individuals; `twinduel` selects two distinct team entries whose expanded member count equals the engine pin's participant count and whose member-identity sets are disjoint. ## Execution and mode boundary @@ -44,4 +44,4 @@ The primary distribution is a rebuildable container containing the pinned Tank R ## External evidence -The external repository records focused evidence for RCL-001 through RCL-009. The accepted configuration implementation at `robocode-dev/rumble-client` commit `d19a90e99649f87664785f1dc20b5aa2f42da7d6` provides initial configuration-loader coverage toward RCL-001; RCL-001 remains incomplete until practice mode accepts configuration without a client identity and integration evidence verifies rejection before side effects. Later CH-012 implementation commits must add the remaining evidence before M-008 can complete. During P-001/M-002, those tests will receive their RCL purpose tags and this criteria artifact can become active without changing the criterion meanings. +[rumble-client#7](https://github.com/robocode-dev/rumble-client/pull/7) at `cec7c8b` holds focused RCL-001, RCL-002, and RCL-004 through RCL-011 evidence; retired RCL-003 has no active evidence obligation. Its tagged integration and unit tests cover configuration before repository access, one validated ranked snapshot, team-aware seeded selection, ranked/practice state separation, Battle Runner execution and replay evidence, durable journaling, and bounded Issues-only submission. Run `./gradlew --no-configuration-cache -PtankRoyaleSource=../tank-royale clean build` in that repository to reproduce the hermetic client and real Runner evidence. RCL-008 and RCL-009 remain deferred by the approved CH-031 split, so P-003/M-008 remains incomplete. During P-001/M-002, the external-evidence reconciliation set must include RCL-010 and RCL-011 before this criteria artifact can become active without changing the criterion meanings. diff --git a/docs/decisions/ADR-047-rumble-catalog-publishes-team-membership.md b/docs/decisions/ADR-047-rumble-catalog-publishes-team-membership.md new file mode 100644 index 000000000..b0cc31192 --- /dev/null +++ b/docs/decisions/ADR-047-rumble-catalog-publishes-team-membership.md @@ -0,0 +1,25 @@ +--- +id: ADR-047 +type: decision +status: verified +links: [CAP-014, CAP-015, CAP-016, ARCH-022, P-003] +title: Rumble catalog publishes immutable team membership +author: agent +accepted-by: Flemming N. Larsen (2026-08-30, Codex conversation) +--- + +# ADR-047 — Rumble catalog publishes immutable team membership + +## Context + +The V1 engine pin counts the four bot processes in a TwinDuel battle, while Battle Runner starts two team entries and result ingestion receives two team results. The catalog previously identified only individual entry fields, so a client could not distinguish two valid teams from four unrelated bots before starting untrusted code. ARCH-022 already defines a TwinDuel team as an entry with exactly two `teamMembers`. + +## Decision + +The generated bot catalog publishes `teamMembers` as immutable catalog identities. Individual entries use an empty list. A TwinDuel team names exactly two active individual entries and preserves repeated member slots, while catalog generation rejects a missing, inactive, unknown, or nested team member. Readers treat an absent field as an empty list for schema-version-one compatibility. + +Ranked TwinDuel selection chooses two distinct active team entries and verifies that their expanded member count equals the engine pin. Cache preparation obtains and verifies the team entry and every member source tree before Battle Runner starts them. Individual game types select only entries without team members. + +## Consequences + +Selection, source preparation, execution identity, and result validation share one explicit team model. Existing individual-only catalogs remain readable, but they cannot satisfy TwinDuel selection until team entries are published. diff --git a/docs/decisions/IDR-004-rumble-client-battle-runner-dependency.md b/docs/decisions/IDR-004-rumble-client-battle-runner-dependency.md index 434c38774..12794a9e3 100644 --- a/docs/decisions/IDR-004-rumble-client-battle-runner-dependency.md +++ b/docs/decisions/IDR-004-rumble-client-battle-runner-dependency.md @@ -16,7 +16,7 @@ CAP-016 requires the Rumble client to execute pinned battles through the Battle ## Decision -During development, `robocode-dev/rumble-client` may consume the merged BR-049 implementation from a local Tank Royale build. Before the client is distributed for ranked use, it pins the first released `dev.robocode.tankroyale:robocode-tank-royale-runner` version that provides the BR-049 behavior-version precondition. +During development, `robocode-dev/rumble-client` may consume the merged BR-049 implementation from a local Tank Royale build. Before the client is distributed for ranked use, it pins the first released `dev.robocode.tankroyale:robocode-tankroyale-runner` version that provides the BR-049 behavior-version precondition. ## Consequences diff --git a/docs/decisions/README.md b/docs/decisions/README.md index 821b8b280..8ea4244b0 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -71,4 +71,5 @@ Writing guidelines: be specific (technical detail, not concepts); show your work - [PDR-010 — Design records use the architecture type](PDR-010-design-record-typing.md) · `inferred` — The corpus needs one vocabulary for documents that describe system structure, including the debugging guide and health reports. - [PDR-011 — Empty extracted specifications create no capability](PDR-011-empty-extraction-directory.md) · `inferred` — The `browser-sample-bots` OpenSpec directory contained no requirements to preserve. - [PDR-012 — Typed decision records replace the legacy decision log](PDR-012-typed-decision-record-carrier.md) · `inferred` — The legacy decision log mixed architecture, process, implementation, and routine history in one carrier that obscured the enduring subject of each choice. +- [ADR-047 — Rumble catalog publishes immutable team membership](ADR-047-rumble-catalog-publishes-team-membership.md) · `verified` — The V1 engine pin counts the four bot processes in a TwinDuel battle, while Battle Runner starts two team entries and result ingestion receives two team results. diff --git a/docs/design/rumble/aggregation-and-dashboard.md b/docs/design/rumble/aggregation-and-dashboard.md index f319a1a21..3968e51af 100644 --- a/docs/design/rumble/aggregation-and-dashboard.md +++ b/docs/design/rumble/aggregation-and-dashboard.md @@ -163,6 +163,8 @@ Averaging per-pairing first means extra samples of one pairing (e.g. from own-bo - The leaderboard ranks only the **latest active version** of each bot (`status: active` in `bots/index.json`, see the submission document). Superseded, retired, and disqualified versions keep their facts and per-version detail shards but leave the ranked table, exactly like a RoboRumble version bump. - Results are partitioned into **epochs by `behaviorVersion`** (the server-owned integer that bumps only on game-observable changes; see the client document's Engine Pinning section). The release version is irrelevant here: a GUI-only release, whatever its semver bump, keeps the behavior version and therefore the epoch. A `behaviorVersion` bump opens a new epoch: the ranked leaderboard is computed from the current epoch only, while old epochs remain browsable archives. This is the honest consequence of "mixed game behavior corrupts comparability": rather than pretending results across behavior versions are comparable, the rumble restarts sampling and lets matchmaking (everything is suddenly under-sampled) rebuild the table quickly. +Before accepting a fact or emitting matchmaking advice, `rumble-data` resolves every entry against the synchronized catalog. `1v1` and `melee` use only distinct active individual entries. TwinDuel uses only pairs of distinct active team entries whose immutable `teamMembers` each resolve to active individuals, expand to the pin's participant count, and are disjoint across the two teams. Any result or proposed pairing that does not meet its game type's eligibility is rejected or omitted, respectively. + ### Matchmaking output `aggregate.py` also regenerates `matchmaking/matches_needed.json`, closing the loop with clients: