The real-group profiles stay as a test, so a change that moves a pick fails. Co-authored-by: Cursor <cursoragent@cursor.com>
419 lines
14 KiB
TypeScript
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();
|
|
});
|