Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions scripts/pm/mailbox-open.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@
* canonically with its number (`triage#1`). Its SLUG is that key as one ref
* segment, because a ref cannot carry `:` or `#`: the default `domain:` kind
* dropped, every other `:` and the `#` turned into `-` (`ui-4`,
* `repo-cloud-1`, `triage-1`). No `domain:` lane is named `triage`,
* `director` or `repo-…`, which is what keeps the slug one-to-one.
* `repo-cloud-1`, `triage-1`). No `domain:` lane may be named `triage`,
* `director` or `repo-…` — `seatKey` refuses one — which keeps the slug one-to-one.
*
* ## What it does, in order (open)
*
Expand Down Expand Up @@ -159,20 +159,34 @@ const LOGIN_SHAPE = /^[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}$/;
const BRANCH_SHAPE = /^(?!\/|.*\/\/|.*\.\.|.*[\s~^:?*[\\]|.*\.lock$|.*\/$)[A-Za-z0-9._/-]+$/;
const SESSION_SHAPE = /^(?:session|cse)_[A-Za-z0-9]+$/;
// A seat: its lane — `domain:<x>`, `repo:<x>`, `triage`, `director`, the four a seat post's title names — then `#N`;
// absent = seat 1. Read lower-cased. The bare lanes are a closed pair, so a login is never read as a seat.
const SEAT_SHAPE = /^((?:domain|repo):[a-z0-9._-]+|triage|director)(?:#([1-9][0-9]*))?$/;
// absent = seat 1. Read lower-cased. The bare lanes are a closed pair, so a login is never read as a seat. The
// two lists below are the ONE spelling of those lane words: the shape is built from them, and so is the
// reserved set a `domain:` lane may not be named after (`seatKey`).
/** The lane kinds that carry a name after the colon: `domain:<x>`, `repo:<x>`. `domain` is the default kind the slug drops. */
export const SEAT_LANE_KINDS = Object.freeze(['domain', 'repo']);
/** The bare lanes, a closed pair: `triage`, `director`. */
export const SEAT_BARE_LANES = Object.freeze(['triage', 'director']);
const SEAT_SHAPE = new RegExp(`^((?:${SEAT_LANE_KINDS.join('|')}):[a-z0-9._-]+|${SEAT_BARE_LANES.join('|')})(?:#([1-9][0-9]*))?$`);
// What a `domain:` lane may NOT be named, so the slug stays one-to-one (header): a bare lane word (`domain:triage`
// would slug to `triage-1`, the triage seat's), or another kind's name with its colon turned into the hyphen the slug
// uses (`domain:repo-cloud` would slug to `repo-cloud-1`, `repo:cloud#1`'s).
const RESERVED_DOMAIN_LANE = new RegExp(`^(?:(?:${SEAT_BARE_LANES.join('|')})$|(?:${SEAT_LANE_KINDS.filter((k) => k !== 'domain').join('|')})-)`);
/** A per-seat mailbox body's first line, `Mailbox-of: <seat> · <login>`. */
const MAILBOX_OF_LINE = /^Mailbox-of:[ \t]*(\S+)[ \t]*·[ \t]*(\S+)$/;
/** The first lines a legacy per-login mailbox opened with: `To: <login>` (this file's), `Mailbox-of: <login>`. */
const LEGACY_OWNER_LINE = /^(?:To|Mailbox-of):[ \t]*(\S+)$/;

/**
* A seat spelled `LANE#N` → `{ key, slug }`, or null when it is not one. `key` is canonical — lower case, the
* number always spelled (`triage` → `triage#1`); `slug` is the key as one ref segment (header). Pure.
* number always spelled (`triage` → `triage#1`); `slug` is the key as one ref segment (header). A `domain:` lane
* named after a reserved lane word (`domain:triage`, `domain:repo-cloud`) is not a seat either: its slug would be
* another seat's, and the header's one-to-one promise is kept here, not assumed. This is the fleet's ONE seat
* grammar — the mailbox `To:` line and the doorbell keys' `LANE#N` fields read with it, never a second shape. Pure.
*/
export function seatKey(text) {
const m = SEAT_SHAPE.exec(String(text ?? '').trim().toLowerCase());
if (!m) return null;
if (m[1].startsWith('domain:') && RESERVED_DOMAIN_LANE.test(m[1].slice('domain:'.length))) return null;
const n = m[2] ?? '1';
return { key: `${m[1]}#${n}`, slug: `${m[1].replace(/^domain:/, '').replace(/:/g, '-')}-${n}` };
}
Expand Down Expand Up @@ -519,6 +533,10 @@ async function selfTest() {
t('another kind keeps its name, its colon turned into a hyphen', [seatKey('repo:cloud#1').slug, seatKey('repo:www.objectos.ai#2').slug], ['repo-cloud-1', 'repo-www.objectos.ai-2']);
t('read lower-cased, so one seat has one key', [seatKey('Domain:UI#4').key, seatKey(' domain:ui#4 ').key], ['domain:ui#4', 'domain:ui#4']);
t('not a seat ⇒ null — ⛔ a login above all, the confusion a per-login mailbox made', ['', 'ui 4', 'domain:ui#0', '#4', 'domain:', null, 'os-bill', 'marchtian', 'epic:#12'].map(seatKey), [null, null, null, null, null, null, null, null, null]);
t('⛔ a short form is not a seat — `skills#2` names no lane, so it is refused as a login is', ['skills#2', 'ui#4', 'server', 'skills'].map(seatKey), [null, null, null, null]);
t('⛔ a `domain:` lane named after a reserved lane word is refused — its slug would be another seat\'s', ['domain:triage', 'domain:director#2', 'domain:repo-cloud#1', 'Domain:Triage#1'].map(seatKey), [null, null, null, null]);
t('…while a `domain:` lane that merely CONTAINS a reserved word, and the reserved words as their own lanes, still read', [seatKey('domain:triage-tools#1').slug, seatKey('domain:directory').slug, seatKey('domain:repository').slug, seatKey('triage#2').slug, seatKey('repo:cloud').slug], ['triage-tools-1', 'directory-1', 'repository-1', 'triage-2', 'repo-cloud-1']);
t('the shape and the reserved set are built from the ONE pair of lane-word lists', SEAT_LANE_KINDS.join() === 'domain,repo' && SEAT_BARE_LANES.join() === 'triage,director' && SEAT_BARE_LANES.every((w) => seatKey(w)?.key === `${w}#1` && seatKey(`domain:${w}`) === null), true);
t('the branch is mailbox/<login>/<slug> — no `:` and no `#` in a ref', mailboxBranch('os-bill', seatKey('domain:ui#4')), 'mailbox/os-bill/ui-4');
t('⭐ two seats under ONE login get two branches', [mailboxBranch('os-sales', seatKey('domain:server#1')), mailboxBranch('os-sales', seatKey('domain:ui#1'))], ['mailbox/os-sales/server-1', 'mailbox/os-sales/ui-1']);

Expand Down
24 changes: 17 additions & 7 deletions scripts/pm/record-recognisers.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1266,18 +1266,23 @@ const RECORD_REF = /^record `?#?([1-9][0-9]*)`?$/;
const BRIEF_REF = /^brief `?#?([1-9][0-9]*)`?$/;
const LANE_REF = /^suggested lane ([A-Za-z][A-Za-z0-9:_-]*)$/;
const VERDICT_REF = /^contract-review (PASS|FAIL)$/;
/** `LANE#N` — `domain:skills#2`, `skills#1`, or a seat that carries no number (`triage`, `director`). */
const SEAT_REF = /^([A-Za-z][A-Za-z0-9:_-]*)(?:#([1-9][0-9]*))?$/;
/** GitHub's own login rule, as `mailbox-open.mjs` spells it for `--login`: alphanumerics and single inner hyphens, ≤ 39. */
const LOGIN_REF = /^[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}$/;
/** The attribution block the platform APPENDS to a comment (AGENTS.md): a rule line and the bare or session-URL footer. Not a second line. */
const FOOTER_RULE = /^-{3,}$/;
const FOOTER_LINE = /^_Generated by \[Claude Code\]\(https:\/\/claude\.ai\/code(?:\/session_[A-Za-z0-9]+)?\)_$/;

/** `LANE#N` → `{ lane, n }`, or null. */
/**
* A key's `LANE#N` field → `{ key, lane, n }`, or null. Read with `seatKey`, the ONE seat grammar (#27891): the
* lane is `domain:<x>`, `repo:<x>`, `triage` or `director`, so the short form `skills#2` is refused here exactly
* as on the `To:` line, an absent number is seat 1 (`triage` → `triage#1`), and a `domain:` lane named after a
* reserved lane word is no seat. `key` is the canonical spelling; `lane` and `n` are its two halves.
*/
function readSeat(part) {
const m = SEAT_REF.exec(String(part ?? '').trim());
return m ? { lane: m[1], n: m[2] === undefined ? null : Number(m[2]) } : null;
const s = seatKey(part);
if (!s) return null;
const at = s.key.lastIndexOf('#');
return { key: s.key, lane: s.key.slice(0, at), n: Number(s.key.slice(at + 1)) };
}

/** `NAME: #a #b …` → the numbers; `NAME: none` → `[]`; anything else (a comma, a word, an empty list) → null. */
Expand Down Expand Up @@ -2572,7 +2577,9 @@ export async function selfTest() {
// key, a second line and an over-long line — the half-state faces — and pin
// each key's spelling to the protocol text, so the reader and the text cannot
// drift apart. Every refusal of a KEY is asked under a `To:` line, so it
// cannot pass vacuously on the missing envelope.
// cannot pass vacuously on the missing envelope. A key's `LANE#N` field is
// read with `seatKey` (#27891): the short form and a reserved `domain:` name
// are refused there exactly as on `To:`.
battery('⭐ the mailbox doorbell: `To: <seat>`, then one of the seven keys read — seat, kind, numbers, sha, login, time — and anything else refused');
const DB_ROW = (body, id = 5101, at = '2026-10-28T07:00:00Z') => ({ id, created_at: at, user: { login: 'os-bill' }, body });
// A message: the addressee line, then the key line.
Expand Down Expand Up @@ -2603,7 +2610,10 @@ export async function selfTest() {
t('⛔ a known key that does not spell its template is unreadable: no head, a 6-hex head, a login GitHub refuses, no mailbox, no lane, a record that is not a number', [`Request: contract-review · PR #26766 · ${HO_HEAD} · x`, 'Request: contract-review · PR #26766 · head 1b03a9 · x', 'Notify: seat-online · domain:skills#2 · -bad-- · mailbox #27120 · takes: none', 'Notify: seat-online · domain:skills#2 · os-bill · takes: none', 'Request: main-red · Test Core · anchor #27090', 'Notify: contract-review PASS · PR #26766 · record six'].every((l) => { const r = readMailboxLine(MSG(l)); return r.state === 'unreadable' && /does not spell/.test(r.reason); }));
t('lists: `none` is the empty list, `#a #b` the numbers; a comma, an empty list or a `PR #n` item is unreadable', (() => { const ok = readMailboxLine(MSG('Notify: split · domain:skills#2 · takes: none · leaves: #1 #2')); return ok.state === 'read' && ok.takes.length === 0 && ok.leaves.join() === '1,2' && ['Notify: split · domain:skills#2 · takes: #1, #2 · leaves: none', 'Notify: split · domain:skills#2 · takes: · leaves: none', 'Notify: split · domain:skills#2 · takes: PR #1 · leaves: none'].every((l) => readMailboxLine(MSG(l)).state === 'unreadable'); })());
t('the spaces around the ` · ` separator are optional', readMailboxLine(MSG('Request: urgent-triage·#27047·main red')).state === 'read' && readMailboxLine(MSG('Request: urgent-triage ·#27047· main red')).card === 27047);
t('a seat with no number reads (`triage`), and the `LANE#N` placeholder itself does not', readMailboxLine(MSG('Notify: split · triage · takes: none · leaves: none')).seat.lane === 'triage' && readMailboxLine(MSG('Notify: split · triage · takes: none · leaves: none')).seat.n === null && readMailboxLine(MSG('Notify: split · LANE#N · takes: none · leaves: none')).state === 'unreadable');
t('a seat with no number reads as seat 1 (`triage` → `triage#1`, as `seatKey` spells it), and the `LANE#N` placeholder itself does not', (() => { const r = readMailboxLine(MSG('Notify: split · triage · takes: none · leaves: none')); return r.seat.lane === 'triage' && r.seat.n === 1 && r.seat.key === 'triage#1' && readMailboxLine(MSG('Notify: split · LANE#N · takes: none · leaves: none')).state === 'unreadable'; })());
t('⭐ a key\'s `LANE#N` field is read with `seatKey` — ONE seat grammar (#27891): a well-formed `domain:X#N` reads as before, carries its canonical `key`, and is read lower-cased', (() => { const r = readMailboxLine(DB.on); const u = readMailboxLine(MSG('Notify: seat-online · Domain:Skills#2 · os-bill · mailbox #27120 · takes: none')); return r.state === 'read' && r.seat.key === 'domain:skills#2' && r.seat.lane === 'domain:skills' && r.seat.n === 2 && u.state === 'read' && u.seat.key === 'domain:skills#2'; })());
t('⛔ the short form `skills#2` is refused in a key field, the same as on `To:` — the key does not spell its template', ['Notify: seat-online · skills#2 · os-bill · mailbox #27120 · takes: none', 'Notify: seat-offline · skills#2 · in-flight: none · stay-behind: none · brief 6450022222', 'Notify: split · skills#2 · takes: none · leaves: none', 'Notify: split · server · takes: none · leaves: none'].every((l) => { const r = readMailboxLine(MSG(l)); return r.state === 'unreadable' && /does not spell/.test(r.reason); }) && readMailboxLine('To: skills#2\nRequest: urgent-triage · #27047 · x').state === 'refused');
t('⛔ a `domain:` lane named after a reserved lane word is refused in a key field and on `To:` alike — its slug would be another seat\'s', ['domain:triage', 'domain:director#2', 'domain:repo-cloud#1'].every((seat) => readMailboxLine(MSG(`Notify: split · ${seat} · takes: none · leaves: none`)).state === 'unreadable' && readMailboxLine(`To: ${seat}\nRequest: urgent-triage · #27047 · x`).state === 'refused') && readMailboxLine(MSG('Notify: split · domain:triage-tools#1 · takes: none · leaves: none')).seat.key === 'domain:triage-tools#1');
t('⭐ `To: <seat>` reads as the addressed seat\'s canonical key (`seatKey`\'s), beside the key\'s own fields — `to` never shadows a key\'s own `seat`', (() => { const r = readMailboxLine(DB.sp); return r.to === 'domain:skills#1' && r.seat.lane === 'domain:skills' && r.seat.n === 2 && readMailboxLine(DB.ut).to === 'triage#1' && readMailboxLine(MSG('Notify: split · triage · takes: none · leaves: none', 'Domain:UI#4')).to === 'domain:ui#4'; })());
t('⛔ a key on the FIRST line — the shape a mailbox took before the seat key — is REFUSED, naming the missing `To:`', ['Notify: split · domain:skills#2 · takes: none · leaves: none', `Request: contract-review · PR #26766 · head ${HO_HEAD} · x`].every((l) => { const r = readMailboxLine(l); return r.state === 'refused' && /`To: <seat>`/.test(r.reason) && typeof r.line === 'string'; }));
t('⛔ a `To:` naming a login, or no seat at all, is REFUSED — a login is not a seat', ['os-bill', 'marchtian', '', 'domain:ui#0', 'ui 4'].every((to) => { const r = readMailboxLine(`To: ${to}\nRequest: urgent-triage · #27047 · x`); return r.state === 'refused' && /names no seat/.test(r.reason); }));
Expand Down
Loading