Harden repo identity speech names
This commit is contained in:
parent
07066ca8ea
commit
4e091a6f45
3 changed files with 84 additions and 4 deletions
|
|
@ -1300,7 +1300,11 @@ async function postRepoIdentityIntent(job: JobRecord, intent: RepoIdentityPostIn
|
|||
if (isNonPublicRepoIdentitySpeech(rawContent)) {
|
||||
throw new Error(`Repo identity ${identity.id} SAY content is not public speech.`);
|
||||
}
|
||||
const content = normalizePublicRepoIdentitySpeech(rawContent);
|
||||
const content = normalizePublicRepoIdentitySpeech(rawContent, {
|
||||
identityId: identity.id,
|
||||
displayName: identity.displayName,
|
||||
repoName: identity.repoName,
|
||||
});
|
||||
if (isNonPublicRepoIdentitySpeech(content)) {
|
||||
throw new Error(`Repo identity ${identity.id} SAY content is not public speech.`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,12 @@ export interface RepoIdentityPostIntent {
|
|||
replyToMessageId?: string;
|
||||
}
|
||||
|
||||
export interface PublicRepoIdentitySpeechNormalizationOptions {
|
||||
identityId?: string;
|
||||
displayName?: string;
|
||||
repoName?: string;
|
||||
}
|
||||
|
||||
type RepoFaceActionBlock = {
|
||||
kind: "say" | "state_note" | "article" | "reddit_thread" | "bifrost_topic" | "update_request";
|
||||
fields: Record<string, string>;
|
||||
|
|
@ -91,11 +97,17 @@ export function isNonPublicRepoIdentitySpeech(value: string): boolean {
|
|||
].includes(normalized);
|
||||
}
|
||||
|
||||
export function normalizePublicRepoIdentitySpeech(value: string): string {
|
||||
return value
|
||||
export function normalizePublicRepoIdentitySpeech(
|
||||
value: string,
|
||||
options: PublicRepoIdentitySpeechNormalizationOptions = {},
|
||||
): string {
|
||||
return stripLeadingSelfLabel(
|
||||
value
|
||||
.replace(/```[A-Za-z0-9_-]*\s*\r?\n([\s\S]*?)\r?\n```/g, (_match, body: string) => body.trim())
|
||||
.replace(/`([^`\r\n]+)`/g, (_match, body: string) => `"${body.trim()}"`)
|
||||
.trim();
|
||||
.trim(),
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
function stripSingleMarkdownFence(value: string): string {
|
||||
|
|
@ -204,3 +216,55 @@ function optionalDslString(value: string | undefined): string | undefined {
|
|||
function requiredDslString(value: string | undefined): string | undefined {
|
||||
return optionalDslString(value);
|
||||
}
|
||||
|
||||
function stripLeadingSelfLabel(
|
||||
value: string,
|
||||
options: PublicRepoIdentitySpeechNormalizationOptions,
|
||||
): string {
|
||||
const aliases = [
|
||||
options.displayName,
|
||||
options.identityId,
|
||||
options.repoName,
|
||||
]
|
||||
.filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0)
|
||||
.map((entry) => normalizeSpeakerKey(entry));
|
||||
|
||||
if (aliases.length === 0) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const match = value.match(/^([^\r\n::-]{1,80})(\s*[::]\s*|\s+-\s+)([\s\S]*)$/);
|
||||
if (!match) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const candidate = normalizeSpeakerKey(
|
||||
match[1].replace(/^["'“”‘’`]+|["'“”‘’`]+$/g, "").trim(),
|
||||
);
|
||||
if (!candidate || !aliases.includes(candidate)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return match[3].trimStart();
|
||||
}
|
||||
|
||||
function normalizeSpeakerKey(value: string): string {
|
||||
return foldKana(
|
||||
value
|
||||
.normalize("NFKC")
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, "")
|
||||
.replace(/["'“”‘’`・.]+/g, ""),
|
||||
);
|
||||
}
|
||||
|
||||
function foldKana(value: string): string {
|
||||
let output = "";
|
||||
for (const char of value) {
|
||||
const code = char.charCodeAt(0);
|
||||
output += code >= 0x30a1 && code <= 0x30f6
|
||||
? String.fromCharCode(code - 0x60)
|
||||
: char;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,18 @@ const cases: Case[] = [
|
|||
"show \"rear pair drifting 38 ms\", \"geometry confidence low\", \"world model provisional\".",
|
||||
detail: "Repo Faces can mention technical labels without speaking in code spans.",
|
||||
},
|
||||
{
|
||||
name: "leading self-label is stripped with kana-folding",
|
||||
run: () => normalizePublicRepoIdentitySpeech(
|
||||
"メタめ: We need one real owner for the public name.",
|
||||
{
|
||||
identityId: "metame",
|
||||
displayName: "メタメ",
|
||||
repoName: "メタメ",
|
||||
},
|
||||
) === "We need one real owner for the public name.",
|
||||
detail: "The public speech boundary owns the speaker label, so mixed-script self-renames in SAY content must be stripped before posting.",
|
||||
},
|
||||
];
|
||||
|
||||
const failures = cases.filter((entry) => !entry.run());
|
||||
|
|
|
|||
Loading…
Reference in a new issue