From 2e2cf4bf42f979abe559b5a5caf0c488828b20d7 Mon Sep 17 00:00:00 2001 From: Don Petry Date: Fri, 17 Jul 2026 07:55:17 -0500 Subject: [PATCH 1/2] refactor(personas): drop address.aliases from the contract (half 2 of 2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second half of the ordered cross-repo pair. petry-projects/.github-private#1289 removed every USE of the field first; this removes the field itself. MERGE ORDER: #1289 MUST land before this. The schema is additionalProperties:false, so dropping `aliases` while qa-lead's manifest still carries `aliases: []` fails validate-personas on main FLEET-WIDE. Verified the rejection is real: a manifest with `aliases` now yields "Additional properties are not allowed ('aliases' was unexpected)". Why aliases are gone (see .github#755 finding 1) They existed so a RENAMED role kept routing — qa-lead listing `petry-projects/murat` so old comments still reached it. Two reasons it goes: 1. Structural: index-free routing could never honour it. The router resolves '@petry-projects/murat' to personas/murat/persona.yml — a 404 after the rename. The alias is declared INSIDE the renamed persona's own manifest, which the router never opens, because it does not know to look there. Chicken-and-egg; the pm_persona_aliases attempt in #754 was unreachable for exactly this reason. 2. Measured: there is nothing live left to route. GitHub stops rendering a renamed team's old handle as a mention at all. Probed it directly — created a team, rendered its mention, renamed it, re-rendered the OLD handle: before rename: @petry-projects/probe after rename:

@petry-projects/probe please review

The old handle becomes PLAIN TEXT. So it is not a silently-broken mention that looks live — the reader sees grey text and knows instantly it did not resolve, and the fix is local: mention the new handle. Aliases would only route text GitHub itself no longer treats as a mention. Not worth a derived index, an extra fetch on every 404, and a contract the router cannot honour. Rule 4 becomes a theorem, not a check "Handles and aliases MUST be unique across all personas" is replaced by "Handles are unique BY CONSTRUCTION": the pattern pins the org, slug == id, id == the persona directory name, and directory names are unique — so two personas cannot claim the same handle. #1289 removed the corresponding check_handles_unique for the same reason: it could never fire. Rule 5 ("a rename keeps the old handle as an alias") is replaced by a §4.1 "Renames" section documenting what to change on a rename and stating plainly that old mentions stop resolving VISIBLY — which is the correct signal, not a regression. Changes - standards/personas/persona.schema.json — drop address.aliases (edited as TEXT, not re-serialized: a json round-trip reformats every inline array and rewrote 264 untouched lines. Diff is +2/-10.) - standards/persona-standards.md — §4.1 rules 4-5, the example, and the new Renames section - standards/personas/TEMPLATE/persona.yml — drop the aliases scaffold line Verification - persona.schema.json is a valid Draft 2020-12 schema; diff is +2/-10. - Conditional behaviour verified: qa-lead's post-#1289 shape (handle only) PASSES; a manifest carrying `aliases` FAILS — which is exactly why #1289 goes first. - TEMPLATE/persona.yml validates against the updated schema. - markdownlint-cli2: clean. - Zero `aliases` references remain outside the deliberate explanatory notes. Co-Authored-By: Claude Opus 4.8 (1M context) --- standards/persona-standards.md | 38 +++++++++++++++++++++---- standards/personas/TEMPLATE/persona.yml | 1 - standards/personas/persona.schema.json | 12 ++------ 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/standards/persona-standards.md b/standards/persona-standards.md index 86498b73d..067d5b275 100644 --- a/standards/persona-standards.md +++ b/standards/persona-standards.md @@ -195,7 +195,6 @@ A persona that enables the `mention` surface MUST declare an `address` block ```yaml address: handle: petry-projects/qa-lead # an org TEAM — mentioned as @petry-projects/qa-lead - aliases: [] # additional handles that route here (renames) ``` **The handle MUST be an org team, never a user account.** This is not a style @@ -226,10 +225,39 @@ two things a bare convention cannot: 3. **The team MUST set `notification_setting: notifications_disabled`.** The handle exists to route a webhook, not to page humans. Membership should be empty or bot-only. -4. **Handles and aliases MUST be unique across all personas.** Cross-file, so - JSON Schema cannot see it — enforced by `validate-personas.py`. -5. **A rename keeps the old handle as an `alias`.** Roles outlive their spelling; - in-flight threads must not break. +4. **Handles are unique across all personas — by construction, not by check.** + The org is pinned by the pattern, the slug equals `id`, `id` equals the + persona's directory name, and directory names are unique. Two personas + therefore *cannot* claim the same handle, so there is deliberately no + cross-file uniqueness check: it could never fire. + +### Renames — and why there is no `aliases` + +An earlier draft of this standard let a persona declare `address.aliases[]` so a +**renamed** role kept routing (`qa-lead` listing `petry-projects/murat`). That +field is gone. Two reasons, one structural and one measured: + +- **Index-free routing could never have honoured it.** The router resolves + `@petry-projects/murat` to `personas/murat/persona.yml` — a 404 after the + rename. The alias is declared *inside the renamed persona's own manifest*, + which the router never opens, because it does not know to look there. +- **There is nothing live left to route.** GitHub stops rendering a renamed + team's old handle as a mention at all — it becomes plain text. Measured: + + ```text + before rename: @petry-projects/probe + after rename:

@petry-projects/probe please review

+ ``` + + So the old handle is not a silently-broken mention that looks live — the + reader sees grey text and knows instantly it did not resolve. The fix is + obvious and local: mention the new handle. + +**To rename a role:** rename the persona directory, `id`, `name`, `canary.agent`, +`opt_out_label`, `evals.path`, and the org team (its slug must keep matching +`id`). In-flight mentions of the old handle stop resolving, visibly — which is +the correct signal, not a regression. See +[`.github#755`](https://github.com/petry-projects/.github/issues/755) finding 1. The **live** properties (rules 2 and 3 — does the team exist, is it closed, are notifications off?) need the network, and `validate-personas.py` is hermetic diff --git a/standards/personas/TEMPLATE/persona.yml b/standards/personas/TEMPLATE/persona.yml index 03adfaeb0..e23280e67 100644 --- a/standards/personas/TEMPLATE/persona.yml +++ b/standards/personas/TEMPLATE/persona.yml @@ -81,7 +81,6 @@ definition: # live properties — the schema cannot. address: handle: petry-projects/change-me # slug MUST equal `id` above - aliases: [] # old handles kept routing after a rename # --- Capabilities the persona can invoke (advisory index) ----------------- skills: [] diff --git a/standards/personas/persona.schema.json b/standards/personas/persona.schema.json index 8628caaa1..e5aadedcb 100644 --- a/standards/personas/persona.schema.json +++ b/standards/personas/persona.schema.json @@ -128,21 +128,13 @@ "address": { "type": "object", "additionalProperties": false, - "description": "How a human addresses this persona from a comment (`@`). REQUIRED when the 'mention' surface is enabled. See persona-standards.md §4.1.", + "description": "How a human addresses this persona from a comment (`@`). REQUIRED when the 'mention' surface is enabled. See persona-standards.md §4.1. There is deliberately NO `aliases` field — see §4.1 'Renames'.", "required": ["handle"], "properties": { "handle": { "type": "string", "pattern": "^petry-projects/[a-z0-9]+(-[a-z0-9]+)*$", - "description": "The addressing handle in 'org/team-slug' form, mentioned as '@org/team-slug'. MUST be an org TEAM, never a user account: GitHub's user namespace is global and first-come, so a bare '@qa-lead' resolves to an unrelated real account and notifies a stranger on every mention (verified: qa-lead, dev-lead, scrum-master are all live accounts). An org team slug cannot collide. The slug MUST equal `id`, so routing is a prefix-strip. Team must be privacy 'closed' (secret teams are unmentionable) with notification_setting 'notifications_disabled'; those are live properties CI verifies, not shape this schema can check." - }, - "aliases": { - "type": "array", - "items": { - "type": "string", - "pattern": "^petry-projects/[a-z0-9]+(-[a-z0-9]+)*$" - }, - "description": "Additional handles that route here — the migration path when a role is renamed. Same org/team-slug rules as `handle`. Every alias must be unique across ALL personas' handles and aliases (cross-file, enforced by validate-personas.py)." + "description": "The addressing handle in 'org/team-slug' form, mentioned as '@org/team-slug'. MUST be an org TEAM, never a user account: GitHub's user namespace is global and first-come, so a bare '@qa-lead' resolves to an unrelated real account and notifies a stranger on every mention (verified: qa-lead, dev-lead, scrum-master are all live accounts). An org team slug cannot collide. The slug MUST equal `id`, so routing is a prefix-strip — which also makes handle uniqueness a theorem rather than a check (org is pinned, slug == id, id == the persona directory name, directory names are unique). Team must be privacy 'closed' (secret teams are unmentionable) with notification_setting 'notifications_disabled'; those are live properties CI verifies, not shape this schema can check." } } }, From 0f42c43aaf6f5b967b17caea86b0ed7b6fcd7cd0 Mon Sep 17 00:00:00 2001 From: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com> Date: Fri, 17 Jul 2026 13:11:35 +0000 Subject: [PATCH 2/2] fix(reviews): address review comments [skip ci-relay] --- standards/persona-standards.md | 2 +- standards/personas/persona.schema.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/standards/persona-standards.md b/standards/persona-standards.md index 067d5b275..45ff5e246 100644 --- a/standards/persona-standards.md +++ b/standards/persona-standards.md @@ -231,7 +231,7 @@ two things a bare convention cannot: therefore *cannot* claim the same handle, so there is deliberately no cross-file uniqueness check: it could never fire. -### Renames — and why there is no `aliases` +### 4.1.1 Renames — and why there is no `aliases` An earlier draft of this standard let a persona declare `address.aliases[]` so a **renamed** role kept routing (`qa-lead` listing `petry-projects/murat`). That diff --git a/standards/personas/persona.schema.json b/standards/personas/persona.schema.json index e5aadedcb..7a946bb00 100644 --- a/standards/personas/persona.schema.json +++ b/standards/personas/persona.schema.json @@ -128,7 +128,7 @@ "address": { "type": "object", "additionalProperties": false, - "description": "How a human addresses this persona from a comment (`@`). REQUIRED when the 'mention' surface is enabled. See persona-standards.md §4.1. There is deliberately NO `aliases` field — see §4.1 'Renames'.", + "description": "How a human addresses this persona from a comment (`@`). REQUIRED when the 'mention' surface is enabled. See persona-standards.md §4.1. There is deliberately NO `aliases` field — see §4.1.1 'Renames'.", "required": ["handle"], "properties": { "handle": {