Files
community-rule/prisma/seed.ts
T
adilalloandCursor bcbc6f30d9 Import the remade decks on stable ids and recommend one card per job, so a saved draft still loads after a chip is renamed.
The real-group profiles stay as a test, so a change that moves a pick fails.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-04 14:22:54 -07:00

419 lines
14 KiB
TypeScript

import { PrismaClient, type Prisma } from "@prisma/client";
import { seedMethodFacets } from "./seed/methodFacets";
import { seedTemplateFacets } from "./seed/templateFacets";
/**
* Curated rule templates for GET /api/templates.
* Home “Popular templates” list uses `lib/templates/governanceTemplateCatalog.ts` (Figma 21764-16435);
* DB titles/descriptions should stay aligned with `governanceTemplateCatalog.ts`.
* `body.sections` use the same category row labels as the final-review Rule
* (Values, Communication, Membership, Decision-making, Conflict management) so
* template review matches that layout; `entries[].title` = chip labels, `body` = supporting text.
* Chip titles per template are sourced from the product **Template Composition** workbook (xlsx column
* layout: Decision-making, Membership Policies, Values, Communication, Conflict Management), mapped in
* `COMPOSITION_BY_SLUG` below.
*/
/** Starter `body` for catalog templates — five category rows match template review / final-review layout. */
function governancePatternBody(coreValues: string): Prisma.InputJsonValue {
return {
sections: [
{
categoryName: "Values",
entries: [{ title: "Core stance", body: coreValues }],
},
{
categoryName: "Communication",
entries: [
{
title: "Transparency",
body: "Updates and decisions are shared through agreed channels so members stay aligned.",
},
],
},
{
categoryName: "Membership",
entries: [
{
title: "Participation",
body: "Roles and eligibility are explicit so people know how to take part.",
},
],
},
{
categoryName: "Decision-making",
entries: [
{
title: "Process",
body: "Steps are documented so legitimacy stays high as you scale.",
},
],
},
{
categoryName: "Conflict management",
entries: [
{
title: "Resolution",
body: "Disputes use fair, documented paths before they harden into splits.",
},
],
},
],
};
}
/** Chip titles from Template Composition.xlsx; bodies stay empty — presets hydrate at publish / display. */
function entriesFromCompositionCell(cell: string): { title: string; body: string }[] {
const trimmed = cell.trim();
if (!trimmed) return [];
return trimmed
.split(/,\s*/)
.map((title) => title.trim())
.filter(Boolean)
.map((title) => ({ title, body: "" }));
}
function bodyFromXlsxComposition(row: {
decisionMaking: string;
membership: string;
values: string;
communication: string;
conflict: string;
}): Prisma.InputJsonValue {
return {
sections: [
{ categoryName: "Values", entries: entriesFromCompositionCell(row.values) },
{
categoryName: "Communication",
entries: entriesFromCompositionCell(row.communication),
},
{
categoryName: "Membership",
entries: entriesFromCompositionCell(row.membership),
},
{
categoryName: "Decision-making",
entries: entriesFromCompositionCell(row.decisionMaking),
},
{
categoryName: "Conflict management",
entries: entriesFromCompositionCell(row.conflict),
},
],
};
}
/**
* Curated template chip rows. Chip titles are card labels from
* `messages/en/create/customRule/*.json` (October 2026 deck remake), so
* `methodSlugFromTitle` resolves each one to a live `methods[].id`.
* Values match `coreValues.json` labels (case-insensitive).
*/
const COMPOSITION_BY_SLUG: Record<
string,
{
decisionMaking: string;
membership: string;
values: string;
communication: string;
conflict: string;
}
> = {
consensus: {
decisionMaking: "Decision speeds, Consensus decision-making, Modified consensus",
membership: "Members approve new members, Vouching",
values: "Consensus, Community care, Horizontalism",
communication: "How we run meetings, In-person meetings, Loomio",
conflict: "Conflict ladder, Mediation",
},
"consensus-clusters": {
decisionMaking: "Consent (sociocracy), Delegated decision-making",
membership: "Join by contributing, Orientation",
values: "Decentralization, Adaptability, Autonomy",
communication: "How we run meetings, Shared records, Slack",
conflict: "Conflict ladder, Circle processes, Restorative practices",
},
"solidarity-network": {
decisionMaking: "Do-ocracy, Modified consensus, Shared money",
membership: "Open admission, Vouching, Member groups",
values: "Solidarity, Mutual aid, Anti-oppression",
communication: "Signal, Matrix / Element",
conflict: "Conflict ladder, Restorative practices",
},
"sortition-jury": {
decisionMaking: "Panel drawn by lot, Rotating roles",
membership: "Open admission, Roster upkeep",
values: "Fairness, Equity, Transparency",
communication: "In-person meetings, Video meetings, Shared records",
conflict: "Conflict ladder, Hearing panel",
},
"liquid-democracy": {
decisionMaking: "Liquid democracy, Continuous voting",
membership: "Open admission, One person one account",
values: "Agency, Flexibility, Transparency",
communication: "Loomio, Discourse (forum)",
conflict: "Conflict ladder, Non-binding arbitration",
},
"do-ocracy": {
decisionMaking: "Do-ocracy, Lazy consensus, Two-person sign-off",
membership: "Join by contributing, Rest and workload limits",
values: "Agency, Autonomy, Voluntarism",
communication: "GitHub / GitLab, Discord",
conflict: "Conflict ladder, Mediation",
},
"quadratic-governance": {
decisionMaking: "Quadratic voting, Two-person sign-off",
membership: "One person one account, Member share or dues",
values: "Fairness, Innovation, Independence",
communication: "Discourse (forum), Discord",
conflict: "Conflict ladder, Conflict working group",
},
"federated-clusters": {
decisionMaking: "Consensus seeking with delegates",
membership: "Member groups, Membership agreement",
values: "Interoperability, Localism, Interdependence",
communication: "Matrix / Element, Discourse (forum)",
conflict: "Conflict ladder, Hearing panel, Non-binding arbitration",
},
devolution: {
decisionMaking: "One person decides, Delegated decision-making",
membership: "Starting with one person, Open admission",
values: "Capacity building, Education, Maintenance",
communication: "Discord, GitHub / GitLab",
conflict: "Conflict ladder, Conflict workshops, Managerial decision",
},
"benevolent-dictator": {
decisionMaking: "One person decides, Hierarchical decision-making",
membership: "Join by contributing, Contributors and maintainers",
values: "Reliability, Stewardship, Leadership",
communication: "Email distribution list, GitHub / GitLab",
conflict: "Conflict ladder, Managerial decision",
},
petition: {
decisionMaking: "Petition and referendum, Majority rule, Ranked choice voting",
membership: "Open admission, One person one account",
values: "Freedom of information, Accountability, Participation",
communication: "Loomio, Discourse (forum)",
conflict: "Conflict ladder, Non-binding arbitration, Legal action",
},
"self-appointed-board": {
decisionMaking: "Appointed board, Advisory committees, Coordinating committee",
membership: "Application and review, Paid staff",
values: "Stewardship, Resilience, Reliability",
communication: "Video meetings, Email distribution list",
conflict: "Conflict ladder, Harassment and discrimination complaints, Hearing panel",
},
"elected-board": {
decisionMaking: "Elected board of directors, General meeting, Majority rule",
membership: "Application and review, Membership agreement",
values: "Accountability, Transparency, Trust",
communication: "Email distribution list, Slack",
conflict: "Conflict ladder, Mediation, Conflict working group",
},
};
function bodyFromSlugComposition(slug: string): Prisma.InputJsonValue {
const row = COMPOSITION_BY_SLUG[slug];
if (!row) {
return governancePatternBody("Template composition pending.");
}
return bodyFromXlsxComposition(row);
}
const TEMPLATES: {
slug: string;
title: string;
category: string;
description: string;
sortOrder: number;
featured: boolean;
body: Prisma.InputJsonValue;
}[] = [
{
slug: "consensus-clusters",
title: "Circles",
category: "Governance pattern",
description:
"Units called Circles have the ability to decide and act on matters in their domains, which their members agree on through a Council.",
sortOrder: 0,
featured: false,
body: bodyFromSlugComposition("consensus-clusters"),
},
{
slug: "consensus",
title: "Consensus",
category: "Governance pattern",
description:
"Important decisions require unanimous agreement. Proposals pass only if no serious objections remain.",
sortOrder: 1,
featured: true,
body: bodyFromSlugComposition("consensus"),
},
{
slug: "elected-board",
title: "Elected Board",
category: "Governance pattern",
description:
"An elected board determines policies and organizes their implementation.",
sortOrder: 2,
featured: false,
body: bodyFromSlugComposition("elected-board"),
},
{
slug: "petition",
title: "Petition",
category: "Governance pattern",
description:
"Any participant can propose a rule change. If enough sign it, it goes to a general vote.",
sortOrder: 3,
featured: false,
body: bodyFromSlugComposition("petition"),
},
{
slug: "solidarity-network",
title: "Solidarity Network",
category: "Governance pattern",
description:
'Power is held by autonomous "cells." A central hub acts as a switchboard for resources but cannot dictate cell activities.',
sortOrder: 4,
featured: true,
body: bodyFromSlugComposition("solidarity-network"),
},
{
slug: "sortition-jury",
title: "Sortition (Jury)",
category: "Governance pattern",
description:
"A representative sample of the community is chosen by lottery to form a temporary council.",
sortOrder: 5,
featured: false,
body: bodyFromSlugComposition("sortition-jury"),
},
{
slug: "liquid-democracy",
title: "Liquid Democracy",
category: "Governance pattern",
description:
"Members can vote directly or delegate their vote to a trusted peer on a per-topic basis.",
sortOrder: 6,
featured: false,
body: bodyFromSlugComposition("liquid-democracy"),
},
{
slug: "do-ocracy",
title: "Do-ocracy",
category: "Governance pattern",
description:
"Authority is granted to those doing the work. If you do the task, you decide how it gets done.",
sortOrder: 7,
featured: true,
body: bodyFromSlugComposition("do-ocracy"),
},
{
slug: "quadratic-governance",
title: "Quadratic Governance",
category: "Governance pattern",
description:
"Voting cost is squared (V²), preventing a majority from steamrolling a passionate minority.",
sortOrder: 8,
featured: false,
body: bodyFromSlugComposition("quadratic-governance"),
},
{
slug: "federated-clusters",
title: "Federated Clusters",
category: "Governance pattern",
description:
"Independent groups share a central brand/charter but have total autonomy over internal rules.",
sortOrder: 9,
featured: false,
body: bodyFromSlugComposition("federated-clusters"),
},
{
slug: "devolution",
title: "Devolution",
category: "Governance pattern",
description:
"Starts as a Dictatorship for speed, moving to a Board, and finally to full community ownership.",
sortOrder: 10,
featured: true,
body: bodyFromSlugComposition("devolution"),
},
{
slug: "benevolent-dictator",
title: "Benevolent Dictator",
category: "Governance pattern",
description:
"A single individual holds ultimate power, usually intended as a temporary state until the project is stable.",
sortOrder: 11,
featured: false,
body: bodyFromSlugComposition("benevolent-dictator"),
},
{
slug: "self-appointed-board",
title: "Self-Appointed Board",
category: "Governance pattern",
description:
"An existing board selects its own successors to preserve a specific mission over time.",
sortOrder: 12,
featured: false,
body: bodyFromSlugComposition("self-appointed-board"),
},
];
const prisma = new PrismaClient();
async function main() {
for (const row of TEMPLATES) {
const { slug, title, category, description, sortOrder, featured, body } =
row;
await prisma.ruleTemplate.upsert({
where: { slug },
create: {
slug,
title,
category,
description,
sortOrder,
featured,
body,
},
update: {
title,
category,
description,
sortOrder,
featured,
body,
},
});
}
const facetSeed = await seedMethodFacets(prisma);
// eslint-disable-next-line no-console -- seed CLI feedback
console.log(
`Seeded MethodFacet rows: ${Object.entries(facetSeed.rowsBySection)
.map(([section, count]) => `${section}=${count}`)
.join(", ")}`,
);
const templateFacetSeed = await seedTemplateFacets(prisma);
// eslint-disable-next-line no-console -- seed CLI feedback
console.log(
`Seeded TemplateFacet rows: ${templateFacetSeed.rowCount}`,
);
}
main()
.then(() => {
// eslint-disable-next-line no-console -- seed CLI feedback
console.log(`Seeded ${TEMPLATES.length} rule template(s).`);
})
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});