The Block and Tackle of Django's Code of Conduct Working Group

2026-08-24 31 预计阅读时间: 1 分钟
来源: djangoproject.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:21 分钟

{"title_zh":"把行为准则变成可验证的基础设施:Django 如何管理一次社区治理重写","body_zh":"# 把行为准则变成可验证的基础设施:Django 如何管理一次社区治理重写\n\n一份行为准则真正难改的部分,通常不是文字,而是权限、审批、公开讨论、敏感记录和后续执行。2026 年,Django 的 Code of Conduct Working Group 采用 Contributor Covenant 3.0,同时把这次重写背后的技术机制整理成了一套可复用的治理基础设施:谁能改什么、谁必须签字、公众如何参与,以及团队如何留下可审计的记录。\n\n## 先把权责边界写进仓库\n\nDjango 的行为准则工作组由 Django Software Foundation 董事会设立,规模保持较小,至少三名成员,并且会考虑地域、背景和生活经验的多样性。成员是志愿者,任期有限;任期改为一年后,如果成员没有在续任确认中及时回应,就会被移出。这个设计降低了“退出是不是让团队失望”的心理负担。\n\n工作组在日常事务上独立运作,但以下事项需要回到董事会:花钱、采取重大惩罚措施,以及修改行为准则正文。在线社区工作组负责日常空间的常规 moderation,只有正式报告、跨空间问题或无法单独解决的事项才会升级到行为准则工作组。两个工作组各自指定联络人;如果联合事项无法达成共识,任一主席都可以提交董事会处理。\n\n工作组内部优先寻求共识。合理时间内无法达成共识时,由不存在利益冲突的成员以三分之二多数表决;如果仍然无法推进,则升级到董事会。关键点是:升级条件和最终裁决者都被提前写清楚,而不是等冲突发生后临时决定。\n\n## 用两行 CODEOWNERS 强制审批\n\n在修改行为准则正文之前,团队先建立了“如何提出修改”的流程。任何 Django 社区成员都可以使用结构化 issue 模板提出变更,说明改什么、为什么改,以及修改的是行为准则正文还是配套文档。错别字、成员列表和 FAQ 等小改动可以走简化流程,其他变更进入月度会议,并根据影响范围在论坛、Discord 或 DSF Slack 征求意见。\n\n支持文档可以由工作组通过共识合并;CODE_OF_CONDUCT.md 则必须获得董事会批准。这个规则不是只放在文档里,而是由 GitHub 的 CODEOWNERS 强制执行:\n\ntext\n* @django/coc-committee\nCODE_OF_CONDUCT.md @django/coc-committee @django/dsf-board\n\n\n第一行意味着仓库中的每个文件都需要工作组审查。第二行额外要求董事会审查行为准则正文。启用分支保护并要求 CODEOWNERS 审批后,GitHub 会阻止缺少任一批准的 PR 合并。你可以把它改成自己的组织名,例如:\n\ntext\n* @acme-community-governance\nCODE_OF_CONDUCT.md @acme-community-governance @acme-board\n\n\n这类配置的价值在于,它把“大家记得遵守流程”变成了仓库平台可以检查的约束。\n\n## 30 天公开评论,让沉默也有明确含义\n\n除行政性变更外,Django 要求 PR 经过 30 天公开评论期。期间没有评论,工作组可以在期限结束后合并;如果有人评论,讨论必须被处理后才能合并。它不要求每个人阅读所有评论,但要求负责团队回应提出的问题。\n\n可以用一个简单的 GitHub Action 检查 PR 的最短存活时间,并为行政变更提供绕过标签:\n\nyaml\nname: Check public comment period\n\non:\n pull_request:\n types: [opened, synchronize, labeled, unlabeled]\n\njobs:\n age-check:\n runs-on: ubuntu-latest\n steps:\n - name: Require 30 days unless expedited\n uses: actions/github-script@v7\n with:\n script: |\n const bypass = 'expedited';\n const minAgeDays = 30;\n const pr = context.payload.pull_request;\n const labels = pr.labels.map(label => label.name);\n if (labels.includes(bypass)) {\n core.info('Administrative bypass label found.');\n return;\n }\n const ageMs = Date.now() - new Date(pr.created_at).getTime();\n const ageDays = ageMs / 86400000;\n if (ageDays < minAgeDays) {\n core.setFailed(`PR is ${ageDays.toFixed(1)} days old; 30 required.`);\n }\n\n\n生产环境中还应把“讨论是否已解决”作为单独检查,或者由工作组在合并前明确记录决议。Django 的流程另外使用 BYPASS_LABELMIN_AGE_DAYS 这类配置,让规则可以适应不同项目。\n\n## 大型政策重写要拆成可审查的交付物\n\nContributor Covenant 3.0 的采用没有被塞进一个巨型 PR。总 issue 拆成了十五个子任务,分别处理 Enforcement Manual、Reporting Guidelines、FAQ、行为准则正文、官网同步,以及博客、论坛、Discord、Slack 和 Reddit 的公告。\n\n实践中仍然会有失败的第一次尝试,也会有上千行的重写 PR。但拆分后的每个交付物都能单独分配、讨论和审查。整个过程从文档进入 git 到最终采用约三个月,其中大部分时间用于让社区阅读、提问和评论,真正写作与审查只用了几天。对于政策类变更,这种时间分配是必要成本,而不是低效。\n\n## 自动生成变更记录,避免“相信我们”\n\n团队还添加了 updates.md 和结构化模板,并通过 GitHub Action 在合并后自动更新 CHANGELOG.md。这里的 changelog 不只是文件 diff,而是用普通语言记录决策和理由,例如为什么删除一项武器政策、为什么将它移动到关联活动指南。\n\n一个最小的 shell 版本可以这样组织:\n\nbash\n#!/usr/bin/env bash\nset -euo pipefail\n\ncommit_message="${1:?usage: $0 'plain-language decision'}"\nentry="$(date -u +%Y-%m-%d) - ${commit_message}"\n\nprintf '%s\\n' "$entry" >> CHANGELOG.md\ngit add CHANGELOG.md\n\n\n实际项目应让 Action 从合并 PR 的标签、标题或正文中提取信息,并在提交前检查格式。重点不在脚本多复杂,而在于每次合并都自动留下“做了什么、为什么做”的公开记录。\n\nupdates.md 还维护自己的小型变更记录,将流程文档的修改与行为准则正文的修改分开。换句话说,治理流程本身也接受治理。\n\n## 敏感报告:分离身份、案件和公开统计\n\n行为准则文本的变更流程,与实际违规报告的处理流程是两套机制。报告统一发送到 conduct@djangoproject.com,由整个工作组接收。目标是一天内确认收到、一周内给出初步回应,但文档也承认志愿者协调可能需要更久。下一步决定至少需要两名成员同意,需要法律意见的严重事项则需要多数同意。处罚可以从私下警告到 30 至 90 天停权、90 天以上停权和永久封禁,严重情形可以跳过较低级别。\n\n记录设计尤其值得借鉴。每个案件获得随机代号,例如 home-shelf;每个被报告者也有跨案件保持一致的代号,例如 Person A。主要跟踪表只保存代号,另一份访问权限更严格的表才保存代号与真实身份的映射。这样,处理案件和识别重复模式可以在不暴露真实姓名的情况下完成。\n\n数据还分为三份表:\n\n- Report Tracker:案件状态、处理来源、安全风险和后果。\n- Person Identity Key:代号到真实身份的唯一映射,权限最严格。\n- Public Tracker:通过 IMPORTRANGE 只读取年度汇总数据,不接触个案。\n\n代号生成器使用绑定到 Google Sheet 的 Apps Script,在本地工作表环境中从形容词和名词列表随机组合,而不是把敏感数据发送给第三方服务。一个可改造的最小实现如下:\n\njavascript\nfunction onOpen() {\n SpreadsheetApp.getUi()\n .createMenu('Community CoC')\n .addItem('Generate code name', 'generateCodeName')\n .addToUi();\n}\n\nfunction generateCodeName() {\n const cell = SpreadsheetApp.getActiveSheet().getActiveCell();\n if (cell.getValue() && !confirmOverwrite_()) return;\n\n const adjectives = ['amber', 'quiet', 'steady', 'open'];\n const nouns = ['anchor', 'window', 'bridge', 'shelf'];\n const pick = list => list[Math.floor(Math.random() * list.length)];\n cell.setValue(`${pick(adjectives)}-${pick(nouns)}`);\n}\n\nfunction confirmOverwrite_() {\n const ui = SpreadsheetApp.getUi();\n return ui.alert('Replace existing value?', ui.ButtonSet.YES_NO) === ui.Button.YES;\n}\n\n\n示例列表只用于演示,实际部署时要使用足够大的、不带现实身份暗示的词表,并限制脚本和表格的访问权限。公开统计只发布报告数、涉及人数、警告、停权和封禁等聚合指标。复制模板时尤其要检查 IMPORTRANGE 是否仍然指向原始表格,并在切换到自己的 Report Tracker 后重新授权。\n\n## 可直接借鉴的落地清单\n\nDjango 这次实践的价值不只是采用了哪一版行为准则,而是把治理工作变成了可追踪的工程流程。准备在自己的开源项目中复制时,可以按以下顺序落地:\n\n1. 建立一个小而明确的治理团队,写清成员任期、续任和退出方式。\n2. 用一份文档定义工作组、日常 moderation 团队和董事会之间的升级边界。\n3. 建立 updates.md、issue 模板和 CODEOWNERS,让提案、审批和理由都进入仓库。\n4. 对非行政变更设置公开评论期,并给真正的紧急行政操作设计明确标签。\n5. 把大型重写拆成可分配、可审查、可公告的子任务。\n6. 将案件记录、身份映射和公开统计分离,默认使用代号和最小权限。\n7. 借鉴其他组织的公开材料时保留来源和许可证信息。\n\n行为准则是一项信任承诺;流程和自动化机制的作用,是让这种信任能够被验证。最可靠的治理不是“请相信维护者会记得”,而是让权限、等待期、审批人、记录和数据边界在工具中留下证据。","title_en":"Turning a Code of Conduct into Verifiable Infrastructure: Django’s Governance Rewrite","body_en":"# Turning a Code of Conduct into Verifiable Infrastructure: Django’s Governance Rewrite\n\nThe difficult part of changing a Code of Conduct is rarely the prose. It is the surrounding machinery: authority, approvals, public input, confidential records, and enforcement. In 2026, Django’s Code of Conduct Working Group adopted Contributor Covenant 3.0 and documented the technical mechanics that made the change manageable. The result is a useful model for projects that want governance to be auditable rather than dependent on memory and goodwill.\n\n## Put authority boundaries in writing\n\nDjango’s Code of Conduct Working Group is established by the Django Software Foundation board. It is intentionally small, with at least three members recruited for diversity of geography, background, and lived experience. Membership is voluntary and term-limited. Terms became annual, and members who do not respond to the January renewal check-in within a week are rolled off without anyone having to frame stepping back as a failure.\n\nThe group operates independently day to day. It returns to the board only for spending money, taking a drastic punitive action, or changing the Code of Conduct text itself. A separate Online Community Working Group handles routine moderation. The two groups become involved together when a formal report, a cross-space issue, or an otherwise unresolved case is escalated. Each group appoints a liaison, and either chair can refer an unresolved joint decision to the board.\n\nWithin the Code of Conduct group, consensus comes first. If consensus does not emerge within a reasonable time, two-thirds of members without a conflict of interest can decide. If that still does not resolve the matter, it goes to the board. The important design choice is that escalation rules and decision owners exist before a conflict occurs.\n\n## Enforce approvals with two CODEOWNERS rules\n\nBefore changing the policy itself, the group created a process for proposing changes. Anyone in the Django community can open an issue using a structured template that asks what is changing, why, and whether the change affects the Code of Conduct text or only supporting documentation. Typos, membership updates, and FAQ edits can use a lightweight path; substantive changes are discussed at a monthly meeting and may be opened to the forum, Discord, or DSF Slack.\n\nThe working group can approve supporting documentation by consensus. The main CODE_OF_CONDUCT.md file requires board approval as well. That distinction is enforced mechanically with GitHub CODEOWNERS:\n\ntext\n* @django/coc-committee\nCODE_OF_CONDUCT.md @django/coc-committee @django/dsf-board\n\n\nThe first line requires the working group to review every file. The second adds the board as a required reviewer for the policy itself. With branch protection configured to require CODEOWNERS approval, GitHub will prevent a merge that lacks either approval. A project with different teams could adapt it like this:\n\ntext\n* @acme-community-governance\nCODE_OF_CONDUCT.md @acme-community-governance @acme-board\n\n\nThis turns a policy that people are expected to remember into a constraint that the repository can enforce.\n\n## Give public comments a defined window\n\nDjango requires a 30-day public comment period for every non-administrative PR. If no one comments, the group may merge after 30 days. If someone does comment, the discussion must be addressed before merging. The rule does not require everyone to read every comment, but it does require the responsible group to respond.\n\nA minimal GitHub Action can check the age of a PR and provide an expedited label for administrative changes:\n\nyaml\nname: Check public comment period\n\non:\n pull_request:\n types: [opened, synchronize, labeled, unlabeled]\n\njobs:\n age-check:\n runs-on: ubuntu-latest\n steps:\n - name: Require 30 days unless expedited\n uses: actions/github-script@v7\n with:\n script: |\n const bypass = 'expedited';\n const minAgeDays = 30;\n const pr = context.payload.pull_request;\n const labels = pr.labels.map(label => label.name);\n if (labels.includes(bypass)) {\n core.info('Administrative bypass label found.');\n return;\n }\n const ageMs = Date.now() - new Date(pr.created_at).getTime();\n const ageDays = ageMs / 86400000;\n if (ageDays < minAgeDays) {\n core.setFailed(`PR is ${ageDays.toFixed(1)} days old; 30 required.`);\n }\n\n\nA production workflow should also represent unresolved discussion as a separate check, or require the working group to record its resolution before merging. Configuration such as BYPASS_LABEL and MIN_AGE_DAYS keeps the same workflow usable by projects with different policies.\n\n## Break a policy rewrite into reviewable deliverables\n\nThe Contributor Covenant 3.0 adoption was not treated as one enormous pull request. The main issue spawned fifteen sub-issues covering the Enforcement Manual, Reporting Guidelines, FAQs, the Code of Conduct text, website synchronization, and separate announcements for the blog, forum, Discord, Slack, and Reddit.\n\nThere was still a failed first attempt and a large rewrite PR of more than a thousand changed lines. The difference is that each deliverable could be assigned, discussed, reviewed, and announced independently. From bringing the documents into git to the final adoption, the work took roughly three months. Most of that time was deliberately spent allowing the community to read, question, and comment. For policy changes, that waiting period is part of the work.\n\n## Automate the paper trail\n\nThe same process change added updates.md and a GitHub Action that regenerates CHANGELOG.md after merges. This changelog is not merely a diff list. It records decisions in plain language and preserves the reasoning behind scope changes, such as moving a weapons policy from the project’s direct responsibility to guidance for affiliated events.\n\nA minimal shell implementation could look like this:\n\nbash\n#!/usr/bin/env bash\nset -euo pipefail\n\ncommit_message="${1:?usage: $0 'plain-language decision'}"\nentry="$(date -u +%Y-%m-%d) - ${commit_message}"\n\nprintf '%s\\n' "$entry" >> CHANGELOG.md\ngit add CHANGELOG.md\n\n\nA real project should derive the entry from the merged PR title, labels, or body and validate the format before committing. The important property is consistency: every merge leaves a public explanation of what changed and why.\n\nupdates.md also has its own small changelog, keeping changes to the process document separate from changes to the policy. The governance process is governed too.\n\n## Separate identities, cases, and public statistics\n\nPolicy changes and violation reports use different mechanisms. Reports arrive in one inbox, conduct@djangoproject.com, and are distributed to the whole working group. The stated targets are acknowledgment within a day and an initial response within a week, with an explicit caveat that volunteer coordination can take longer. At least two members must agree on next steps; cases requiring legal advice need a majority. Outcomes range from a private warning through temporary suspension and permanent bans, with lower levels skipped when severity requires it.\n\nThe record-keeping model is particularly reusable. Each case gets a random code name, and each reported person gets a persistent pseudonym across cases. The primary tracker contains code names rather than real identities. A separately restricted spreadsheet stores the mapping. This allows case discussion and pattern detection without exposing a real name to everyone who needs to work on the case.\n\nThe data is divided across three spreadsheets:\n\n- Report Tracker: case status, resolution source, safety risk, and consequences.\n- Person Identity Key: the only mapping from pseudonyms to real identities, with the tightest access control.\n- Public Tracker: aggregate annual statistics imported from the tracker, with no case-level data.\n\nThe code-name generator is a Google Apps Script bound to the spreadsheet, so sensitive values do not need to be sent to an external service. A small adaptable implementation is:\n\njavascript\nfunction onOpen() {\n SpreadsheetApp.getUi()\n .createMenu('Community CoC')\n .addItem('Generate code name', 'generateCodeName')\n .addToUi();\n}\n\nfunction generateCodeName() {\n const cell = SpreadsheetApp.getActiveSheet().getActiveCell();\n if (cell.getValue() && !confirmOverwrite_()) return;\n\n const adjectives = ['amber', 'quiet', 'steady', 'open'];\n const nouns = ['anchor', 'window', 'bridge', 'shelf'];\n const pick = list => list[Math.floor(Math.random() * list.length)];\n cell.setValue(`${pick(adjectives)}-${pick(nouns)}`);\n}\n\nfunction confirmOverwrite_() {\n const ui = SpreadsheetApp.getUi();\n return ui.alert('Replace existing value?', ui.ButtonSet.YES_NO) === ui.Button.YES;\n}\n\n\nThe lists above are illustrative. A real deployment should use a sufficiently large vocabulary without references that could suggest real identities, and should restrict both the script and the spreadsheets. Public reporting should contain aggregate counts only: reports, people named, warnings, suspensions, bans, and similar totals. When copying a public tracker template, verify that its IMPORTRANGE formula still points to the source spreadsheet; replace that URL with your own tracker and grant access once when Sheets asks.\n\n## A practical adoption checklist\n\nDjango’s example is valuable less because of the particular policy version and more because it turns governance into an inspectable engineering workflow. A project borrowing the approach can start here:\n\n1. Define a small governance group with explicit terms, renewal, and exit rules.\n2. Document escalation boundaries between governance, routine moderation, and the board or steering committee.\n3. Add updates.md, an issue template, and CODEOWNERS so proposals, approvals, and rationale enter the repository.\n4. Set a public comment period for substantive changes and define a narrow, visible administrative bypass.\n5. Split large rewrites into assignable, reviewable, and announceable deliverables.\n6. Separate case records, identity mappings, and public statistics; use pseudonyms and least privilege by default.\n7. Preserve source and license information when borrowing published material from other organizations.\n\nA Code of Conduct is an exercise in trust. The purpose of the surrounding process is to make that trust verifiable. The strongest governance is not a request to remember what maintainers promised; it is a system where permissions, waiting periods, approvers, records, and data boundaries leave evidence in the tools people already use.","seo_description_en":"How Django made Code of Conduct governance auditable with CODEOWNERS, public review windows, automated changelogs, and privacy-conscious case tracking."}


相关推荐