PR-OUTBOX-PREREQ-PROTO — Avro schemas for identity ticket lifecycle events #3

Merged
hibryda merged 5 commits from feat/outbox-prereq-avro-schemas into main 2026-05-26 11:33:38 +02:00
Owner

Summary

Protobuf-side half of PR-OUTBOX-PREREQ per ADR-0002 §3b (im2be-mono). Adds three Avro schemas under avro/identity/ticket/event/v1/ for the Kafka outbox topics emitted by identity-service on the Centrifugo opaque-ticket lifecycle.

What's in

  • ticket_minted.avscTicketMinted (UUIDv7 event_id, full user/family/tier/parental snapshot, expires_at)
  • ticket_expired.avscTicketExpired (UUIDv3-deterministic event_id from ticket_id + expires_at_iso8601 so sweeper double-fires collapse at consumer dedup per ADR-0014 D-4, plus consumed flag)
  • ticket_revoked.avscTicketRevoked (UUIDv7 event_id, TicketRevokeReason enum with UNSPECIFIED fail-closed default)
  • avro/README.md — layout + naming + backward-compat rules (Apicurio BACKWARD mode per ADR-0011 §C)

All three records carry the canonical Kafka envelope (event_id + event_time + schema_version + trace_id).

What's NOT in

  • identity-service producer code + outbox publisher worker + TicketExpirySweeper — lands as PR-OUTBOX-1 in im2be-identity-service.
  • Apicurio CLI lint workflow for .avsc files — TODO follow-up; today's validation is the manual python3 -c json.load syntax check + the implicit Apicurio-register-on-startup gate in PR-OUTBOX-1.
  • Consumer wiring (family-service / admin-service / chatbot-service) — lands as PR-OUTBOX-{family,admin,chatbot} later in Wave A/B/C.

Cross-references

  • ADR-0002 §3b in im2be-mono — the canonical specification for these schemas (drafted at meta-repo 2ec2c3b)
  • ADR-0011 §B — Apicurio Schema Registry + BACKWARD compatibility mode
  • ADR-0014 D-3 + D-4 — Avro on-wire encoding + processed_kafka_events compound-key dedup
  • .planning/26-stage-b-outbox-parity.md §10 — Wave A execution plan
  • Memora #3512 — §3b authoring + scope split

Test plan

  • All 3 .avsc files parse as valid JSON (python3 -c 'json.load(open(...))')
  • avro-tools round-trip (deferred to local validation by the reviewer)
  • Java codegen via avro-maven-plugin (validated in PR-OUTBOX-1 when identity-service consumes these)

🤖 Generated with Claude Code

## Summary Protobuf-side half of `PR-OUTBOX-PREREQ` per ADR-0002 §3b (im2be-mono). Adds three Avro schemas under `avro/identity/ticket/event/v1/` for the Kafka outbox topics emitted by identity-service on the Centrifugo opaque-ticket lifecycle. ## What's in - `ticket_minted.avsc` — `TicketMinted` (UUIDv7 event_id, full user/family/tier/parental snapshot, expires_at) - `ticket_expired.avsc` — `TicketExpired` (**UUIDv3-deterministic** event_id from `ticket_id + expires_at_iso8601` so sweeper double-fires collapse at consumer dedup per ADR-0014 D-4, plus `consumed` flag) - `ticket_revoked.avsc` — `TicketRevoked` (UUIDv7 event_id, `TicketRevokeReason` enum with UNSPECIFIED fail-closed default) - `avro/README.md` — layout + naming + backward-compat rules (Apicurio BACKWARD mode per ADR-0011 §C) All three records carry the canonical Kafka envelope (`event_id` + `event_time` + `schema_version` + `trace_id`). ## What's NOT in - identity-service producer code + outbox publisher worker + `TicketExpirySweeper` — lands as `PR-OUTBOX-1` in `im2be-identity-service`. - Apicurio CLI lint workflow for `.avsc` files — TODO follow-up; today's validation is the manual `python3 -c json.load` syntax check + the implicit Apicurio-register-on-startup gate in PR-OUTBOX-1. - Consumer wiring (family-service / admin-service / chatbot-service) — lands as `PR-OUTBOX-{family,admin,chatbot}` later in Wave A/B/C. ## Cross-references - [ADR-0002 §3b](../im2be-mono/docs/decisions/0002-centrifugo-opaque-ticket.md) in `im2be-mono` — the canonical specification for these schemas (drafted at meta-repo `2ec2c3b`) - [ADR-0011 §B](../im2be-mono/docs/decisions/0011-kafka-target-broker.md) — Apicurio Schema Registry + BACKWARD compatibility mode - [ADR-0014 D-3 + D-4](../im2be-mono/docs/decisions/0014-outbox-record-version-parity.md) — Avro on-wire encoding + `processed_kafka_events` compound-key dedup - `.planning/26-stage-b-outbox-parity.md` §10 — Wave A execution plan - Memora #3512 — §3b authoring + scope split ## Test plan - [x] All 3 `.avsc` files parse as valid JSON (`python3 -c 'json.load(open(...))'`) - [ ] `avro-tools` round-trip (deferred to local validation by the reviewer) - [ ] Java codegen via `avro-maven-plugin` (validated in PR-OUTBOX-1 when identity-service consumes these) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(avro): PR-OUTBOX-PREREQ-PROTO — 3 ticket-lifecycle Kafka event schemas
Some checks failed
protobuf CI / scripts/check-import-direction.sh (pull_request) Successful in 2s
protobuf CI / buf lint (pull_request) Failing after 4s
protobuf CI / buf build (pull_request) Failing after 4s
689ea28a7f
Adds avro/identity/ticket/event/v1/{ticket_minted,ticket_expired,ticket_revoked}.avsc
matching ADR-0002 §3b.3-§3b.5 in im2be-mono. These are the on-wire payloads for
identity-service's three Kafka outbox topics:

- identity.ticket.minted.v1   (TicketMinted, UUIDv7 event_id)
- identity.ticket.expired.v1  (TicketExpired, UUIDv3-deterministic event_id from
                               ticket_id + expires_at, so sweeper double-emits
                               collapse at consumer dedup per ADR-0014 D-4)
- identity.ticket.revoked.v1  (TicketRevoked, UUIDv7 event_id, UNSPECIFIED reason
                               = fail-closed)

All three records carry the canonical Kafka event envelope (event_id +
event_time + schema_version + trace_id) plus per-event payload. Java
namespace com.aim2be.identity.ticket.event mirrors the planned avro-maven-plugin
codegen target in identity-service.

New avro/README.md documents:
- Layout convention (proto vs avro coexistence in the same module)
- Naming (namespace, topic, subject, event_id derivation)
- Backward-compatibility rules (Apicurio BACKWARD mode per ADR-0011 §C)
- Manual validation step (no Avro-specific CI workflow yet — TODO follow-up)

Sticky-partitioned by user_id across all three topics so a single consumer
processes all lifecycle transitions for one user in order.

Cross-refs:
- ADR-0002 §3b in im2be-mono (this is the protobuf-side half of PR-OUTBOX-PREREQ)
- ADR-0011 §B (Apicurio registry; BACKWARD compatibility mode)
- ADR-0014 D-3 (Avro as on-wire encoding) + D-4 (processed_kafka_events compound dedup)
- .planning/26-stage-b-outbox-parity.md §10 (PR-OUTBOX-PREREQ scope)

Gates PR-OUTBOX-1 (identity-service producer code + TicketExpirySweeper).

Superseded by round 2.

Show previous round

hib-pr-reviewer review — PR #3 (affinity-intelligence-rework/im2be-protobuf)

Round 1 — head 689ea28a7f68, base main, trigger opened

TL;DR: NEEDS_WORK — kept 5 findings (1 agreed minor, 1 A-major, 2 A/B minor, 1 info-defer); dropped B's blocking claim on revoke_reason because its core premise — that Avro enum type-level default is silently ignored — is factually incorrect per the Apache Avro specification.

Summary

Arbitration — PR #3 im2be-protobuf Avro schemas

Memora: No prior runs found for this PR. First arbitration. Run summary persisted (memory ID 278).

Verification performed

Claim File:line verified Result
README idl2schemata wrong subcommand README.md:54 Confirmed — idl2schemata present, both A+B agree
SubscriptionTier enum type missing default ticket_minted.avsc:41-46 Confirmed — no "default" key inside the enum type object
schema_version doc contradicts README ticket_minted.avsc:21 vs README:34 Confirmed — L21 says "breaking change", README L34 says "backward-compatible additions"
subscription_tier field missing field-level default ticket_minted.avsc:40-47 Confirmed — no "default" at the field level
README no CI lint TODO README.md:50 Confirmed — acknowledged TODO
B-blocking: revoke_reason default silently ignored ticket_revoked.avsc:33-48 B's premise is factually wrong. The Apache Avro specification (§Enums) explicitly states that an enum type MAY have a "default" attribute used during schema resolution when a reader encounters an unknown symbol. It is not silently ignored. The "default": "UNSPECIFIED" at L45 inside TicketRevokeReason is correctly placed and functions as intended. B's conclusion that revoke_reason has "no actual default" and "breaks the fail-closed contract" does not follow. → DROPPED.

Reconciliation outcome

  • 1 agreed finding kept (README subcommand) — minor
  • 2 unique-to-A findings kept after verification (enum-type default missing; schema_version doc contradiction) — major + minor
  • 1 unique-to-A deferred kept (no CI lint) — info
  • 1 unique-to-B minor kept (subscription_tier field-level default missing) — minor; complements A's major finding on the same field with a different required fix location
  • 1 unique-to-B blocking dropped — core premise (enum type default silently ignored) is incorrect per Avro spec; consequence described (fail-closed contract broken) is unverified and does not follow from the actual code

Blast Radius

All changes are net-new files in a newly created avro/ subdirectory; no existing proto files or service code are modified. Blast is bounded to future consumers of the three new Kafka topics defined by these schemas. The schema evolution gaps (missing enum-type default, missing field-level defaults) would only materialize when a new symbol or field is added in a future v1 revision.

BLAST_SCORE: 3/10

CI status (head 689ea28a7f68)

Overall: ✗ failure

3 checks: 3 pending

Check State Link
protobuf CI / scripts/check-import-direction.sh (pull_request) pending details
protobuf CI / buf lint (pull_request) pending details
protobuf CI / buf build (pull_request) pending details

Findings (5)

[MINOR] Wrong avro-tools subcommand: idl2schemata does not accept .avsc input

avro/README.md:54

Line 54 recommends:

java -jar avro-tools.jar idl2schemata --output /tmp avro/identity/ticket/event/v1/ticket_minted.avsc

idl2schemata parses Avro IDL (.avdl) textual definitions, not JSON schema (.avsc). Passing a .avsc file throws org.apache.avro.AvroRuntimeException: Not an avro IDL file immediately — no validation occurs.

Replace with the compile schema subcommand, which parses and validates JSON schema structure:

java -jar avro-tools.jar compile schema \
  avro/identity/ticket/event/v1/ticket_minted.avsc /tmp/avro-out

Run once per file (or glob all three). This is the standard avro-tools path for validating .avsc files locally and will surface parse errors, unknown logicalTypes, and bad field structure.

[MAJOR] SubscriptionTier enum type has no "default" — old consumers will hard-fail on any new tier symbol

avro/identity/ticket/event/v1/ticket_minted.avsc:40

The subscription_tier field's inline enum type (lines 41-46) has no "default" attribute:

"type": {
  "type": "enum",
  "name": "SubscriptionTier",
  "symbols": ["UNSPECIFIED", "FREE", "PREMIUM", "FAMILY", "FAMILY_PLUS"],
  "doc": "..."
}

Per the Avro specification (§Enums), the enum-type "default" is the fallback used during schema resolution when a reader (old schema) encounters a symbol written by a newer producer that it does not recognise. Without it, adding any new tier symbol (e.g. ENTERPRISE) in a future schema version will throw SchemaResolutionException in every consumer that has not yet deployed the updated schema — a silent compatibility cliff that contradicts the stated BACKWARD compat stance in README §Backward-compatibility rules.

Note the contrast with ticket_revoked.avsc: TicketRevokeReason correctly has "default": "UNSPECIFIED" inside its enum type object and this is exactly right per the Avro spec. Apply the identical pattern here:

"type": {
  "type": "enum",
  "name": "SubscriptionTier",
  "symbols": ["UNSPECIFIED", "FREE", "PREMIUM", "FAMILY", "FAMILY_PLUS"],
  "default": "UNSPECIFIED",
  "doc": "Mirrors realtime.v1.SubscriptionTier ..."
}

"UNSPECIFIED" must be in symbols (it is) and should be the first symbol (it is) — the constraint is satisfied.

[MINOR] schema_version doc says "every breaking change" — directly contradicts README §Naming conventions

avro/identity/ticket/event/v1/ticket_minted.avsc:21

Line 21 doc reads:

"Bumped on every breaking change; Apicurio schema-registry compatibility mode is BACKWARD per ADR-0011 §C."

The README (line 34) says the opposite: schema_version is "bumped on backward-compatible additions". Breaking changes require creating a new v2/ directory (README line 24); a schema_version bump is for safe additions within the same major version. The inline doc inverts the rule — any implementer reading only the .avsc will do the wrong thing.

Change line 21 to:

"doc": "Bumped on every backward-compatible addition within this major version (v1). A breaking change creates v2/ instead — see ADR-0011 §C."

Note: ticket_expired.avsc and ticket_revoked.avsc omit the schema_version doc entirely — adding a consistent doc string there would also help.

[MINOR] subscription_tier field has no field-level "default" — forecloses the remove-with-default evolution path

avro/identity/ticket/event/v1/ticket_minted.avsc:40

The subscription_tier field (line 40) declares an inline enum type but has no "default" at the field level (i.e. as a sibling of "name" and "type", outside the type object). This is a separate concern from the missing enum-type default (see preceding finding).

Per README §Backward-compatibility rules line 43: " Removing a field that had a default." A field with no field-level default can never be safely dropped in a BACKWARD-compatible schema change. "UNSPECIFIED" is already the first symbol (Avro requires the field default to be one of the declared symbols), so adding it now has zero impact on existing producers or consumers:

{
  "name": "subscription_tier",
  "type": {
    "type": "enum",
    "name": "SubscriptionTier",
    "symbols": ["UNSPECIFIED", "FREE", "PREMIUM", "FAMILY", "FAMILY_PLUS"],
    "default": "UNSPECIFIED",
    "doc": "..."
  },
  "default": "UNSPECIFIED",
  "doc": "Snapshot at mint time..."
}

This also keeps all future dual-write transition options open for re-typing the field.

[INFO] No CI Avro lint — schema breakage undetectable until Apicurio registration at service startup

avro/README.md:50

README line 50 acknowledges: "There is no Avro-specific CI lint workflow yet (TODO follow-up)." Until a CI job validates .avsc syntax on every PR, a malformed schema will only surface when PR-OUTBOX-1 hits the Apicurio registration call at service startup. Consider filing a follow-up issue to add avro-tools compile schema as a pre-merge check. (Out of scope for this PR — tracked as a follow-up tech-debt item.)

Verdict

NEEDS_WORK


hib-pr-reviewer • round 1 • 5 findings (1M/3m/1i) • 2026-05-26T09:08:08.024Z → 2026-05-26T09:13:05.507Z • posted-as: pr-reviewer-bot

<!-- hib-pr-reviewer collapsed --> > _Superseded by round 2._ <details> <summary>Show previous round</summary> ## hib-pr-reviewer review — PR #3 (affinity-intelligence-rework/im2be-protobuf) **Round 1** — head `689ea28a7f68`, base `main`, trigger `opened` **TL;DR:** NEEDS_WORK — kept 5 findings (1 agreed minor, 1 A-major, 2 A/B minor, 1 info-defer); dropped B's blocking claim on revoke_reason because its core premise — that Avro enum type-level `default` is silently ignored — is factually incorrect per the Apache Avro specification. ### Summary ## Arbitration — PR #3 `im2be-protobuf` Avro schemas **Memora**: No prior runs found for this PR. First arbitration. Run summary persisted (memory ID 278). ### Verification performed | Claim | File:line verified | Result | |---|---|---| | README `idl2schemata` wrong subcommand | README.md:54 | ✅ Confirmed — `idl2schemata` present, both A+B agree | | `SubscriptionTier` enum type missing `default` | ticket_minted.avsc:41-46 | ✅ Confirmed — no `"default"` key inside the enum type object | | `schema_version` doc contradicts README | ticket_minted.avsc:21 vs README:34 | ✅ Confirmed — L21 says "breaking change", README L34 says "backward-compatible additions" | | `subscription_tier` field missing field-level default | ticket_minted.avsc:40-47 | ✅ Confirmed — no `"default"` at the field level | | README no CI lint TODO | README.md:50 | ✅ Confirmed — acknowledged TODO | | B-blocking: `revoke_reason` default silently ignored | ticket_revoked.avsc:33-48 | ❌ **B's premise is factually wrong.** The Apache Avro specification (§Enums) explicitly states that an enum type MAY have a `"default"` attribute used during schema resolution when a reader encounters an unknown symbol. It is **not** silently ignored. The `"default": "UNSPECIFIED"` at L45 inside `TicketRevokeReason` is correctly placed and functions as intended. B's conclusion that `revoke_reason` has "no actual default" and "breaks the fail-closed contract" does not follow. → **DROPPED**. | ### Reconciliation outcome - **1 agreed finding kept** (README subcommand) — minor - **2 unique-to-A findings kept** after verification (enum-type default missing; schema_version doc contradiction) — major + minor - **1 unique-to-A deferred kept** (no CI lint) — info - **1 unique-to-B minor kept** (subscription_tier field-level default missing) — minor; complements A's major finding on the same field with a different required fix location - **1 unique-to-B blocking dropped** — core premise (enum type `default` silently ignored) is incorrect per Avro spec; consequence described (fail-closed contract broken) is unverified and does not follow from the actual code ### Blast Radius All changes are net-new files in a newly created `avro/` subdirectory; no existing proto files or service code are modified. Blast is bounded to future consumers of the three new Kafka topics defined by these schemas. The schema evolution gaps (missing enum-type default, missing field-level defaults) would only materialize when a new symbol or field is added in a future v1 revision. **BLAST_SCORE: 3/10** ### CI status (head `689ea28a7f68`) **Overall: ✗ failure** 3 checks: 3 pending | Check | State | Link | |---|---|---| | protobuf CI / scripts/check-import-direction.sh (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-protobuf/actions/runs/8/jobs/0) | | protobuf CI / buf lint (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-protobuf/actions/runs/8/jobs/1) | | protobuf CI / buf build (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-protobuf/actions/runs/8/jobs/2) | ### Findings (5) #### **[MINOR]** Wrong avro-tools subcommand: `idl2schemata` does not accept `.avsc` input _avro/README.md:54_ Line 54 recommends: ```bash java -jar avro-tools.jar idl2schemata --output /tmp avro/identity/ticket/event/v1/ticket_minted.avsc ``` `idl2schemata` parses Avro IDL (`.avdl`) textual definitions, not JSON schema (`.avsc`). Passing a `.avsc` file throws `org.apache.avro.AvroRuntimeException: Not an avro IDL file` immediately — no validation occurs. Replace with the `compile schema` subcommand, which parses and validates JSON schema structure: ```bash java -jar avro-tools.jar compile schema \ avro/identity/ticket/event/v1/ticket_minted.avsc /tmp/avro-out ``` Run once per file (or glob all three). This is the standard avro-tools path for validating `.avsc` files locally and will surface parse errors, unknown logicalTypes, and bad field structure. #### **[MAJOR]** `SubscriptionTier` enum type has no `"default"` — old consumers will hard-fail on any new tier symbol _avro/identity/ticket/event/v1/ticket_minted.avsc:40_ The `subscription_tier` field's inline enum type (lines 41-46) has no `"default"` attribute: ```json "type": { "type": "enum", "name": "SubscriptionTier", "symbols": ["UNSPECIFIED", "FREE", "PREMIUM", "FAMILY", "FAMILY_PLUS"], "doc": "..." } ``` Per the Avro specification (§Enums), the enum-type `"default"` is the fallback used during schema resolution when a **reader** (old schema) encounters a symbol written by a newer producer that it does not recognise. Without it, adding any new tier symbol (e.g. `ENTERPRISE`) in a future schema version will throw `SchemaResolutionException` in every consumer that has not yet deployed the updated schema — a silent compatibility cliff that contradicts the stated BACKWARD compat stance in README §Backward-compatibility rules. Note the contrast with `ticket_revoked.avsc`: `TicketRevokeReason` **correctly** has `"default": "UNSPECIFIED"` inside its enum type object and this is exactly right per the Avro spec. Apply the identical pattern here: ```json "type": { "type": "enum", "name": "SubscriptionTier", "symbols": ["UNSPECIFIED", "FREE", "PREMIUM", "FAMILY", "FAMILY_PLUS"], "default": "UNSPECIFIED", "doc": "Mirrors realtime.v1.SubscriptionTier ..." } ``` `"UNSPECIFIED"` must be in `symbols` (it is) and should be the first symbol (it is) — the constraint is satisfied. #### **[MINOR]** `schema_version` doc says "every breaking change" — directly contradicts README §Naming conventions _avro/identity/ticket/event/v1/ticket_minted.avsc:21_ Line 21 doc reads: ``` "Bumped on every breaking change; Apicurio schema-registry compatibility mode is BACKWARD per ADR-0011 §C." ``` The README (line 34) says the opposite: `schema_version` is _"bumped on backward-compatible additions"_. Breaking changes require creating a new `v2/` directory (README line 24); a `schema_version` bump is for safe additions within the same major version. The inline doc inverts the rule — any implementer reading only the `.avsc` will do the wrong thing. Change line 21 to: ```json "doc": "Bumped on every backward-compatible addition within this major version (v1). A breaking change creates v2/ instead — see ADR-0011 §C." ``` Note: `ticket_expired.avsc` and `ticket_revoked.avsc` omit the `schema_version` doc entirely — adding a consistent doc string there would also help. #### **[MINOR]** `subscription_tier` field has no field-level `"default"` — forecloses the remove-with-default evolution path _avro/identity/ticket/event/v1/ticket_minted.avsc:40_ The `subscription_tier` field (line 40) declares an inline enum type but has no `"default"` at the **field** level (i.e. as a sibling of `"name"` and `"type"`, outside the type object). This is a separate concern from the missing enum-type `default` (see preceding finding). Per README §Backward-compatibility rules line 43: _"✅ Removing a field that **had** a default."_ A field with no field-level default can never be safely dropped in a BACKWARD-compatible schema change. `"UNSPECIFIED"` is already the first symbol (Avro requires the field default to be one of the declared symbols), so adding it now has zero impact on existing producers or consumers: ```json { "name": "subscription_tier", "type": { "type": "enum", "name": "SubscriptionTier", "symbols": ["UNSPECIFIED", "FREE", "PREMIUM", "FAMILY", "FAMILY_PLUS"], "default": "UNSPECIFIED", "doc": "..." }, "default": "UNSPECIFIED", "doc": "Snapshot at mint time..." } ``` This also keeps all future dual-write transition options open for re-typing the field. #### **[INFO]** No CI Avro lint — schema breakage undetectable until Apicurio registration at service startup _avro/README.md:50_ README line 50 acknowledges: _"There is no Avro-specific CI lint workflow yet (TODO follow-up)."_ Until a CI job validates `.avsc` syntax on every PR, a malformed schema will only surface when `PR-OUTBOX-1` hits the Apicurio registration call at service startup. Consider filing a follow-up issue to add `avro-tools compile schema` as a pre-merge check. (Out of scope for this PR — tracked as a follow-up tech-debt item.) ### Verdict **NEEDS_WORK** --- <sub>hib-pr-reviewer • round 1 • 5 findings (1M/3m/1i) • 2026-05-26T09:08:08.024Z → 2026-05-26T09:13:05.507Z • posted-as: pr-reviewer-bot</sub> </details>
fix(avro): R2 — defaults on SubscriptionTier + reconcile schema_version doc + correct avro-tools README
Some checks failed
protobuf CI / scripts/check-import-direction.sh (pull_request) Successful in 2s
protobuf CI / buf lint (pull_request) Failing after 4s
protobuf CI / buf build (pull_request) Failing after 4s
76a094504a
Addresses R1 NEEDS_WORK 4 findings (1 MAJOR + 3 MINOR):

- MAJOR: SubscriptionTier enum lacked type-level "default". Without it, an
  older consumer reading a new event with FAMILY_PRO (future symbol) hard-
  fails at deserialization. Added type-level default=UNSPECIFIED so unknown
  future symbols resolve to UNSPECIFIED → fail-closed at the consumer side.
- MINOR: subscription_tier field also lacked field-level default. Added
  field-level default=UNSPECIFIED so the field can be safely removed in a
  future major (old readers substitute UNSPECIFIED → least-privileged tier).
- MINOR: schema_version doc said "Bumped on every breaking change" — wrong;
  README §Backward-compat says it's bumped on backward-compatible additions.
  Doc reconciled: bumped on backward-compat additions; breaking changes go
  through the topic .vN suffix.
- MINOR: README local-validation example used `avro-tools idl2schemata` which
  reads .avdl IDL files, NOT .avsc JSON. Replaced with `compile schema` (the
  correct subcommand that parses + validates .avsc) with explanatory comment.

INFO finding (no Avro CI lint workflow yet) deferred per reviewer footer;
TODO entry added to README pointing at PR-OUTBOX-PREREQ-PROTO-FOLLOWUP-avro-ci.

Superseded by round 3.

Show previous round

hib-pr-reviewer review — PR #3 (affinity-intelligence-rework/im2be-protobuf)

Round 2 — head 76a094504a6a, base main, trigger synchronize

TL;DR: CONDITIONAL_APPROVE — kept 1 agreed finding (revoke_reason missing field-level default) and 2 verified unique-to-B findings (schema_version missing doc in ticket_expired.avsc and ticket_revoked.avsc); all 3 are minor, no blockers.

Summary

Recalled 0 Memora memories for this PR (no prior run history stored). Verified all 4 candidate findings against HEAD files at the checkout path.

Agreed finding (A + B): ticket_revoked.avsc:33revoke_reason field carries a type-level "default": "UNSPECIFIED" inside the TicketRevokeReason enum (line 45) but has no field-level "default" on the field object itself (lines 33–49 read and confirmed). This directly parallels the R1-4 fix applied to subscription_tier in ticket_minted.avsc and forecloses the remove-with-default evolution path. KEPT.

Unique-to-B finding 1 (verified): ticket_expired.avsc:18schema_version at lines 18–21 reads {"name":"schema_version","type":"int","default":1} with no "doc". The corresponding field in ticket_minted.avsc carries the evolution-guidance doc added in round 1. KEPT.

Unique-to-B finding 2 (verified): ticket_revoked.avsc:18 — identical gap at lines 18–21. KEPT.

Memora persisted as new record (id=279, review_count=2) per the create-new fallback (no prior run memory existed despite round=2). 3 minor findings kept; 0 dropped.

Blast Radius

The diff adds four new files (3 Avro schemas + 1 README) with no modifications to existing files. The schemas are new Kafka event contracts not yet consumed by any production service — blast radius is bounded to downstream codegen and the as-yet-unmerged outbox publisher PR (PR-OUTBOX-1). The missing field-level default on revoke_reason is the highest-impact gap: it would silently foreclose a future backward-compatible evolution step.

BLAST_SCORE: 3/10

Risk Indicators

Indicator Value
Sensitive functions TicketRevokeReason.SECURITY_INCIDENT, TicketRevokeReason.USER_SUSPENDED, TicketRevokeReason.ADMIN_FORCE_DISCONNECT
Migration touched
Test delta
Dependency changes

CI status (head 76a094504a6a)

Overall: ✗ failure

3 checks: 3 pending

Check State Link
protobuf CI / scripts/check-import-direction.sh (pull_request) pending details
protobuf CI / buf lint (pull_request) pending details
protobuf CI / buf build (pull_request) pending details

Findings (3)

[MINOR] revoke_reason enum field has type-level default but no field-level "default": "UNSPECIFIED" — inconsistent with subscription_tier pattern, forecloses evolution

avro/identity/ticket/event/v1/ticket_revoked.avsc:33

Verified at HEAD lines 33–49: the revoke_reason field object has no "default" sibling to "name". The TicketRevokeReason enum type embedded at lines 34–47 does carry "default": "UNSPECIFIED" (line 45), but that is the type-level (unknown-symbol resolution) default — a distinct Avro mechanism from the field-level default (field-resolution when a writer omits the field entirely).

Consequences without the field-level default:

  1. revoke_reason cannot be cleanly removed in a backward-compatible way under the README's own rule (" Removing a field that had a default"); a future v1 revision would have to introduce the field-level default as an intermediate step anyway.
  2. Under rolling-deploy scenarios where an old writer omits the field, Avro field resolution will throw Field revoke_reason type:ENUM pos:2 not found rather than substituting UNSPECIFIED.
  3. Directly inconsistent with subscription_tier in ticket_minted.avsc (lines 40–49), which this PR explicitly fixed in round 1 to carry both type-level and field-level defaults with an explanatory doc.

Fix — add "default": "UNSPECIFIED" as a sibling of "doc" on the field object and update "doc" to match the subscription_tier rationale pattern:

{
  "name": "revoke_reason",
  "type": {
    "type": "enum",
    "name": "TicketRevokeReason",
    "symbols": ["UNSPECIFIED", "PARENTAL_STATE_CHANGED", "USER_SUSPENDED",
                "FAMILY_MEMBERSHIP_REVOKED", "ADMIN_FORCE_DISCONNECT", "SECURITY_INCIDENT"],
    "default": "UNSPECIFIED",
    "doc": "..."
  },
  "default": "UNSPECIFIED",
  "doc": "What triggered the revoke. Field-level default=UNSPECIFIED keeps the schema evolvable (fail-closed: a reader that never wrote this field substitutes UNSPECIFIED, which consumers must already treat as full revocation). See §3b.9 for per-reason consumer obligations."
}

[MINOR] schema_version missing "doc" — evolution-guidance clarification from round-1 fix not propagated from ticket_minted.avsc

avro/identity/ticket/event/v1/ticket_expired.avsc:18

Verified at HEAD lines 18–21: the schema_version field in ticket_expired.avsc is defined as {"name":"schema_version","type":"int","default":1} with no "doc".

The round-1 fix to ticket_minted.avsc added the following "doc" to resolve the ambiguity about when to bump schema_version vs. create a new v2/ directory:

"Bumped on every BACKWARD-COMPATIBLE schema addition (matches README §Backward-compatibility rules). Breaking changes are handled at the topic-namespace level via the .vN suffix (e.g. .v1 → .v2), NOT by bumping this counter."

Without this clarification in ticket_expired.avsc, a maintainer reading only that file has no guidance and faces the exact ambiguity that prompted finding R1-3.

Fix: add the identical "doc" text to schema_version in ticket_expired.avsc.

[MINOR] schema_version missing "doc" — same evolution-guidance gap as ticket_expired.avsc

avro/identity/ticket/event/v1/ticket_revoked.avsc:18

Verified at HEAD lines 18–21: the schema_version field in ticket_revoked.avsc is defined as {"name":"schema_version","type":"int","default":1} with no "doc" — identical gap to ticket_expired.avsc.

The fix applied to ticket_minted.avsc in round 1 should have been propagated to all three schema files. It was not.

Fix: add the same "doc" text:

"doc": "Bumped on every BACKWARD-COMPATIBLE schema addition (matches README §Backward-compatibility rules). Breaking changes are handled at the topic-namespace level via the .vN suffix (e.g. .v1 → .v2), NOT by bumping this counter."

Verdict

CONDITIONAL_APPROVE


hib-pr-reviewer • round 2 • 3 findings (3m) • 2026-05-26T09:18:31.431Z → 2026-05-26T09:20:11.375Z • posted-as: pr-reviewer-bot

<!-- hib-pr-reviewer collapsed --> > _Superseded by round 3._ <details> <summary>Show previous round</summary> ## hib-pr-reviewer review — PR #3 (affinity-intelligence-rework/im2be-protobuf) **Round 2** — head `76a094504a6a`, base `main`, trigger `synchronize` **TL;DR:** CONDITIONAL_APPROVE — kept 1 agreed finding (revoke_reason missing field-level default) and 2 verified unique-to-B findings (schema_version missing doc in ticket_expired.avsc and ticket_revoked.avsc); all 3 are minor, no blockers. ### Summary Recalled 0 Memora memories for this PR (no prior run history stored). Verified all 4 candidate findings against HEAD files at the checkout path. **Agreed finding (A + B):** `ticket_revoked.avsc:33` — `revoke_reason` field carries a type-level `"default": "UNSPECIFIED"` inside the `TicketRevokeReason` enum (line 45) but has **no field-level `"default"`** on the field object itself (lines 33–49 read and confirmed). This directly parallels the R1-4 fix applied to `subscription_tier` in `ticket_minted.avsc` and forecloses the remove-with-default evolution path. **KEPT.** **Unique-to-B finding 1 (verified):** `ticket_expired.avsc:18` — `schema_version` at lines 18–21 reads `{"name":"schema_version","type":"int","default":1}` with no `"doc"`. The corresponding field in `ticket_minted.avsc` carries the evolution-guidance doc added in round 1. **KEPT.** **Unique-to-B finding 2 (verified):** `ticket_revoked.avsc:18` — identical gap at lines 18–21. **KEPT.** Memora persisted as new record (id=279, review_count=2) per the create-new fallback (no prior run memory existed despite round=2). 3 minor findings kept; 0 dropped. ### Blast Radius The diff adds four new files (3 Avro schemas + 1 README) with no modifications to existing files. The schemas are new Kafka event contracts not yet consumed by any production service — blast radius is bounded to downstream codegen and the as-yet-unmerged outbox publisher PR (PR-OUTBOX-1). The missing field-level default on `revoke_reason` is the highest-impact gap: it would silently foreclose a future backward-compatible evolution step. **BLAST_SCORE: 3/10** ### Risk Indicators | Indicator | Value | |---|---| | Sensitive functions | `TicketRevokeReason.SECURITY_INCIDENT`, `TicketRevokeReason.USER_SUSPENDED`, `TicketRevokeReason.ADMIN_FORCE_DISCONNECT` | | Migration touched | — | | Test delta | — | | Dependency changes | — | ### CI status (head `76a094504a6a`) **Overall: ✗ failure** 3 checks: 3 pending | Check | State | Link | |---|---|---| | protobuf CI / scripts/check-import-direction.sh (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-protobuf/actions/runs/9/jobs/0) | | protobuf CI / buf lint (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-protobuf/actions/runs/9/jobs/1) | | protobuf CI / buf build (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-protobuf/actions/runs/9/jobs/2) | ### Findings (3) #### **[MINOR]** `revoke_reason` enum field has type-level default but no field-level `"default": "UNSPECIFIED"` — inconsistent with `subscription_tier` pattern, forecloses evolution _avro/identity/ticket/event/v1/ticket_revoked.avsc:33_ Verified at HEAD lines 33–49: the `revoke_reason` field object has no `"default"` sibling to `"name"`. The `TicketRevokeReason` enum type embedded at lines 34–47 does carry `"default": "UNSPECIFIED"` (line 45), but that is the **type-level** (unknown-symbol resolution) default — a distinct Avro mechanism from the **field-level** default (field-resolution when a writer omits the field entirely). Consequences without the field-level default: 1. `revoke_reason` cannot be cleanly removed in a backward-compatible way under the README's own rule ("✅ Removing a field that **had a default**"); a future v1 revision would have to introduce the field-level default as an intermediate step anyway. 2. Under rolling-deploy scenarios where an old writer omits the field, Avro field resolution will throw `Field revoke_reason type:ENUM pos:2 not found` rather than substituting `UNSPECIFIED`. 3. Directly inconsistent with `subscription_tier` in `ticket_minted.avsc` (lines 40–49), which this PR explicitly fixed in round 1 to carry both type-level and field-level defaults with an explanatory `doc`. **Fix** — add `"default": "UNSPECIFIED"` as a sibling of `"doc"` on the field object and update `"doc"` to match the `subscription_tier` rationale pattern: ```json { "name": "revoke_reason", "type": { "type": "enum", "name": "TicketRevokeReason", "symbols": ["UNSPECIFIED", "PARENTAL_STATE_CHANGED", "USER_SUSPENDED", "FAMILY_MEMBERSHIP_REVOKED", "ADMIN_FORCE_DISCONNECT", "SECURITY_INCIDENT"], "default": "UNSPECIFIED", "doc": "..." }, "default": "UNSPECIFIED", "doc": "What triggered the revoke. Field-level default=UNSPECIFIED keeps the schema evolvable (fail-closed: a reader that never wrote this field substitutes UNSPECIFIED, which consumers must already treat as full revocation). See §3b.9 for per-reason consumer obligations." } ``` #### **[MINOR]** `schema_version` missing `"doc"` — evolution-guidance clarification from round-1 fix not propagated from `ticket_minted.avsc` _avro/identity/ticket/event/v1/ticket_expired.avsc:18_ Verified at HEAD lines 18–21: the `schema_version` field in `ticket_expired.avsc` is defined as `{"name":"schema_version","type":"int","default":1}` with no `"doc"`. The round-1 fix to `ticket_minted.avsc` added the following `"doc"` to resolve the ambiguity about when to bump `schema_version` vs. create a new `v2/` directory: > *"Bumped on every BACKWARD-COMPATIBLE schema addition (matches README §Backward-compatibility rules). Breaking changes are handled at the topic-namespace level via the .vN suffix (e.g. .v1 → .v2), NOT by bumping this counter."* Without this clarification in `ticket_expired.avsc`, a maintainer reading only that file has no guidance and faces the exact ambiguity that prompted finding R1-3. **Fix**: add the identical `"doc"` text to `schema_version` in `ticket_expired.avsc`. #### **[MINOR]** `schema_version` missing `"doc"` — same evolution-guidance gap as `ticket_expired.avsc` _avro/identity/ticket/event/v1/ticket_revoked.avsc:18_ Verified at HEAD lines 18–21: the `schema_version` field in `ticket_revoked.avsc` is defined as `{"name":"schema_version","type":"int","default":1}` with no `"doc"` — identical gap to `ticket_expired.avsc`. The fix applied to `ticket_minted.avsc` in round 1 should have been propagated to all three schema files. It was not. **Fix**: add the same `"doc"` text: ```json "doc": "Bumped on every BACKWARD-COMPATIBLE schema addition (matches README §Backward-compatibility rules). Breaking changes are handled at the topic-namespace level via the .vN suffix (e.g. .v1 → .v2), NOT by bumping this counter." ``` ### Verdict **CONDITIONAL_APPROVE** --- <sub>hib-pr-reviewer • round 2 • 3 findings (3m) • 2026-05-26T09:18:31.431Z → 2026-05-26T09:20:11.375Z • posted-as: pr-reviewer-bot</sub> </details>
fix(avro): R3 — propagate field-level default + schema_version doc to sister files
Some checks failed
protobuf CI / scripts/check-import-direction.sh (pull_request) Successful in 2s
protobuf CI / buf lint (pull_request) Failing after 5s
protobuf CI / buf build (pull_request) Failing after 5s
91e2ed169b
Addresses R2 CONDITIONAL_APPROVE 3 MINOR (all propagation gaps from R2 — fixes
applied only to ticket_minted.avsc; sister files needed the same treatment):

- ticket_revoked.avsc revoke_reason: added field-level default=UNSPECIFIED
  (matches subscription_tier pattern from R1 fix; keeps schema evolvable).
- ticket_expired.avsc schema_version: added evolution-guidance doc
  (backward-compat additions vs topic .vN for breaking).
- ticket_revoked.avsc schema_version: same evolution-guidance doc.

Superseded by round 4.

Show previous round

hib-pr-reviewer review — PR #3 (affinity-intelligence-rework/im2be-protobuf)

Round 3 — head 91e2ed169b9a, base main, trigger synchronize

TL;DR: CONDITIONAL_APPROVE — kept 1 verified unique-to-A minor finding (consumed boolean missing default:false in ticket_expired.avsc); Reviewer B output was truncated (0 findings); no blocking issues.

Summary

Round 3 Arbitration

Reviewer B produced 0 usable findings (output truncated mid-JSON, parse-fallback).

Reviewer A raised 1 unique-to-A minor finding: consumed boolean in ticket_expired.avsc missing "default": false.

Verification performed (Read tool, /var/lib/pr-reviewer/repos/affinity-intelligence-rework/im2be-protobuf/767/avro/identity/ticket/event/v1/ticket_expired.avsc):

  • Lines 38–42 confirmed: consumed field declares "type": "boolean" with no "default" key — finding holds exactly as cited.
  • Cross-check in ticket_minted.avsc lines 78–80 confirmed: structurally identical is_admin boolean carries "default": false — asymmetry verified.
  • README backward-compat rules (also new-in-this-PR) explicitly state ✅ Removing a field that had a default as the safe Avro evolution path; absence of a default on consumed forecloses that path without a major-version bump.

Kept 1 finding (unique-to-A, verified). Dropped 0. Reviewer B contributed nothing verifiable due to truncation.

Note: Memora persistence attempted but failed due to tag allowlist restrictions on the MCP server; run history could not be stored.

Blast Radius

This PR adds new Avro schema files and a README under avro/identity/ticket/event/v1/. The blast radius is limited to the three new event schemas (ticket_minted, ticket_expired, ticket_revoked) and their downstream Kafka consumers. No existing proto files or service contracts are modified; the schemas are not yet registered in production (registration happens in PR-OUTBOX-1).

BLAST_SCORE: 3/10

CI status (head 91e2ed169b9a)

Overall: ✗ failure

3 checks: 3 pending

Check State Link
protobuf CI / scripts/check-import-direction.sh (pull_request) pending details
protobuf CI / buf lint (pull_request) pending details
protobuf CI / buf build (pull_request) pending details

Findings (1)

[MINOR] consumed boolean missing "default": false — inconsistent with is_admin pattern and forecloses README's two-step removal path

avro/identity/ticket/event/v1/ticket_expired.avsc:39

At lines 38–42, the consumed field is declared as "type": "boolean" with no "default" key:

{
  "name": "consumed",
  "type": "boolean",
  "doc": "True iff Centrifugo connected with this ticket before TTL elapse ..."
}

Compare ticket_minted.avsc lines 77–81 where the structurally identical boolean field is_admin carries "default": false.

The README's backward-compatibility rules (new in this very PR) list ✅ Removing a field that had a default as the safe evolution path. Without a default on consumed, a future schema that drops this field cannot be registered as a backward-compatible v1.x change — it would require a major-version bump (v1/v2/). The doc itself states "False = ticket was minted but never used", confirming false is the correct missing-data sentinel.

Fix: add "default": false between "type" and "doc":

{
  "name": "consumed",
  "type": "boolean",
  "default": false,
  "doc": "True iff ..."
}

Verdict

CONDITIONAL_APPROVE


hib-pr-reviewer • round 3 • 1 finding (1m) • 2026-05-26T09:24:54.399Z → 2026-05-26T09:26:18.943Z • posted-as: pr-reviewer-bot

<!-- hib-pr-reviewer collapsed --> > _Superseded by round 4._ <details> <summary>Show previous round</summary> ## hib-pr-reviewer review — PR #3 (affinity-intelligence-rework/im2be-protobuf) **Round 3** — head `91e2ed169b9a`, base `main`, trigger `synchronize` **TL;DR:** CONDITIONAL_APPROVE — kept 1 verified unique-to-A minor finding (consumed boolean missing default:false in ticket_expired.avsc); Reviewer B output was truncated (0 findings); no blocking issues. ### Summary ## Round 3 Arbitration **Reviewer B** produced 0 usable findings (output truncated mid-JSON, parse-fallback). **Reviewer A** raised 1 unique-to-A minor finding: `consumed` boolean in `ticket_expired.avsc` missing `"default": false`. **Verification performed** (Read tool, `/var/lib/pr-reviewer/repos/affinity-intelligence-rework/im2be-protobuf/767/avro/identity/ticket/event/v1/ticket_expired.avsc`): - Lines 38–42 confirmed: `consumed` field declares `"type": "boolean"` with no `"default"` key — finding holds exactly as cited. - Cross-check in `ticket_minted.avsc` lines 78–80 confirmed: structurally identical `is_admin` boolean carries `"default": false` — asymmetry verified. - README backward-compat rules (also new-in-this-PR) explicitly state `✅ Removing a field that had a default` as the safe Avro evolution path; absence of a default on `consumed` forecloses that path without a major-version bump. **Kept 1 finding** (unique-to-A, verified). **Dropped 0**. Reviewer B contributed nothing verifiable due to truncation. *Note: Memora persistence attempted but failed due to tag allowlist restrictions on the MCP server; run history could not be stored.* ### Blast Radius This PR adds new Avro schema files and a README under avro/identity/ticket/event/v1/. The blast radius is limited to the three new event schemas (ticket_minted, ticket_expired, ticket_revoked) and their downstream Kafka consumers. No existing proto files or service contracts are modified; the schemas are not yet registered in production (registration happens in PR-OUTBOX-1). **BLAST_SCORE: 3/10** ### CI status (head `91e2ed169b9a`) **Overall: ✗ failure** 3 checks: 3 pending | Check | State | Link | |---|---|---| | protobuf CI / scripts/check-import-direction.sh (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-protobuf/actions/runs/10/jobs/0) | | protobuf CI / buf lint (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-protobuf/actions/runs/10/jobs/1) | | protobuf CI / buf build (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-protobuf/actions/runs/10/jobs/2) | ### Findings (1) #### **[MINOR]** `consumed` boolean missing `"default": false` — inconsistent with `is_admin` pattern and forecloses README's two-step removal path _avro/identity/ticket/event/v1/ticket_expired.avsc:39_ At lines 38–42, the `consumed` field is declared as `"type": "boolean"` with no `"default"` key: ```json { "name": "consumed", "type": "boolean", "doc": "True iff Centrifugo connected with this ticket before TTL elapse ..." } ``` Compare `ticket_minted.avsc` lines 77–81 where the structurally identical boolean field `is_admin` carries `"default": false`. The README's backward-compatibility rules (new in this very PR) list `✅ Removing a field that had a default` as the safe evolution path. Without a default on `consumed`, a future schema that drops this field cannot be registered as a backward-compatible v1.x change — it would require a major-version bump (`v1/` → `v2/`). The `doc` itself states "False = ticket was minted but never used", confirming `false` is the correct missing-data sentinel. **Fix:** add `"default": false` between `"type"` and `"doc"`: ```json { "name": "consumed", "type": "boolean", "default": false, "doc": "True iff ..." } ``` ### Verdict **CONDITIONAL_APPROVE** --- <sub>hib-pr-reviewer • round 3 • 1 finding (1m) • 2026-05-26T09:24:54.399Z → 2026-05-26T09:26:18.943Z • posted-as: pr-reviewer-bot</sub> </details>
fix(avro): R4 — consumed boolean default:false in ticket_expired.avsc
Some checks failed
protobuf CI / scripts/check-import-direction.sh (pull_request) Successful in 3s
protobuf CI / buf lint (pull_request) Failing after 5s
protobuf CI / buf build (pull_request) Failing after 4s
b2f2b369f7
Addresses R3 CONDITIONAL_APPROVE 1 MINOR: consumed boolean lacked field-level
default. Matches the is_admin pattern in ticket_minted.avsc and preserves the
README two-step removal evolution path.

Superseded by round 5.

Show previous round

hib-pr-reviewer review — PR #3 (affinity-intelligence-rework/im2be-protobuf)

Round 4 — head b2f2b369f7f2, base main, trigger synchronize

TL;DR: CONDITIONAL_APPROVE — all 9 prior-round findings confirmed resolved; kept 1 verified minor unique-to-B (README validation command omits two of three schema files).

Summary

Arbiter Reconciliation — Round 4

Prior findings (R1–R3, 9 total): Both reviewers independently confirm all resolved. Not re-raised.

New findings this round:

  • Reviewer A: 0 new findings.
  • Reviewer B: 1 new minor finding — README line 58 compile schema command names only ticket_minted.avsc, leaving ticket_expired.avsc and ticket_revoked.avsc outside the manual-validation path.

Verification (unique-to-B finding): Read /var/lib/pr-reviewer/repos/affinity-intelligence-rework/im2be-protobuf/768/avro/README.md line 58. Confirmed exact text:

java -jar avro-tools.jar compile schema avro/identity/ticket/event/v1/ticket_minted.avsc /tmp/avro-out

Only one of three .avsc files is named. ticket_expired.avsc and ticket_revoked.avsc are silently omitted. Finding kept.

Memora persistence: memory_create and memory_absorb rejected due to tag-allowlist constraints in the Memora instance; persistence attempted but unavailable this session.

Outcome: 1 verified minor finding kept; verdict CONDITIONAL_APPROVE.

Blast Radius

The diff adds three new Avro schema files and a README under avro/. No existing files are modified. The schema files are not yet consumed by any service in this repo — the outbox publisher lands in a separate PR (PR-OUTBOX-1). Blast radius is low and contained to the new avro/ subtree.

BLAST_SCORE: 2/10

CI status (head b2f2b369f7f2)

Overall: ✗ failure

3 checks: 3 pending

Check State Link
protobuf CI / scripts/check-import-direction.sh (pull_request) pending details
protobuf CI / buf lint (pull_request) pending details
protobuf CI / buf build (pull_request) pending details
  • PR-OUTBOX-1 (im2be-identity-service — outbox publisher, registers these schemas at startup)

Findings (1)

[MINOR] Manual-validation command covers only ticket_minted.avsc — two schemas silently skipped

avro/README.md:58

The compile schema invocation at line 58 is:

java -jar avro-tools.jar compile schema avro/identity/ticket/event/v1/ticket_minted.avsc /tmp/avro-out

A developer following this verbatim will never lint ticket_expired.avsc or ticket_revoked.avsc. avro-tools compile schema accepts a directory as its schema argument and will process all .avsc files within it; change the invocation to:

java -jar avro-tools.jar compile schema avro/identity/ticket/event/v1/ /tmp/avro-out

Alternatively list all three files explicitly. This gap is especially consequential while the CI follow-up (PR-OUTBOX-PREREQ-PROTO-FOLLOWUP-avro-ci) remains pending and the manual command is the only validation path.

Verdict

CONDITIONAL_APPROVE


hib-pr-reviewer • round 4 • 1 finding (1m) • 2026-05-26T09:28:52.005Z → 2026-05-26T09:30:07.399Z • posted-as: pr-reviewer-bot

<!-- hib-pr-reviewer collapsed --> > _Superseded by round 5._ <details> <summary>Show previous round</summary> ## hib-pr-reviewer review — PR #3 (affinity-intelligence-rework/im2be-protobuf) **Round 4** — head `b2f2b369f7f2`, base `main`, trigger `synchronize` **TL;DR:** CONDITIONAL_APPROVE — all 9 prior-round findings confirmed resolved; kept 1 verified minor unique-to-B (README validation command omits two of three schema files). ### Summary ## Arbiter Reconciliation — Round 4 **Prior findings (R1–R3, 9 total):** Both reviewers independently confirm all resolved. Not re-raised. **New findings this round:** - Reviewer A: 0 new findings. - Reviewer B: 1 new minor finding — README line 58 `compile schema` command names only `ticket_minted.avsc`, leaving `ticket_expired.avsc` and `ticket_revoked.avsc` outside the manual-validation path. **Verification (unique-to-B finding):** Read `/var/lib/pr-reviewer/repos/affinity-intelligence-rework/im2be-protobuf/768/avro/README.md` line 58. Confirmed exact text: ``` java -jar avro-tools.jar compile schema avro/identity/ticket/event/v1/ticket_minted.avsc /tmp/avro-out ``` Only one of three `.avsc` files is named. `ticket_expired.avsc` and `ticket_revoked.avsc` are silently omitted. Finding kept. **Memora persistence:** `memory_create` and `memory_absorb` rejected due to tag-allowlist constraints in the Memora instance; persistence attempted but unavailable this session. **Outcome:** 1 verified minor finding kept; verdict CONDITIONAL_APPROVE. ### Blast Radius The diff adds three new Avro schema files and a README under `avro/`. No existing files are modified. The schema files are not yet consumed by any service in this repo — the outbox publisher lands in a separate PR (`PR-OUTBOX-1`). Blast radius is low and contained to the new `avro/` subtree. **BLAST_SCORE: 2/10** ### CI status (head `b2f2b369f7f2`) **Overall: ✗ failure** 3 checks: 3 pending | Check | State | Link | |---|---|---| | protobuf CI / scripts/check-import-direction.sh (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-protobuf/actions/runs/11/jobs/0) | | protobuf CI / buf lint (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-protobuf/actions/runs/11/jobs/1) | | protobuf CI / buf build (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-protobuf/actions/runs/11/jobs/2) | ### Related PRs - PR-OUTBOX-1 (im2be-identity-service — outbox publisher, registers these schemas at startup) ### Findings (1) #### **[MINOR]** Manual-validation command covers only `ticket_minted.avsc` — two schemas silently skipped _avro/README.md:58_ The `compile schema` invocation at line 58 is: ```bash java -jar avro-tools.jar compile schema avro/identity/ticket/event/v1/ticket_minted.avsc /tmp/avro-out ``` A developer following this verbatim will never lint `ticket_expired.avsc` or `ticket_revoked.avsc`. `avro-tools compile schema` accepts a directory as its schema argument and will process all `.avsc` files within it; change the invocation to: ```bash java -jar avro-tools.jar compile schema avro/identity/ticket/event/v1/ /tmp/avro-out ``` Alternatively list all three files explicitly. This gap is especially consequential while the CI follow-up (`PR-OUTBOX-PREREQ-PROTO-FOLLOWUP-avro-ci`) remains pending and the manual command is the only validation path. ### Verdict **CONDITIONAL_APPROVE** --- <sub>hib-pr-reviewer • round 4 • 1 finding (1m) • 2026-05-26T09:28:52.005Z → 2026-05-26T09:30:07.399Z • posted-as: pr-reviewer-bot</sub> </details>
fix(avro): R5 — README validate-all loop instead of single-file example
Some checks failed
protobuf CI / scripts/check-import-direction.sh (pull_request) Successful in 2s
protobuf CI / buf lint (pull_request) Failing after 5s
protobuf CI / buf build (pull_request) Failing after 4s
eec0073d6b
Addresses R4 CONDITIONAL_APPROVE 1 MINOR: README local-validation example only
covered ticket_minted.avsc; two sister files silently skipped. Replaced with
find + xargs loop that validates every .avsc under avro/. Single-file
invocation still valid for ad-hoc checks (noted).

hib-pr-reviewer review — PR #3 (affinity-intelligence-rework/im2be-protobuf)

Round 5 — head eec0073d6b81, base main, trigger synchronize

TL;DR: NO_NEW_FINDINGS — No new findings this round.

Summary

[quorum-converged] A=0 = B=0. Round 5 — All 10 prior findings resolved; no new actionable findings.

Full file reads confirm every open concern from rounds 1–4 has been addressed:

Round Finding Status
R1 README wrong idl2schemata subcommand Fixed — explicit DO NOT use idl2schemata comment + correct compile schema usage
R1 SubscriptionTier no type-level default Fixed — "default": "UNSPECIFIED" on the enum type at line 45
R1 schema_version doc contradicted README Fixed — doc now says BACKWARD-COMPATIBLE additions, .vN for breaking changes
R1 subscription_tier no field-level default Fixed — field-level "default": "UNSPECIFIED" at line 48
R1 No CI Avro lint Acknowledged — explicit TODO tracked as PR-OUTBOX-PREREQ-PROTO-FOLLOWUP-avro-ci in im2be-mono
R2 revoke_reason no field-level default Fixed — "default": "UNSPECIFIED" at ticket_revoked.avsc line 49
R2 ticket_expired.avsc schema_version missing doc Fixed — full doc present at line 21
R2 ticket_revoked.avsc schema_version missing doc Fixed — full doc present at line 21
R3 consumed boolean missing default: false Fixed — "default": false at ticket_expired.avsc line 41
R4 Manual-validation command covered only one schema Fixed — find avro -name '*.avsc' -print0 | xargs -0 -n1 loop at README lines 63–64

All three .avsc files are internally consistent: every evolvable field carries a default, both type-level and field-level defaults are present on inline enum types (SubscriptionTier, TicketRevokeReason), schema_version docs are uniform across all three schemas, and the README lint command now covers every .avsc file via glob. Schemas are ready for registration.

CI status (head eec0073d6b81)

Overall: ✗ failure

3 checks: 3 pending

Check State Link
protobuf CI / scripts/check-import-direction.sh (pull_request) pending details
protobuf CI / buf lint (pull_request) pending details
protobuf CI / buf build (pull_request) pending details

Findings

No new findings this round.

Quorum converged on empty findings (A + B both returned 0).

Verdict

NO_NEW_FINDINGS


hib-pr-reviewer • round 5 • 0 findings • 2026-05-26T09:30:41.583Z → 2026-05-26T09:33:10.659Z • posted-as: pr-reviewer-bot • [bookkeeping fallback]

## hib-pr-reviewer review — PR #3 (affinity-intelligence-rework/im2be-protobuf) **Round 5** — head `eec0073d6b81`, base `main`, trigger `synchronize` **TL;DR:** NO_NEW_FINDINGS — No new findings this round. ### Summary [quorum-converged] A=0 = B=0. **Round 5 — All 10 prior findings resolved; no new actionable findings.** Full file reads confirm every open concern from rounds 1–4 has been addressed: | Round | Finding | Status | |-------|---------|--------| | R1 | README wrong `idl2schemata` subcommand | ✅ Fixed — explicit `DO NOT use idl2schemata` comment + correct `compile schema` usage | | R1 | `SubscriptionTier` no type-level `default` | ✅ Fixed — `"default": "UNSPECIFIED"` on the enum type at line 45 | | R1 | `schema_version` doc contradicted README | ✅ Fixed — doc now says BACKWARD-COMPATIBLE additions, .vN for breaking changes | | R1 | `subscription_tier` no field-level `default` | ✅ Fixed — field-level `"default": "UNSPECIFIED"` at line 48 | | R1 | No CI Avro lint | ✅ Acknowledged — explicit TODO tracked as `PR-OUTBOX-PREREQ-PROTO-FOLLOWUP-avro-ci` in `im2be-mono` | | R2 | `revoke_reason` no field-level `default` | ✅ Fixed — `"default": "UNSPECIFIED"` at `ticket_revoked.avsc` line 49 | | R2 | `ticket_expired.avsc schema_version` missing `doc` | ✅ Fixed — full doc present at line 21 | | R2 | `ticket_revoked.avsc schema_version` missing `doc` | ✅ Fixed — full doc present at line 21 | | R3 | `consumed` boolean missing `default: false` | ✅ Fixed — `"default": false` at `ticket_expired.avsc` line 41 | | R4 | Manual-validation command covered only one schema | ✅ Fixed — `find avro -name '*.avsc' -print0 \| xargs -0 -n1` loop at README lines 63–64 | All three `.avsc` files are internally consistent: every evolvable field carries a `default`, both type-level and field-level defaults are present on inline enum types (`SubscriptionTier`, `TicketRevokeReason`), `schema_version` docs are uniform across all three schemas, and the README lint command now covers every `.avsc` file via glob. Schemas are ready for registration. ### CI status (head `eec0073d6b81`) **Overall: ✗ failure** 3 checks: 3 pending | Check | State | Link | |---|---|---| | protobuf CI / scripts/check-import-direction.sh (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-protobuf/actions/runs/12/jobs/0) | | protobuf CI / buf lint (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-protobuf/actions/runs/12/jobs/1) | | protobuf CI / buf build (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-protobuf/actions/runs/12/jobs/2) | ### Findings **No new findings this round.** _Quorum converged on empty findings (A + B both returned 0)._ ### Verdict **NO_NEW_FINDINGS** --- <sub>hib-pr-reviewer • round 5 • 0 findings • 2026-05-26T09:30:41.583Z → 2026-05-26T09:33:10.659Z • posted-as: pr-reviewer-bot • [bookkeeping fallback]</sub>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
affinity-intelligence-rework/im2be-protobuf!3
No description provided.