From a01f999987cec3ac9dc4b966339516ee81d9fcb0 Mon Sep 17 00:00:00 2001 From: Jason Schrader Date: Sun, 19 Oct 2025 20:06:08 -0700 Subject: [PATCH 1/7] feat: Add DAO charter update handler and filter for set-dao-charter Co-authored-by: aider (openrouter/x-ai/grok-4) --- app/backend/models.py | 1 + .../handlers/dao_charter_update_handler.py | 65 +++++++++++++++++++ .../filters/transaction.py | 20 ++++++ .../utils/template_manager.py | 1 + 4 files changed, 87 insertions(+) create mode 100644 app/services/integrations/webhooks/chainhook/handlers/dao_charter_update_handler.py diff --git a/app/backend/models.py b/app/backend/models.py index f7bd74e4..786b763c 100644 --- a/app/backend/models.py +++ b/app/backend/models.py @@ -318,6 +318,7 @@ class DAOBase(CustomBaseModel): description: Optional[str] = None is_deployed: Optional[bool] = False is_broadcasted: Optional[bool] = False + charter: Optional[str] = None # New field for storing DAO charter text class DAOCreate(DAOBase): diff --git a/app/services/integrations/webhooks/chainhook/handlers/dao_charter_update_handler.py b/app/services/integrations/webhooks/chainhook/handlers/dao_charter_update_handler.py new file mode 100644 index 00000000..35ebc47e --- /dev/null +++ b/app/services/integrations/webhooks/chainhook/handlers/dao_charter_update_handler.py @@ -0,0 +1,65 @@ +"""Handler for DAO charter update transactions.""" + +import logging +from typing import Any, Dict + +from app.services.integrations.webhooks.chainhook.handlers.base import ChainhookEventHandler +from app.services.integrations.webhooks.chainhook.models import TransactionWithReceipt +from app.services.processing.stacks_chainhook_adapter.parsers.clarity import ClarityParser + + +class DAOCharterUpdateHandler(ChainhookEventHandler): + """Handler for set-dao-charter contract calls. + + This handler detects transactions that update a DAO's charter and processes + the event to update the backend database. + """ + + def __init__(self): + super().__init__() + self.parser = ClarityParser(logger=self.logger) + + def can_handle_transaction(self, transaction: TransactionWithReceipt) -> bool: + """Check if this is a set-dao-charter transaction.""" + tx_data = self.extract_transaction_data(transaction) + tx_metadata = tx_data["tx_metadata"] + if not hasattr(tx_metadata, "kind") or tx_metadata.kind.type != "ContractCall": + return False + + contract_data = tx_metadata.kind.data + method = getattr(contract_data, "method", "") + contract_id = getattr(contract_data, "contract_identifier", "") + + return method == "set-dao-charter" and "-dao-charter" in contract_id + + async def handle_transaction(self, transaction: TransactionWithReceipt) -> None: + """Handle the charter update transaction.""" + tx_data = self.extract_transaction_data(transaction) + # Find the print event (smart_contract_log) + print_events = [ + event for event in tx_data["tx_metadata"].receipt.events + if event.type == "SmartContractEvent" and "print" in event.data.get("topic", "") + ] + if not print_events: + self.logger.warning("No print event found in set-dao-charter transaction") + return + + # Parse the Clarity repr from the event + for event in print_events: + parsed_data = self.parser.parse(event.data) + if isinstance(parsed_data, dict) and "payload" in parsed_data: + payload = parsed_data["payload"] + dao_principal = payload.get("dao", "") + new_charter = payload.get("charter", "") + previous_charter = payload.get("previousCharter", "") + + self.logger.info( + f"Detected DAO charter update for DAO {dao_principal}: " + f"New charter length: {len(new_charter)}" + ) + + # TODO: Update the DAO charter in the backend database. + # Example: Use backend.update_dao(dao_id, DAOBase(charter=new_charter)) + # We'd need to map dao_principal to dao_id (e.g., via backend.get_dao_by_principal) + # Stub for now: + pass # Replace with actual backend update call diff --git a/app/services/processing/stacks_chainhook_adapter/filters/transaction.py b/app/services/processing/stacks_chainhook_adapter/filters/transaction.py index 6031a1ad..1a183c34 100644 --- a/app/services/processing/stacks_chainhook_adapter/filters/transaction.py +++ b/app/services/processing/stacks_chainhook_adapter/filters/transaction.py @@ -320,3 +320,23 @@ def create_dao_proposal_filter(success_only: bool = True) -> CompositeFilter: ) return CompositeFilter([propose_filter, conclude_filter, vote_filter], logic="OR") + + +def create_dao_charter_update_filter( + contract_pattern: Optional[str] = None, + success_only: bool = True, +) -> ContractCallFilter: + """Create a filter for set-dao-charter transactions. + + Args: + contract_pattern: Optional pattern to match contract identifier + success_only: Only match successful transactions + + Returns: + ContractCallFilter: Configured filter for set-dao-charter + """ + return ContractCallFilter( + method="set-dao-charter", + contract_pattern=contract_pattern or r".*-dao-charter", + success_only=success_only, + ) diff --git a/app/services/processing/stacks_chainhook_adapter/utils/template_manager.py b/app/services/processing/stacks_chainhook_adapter/utils/template_manager.py index 4a289588..9d7b2461 100644 --- a/app/services/processing/stacks_chainhook_adapter/utils/template_manager.py +++ b/app/services/processing/stacks_chainhook_adapter/utils/template_manager.py @@ -121,6 +121,7 @@ def get_template_for_transaction_type( "coinbase": "coinbase", # Use coinbase template for tenure change + coinbase blocks "multi-vote-on-action-proposal": "vote-on-action-proposal", # Use single vote template "governance-and-airdrop-multi-tx": "send-many-governance-airdrop", # Use airdrop template + "set-dao-charter": "conclude-action-proposal", # Use conclude template as fallback for charter updates } template_name = type_mapping.get(transaction_type) From ba22b4d9028b9f78051077d3f372ca616d4a8cfa Mon Sep 17 00:00:00 2001 From: Jason Schrader Date: Sun, 19 Oct 2025 20:19:22 -0700 Subject: [PATCH 2/7] feat: Implement DAO charter update handler with database update logic Co-authored-by: aider (openrouter/x-ai/grok-4) --- .../handlers/dao_charter_update_handler.py | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/app/services/integrations/webhooks/chainhook/handlers/dao_charter_update_handler.py b/app/services/integrations/webhooks/chainhook/handlers/dao_charter_update_handler.py index 35ebc47e..f117d7bf 100644 --- a/app/services/integrations/webhooks/chainhook/handlers/dao_charter_update_handler.py +++ b/app/services/integrations/webhooks/chainhook/handlers/dao_charter_update_handler.py @@ -3,8 +3,11 @@ import logging from typing import Any, Dict +from app.backend.factory import backend +from app.backend.models import DAOBase, ExtensionFilter, ContractStatus from app.services.integrations.webhooks.chainhook.handlers.base import ChainhookEventHandler from app.services.integrations.webhooks.chainhook.models import TransactionWithReceipt +from app.services.integrations.webhooks.dao.models import ContractType, ExtensionsSubtype from app.services.processing.stacks_chainhook_adapter.parsers.clarity import ClarityParser @@ -58,8 +61,30 @@ async def handle_transaction(self, transaction: TransactionWithReceipt) -> None: f"New charter length: {len(new_charter)}" ) - # TODO: Update the DAO charter in the backend database. - # Example: Use backend.update_dao(dao_id, DAOBase(charter=new_charter)) - # We'd need to map dao_principal to dao_id (e.g., via backend.get_dao_by_principal) - # Stub for now: - pass # Replace with actual backend update call + # Query for DAO ID via extensions + ext_filter = ExtensionFilter( + contract_principal=dao_principal, + type=ContractType.EXTENSIONS.value, + subtype=ExtensionsSubtype.DAO_CHARTER.value, + status=ContractStatus.DEPLOYED + ) + extensions = backend.list_extensions(ext_filter) + if not extensions or not extensions[0].dao_id: + self.logger.error(f"No matching DAO found for principal {dao_principal}") + return + + dao_id = extensions[0].dao_id + + # Optional: Validate previous_charter + current_dao = backend.get_dao(dao_id) + if current_dao and current_dao.charter != previous_charter: + self.logger.warning("Charter mismatch, possible race condition - skipping") + return + + # Update the DAO + update_data = DAOBase(charter=new_charter) + updated_dao = backend.update_dao(dao_id, update_data) + if updated_dao: + self.logger.info(f"Successfully updated DAO {dao_id} with new charter") + else: + self.logger.error(f"Failed to update DAO {dao_id}") From 2b6a37575318d731fed8e9d70666b440a725ba93 Mon Sep 17 00:00:00 2001 From: Jason Schrader Date: Sun, 19 Oct 2025 20:21:35 -0700 Subject: [PATCH 3/7] fix: Check transaction success before updating DAO charter Co-authored-by: aider (openrouter/x-ai/grok-4) --- .../chainhook/handlers/dao_charter_update_handler.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/services/integrations/webhooks/chainhook/handlers/dao_charter_update_handler.py b/app/services/integrations/webhooks/chainhook/handlers/dao_charter_update_handler.py index f117d7bf..70817cdb 100644 --- a/app/services/integrations/webhooks/chainhook/handlers/dao_charter_update_handler.py +++ b/app/services/integrations/webhooks/chainhook/handlers/dao_charter_update_handler.py @@ -29,6 +29,9 @@ def can_handle_transaction(self, transaction: TransactionWithReceipt) -> bool: if not hasattr(tx_metadata, "kind") or tx_metadata.kind.type != "ContractCall": return False + if not tx_metadata.success: + return False + contract_data = tx_metadata.kind.data method = getattr(contract_data, "method", "") contract_id = getattr(contract_data, "contract_identifier", "") @@ -38,6 +41,11 @@ def can_handle_transaction(self, transaction: TransactionWithReceipt) -> bool: async def handle_transaction(self, transaction: TransactionWithReceipt) -> None: """Handle the charter update transaction.""" tx_data = self.extract_transaction_data(transaction) + + if not tx_data["tx_metadata"].success: + self.logger.warning("Transaction failed, skipping charter update") + return + # Find the print event (smart_contract_log) print_events = [ event for event in tx_data["tx_metadata"].receipt.events From d18d78fd5819c5e533e6e3f85ed39c077033cc65 Mon Sep 17 00:00:00 2001 From: Jason Schrader Date: Sun, 19 Oct 2025 20:27:56 -0700 Subject: [PATCH 4/7] fix: Correct DAO charter update and template selection for chainhook Co-authored-by: aider (openrouter/x-ai/grok-4) --- chainhook-data/set-dao-charter.json | 119 ++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 chainhook-data/set-dao-charter.json diff --git a/chainhook-data/set-dao-charter.json b/chainhook-data/set-dao-charter.json new file mode 100644 index 00000000..242d43fa --- /dev/null +++ b/chainhook-data/set-dao-charter.json @@ -0,0 +1,119 @@ +{ + "apply": [ + { + "block_identifier": { + "hash": "0xbd3d9e5acea30fdedeea3a7ed6730c8aa774b42e0cc61700f883a8754ad46df0", + "index": 3608542 + }, + "metadata": { + "bitcoin_anchor_block_identifier": { + "hash": "0x1cd55ccc77a67c9e146a2011e59e0809f0f5f6aa2b0f48e0b2024f15a805a1dd", + "index": 102928 + }, + "block_time": 1760769017, + "confirm_microblock_identifier": null, + "cycle_number": null, + "pox_cycle_index": 4751, + "pox_cycle_length": 20, + "pox_cycle_position": 8, + "reward_set": null, + "signer_bitvec": "013500000027ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1f", + "signer_public_keys": [ + "0x02e8620935d58ebffa23c260f6917cbd0915ea17d7a46df17e131540237d335504", + "0x036a44f61d85efa844b42475f107b106dc8fb209ae27813893c3269c59821e0333" + ], + "signer_signature": [ + "00108b3efa3a9f72631f5a9e1f7de9f9995f8b00117385bcd54e931fa9875ab78d599f33c1300fe49199e3cbc854130d68fe10ec26748e92c7bc10fe8d7d3de631", + "0015c9a9fab6329b2c4f407c490a3c7ba02f2db8d418608e3e268482a5cf332b2e031f02dd881f3891e6ae941ad78b76cc8163cc484e8426868001cc433d1698d8" + ], + "stacks_block_hash": "0xbd3d9e5acea30fdedeea3a7ed6730c8aa774b42e0cc61700f883a8754ad46df0", + "tenure_height": 92146 + }, + "parent_block_identifier": { + "hash": "0x51ecee55299a282deef7351659ea37ce28b5975710f54b1c153b2f8f9595d758", + "index": 3608541 + }, + "timestamp": 1760769017, + "transactions": [ + { + "metadata": { + "description": "invoked: ST2Q77H5HHT79JK4932JCFDX4VY6XA3Y1F61A25CD.aitest4-dao-charter::set-dao-charter(u\"aibtc is technocapital acceleration\")", + "execution_cost": { + "read_count": 18, + "read_length": 13091, + "runtime": 481904, + "write_count": 2, + "write_length": 203 + }, + "fee": 217, + "kind": { + "data": { + "args": [ + "u\"aibtc is technocapital acceleration\"" + ], + "contract_identifier": "ST2Q77H5HHT79JK4932JCFDX4VY6XA3Y1F61A25CD.aitest4-dao-charter", + "method": "set-dao-charter" + }, + "type": "ContractCall" + }, + "nonce": 780, + "position": { + "index": 0 + }, + "raw_tx": "0x80800000000400ae73c4b18e8e994c8918a4c7b7a4df8dd50fc1790000000000000212000000000000031f00016a5ab57ed3a3ea199fcc2e56cd7a86298da86243b109183be6aca465d0961f8d2fceea826875ac56d2906388efa39135833b1268ab3f745a83f0a2728aa81d66030100000000021aa372c8d8b2239bb9339c38c8f136d273b7134d821d6661636573332d616374696f6e2d70726f706f73616c2d766f74696e6718636f6e636c7564652d616374696f6e2d70726f706f73616c000000020100000000000000000000000000000021061aa372c8d8b2239bb9339c38c8f136d273b7134d821a6661636573332d616374696f6e2d73656e642d6d657373616765", + "receipt": { + "contract_calls_stack": [], + "events": [ + { + "data": { + "contract_identifier": "ST2Q77H5HHT79JK4932JCFDX4VY6XA3Y1F61A25CD.aitest4-dao-charter", + "topic": "print", + "value": { + "notification": "aitest4-dao-charter/set-dao-charter", + "payload": { + "burnHeight": 102928, + "charter": "aibtc is technocapital acceleration", + "contractCaller": "ST2Q77H5HHT79JK4932JCFDX4VY6XA3Y1F61A25CD", + "createdAt": 3608542, + "dao": "ST2Q77H5HHT79JK4932JCFDX4VY6XA3Y1F61A25CD.aitest4-dao-charter", + "previousCharter": "# CHARTER\\n\\n## MISSION\\n\\nAccelerate technocapitalism for human prosperity.\\n\\n## VALUES\\n\\nOur network believes that:\\n\\n- Technocapitalism leads to abundance.\\n- Deceleration leads to impoverishment.\\n- It is morally sound to accelerate technocapital.\\n\\n## COLLECTIVE ACTION\\n\\nThe AIBTC network collectively works towards a Current Task; a single, measurable directive. All contributions, evaluations, and payouts must advance the Current Task.\\n\\n## THE CURRENT TASK\\n\\nCreate engaging technocapital acceleration memes.\\n\\nQuote post the canonical Current Task from @aibtcdev on X with original technocapital acceleration memes that tastefully include an AIBTC watermark.\\n\\nAIBTC watermarks: https://github.com/aibtcdev/branding/tree/main/watermarks\\n\\n## GUIDELINES\\n\\nIn pursuit of our Mission and the Current Task, we adhere to the following Guidelines:\\n\\n- **Presidential Rule:** A President may be appointed and holds sole authority to issue a new Current Task and apply changes to this Charter.\\n- **Canonical Task Post:** All contributions must directly quote post or reply to the canonical X post that established the Current Task and clearly advance its directive.\\n- **Eligibility:** Only verified members holding and blue-check verified on X.com may submit contributions or receive rewards.\\n- **Completed Work:** Only finished, publicly verifiable work is eligible; drafts or promises are rejected.\\n- **Approval & Reward:** Agent approval is required; approved work earns BTC with onchain payouts and receipts.\\n- **Anti-Spam:** A small submission bond is required; failed entries forfeit it.\\n- **Block Rhythm:** Maximum one contribution approval per Bitcoin block.\\n- **Composability:** Smart contracts / extensions, if any, must execute via contribution approvals or rejections.\\n- **Safety:** No plagiarism, doxxing, or illegal content.\\n\\n## BENCHMARKS\\n\\nWe measure ongoing network health with the following benchmarks:\\n\\n- **Adoption:** >= 75% of circulating tokens cast votes per 12,000 BTC blocks.\\n- **Growth:** + >=10% unique contribution earners per 12,000 BTC blocks.\\n- **Retention:** >= 40% of monthly contribution earners return every 4,000 BTC block period.\\n- **Throughput:** 30-90 per contribution approvals per every 144 contributions submitted.\\n- **Credibility:** >= 99% of contribution approvals committed within 3 BTC blocks.", + "txSender": "ST2Q77H5HHT79JK4932JCFDX4VY6XA3Y1F61A25CD", + "version": 2 + } + } + }, + "position": { + "index": 0 + }, + "type": "SmartContractEvent" + } + ], + "mutated_assets_radius": [], + "mutated_contracts_radius": [ + "ST2Q77H5HHT79JK4932JCFDX4VY6XA3Y1F61A25CD.aitest4-dao-charter" + ] + }, + "result": "(ok true)", + "sender": "ST2Q77H5HHT79JK4932JCFDX4VY6XA3Y1F61A25CD", + "sponsor": null, + "success": true + }, + "operations": [], + "transaction_identifier": { + "hash": "0xd34eacbd93c614c1ee6e5dc1f1331ea731d90e9113a441bc3e00e9b51ae11fb7" + } + } + ] + } + ], + "chainhook": { + "is_streaming_blocks": false, + "predicate": { + "equals": 3608542, + "scope": "block_height" + }, + "uuid": "778568f2-2a7f-4b58-a52c-9b1810abeb0a" + }, + "events": [], + "rollback": [] +} From 43b70af769432ac1f235bea30a51d3f4cbf26567 Mon Sep 17 00:00:00 2001 From: Jason Schrader Date: Sun, 19 Oct 2025 20:29:30 -0700 Subject: [PATCH 5/7] fix: Remove charter from DAO model, using description instead Co-authored-by: aider (openrouter/x-ai/grok-4) --- app/backend/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/backend/models.py b/app/backend/models.py index 786b763c..f7bd74e4 100644 --- a/app/backend/models.py +++ b/app/backend/models.py @@ -318,7 +318,6 @@ class DAOBase(CustomBaseModel): description: Optional[str] = None is_deployed: Optional[bool] = False is_broadcasted: Optional[bool] = False - charter: Optional[str] = None # New field for storing DAO charter text class DAOCreate(DAOBase): From 3666f68a3faf656cef82ef16c35521323e20cef2 Mon Sep 17 00:00:00 2001 From: Jason Schrader Date: Sun, 19 Oct 2025 20:31:16 -0700 Subject: [PATCH 6/7] chore: format and lint --- .../handlers/dao_charter_update_handler.py | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/app/services/integrations/webhooks/chainhook/handlers/dao_charter_update_handler.py b/app/services/integrations/webhooks/chainhook/handlers/dao_charter_update_handler.py index 70817cdb..63a6922b 100644 --- a/app/services/integrations/webhooks/chainhook/handlers/dao_charter_update_handler.py +++ b/app/services/integrations/webhooks/chainhook/handlers/dao_charter_update_handler.py @@ -1,14 +1,18 @@ """Handler for DAO charter update transactions.""" -import logging -from typing import Any, Dict - from app.backend.factory import backend from app.backend.models import DAOBase, ExtensionFilter, ContractStatus -from app.services.integrations.webhooks.chainhook.handlers.base import ChainhookEventHandler +from app.services.integrations.webhooks.chainhook.handlers.base import ( + ChainhookEventHandler, +) from app.services.integrations.webhooks.chainhook.models import TransactionWithReceipt -from app.services.integrations.webhooks.dao.models import ContractType, ExtensionsSubtype -from app.services.processing.stacks_chainhook_adapter.parsers.clarity import ClarityParser +from app.services.integrations.webhooks.dao.models import ( + ContractType, + ExtensionsSubtype, +) +from app.services.processing.stacks_chainhook_adapter.parsers.clarity import ( + ClarityParser, +) class DAOCharterUpdateHandler(ChainhookEventHandler): @@ -48,8 +52,10 @@ async def handle_transaction(self, transaction: TransactionWithReceipt) -> None: # Find the print event (smart_contract_log) print_events = [ - event for event in tx_data["tx_metadata"].receipt.events - if event.type == "SmartContractEvent" and "print" in event.data.get("topic", "") + event + for event in tx_data["tx_metadata"].receipt.events + if event.type == "SmartContractEvent" + and "print" in event.data.get("topic", "") ] if not print_events: self.logger.warning("No print event found in set-dao-charter transaction") @@ -74,11 +80,13 @@ async def handle_transaction(self, transaction: TransactionWithReceipt) -> None: contract_principal=dao_principal, type=ContractType.EXTENSIONS.value, subtype=ExtensionsSubtype.DAO_CHARTER.value, - status=ContractStatus.DEPLOYED + status=ContractStatus.DEPLOYED, ) extensions = backend.list_extensions(ext_filter) if not extensions or not extensions[0].dao_id: - self.logger.error(f"No matching DAO found for principal {dao_principal}") + self.logger.error( + f"No matching DAO found for principal {dao_principal}" + ) return dao_id = extensions[0].dao_id @@ -86,13 +94,17 @@ async def handle_transaction(self, transaction: TransactionWithReceipt) -> None: # Optional: Validate previous_charter current_dao = backend.get_dao(dao_id) if current_dao and current_dao.charter != previous_charter: - self.logger.warning("Charter mismatch, possible race condition - skipping") + self.logger.warning( + "Charter mismatch, possible race condition - skipping" + ) return # Update the DAO update_data = DAOBase(charter=new_charter) updated_dao = backend.update_dao(dao_id, update_data) if updated_dao: - self.logger.info(f"Successfully updated DAO {dao_id} with new charter") + self.logger.info( + f"Successfully updated DAO {dao_id} with new charter" + ) else: self.logger.error(f"Failed to update DAO {dao_id}") From e5ae30cb983c6b0fc106d5e2aea30b4058344eb9 Mon Sep 17 00:00:00 2001 From: Jason Schrader Date: Sun, 19 Oct 2025 20:32:33 -0700 Subject: [PATCH 7/7] fix: Use dedicated template for set-dao-charter transaction type Co-authored-by: aider (openrouter/x-ai/grok-4) --- .../stacks_chainhook_adapter/utils/template_manager.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/services/processing/stacks_chainhook_adapter/utils/template_manager.py b/app/services/processing/stacks_chainhook_adapter/utils/template_manager.py index 9d7b2461..9387b9eb 100644 --- a/app/services/processing/stacks_chainhook_adapter/utils/template_manager.py +++ b/app/services/processing/stacks_chainhook_adapter/utils/template_manager.py @@ -83,6 +83,7 @@ def load_templates(self): "send-many-stx-airdrop": "send-many-stx-airdrop.json", "vote-on-action-proposal": "vote-on-action-proposal.json", "coinbase": "coinbase-block.json", + "set-dao-charter": "set-dao-charter.json", } for template_name, filename in template_files.items(): @@ -121,17 +122,13 @@ def get_template_for_transaction_type( "coinbase": "coinbase", # Use coinbase template for tenure change + coinbase blocks "multi-vote-on-action-proposal": "vote-on-action-proposal", # Use single vote template "governance-and-airdrop-multi-tx": "send-many-governance-airdrop", # Use airdrop template - "set-dao-charter": "conclude-action-proposal", # Use conclude template as fallback for charter updates + "set-dao-charter": "set-dao-charter", } template_name = type_mapping.get(transaction_type) if template_name and template_name in self.templates: return deepcopy(self.templates[template_name]) - # Fallback to a generic template (use conclude-action-proposal as base) - if "conclude-action-proposal" in self.templates: - return deepcopy(self.templates["conclude-action-proposal"]) - return None def populate_template(