Files
2026-05-22 13:36:23 -06:00

417 lines
13 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 — sourced from product Template Composition.xlsx
* (Governance Template × category columns).
*/
const COMPOSITION_BY_SLUG: Record<
string,
{
decisionMaking: string;
membership: string;
values: string;
communication: string;
conflict: string;
}
> = {
consensus: {
decisionMaking: "Consensus Decision-Making, Modified Consensus",
membership: "Consensus or Vote-Based Approval, Peer Sponsorship",
values: "Consensus, Community Care, Horizontalism",
communication: "In-Person Meetings, Loomio",
conflict: "Consensus Building, Facilitated Negotiation",
},
"consensus-clusters": {
decisionMaking: "Sociocracy, Holacracy",
membership: "Contribution Based, Orientation Required",
values: "Decentralization, Adaptability, Autonomy",
communication: "Slack, Matrix / Element",
conflict: "Circle Processes, Restorative Practices",
},
"solidarity-network": {
decisionMaking: "Do-ocracy, Modified Consensus",
membership: "Open Access, Peer Sponsorship",
values: "Solidarity, Mutual Aid, Anti-oppression",
communication: "Signal, Matrix / Element",
conflict: "Peer Mediation, Restorative Practices",
},
"sortition-jury": {
decisionMaking: "Lottery/Sortition, Deliberative Polling",
membership: "Lottery / Sortition",
values: "Fairness, Equity, Transparency",
communication: "In-Person Meetings, Video Meetings",
conflict: "Lottery/Sortition, Rotational Judging",
},
"liquid-democracy": {
decisionMaking: "Delegated Decision-Making, Continuous Voting",
membership: "Identity Verification, Open Access",
values: "Agency, Flexibility, Transparency",
communication: "Loomio, Discourse (Forum)",
conflict: "Ad Hoc Arbitration, Peer Mediation",
},
"do-ocracy": {
decisionMaking: "Do-ocracy, Lazy Consensus",
membership: "Contribution Based, Skill-Based Contribution",
values: "Agency, Autonomy, Voluntarism",
communication: "GitHub / GitLab, Discord",
conflict: "Peer Mediation, Facilitated Negotiation",
},
"quadratic-governance": {
decisionMaking: "Quadratic Voting",
membership: "Identity Verification, Pay-to-Join",
values: "Fairness, Innovation, Independence",
communication: "Discourse (Forum), Discord",
conflict: "Supermajority Vote, Conflict Resolution Council",
},
"federated-clusters": {
decisionMaking: "Consensus Seeking with Delegates",
membership: "Hybrid Approval Process, Membership Agreement or Pledge",
values: "Interoperability, Localism, Interdependence",
communication: "Matrix / Element, Discourse (Forum)",
conflict: "Internal Tribunal, Ad Hoc Arbitration",
},
devolution: {
decisionMaking: "Autocratic Decision-Making, Delegated Decision-Making",
membership: "Invitation Only, Open Access",
values: "Capacity Building, Education, Maintenance",
communication: "Discord, GitHub / GitLab",
conflict: "Conflict Workshops, Managerial Decision",
},
"benevolent-dictator": {
decisionMaking: "Autocratic Decision-Making, Hierarchical Decision-Making",
membership: "Invitation Only, Mentorship",
values: "Reliability, Stewardship, Leadership",
communication: "Email Distribution List, GitHub / GitLab",
conflict: "Managerial Decision, Binding Contracts",
},
petition: {
decisionMaking: "Ranked Choice Voting, Majority Rule",
membership: "Open Access, Identity Verification",
values: "Freedom of Information, Accountability, Participation",
communication: "Loomio, Discourse (Forum)",
conflict: "Supermajority Vote, Binding Arbitration",
},
"self-appointed-board": {
decisionMaking: "Advisory Committees, Executive Committees",
membership: "Invitation Only, Application & Review",
values: "Stewardship, Resilience, Reliability",
communication: "Video Meetings, Email Distribution List",
conflict: "Judicial Committees, Internal Tribunal",
},
"elected-board": {
decisionMaking: "Elected Board of Directors, Majority Rule",
membership: "Application & Review, Membership Agreement or Pledge",
values: "Accountability, Transparency, Trust",
communication: "Email Distribution List, Slack",
conflict: "Conflict Resolution Council, Mediation",
},
};
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: false,
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: true,
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();
});