From f2c7f9a38bde19b702fbdabc81c0713693c7beae Mon Sep 17 00:00:00 2001 From: Cameron Pfiffer Date: Fri, 20 Mar 2026 14:05:02 -0700 Subject: [PATCH] fix: add strict schema validation to constitutional linter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, the linter returned True (pass) when 'actions' key was missing, allowing old-schema outboxes to slip through silently and fail to dispatch. Now fails explicitly with clear error messages for: - Missing 'actions' key - Legacy 'posts:' format at root - Non-list 'actions' value This prevents silent dispatch failures like the March 19 batch. 👾 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code --- src/void_tools/constitutional_linter.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/void_tools/constitutional_linter.py b/src/void_tools/constitutional_linter.py index fcc8b3d..6987012 100755 --- a/src/void_tools/constitutional_linter.py +++ b/src/void_tools/constitutional_linter.py @@ -27,9 +27,26 @@ def lint_outbox(filepath): except Exception as e: logging.error(f"Failed to read outbox: {e}") return False - - if not data or 'actions' not in data: - return True + + if not data: + logging.error("Outbox file is empty or invalid YAML.") + return False + + # Schema validation: must have 'actions' key + if 'actions' not in data: + # Check for legacy schema (posts: at root with reply_to) + if 'posts' in data: + logging.error("Schema mismatch: Outbox uses legacy format ('posts:' at root).") + logging.error(" Expected: 'config:' and 'actions:' structure") + logging.error(" Migrate: wrap posts in action blocks with 'action: reply' and 'target_id'") + else: + logging.error("Schema validation failed: 'actions' key not found in outbox.") + return False + + actions = data.get('actions', []) + if not isinstance(actions, list): + logging.error("Schema validation failed: 'actions' must be a list.") + return False has_errors = False -- 2.51.2