Skip to content

Enforce lowercase snake_case for all machine identifiers#245

Merged
hotlong merged 4 commits intomainfrom
copilot/enforce-lowercase-metadata
Jan 26, 2026
Merged

Enforce lowercase snake_case for all machine identifiers#245
hotlong merged 4 commits intomainfrom
copilot/enforce-lowercase-metadata

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Jan 26, 2026

Machine identifiers (object names, field names, roles, permissions, actions, etc.) now require lowercase snake_case. Event names use dot notation (user.created).

Changes

New schemas (src/shared/identifiers.zod.ts):

  • SystemIdentifierSchema - allows underscores and dots: /^[a-z][a-z0-9_.]*$/
  • SnakeCaseIdentifierSchema - strict snake_case: /^[a-z][a-z0-9_]*$/
  • EventNameSchema - event names with dots: /^[a-z][a-z0-9_.]*$/

Updated schemas:

  • Data: SelectOption.value, Mapping.name, Field.select() helper
  • Auth/Permission: Role.name, PermissionSet.name, Territory.name
  • Automation: WorkflowRule.name, Webhook.name, Event.name
  • UI: App.name, Page.name, Action.name, NavigationItem.id, ListView.name

Field.select() auto-conversion:

// Automatically converts to lowercase snake_case
Field.select(['In Progress', 'Closed Won'])
// → [{ label: 'In Progress', value: 'in_progress' }, 
//    { label: 'Closed Won', value: 'closed_won' }]

Examples

// Valid
{ name: 'sales_manager', label: 'Sales Manager' }      // Role
{ name: 'app_crm', label: 'CRM' }                      // App
{ name: 'user.created', payload: {...} }               // Event
{ value: 'in_progress', label: 'In Progress' }         // Select option

// Invalid (will fail validation)
{ name: 'SalesManager' }    // PascalCase
{ name: 'sales-manager' }   // kebab-case
{ name: 'Sales Manager' }   // spaces
{ name: '_internal' }       // leading underscore

Impact

All machine identifiers must now be lowercase. Labels remain case-insensitive for display.

Original prompt

在构建像 Salesforce 或 ObjectStack 这样高度抽象的元数据驱动系统时,“区分人类读的(Label)”与“机器读的(API Name)”是核心原则。
凡是属于“机器标识符 (Machine Identifier)”的元数据,为了保证跨平台兼容性、URL 友好性和代码一致性,都强烈建议强制全小写(通常配合下划线 snake_case 或中划线 kebab-case)

除了数据表(Object Name)和字段(Field Name),以下 5 类元数据也必须严格遵守“全小写”规则:

  1. 权限与组织架构 (Security & Org)
    在代码中判断权限时,我们不希望因为大小写导致安全漏洞。
  • 角色代码 (Role API Name)
    • ❌ SalesManager, CEO
    • ✅ sales_manager, ceo, region_east_vp
    • 场景:if (user.role === 'sales_manager')
  • 权限集/简档 (Permission Set / Profile Name)
    • ❌ Read_Only, SystemAdmin
    • ✅ read_only, system_admin, standard_user
  • 共享规则组 (Public Group)
    • ✅ all_internal_users
  1. 自动化逻辑与事件 (Logic & Events)
    这些标识符通常会作为字符串在不同的系统间传递(例如 webhook),必须消除大小写歧义。
  • 触发器/动作名称 (Action/Trigger Name)
    • ❌ OnCloseDeal, SendEmail
    • ✅ on_close_deal, send_welcome_email, approve_contract
    • 注意:这是指在元数据中注册的 Action ID。具体的 JS 函数名可以用小驼峰 (onCloseDeal),但配置 ID 必须小写。
  • 系统事件/消息队列 (Event Keys)
    • ❌ OrderCreated, User.Login
    • ✅ order.created, user.login_success, alarm.high_cpu
    • 理由:Kafka、RabbitMQ 等中间件的主题通常推荐使用点分小写格式。
  • API URL 路径 (RESTful Routes)
    • ❌ /api/v1/Account/GetInfo
    • ✅ /api/v1/crm_account/summary
    • 理由:URL 标准虽然对路径部分区分大小写,但惯例是全小写(kebab-case),避免用户手动输入时出错。
  1. 应用结构与菜单 (App Structure)
  • 应用 ID (Application ID)
    • ❌ CRM, FinanceApp
    • ✅ app_crm, app_finance, app_portal
  • 菜单/页面 ID (Menu/Page Key)
    • ❌ Page_Dashboard, SettingsMenu
    • ✅ page_dashboard, menu_settings_general
    • 场景:前端路由通常映射为 /admin/page_dashboard,大写在浏览器地址栏非常违和。
  1. 选项列表值 (Select/Enum Values)
    这是最容易踩坑的地方。Label 是给用户看的,Value 是存数据库的。
  • 枚举值 (Value)
    • ❌ New, In Progress, Closed_Won
    • ✅ new, in_progress, closed_won
    • 理由:存入数据库的值如果是 In Progress,查询时 WHERE status = 'in progress' 可能会失败。统一小写存储,前端展示时再映射 Label。
  1. 文件与包管理 (File System)
  • 文件名 (Physical File Names)
    • ❌ AccountController.ts, Logo.png
    • ✅ account_controller.ts, logo.png
    • 理由:除了前面提到的跨平台问题,前端引入图片资源时大小写极其敏感。
  • NPM 包名
    • 强制规定必须小写(npm 官方标准)。
      总结:一套通用的命名策略
      建议你在开发指引中定义一个通用的 ApiName 概念,适用于上述所有场景。
      | 概念 | 命名风格 | 示例 | 适用范围 |
      |---|---|---|---|
      | ApiName / ID | snake_case | crm_account, btn_submit, role_admin | 所有元数据 ID、数据库表、URL路径、事件名 |
      | Label / Title | Title Case | Client Account, Submit Form | 用户界面显示的文本 |
      | Code Class | PascalCase | AccountService, LeadComponent | JS/TS 类定义、React 组件 |
      | Code Variable | camelCase | accountList, isValid | JS/TS 内部变量 |
      对应的 Zod 校验策略更新
      你可以将之前的 SnakeCaseString 升级为通用的 SystemIdentifierSchema,应用到所有上述字段中:
      // 通用系统标识符校验
      export const SystemIdentifierSchema = z.string()
      .min(2)
      .regex(/^[a-z][a-z0-9_.]*$/, { // 允许点号用于 event (user.login)
      message: "系统标识符必须全小写 (允许下划线或点号), e.g. 'sys_admin' or 'order.paid'"
      });

// 应用于不同场景
const RoleSchema = z.object({
name: SystemIdentifierSchema, // ✅ sales_manager
label: z.string(), // Sales Manager
});

const EnumOptionSchema = z.object({
value: SystemIdentifierSchema, // ✅ new
label: z.string(), // New
});

一句话总结:除了类名(Class)和组件名(Component),凡是需要持久化存储、网络传输或作为配置 Key 的,通通小写。


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

@vercel
Copy link
Copy Markdown

vercel bot commented Jan 26, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
spec Ready Ready Preview, Comment Jan 26, 2026 4:21pm

Request Review

…se naming

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…case naming

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…ke_case)

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] Enforce lowercase for machine identifiers in metadata Enforce lowercase snake_case for all machine identifiers Jan 26, 2026
Copilot AI requested a review from hotlong January 26, 2026 16:22
@hotlong hotlong marked this pull request as ready for review January 26, 2026 16:29
Copilot AI review requested due to automatic review settings January 26, 2026 16:29
@hotlong hotlong merged commit 6170ec6 into main Jan 26, 2026
5 checks passed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request enforces lowercase snake_case for all machine identifiers across the ObjectStack specification to ensure cross-platform compatibility, URL-friendliness, database consistency, and security.

Changes:

  • Introduces three new shared identifier schemas (SystemIdentifierSchema, SnakeCaseIdentifierSchema, EventNameSchema) with strict validation rules that reject leading underscores, uppercase letters, and other invalid characters
  • Updates machine identifier fields across UI (app, page, view, action), Auth (role), Permission (permission set, territory), Automation (workflow, webhook), System (events), and Data (field, mapping) modules to use the new schemas
  • Adds auto-conversion logic to Field.select() helper to automatically transform option values to lowercase snake_case

Reviewed changes

Copilot reviewed 70 out of 70 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/shared/identifiers.zod.ts New centralized schemas for system identifiers with comprehensive documentation
src/shared/index.ts Exports the new identifier schemas
src/ui/*.zod.ts Updates app, page, view, and action schemas to use SnakeCaseIdentifierSchema
src/auth/role.zod.ts Updates role names to use SnakeCaseIdentifierSchema
src/permission/*.zod.ts Updates permission set and territory names to use SnakeCaseIdentifierSchema
src/automation/*.zod.ts Updates workflow and webhook names to use SnakeCaseIdentifierSchema
src/system/events.zod.ts Updates event names to use EventNameSchema (allows dots)
src/data/field.zod.ts Updates SelectOption.value to use SystemIdentifierSchema and adds auto-conversion in Field.select()
src/data/mapping.zod.ts Updates mapping names to use SnakeCaseIdentifierSchema
*.test.ts Updates tests to verify leading underscores are now rejected
json-schema/* Updates generated JSON schemas with consistent patterns and descriptions
content/docs/* Updates documentation to reflect new naming requirements

options = optionsOrConfig.map(o =>
typeof o === 'string'
? { label: o, value: toSnakeCase(o) } // Auto-convert string to snake_case
: { ...o, value: o.value.toLowerCase() } // Ensure value is lowercase
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a SelectOption object is provided (not a string), the code only applies .toLowerCase() to the value. This won't convert spaces, hyphens, or other invalid characters to valid snake_case format. For example, { label: 'Test', value: 'My Value' } becomes { label: 'Test', value: 'my value' }, which will fail the SystemIdentifierSchema validation because spaces are not allowed.

Consider applying the same toSnakeCase() transformation to object values: { ...o, value: toSnakeCase(o.value) }

Copilot uses AI. Check for mistakes.
options = (optionsOrConfig.options || []).map(o =>
typeof o === 'string'
? { label: o, value: toSnakeCase(o) } // Auto-convert string to snake_case
: { ...o, value: o.value.toLowerCase() } // Ensure value is lowercase
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as line 282: When a SelectOption object is provided (not a string), only .toLowerCase() is applied. This won't convert invalid characters like spaces or hyphens to valid snake_case format, causing validation failures.

Consider applying the toSnakeCase() transformation: { ...o, value: toSnakeCase(o.value) }

Copilot uses AI. Check for mistakes.
Comment on lines +264 to +268
const toSnakeCase = (str: string): string => {
return str
.toLowerCase()
.replace(/\s+/g, '_') // Replace spaces with underscores
.replace(/[^a-z0-9_]/g, ''); // Remove invalid characters (keeping underscores only)
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The toSnakeCase helper removes all non-alphanumeric characters except underscores (line 268), which means it strips dots. However, SelectOption.value uses SystemIdentifierSchema which explicitly allows dots in its pattern /^[a-z][a-z0-9_.]*$/.

This creates an inconsistency: if someone passes a string like "user.status", it gets converted to "userstatus" by this function, even though "user.status" would be valid according to the schema.

Consider whether SelectOption values should allow dots (like events), or if they should strictly be snake_case without dots. If dots should not be allowed for select options, use SnakeCaseIdentifierSchema instead of SystemIdentifierSchema for the value field. If dots should be allowed, update the toSnakeCase function to preserve them.

Copilot uses AI. Check for mistakes.
label: z.string().describe('Display label'),
value: z.string().describe('Stored value'),
label: z.string().describe('Display label (human-readable, any case allowed)'),
value: SystemIdentifierSchema.describe('Stored value (lowercase machine identifier)'),
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The value field uses SystemIdentifierSchema, which allows dots in identifiers (pattern: /^[a-z][a-z0-9_.]*$/). However, select option values are typically stored in database columns and should use strict snake_case without dots (e.g., 'in_progress', 'closed_won' as shown in the examples above).

Consider using SnakeCaseIdentifierSchema instead, which enforces lowercase snake_case without dots. This would be more appropriate for database-stored enum values and would align with the documented examples.

Copilot uses AI. Check for mistakes.
Comment on lines +93 to +95
* @example Invalid
* - 'UserCreated' (camelCase)
* - 'user_created' (should use dots for namespacing)
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment states that 'user_created' is invalid and "should use dots for namespacing", but the regex pattern /^[a-z][a-z0-9_.]*$/ actually accepts underscores, so 'user_created' would pass validation.

This comment is misleading. If the intent is to strongly encourage dots for namespacing in event names, consider either:

  1. Changing this to an "@example Discouraged" section rather than "Invalid"
  2. Or if dots should be required, update the regex to enforce at least one dot (e.g., /^[a-z][a-z0-9_]*\.[a-z0-9_.]+$/)

Copilot uses AI. Check for mistakes.
Copilot AI added a commit that referenced this pull request Jan 26, 2026
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants