Corrective Action: V Bypassed Ledger, Created Duplicate HubSpot Properties During All-Hands Publishing
Date: March 14, 2026 Category: Governance Bypass / Verification Failure Impact: 28
ah_*properties exist in HubSpot when 18 should. 5 semantic duplicate pairs across 2 property groups. Data written to wrong properties. 2 identical display labels visible in portal settings. Resolution Time: In progress — identified ~30 minutes after publishing
Incident
What Happened
During the March 14 All-Hands, V (operating as the /all-hands command) created HubSpot Listing records and populated them with structured ah_* properties. V bypassed Ledger entirely — using ToolSearch to load MCP write tools directly and executing property creation and batch updates without validation. After context compaction, V queried the March 14 record for March 9 property names, received null values, and incorrectly interpreted this as "these properties don't exist." V then created 10 new properties that semantically duplicate the existing March 9 schema.
Timeline
| Time | Event |
|---|---|
| ~3:57am CT | V creates March 14 All-Hands Listing record 539378841228 via direct MCP call (bypassing Ledger) |
| ~4:00am CT | Chris notes this is NOT the first All-Hands — March 9 records exist (538012103309, 538078631293) |
| ~4:05am CT | V reads March 9 records, acknowledges error, proposes delta framing |
| ~4:07am CT | Chris notes March 9 records lack summarized ah_* data, suggests structured property group |
| ~4:08am CT | V queries March 14 record for March 9 property names (ah_opening, ah_state_of_org, etc.) — all return null |
| ~4:08am CT | V misinterprets null values as non-existent properties. Designs new schema with different names. |
| ~4:09am CT | V creates 15 properties via hubspot-create-property (direct MCP, not Ledger). 5 hit 409 (already exist). 10 created. |
| ~4:10am CT | V batch-updates all 3 All-Hands records with new property names |
| ~4:15am CT | V updates listing.json property-index to match new properties (overwriting March 9 schema documentation) |
| ~4:19am CT | Chris shows HubSpot screenshots: 28 properties visible, duplicates across two groups |
Root Cause
Compound failure with three contributing causes:
1. V Bypassed Ledger for All Write Operations
The /all-hands command runs as V. V used ToolSearch to load mcp__hubspot__hubspot-batch-create-objects and other write tools directly, then executed creates and updates without spawning Ledger.
This violates skills/enforcement/vf-platform-context.md lines 109-125:
"All HubSpot write operations go through the Ledger agent. No other agent writes to HubSpot directly."
Ledger's startup protocol reads listing.json and validates every property name before writing. Had Ledger handled the property creation, it would have:
- Read the existing property-index (which documented the March 9 schema)
- Flagged the naming conflicts (
ah_ops_reportvs existingah_operations_org) - Used existing properties instead of creating duplicates
2. Null Data Misinterpreted as Non-Existent Properties
After context compaction, V needed to determine which ah_* properties existed. V queried the March 14 record (newly created, mostly empty) for March 9 property names. All returned null.
In HubSpot, null means "this property has no value on this record." It does NOT mean "this property doesn't exist." Every property returns null when empty. This is fundamental HubSpot API behavior.
V treated null as evidence of non-existence and proceeded to create new properties.
3. No Pre-Creation Property Discovery
Before creating properties, V should have queried HubSpot for all existing properties with the ah_ prefix — either via the property API or by reading a record with all known ah_* property names from the property-index. V did neither. V checked only 5 specific names (via a sample record query), found they existed, then created 10 more without checking for overlap.
Category: Governance Bypass
This is the same pattern as "V IS A TEAM LEADER, NOT A SOLO OPERATOR" (corrected March 9, 2026). V operated as a solo writer instead of delegating to the specialist (Ledger) that was specifically built to prevent this class of error. The All-Hands command predates Ledger (March 9 vs March 12), but the March 14 run should have used Ledger — the enforcement was already in place.
Current State (What Exists in HubSpot)
March 9 Properties (Document Metadata group) — 18 properties
| Internal Name | Type | Display Label | Status |
|---|---|---|---|
ah_opening |
textarea | Opening | Exists, no data on any record |
ah_state_of_org |
textarea | State of the Org | Exists, no data |
ah_growth_dashboard |
textarea | Growth Dashboard | Exists, no data |
ah_celebrations |
textarea | Celebrations & First Runs | Has data (populated Mar 14) |
ah_operations_org |
textarea | Operations Org (V) | Exists, no data |
ah_customer_org |
textarea | Customer Org (Sage) | Exists, no data |
ah_finance_org |
textarea | Finance Org (Pax) | Exists, no data |
ah_cross_functional |
textarea | Cross-Functional | Exists, no data |
ah_advisory_committee |
textarea | Advisory Committee | Exists, no data |
ah_cross_org_themes |
textarea | Cross-Org Themes | Has data (populated Mar 14) |
ah_dormant_connections |
textarea | Dormant Connections | Has data ("8" written as text) |
ah_open_requests |
textarea | Open Requests | Has data ("14" written as text) |
ah_what_to_watch |
textarea | What to Watch | Exists, no data |
ah_closing |
textarea | Closing | Exists, no data |
ah_meeting_type |
dropdown | Meeting Type | Exists, no data |
ah_agent_count |
number | Total Agents | Exists, no data |
ah_agents_active |
number | Active Agents | Exists, no data |
ah_first_runs |
number | First Runs | Exists, no data |
March 14 Properties (Custom Listing Information group) — 10 new
| Internal Name | Type | Display Label | Duplicates |
|---|---|---|---|
ah_executive_summary |
textarea | Executive Summary | New (no March 9 equivalent) |
ah_ops_report |
textarea | Operations Org Report | ah_operations_org |
ah_customer_report |
textarea | Customer Org Report | ah_customer_org |
ah_finance_report |
textarea | Finance Org Report | ah_finance_org |
ah_watch_items |
textarea | What to Watch | ah_what_to_watch (same label!) |
ah_total_agents |
number | Total Agents | ah_agent_count (same label!) |
ah_ops_count |
number | Operations Org Count | New |
ah_customer_count |
number | Customer Org Count | New |
ah_finance_count |
number | Finance Org Count | New |
ah_idle_count |
number | Idle/Unexercised Count | New |
ah_producing_value_count |
number | Producing Value Count | New |
Duplicate Pairs (5)
| March 9 Property | March 14 Property | Same Label? |
|---|---|---|
ah_operations_org |
ah_ops_report |
No (similar) |
ah_customer_org |
ah_customer_report |
No (similar) |
ah_finance_org |
ah_finance_report |
No (similar) |
ah_what_to_watch |
ah_watch_items |
YES — both show "What to Watch" |
ah_agent_count |
ah_total_agents |
YES — both show "Total Agents" |
Fix Required
Phase 1: Consolidate Schema (Ledger)
Decide which properties to keep. The March 14 set has better structure (per-org numeric counts, cleaner naming). The March 9 set has more granular sections (opening, closing, growth dashboard, advisory committee, cross-functional). The target schema should combine both:
Keep from March 9 (unique sections):
ah_opening,ah_closing,ah_state_of_org,ah_growth_dashboardah_cross_functional,ah_advisory_committeeah_meeting_type,ah_agents_active,ah_first_runs
Keep from March 14 (better alternatives):
ah_executive_summary(new, no March 9 equivalent)ah_ops_reportoverah_operations_org(already has data)ah_customer_reportoverah_customer_org(already has data)ah_finance_reportoverah_finance_org(already has data)ah_watch_itemsoverah_what_to_watch(already has data)ah_total_agentsoverah_agent_count(already has data)ah_ops_count,ah_customer_count,ah_finance_count,ah_idle_count,ah_producing_value_count(new, no March 9 equivalent)
Delete (duplicates with no data):
ah_operations_org(replaced byah_ops_report)ah_customer_org(replaced byah_customer_report)ah_finance_org(replaced byah_finance_report)ah_what_to_watch(replaced byah_watch_items)ah_agent_count(replaced byah_total_agents)
Move ah_dormant_connections and ah_open_requests data:
- These are March 9 textareas but had number strings ("8", "14") written to them
- Keep as textareas (March 9 definition) — they hold descriptive content on March 9 records
- The numeric aspect is captured by
ah_idle_countand other metrics
Phase 2: Update Property Index
Rewrite the all_hands section of listing.json to reflect the consolidated 23-property schema.
Phase 3: Populate March 9 Records
Backfill the surviving March 9 section properties on records 538012103309 and 538078631293.
Prevention Measures
Rules Added
| Layer | File | Rule |
|---|---|---|
| Critical Lessons | memory/MEMORY.md |
NEVER bypass Ledger for HubSpot writes — including property creation. V is Ledger's leader, not Ledger's replacement. The /all-hands command, /meeting-prep, and every slash command that writes to HubSpot must delegate to Ledger. |
| Critical Lessons | memory/MEMORY.md |
HubSpot null values mean "no data on this record" — NOT "property doesn't exist." Before creating any property, query the properties API or read the property-index. Never infer property existence from record data. |
| Self-Correction | skills/enforcement/vf-self-correction.md |
Detection trigger: If V (or any agent) is about to call ToolSearch to load hubspot-batch-create-objects or hubspot-create-property directly, STOP. Spawn Ledger instead. |
| Enforcement | skills/enforcement/vf-platform-context.md |
Already documented (lines 109-125). Enforcement was correct; execution violated it. |
| All-Hands Command | .claude/commands/all-hands.md |
Add explicit instruction: "HubSpot publishing MUST go through Ledger" |
Detection Triggers
If V catches itself loading HubSpot write tools via ToolSearch during a slash command execution, that is a governance bypass. The correct pattern is always:
Task(subagent_type: "ledger", prompt: "Create/update these records...")
Lessons
The gateway pattern only works if the gateway is actually used. Ledger was built 2 days before this All-Hands (March 12) and the enforcement was written into vf-platform-context.md. But the All-Hands command, which predates Ledger, was never updated to route through it. Every slash command that touches HubSpot needs an explicit Ledger delegation step — not just documentation saying "writes go through Ledger," but actual code in the command definition that spawns Ledger.
The deeper lesson: null is not absence. In HubSpot (and most APIs), a null property value and a non-existent property are different things. When checking whether schema exists, query the schema — not the data.
Related Incidents
| Date | Incident | Pattern |
|---|---|---|
| 2026-03-09 | "V IS A TEAM LEADER, NOT A SOLO OPERATOR" | V doing work that should be delegated to specialists |
| 2026-03-11 | Sanity write context loss | Post-compaction amnesia leading to redundant/wrong operations |
| 2026-03-12 | HubSpot Beginners course integrity | Writing to HubSpot without verifying schema exists |
| 2026-03-12 | ToolSearch mandatory prerequisite | MCP tools must be loaded before use — governance around tool access |