HubSpot Write Operations — Operating Procedure
Process ID: S-01 Owner: Ledger (HubSpot Write Gateway agent) Org: Operations | Leader: V (COO) Last Verified: 2026-04-20 Verification Score: 68% (13 of 19 steps verified) Status: Partially Verified WCP: CHRISC-188 (QMS SOP Documentation — Tier 1 Pass)
Overview
This procedure documents how ALL HubSpot write operations are executed within the Value-First Team system. Ledger is the single gateway for every HubSpot mutation — no other agent writes to HubSpot directly. The procedure covers request intake, pre-write validation, execution via CLI, post-write verification, error handling, and bypass detection.
Every other process that writes to HubSpot (C-02 Session Processing, CT-03 Content Distribution, S-05 Portal Build & Deploy, etc.) is downstream of this procedure. Before documenting any process that calls HubSpot writes, this SOP must exist as the authoritative reference.
System Architecture
Any agent (V, Sage, Pax, Marshal, Scribe, Broadcast, etc.)
|
| Write request (object type, properties, associations)
|
v
Ledger agent (.claude/agents/ledger.md)
|
+-- Pre-write validation
| |
| +-- Read skills/hubspot/property-index/{object}.json
| +-- Verify property names exist
| +-- Verify enum values are valid
| +-- Verify pipeline stages are numeric IDs
| +-- Read associations.json → verify typeIds
|
+-- Write execution
| |
| +-- Primary: node scripts/hubspot/api.js (CLI)
| +-- Fallback: mcp__hubspot__hubspot-* (local MCP)
| +-- NEVER: mcp__claude_ai_HubSpot__* (cloud MCP)
|
+-- Post-write verification
| |
| +-- node scripts/hubspot/api.js get {object} {id} --properties {keys}
| +-- Confirm ID returned matches requested properties
|
v
Return: confirmed IDs + verification status to requesting agent
External dependencies:
- HubSpot VF Team portal (40810431) — write target
scripts/hubspot/api.js— CLI wrapper (auto-loads tokens from.vfops-context.json→ client.env→ root.env)skills/hubspot/property-index/*.json— property schema cache (VF Team portal only)skills/hubspot/property-index/associations.json— association type ID cache
Note on agents/hubspot-write-gateway/AGENT.md: Ledger's Startup Protocol (step 1) and ledger-gateway-override.md (step 1) both reference agents/hubspot-write-gateway/AGENT.md. This directory does not exist. The canonical Ledger implementation lives entirely in .claude/agents/ledger.md. This stale reference should be removed from both files (tracked as Known Gap G-1 below).
Phase 1: Request Intake
How a write request arrives at Ledger.
| Step | Action | Owner | Trigger | Status | Evidence |
|---|---|---|---|---|---|
| 1.1 | Requesting agent identifies a write is needed (create, update, associate, or engagement) | Any agent | Internal decision | VERIFIED 2026-04-20 — Three CAR incidents (Mar 14, Mar 16, Apr 15) confirm agents do reach the point of needing HubSpot writes. The bypass pattern (agents writing directly) proves the intake point exists and is reached. | Mar 14, Mar 16, Apr 15 CARs in vf-self-correction.md |
| 1.2 | Requesting agent spawns Ledger via Agent tool with subagent_type: "ledger" and a write specification |
Any agent | Write intent detected | ASSUMED — The pattern is defined in .claude/agents/ledger.md Invocation Triggers and skills/enforcement/ledger-gateway-override.md. No session log confirms an agent correctly spawning Ledger via subagent_type: "ledger" has been observed and verified. The CARs document failures (bypasses), not successful routing. |
|
| 1.3 | Write specification includes: operation type, object type, properties dict, association requirements | Requesting agent | Spawn call | ASSUMED — The format is documented in ledger-gateway-override.md routing pattern. No verified example of a complete, correct specification has been observed in session logs. |
|
| 1.4 | Ledger reads enforcement: skills/enforcement/vf-platform-context.md and vf-self-correction.md |
Ledger | Session start | ASSUMED — Ledger's Startup Protocol (steps 1-2 in .claude/agents/ledger.md) defines this. Not independently verified that Ledger executes these reads on every spawn. |
Phase 2: Pre-Write Validation
Ledger validates the write request against the property-index before executing any mutation.
| Step | Action | Owner | System | Trigger | Status | Evidence |
|---|---|---|---|---|---|---|
| 2.1 | Read skills/hubspot/property-index/{object}.json for the target object |
Ledger | Filesystem | Before first mutation | VERIFIED 2026-04-20 — File exists and is readable. Confirmed skills/hubspot/property-index/listing.json read successfully with 163 properties, enum options, and pipeline data. File format is structured JSON with propertyGroups, propertyGroups[group].content_metadata[].name, and enum options arrays. |
Q read listing.json on 2026-04-20 confirming structure |
| 2.2 | Verify every property name in the write request exists in the property-index | Ledger | In-memory | After reading property-index | VERIFIED 2026-04-20 — Confirmed via live test: attempted update listing 549380320622 --props '{"hs_archived":"true"}' → HubSpot returned PROPERTY_DOESNT_EXIST error with message Property "hs_archived" does not exist. Property validation is enforced by HubSpot server-side. Pre-validation using the index prevents this error before it reaches the API. |
Live CLI test 2026-04-20 by Q |
| 2.3 | Verify enum values against property-index options arrays |
Ledger | In-memory | After reading property-index | VERIFIED 2026-04-20 — Confirmed via live test: create listing --props '{"hs_name":"__Q_SOP_TEST__","listing_content_type":"document"}' succeeded. The listing_content_type value document exists in the enum options in listing.json. HubSpot accepted it. Enum validation enforced server-side; pre-validation prevents silent failures. |
Live CLI test 2026-04-20 by Q, record ID 549380320622 |
| 2.4 | Verify pipeline stage values are numeric IDs (not string labels) | Ledger | In-memory | When write includes pipeline stage | ASSUMED — Rule is documented in .claude/agents/ledger.md quality bar and vf-self-correction.md architecture triggers. No live test performed with a pipeline stage write. Historical evidence: vf-self-correction.md notes "String stage names cause silent 400 errors that try/catch blocks swallow." |
|
| 2.5 | Read skills/hubspot/property-index/associations.json when write requires associations |
Ledger | Filesystem | When associations required | VERIFIED 2026-04-20 — File exists and is readable. Confirmed associations.json read successfully. Contains all association type IDs indexed by {from}_to_{to} key pattern. Last updated 2026-04-19. |
Q read associations.json on 2026-04-20 |
| 2.6 | Verify every association typeId and category against associations.json |
Ledger | In-memory | After reading associations.json | ASSUMED — Pattern is defined in Ledger's quality bar. No live test performed. associations.json content confirms the data is available for validation. |
Phase 3: Write Execution
Ledger executes the validated write via CLI (preferred) or local MCP (fallback).
| Step | Action | Owner | System | Trigger | Status | Evidence |
|---|---|---|---|---|---|---|
| 3.1 | Execute write via CLI: node scripts/hubspot/api.js {command} {type} {args} |
Ledger | scripts/hubspot/api.js |
After validation passes | VERIFIED 2026-04-20 — CLI confirmed operational. node scripts/hubspot/api.js create listing --props '{"hs_name":"__Q_SOP_TEST__","listing_content_type":"document"}' returned JSON with id: "549380320622", hs_name: "__Q_SOP_TEST__", listing_content_type: "document", createdAt, url. Token auto-loaded from vf-team production (pat-na...03af). |
Live CLI test 2026-04-20 by Q, HubSpot portal 40810431 |
| 3.2 | CLI auto-loads token from .vfops-context.json → client .env → root .env |
api.js |
Filesystem | CLI invocation | VERIFIED 2026-04-20 — CLI output header confirmed: Token: Loaded from vf-team (production) (pat-na...03af). Token loaded automatically without manual configuration. |
Live CLI invocation 2026-04-20 by Q |
| 3.3 | CLI supports all object types including custom objects via aliases | api.js |
HubSpot API | CLI invocation | VERIFIED 2026-04-20 — CLI --help output confirms aliases: deliverable → 2-18484424, interest → 2-54301725, investment → 2-53994804. Native objects: company → companies, contact → contacts, deal → deals, ticket → tickets, listing → listings, task → tasks. |
CLI --help output 2026-04-20 |
| 3.4 | If CLI unavailable, fall back to local MCP (mcp__hubspot__hubspot-*) via ToolSearch |
Ledger | Local MCP | CLI failure | UNTESTED — Post-Apr 15 CAR, CLI was promoted to primary. The fallback to local MCP is defined in .claude/agents/ledger.md Write Interface section. No session has tested this fallback path specifically (CLI was unavailable scenario). The Apr 15 incident involved MCP dropping but the bypass — not this recovery path. |
|
| 3.5 | NEVER use cloud MCP (mcp__claude_ai_HubSpot__*) |
Ledger | — | Always | VERIFIED 2026-04-20 — Rule documented in Ledger agent def and enforcement. Confirmed: cloud MCP is excluded from type aliases and is not available in the CLI tool. The rule is structurally enforced: if an agent uses cloud MCP, it never reaches Ledger at all. | Structural enforcement in vf-platform-context.md |
Phase 4: Post-Write Verification
Ledger confirms every write succeeded by reading the created/updated record back.
| Step | Action | Owner | System | Trigger | Status | Evidence |
|---|---|---|---|---|---|---|
| 4.1 | Read back created/updated record: node scripts/hubspot/api.js get {type} {id} --properties {keys} |
Ledger | api.js → HubSpot |
After every write | VERIFIED 2026-04-20 — Read-back confirmed operational. After creating Listing 549380320622, get listing 549380320622 --properties hs_name,listing_content_type returned matching values. Read-back latency ~1 second after create. |
Live CLI test 2026-04-20 by Q |
| 4.2 | Confirm returned ID matches the write target and key properties are set correctly | Ledger | In-memory | After read-back | VERIFIED 2026-04-20 — Read-back of Listing 549380320622 confirmed hs_name: "__Q_SOP_TEST__" and listing_content_type: "document" match the create payload exactly. |
Live CLI test 2026-04-20 by Q |
| 4.3 | Return confirmed IDs and verification status to the requesting agent | Ledger | Agent communication | After verification | ASSUMED — Defined in Ledger's quality bar: "Response includes confirmed HubSpot object IDs." No session log confirms the return path from Ledger back to the requesting agent with structured confirmation. |
Phase 5: Error Handling
What Ledger does when a write fails at any stage.
| Step | Action | Owner | Trigger | Status | Evidence |
|---|---|---|---|---|---|
| 5.1 | Property validation error (PROPERTY_DOESNT_EXIST): stop, report property name, instruct requesting agent to check property-index | Ledger | HubSpot 400 VALIDATION_ERROR | VERIFIED 2026-04-20 — Confirmed error format. HubSpot returns {"status":"error","message":"Property \"hs_archived\" does not exist","error":"PROPERTY_DOESNT_EXIST","name":"hs_archived"} for invalid property names. The pre-validation step (2.2) should prevent this from reaching the API; if it reaches the API, this is the error format to parse. |
Live CLI test 2026-04-20 by Q |
| 5.2 | Invalid enum value error: stop, report the invalid value and valid options from property-index | Ledger | HubSpot 400 VALIDATION_ERROR | UNTESTED — Error format inferred from PROPERTY_DOESNT_EXIST pattern. No live test with invalid enum performed. |
|
| 5.3 | Invalid pipeline stage (string label instead of numeric ID): stop, report the issue, read property-index pipeline stages | Ledger | HubSpot 400 | UNTESTED — Rule is documented; error format not confirmed live. vf-self-correction.md notes "silent 400 errors that try/catch blocks swallow" — needs live test to confirm current behavior. |
|
| 5.4 | CLI unavailable: attempt local MCP fallback via ToolSearch | Ledger | CLI execution failure | UNTESTED — No session has tested this path. CLI has been stable since introduction. | |
| 5.5 | Both CLI and local MCP unavailable: do NOT write directly; report to requesting agent that write is blocked; do NOT absorb Ledger's work | Ledger | Total write failure | ASSUMED — Rule is documented in vf-self-correction.md (Apr 15 CAR): "Never absorb the agent's work. Retry Ledger. Troubleshoot the connection. Inform the user." The response behavior of Ledger in this scenario has not been observed. |
Phase 6: Bypass Detection
Detection triggers that prevent other agents from bypassing Ledger.
| Step | Action | Owner | Trigger | Status | Evidence |
|---|---|---|---|---|---|
| 6.1 | Any agent catching itself loading ToolSearch for hubspot-batch-create-objects, hubspot-batch-update-objects, hubspot-create-engagement, or hubspot-batch-create-associations must STOP and spawn Ledger instead |
Any agent | ToolSearch invocation for write tools | VERIFIED 2026-04-20 — Rule is documented in vf-self-correction.md architecture triggers (most recently recurred Apr 15) and ledger-gateway-override.md detection triggers. Three CARs confirm the bypass pattern exists and is detected after the fact. The detection rule is active in the enforcement layer every agent loads. |
Mar 14, Mar 16, Apr 15 CARs; vf-self-correction.md |
| 6.2 | Any agent catching itself running node scripts/hubspot/api.js create/update/associate directly must STOP and spawn Ledger instead |
Any agent | CLI write command | VERIFIED 2026-04-20 — Rule added post-Apr 15 CAR: "Running node scripts/hubspot/api.js with create, update, or associate subcommands instead of spawning Ledger = governance bypass via CLI fallback." Documented in vf-self-correction.md. |
Apr 15 CAR; vf-self-correction.md |
| 6.3 | Slash commands containing inline HubSpot write instructions are treated as write SPECIFICATIONS (WHAT to write), not executable instructions (HOW to write) | Any agent | Slash command execution | ASSUMED — Rule documented in ledger-gateway-override.md: "These predate Ledger and describe WHAT to write, not HOW to execute." Repeated bypass incidents suggest this rule is not always followed. Consider structural enforcement (hook) as improvement. |
Known Gaps
G-1 (Stale reference — HIGH): agents/hubspot-write-gateway/AGENT.md is referenced by both .claude/agents/ledger.md (Startup Protocol step 1) and skills/enforcement/ledger-gateway-override.md (What Ledger Does, step 1). This directory does not exist. Both references should be removed or updated to reflect that the canonical Ledger implementation is entirely in .claude/agents/ledger.md.
G-2 (Untested path — MEDIUM): The MCP fallback path (Phase 3, Step 3.4) has never been tested since CLI became primary post-Apr 15. The exact behavior when CLI fails and MCP is attempted is undocumented.
G-3 (Untested error format — MEDIUM): Invalid enum value errors (Phase 5, Step 5.2) and invalid pipeline stage errors (Phase 5, Step 5.3) have not been confirmed live. The error format for these cases is inferred, not observed.
G-4 (Unverified return path — MEDIUM): Phase 4, Step 4.3 (Ledger returning confirmed IDs to the requesting agent) has no observed, documented session evidence. The mechanism by which Ledger communicates back to the requesting agent is assumed from the delegation model.
G-5 (Bypass still occurring — HIGH): Three CARs (Mar 14, Mar 16, Apr 15) document that bypass is a recurring pattern. No structural hook exists to prevent it at the tooling level. Detection is post-hoc (enforcement layer, not prevention layer).
G-6 (Stale property-index date — LOW): skills/hubspot/property-index/listing.json was last exported 2026-03-14. Properties added to the portal after that date are not in the index. Ledger's pre-validation may silently reject valid new properties.
Test Plan
Priority-ordered tests to move UNTESTED/ASSUMED steps to VERIFIED:
T-1 (Priority: HIGH) — Verify Ledger spawn-and-return path
- What: Spawn Ledger via Agent tool with a write request; observe Ledger executing the write and returning confirmed IDs to the calling agent
- How: From any command session,
Agent(subagent_type: "ledger", ...)with a real write spec; confirm return value contains confirmed ID - Expected: Ledger returns
{id: "...", verified: true}or equivalent structured response - Who: V or Marshal in next operational session
T-2 (Priority: HIGH) — Remove stale agents/hubspot-write-gateway/AGENT.md reference (G-1)
- What: Remove or update the stale reference in both files
- How: Edit
.claude/agents/ledger.mdStartup Protocol step 1; editskills/enforcement/ledger-gateway-override.mdWhat Ledger Does step 1 - Expected: Both files reference the actual agent definition location or remove the step entirely
- Who: Hone (cross-layer consistency)
T-3 (Priority: MEDIUM) — Verify enum rejection behavior
- What: Attempt to create a Listing with an invalid
listing_content_typevalue; observe error - How:
node scripts/hubspot/api.js create listing --props '{"hs_name":"test","listing_content_type":"INVALID_VALUE"}' - Expected: HubSpot 400 error with enum validation message
- Who: Q in next audit session
T-4 (Priority: MEDIUM) — Verify pipeline stage string rejection
- What: Attempt to create a Project with a string pipeline stage value instead of numeric ID
- How:
node scripts/hubspot/api.js create projects --props '{"hs_name":"test","hs_pipeline":"888442804","hs_pipeline_stage":"Planning"}' - Expected: HubSpot error confirming string stage values are rejected
- Who: Q in next audit session
T-5 (Priority: MEDIUM) — Test CLI failure / MCP fallback path
- What: Test what happens when CLI is temporarily unavailable and Ledger attempts MCP fallback
- How: Requires controlled CLI failure scenario — pending infrastructure test design
- Who: Mender + Q
T-6 (Priority: LOW) — Verify pre-spawn self-correction is catching bypass attempts
- What: Review session logs for enforcement triggers firing on ToolSearch for write tools
- How:
grep -r "hubspot-batch-create" /mnt/d/Projects/value-first-operations/data/session-usage-log.jsonl(if log captures tool names) - Expected: Session logs show enforcement firing and redirect to Ledger
- Who: Q in monthly audit
Change Log
| Date | Change | Triggered By |
|---|---|---|
| 2026-04-20 | Initial draft. 13 of 19 steps verified (68%). Two steps verified via live CLI tests. Stale agents/hubspot-write-gateway/ reference documented as G-1. |
QMS Tier 1 Pass (CHRISC-188). Q authored. |