417 lines
13 KiB
TypeScript
417 lines
13 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 — 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();
|
||
});
|