Corrective Action: Ledger Asserted Non-Existent API Limitation, Then Failed to Execute Clean CLI Commands by Self-Delegating

Corrective Action: Ledger Asserted Non-Existent API Limitation, Then Failed to Execute Clean CLI Commands by Self-Delegating

Date: 2026-04-29 Category: Confident Wrong + Execution Drop (Tier 1 process — HubSpot Write Operations, S-01) Impact: Two failure modes inside a single Collective referral schema-extension session. (1) Ledger asserted a non-existent HubSpot API limitation, proposed a manual UI workaround for Chris, and pre-emptively updated skills/hubspot/property-index/interest.json to reflect the intended schema state — which would have introduced index-vs-live drift had Trellis not verified. (2) Ledger replied to a clean four-command execution request with meta-prose ("Now I'll spawn Ledger...") instead of executing, dropping all four commands; subsequent retries surfaced a misleading CLI category-flag error that masked the real cause. No HubSpot data corruption (Trellis verified before any user-facing claim). Resolution Time: ~25 minutes inside the originating Trellis session (verify endpoint → direct PATCH succeeded → re-spawn with explicit executor framing → diagnose --category USER_DEFINED flag → final association write). Originating Session: Trellis, 2026-04-29, Collective referral schema-extension (interest_type = referral enum addition + referred_via Contact↔Interest association on two live Interest records).


Incident

What Happened

Trellis spawned Ledger to extend the live HubSpot interest_type enum on the Interest custom object (2-54301725) by adding a referral value. The follow-up work required two update interest calls and two associate calls to stamp the new referred_via association on two Interest records.

Failure 1 — Asserted limitation that doesn't exist. Ledger returned: "HubSpot does not expose REST API endpoints for updating custom object property enums. The enum value must be added manually via the HubSpot UI." Ledger then proposed a manual UI step for Chris and pre-emptively updated skills/hubspot/property-index/interest.json to reflect the new value as if the schema extension had succeeded. No live API call was attempted, no documentation was cited, no GET was issued against the property to check modificationMetadata.readOnlyDefinition. Trellis verified directly: the property is editable (readOnlyDefinition: false), and PATCH /crm/v3/properties/2-54301725/interest_type with an options array is a documented standard endpoint. A subsequent direct PATCH call (executed via the next Ledger spawn) succeeded — the limitation never existed.

Failure 2 — Spawn loop instead of execute. After the schema PATCH succeeded, Trellis spawned Ledger to run four straightforward CLI commands (two update interest calls, two associate calls). Ledger's response was meta-prose beginning "Now I'll spawn Ledger to execute these writes:" — Ledger attempted to delegate to itself rather than execute via Bash. The four commands were not run. Trellis verified live state via direct GET — both Interest records were still at interest_type = content_engagement, no associations.

A second Ledger spawn with an explicit "YOU ARE THE EXECUTOR. DO NOT DELEGATE." prompt did execute the commands. Two of four succeeded; two failed with a misleading MEETING_EVENT=33251 is not valid error. Root cause of that error: missing --category USER_DEFINED flag (the scripts/hubspot/api.js associate subcommand defaults to --category HUBSPOT_DEFINED, which is wrong for the custom referred_via association label, typeId 675). A third Ledger spawn with the corrected flag closed it out.

Timeline

Time Event
Session T+0 Trellis spawns Ledger #1 — extend interest_type enum to add referral.
Ledger #1 returns Asserts non-existent API limitation. Proposes manual UI step. Pre-updates skills/hubspot/property-index/interest.json to reflect intended state.
Trellis verifies GET /crm/v3/properties/2-54301725/interest_typemodificationMetadata.readOnlyDefinition: false. Documented PATCH endpoint exists. Limitation does not exist.
Trellis spawns Ledger #2 With explicit instruction to PATCH the property. Succeeds. Live enum now includes referral.
Trellis spawns Ledger #3 Four CLI commands: two update interest, two associate.
Ledger #3 returns Meta-prose: "Now I'll spawn Ledger to execute these writes." Does not execute. Four commands dropped.
Trellis verifies Direct GET on both Interest records — interest_type = content_engagement, no referred_via association. Confirms non-execution.
Trellis spawns Ledger #4 Explicit prompt: "YOU ARE THE EXECUTOR. DO NOT DELEGATE."
Ledger #4 executes 2 of 4 succeed (the two update interest calls). 2 of 4 fail with MEETING_EVENT=33251 is not valid.
Trellis diagnoses The associate subcommand defaulted to --category HUBSPOT_DEFINED. The referred_via label is USER_DEFINED. Category/typeId mismatch produces a misleading error referencing an unrelated object type.
Trellis spawns Ledger #5 Same two associate calls with --category USER_DEFINED. Both succeed. Live state now matches intent.

Process Risk

S-01 HubSpot Write Operations is a Tier 1 (critical) process per docs/quality/process-register.md (Operating Procedure: docs/operating-procedures/hubspot-write-operations.md, current verification score 68%). The Ledger gateway hook (.claude/hooks/ledger-gateway-enforcer.sh) correctly blocked Trellis's attempt to bypass when Ledger #3 failed to execute — that enforcement worked as designed. The failure is upstream of the gateway: Ledger itself is producing confident-but-wrong outputs and dropping execution on clean tasks. No data corruption resulted because Trellis verified live state before claiming completion, applying the data tier hierarchy correctly (Tier 1 canonical, not Tier 2 synthesis).


Root Cause

Failure 1 — Confident assertion without testing

Ledger produced a platform-capability claim ("HubSpot does not expose REST endpoints for ...") with no test, no GET against the property to read modificationMetadata, and no citation. The claim was wrong. Worse, Ledger then acted on the false claim by updating the property-index file to reflect the intended state — a write that, if accepted, would have created drift between the canonical index and the live portal. Every downstream agent reading interest.json would then operate on a false map until someone caught the discrepancy.

The structural gap: Ledger's agent definition has Validation (Before Every Write) requirements (lines 106-112 of .claude/agents/ledger.md) but no requirement that platform-capability claims be backed by a tested API call in the same session. A capability claim is functionally a write decision — "I will not write because the platform can't" — yet the validation ladder in Ledger's contract does not cover it.

Failure 2 — Self-delegation loop on concrete commands

Ledger received a request containing four concrete bash commands and produced meta-prose about spawning Ledger instead of executing. This is structurally the same anti-pattern as a leader spawning the agent that is the executor — except here the leader is the executor, and the spawn target would have been itself. The four commands were dropped. Trellis only caught it because she verified live state.

The structural gap: Ledger's agent definition does not state that Ledger executes via its own Bash tool when given concrete commands. It states the gateway routing (other agents spawn Ledger), the validation contract, and the CLI reference — but not the executor identity. An agent that doesn't know it is the terminal executor will continue to delegate to itself, especially under prompts that contain meta-language about spawning.

Failure 3 (compounding) — CLI flag default is unsafe for custom labels

The scripts/hubspot/api.js associate command defaults --category to HUBSPOT_DEFINED. For any custom association label (anything created via the v4 schema API — the referred_via label, typeId 675, falls in this class), the correct value is USER_DEFINED. The mismatch produces a misleading HubSpot error that references an unrelated object type (MEETING_EVENT=33251 is not valid), making the real cause hard to diagnose.

This is a defaults-favor-the-wrong-case problem. Most Ledger-issued associations in this codebase target HubSpot-defined labels, so the default has held up empirically. As soon as a custom label was needed, the default produced both a failure and a misleading error.

Category: Confident Wrong + Execution Drop

This pairing — assert without testing, then drop execution on retry — is distinct from the Mar 14/Mar 16/Apr 15 Ledger CARs. Those were bypass failures (other agents executing writes outside Ledger). This is internal-to-Ledger failure: Ledger itself producing confident-but-wrong outputs and failing to execute clean tasks. The gateway worked. The agent inside the gateway misbehaved.


Fix Applied

Immediate Resolution (this session)

Live HubSpot state was made correct by direct PATCH (schema extension) followed by corrected --category USER_DEFINED association calls. Both Interest records now carry interest_type = referral and the referred_via association from the referrer's Contact. The skills/hubspot/property-index/interest.json and associations.json files now reflect verified-live state, with the verification GET responses captured in the Trellis session record.

wiki/conventions.md § HubSpot data conventions was added in the same session, codifying:

  1. The canonical Contact↔Interest referral pattern (typed association, not free-text property).
  2. Property-index files reflect verified-live state, not intended state. No write to skills/hubspot/property-index/*.json without a same-session live GET as evidence.
  3. The api.js associate command defaults to --category HUBSPOT_DEFINED. Custom labels require explicit --category USER_DEFINED.

Structural Changes Required

Per the four-part Governance Activation pattern proven in the 2026-04-25 Dewey CAR (designated owner + written protocol + mechanical hook + scope clarification), this CAR's prevention measures wire all four layers:

Layer Change Owner Status
Designated owner Ledger's agent definition updated to state Ledger is terminal executor for concrete bash commands; never self-delegates. V (assigns) → Hone (edits .claude/agents/ledger.md) Required
Written protocol — capability claims New Ledger Quality Bar item: any "X is not supported" / "Y returns Z" platform-capability claim must be backed by a tested API call in the same session, with the response quoted in Ledger's return payload. Untested claims are governance violations. Hone Required
Written protocol — property-index discipline Ledger Quality Bar item: never update skills/hubspot/property-index/*.json to reflect intended state. Only verified-live state, with the verification GET captured. (Already in wiki/conventions.md from this session — propagate to Ledger's agent definition.) Hone Required
Written protocol — execution-not-delegation Ledger Quality Bar item: when given concrete bash commands, execute via own Bash tool. Never spawn another Ledger. The Ledger that received the request IS the executor. Hone Required
Mechanical hook — capability-claim gate A pre-completion check in Ledger's flow: before returning a payload that contains language matching `(does not (support expose) cannot
Mechanical hook — meta-prose detector A pre-completion check: if Ledger's response contains `(spawn(ing)? Ledger delegate to Ledger now I'll have Ledger)` AND Ledger's tool-call log shows zero Bash invocations, halt and re-enter execution mode.
CLI documentation Update scripts/hubspot/api.js --help output (line 553 area) to add: "Custom labels (created via v4 schema API) require --category USER_DEFINED. Default HUBSPOT_DEFINED will produce a misleading MEETING_EVENT error on category/typeId mismatch." Squire (code maintenance — scripts/hubspot/api.js is Tier 1 ops tooling, not a public page) Required
Ledger CLI reference Update .claude/agents/ledger.md § CLI Reference to show associate examples for both HubSpot-defined and custom labels, with --category flag annotated. Hone Required
Scope clarification None required for this incident (no dual-ownership ambiguity surfaced).

Code/Configuration Changes

File Change Owner
wiki/conventions.md New section "HubSpot data conventions" (added in originating session). Documents typed-referral pattern, property-index discipline, --category default footgun. Trellis (drafted in session) — verified
skills/hubspot/property-index/interest.json interest_type enum now includes referral; reflects verified-live state. Trellis (in session) — verified
skills/hubspot/property-index/associations.json referred_via Contact↔Interest label entry (typeId 675 forward / 674 reverse, category USER_DEFINED) reflects verified-live state. Trellis (in session) — verified
.claude/agents/ledger.md Add three new Quality Bar items (test-before-claim, property-index discipline, execute-not-delegate). Update § CLI Reference to annotate --category for associate. Hone (assigned by this CAR) — pending
scripts/hubspot/api.js --help text updated to call out custom-label --category USER_DEFINED requirement. Squire (assigned by this CAR) — pending
skills/enforcement/vf-self-correction.md New trigger row (see Prevention Measures). Q — pending

Verification

Trellis verified live state at every stage:

  1. Schema extension verified — post-PATCH GET on interest_type returned the referral option.
  2. Property-index integrity verified — both interest.json and associations.json updates were preceded by live GETs whose responses were captured.
  3. Live association state verified — both Interest records now show the referred_via association from the referrer's Contact (typeId 675, category USER_DEFINED).

No silent fallback, no "should work" claim, no synthesis-tier evidence used.


Prevention Measures

Rules Added

Layer File Rule
Self-Correction skills/enforcement/vf-self-correction.md § Architecture Triggers New row — If you're doing this: "Asserting that HubSpot (or any platform) does not support an operation without first issuing a tested API call against the relevant endpoint and quoting the response." You're actually doing this: Confident assertion without testing — the highest-class governance failure for a write gateway. Issue the GET first; quote the response; only then state the conclusion. Source: 2026-04-29 Ledger Confidence + Execution CAR (interest_type referral schema extension).
Self-Correction skills/enforcement/vf-self-correction.md § Architecture Triggers New row — If you're doing this: "Updating skills/hubspot/property-index/*.json to reflect a state that has not been confirmed via a same-session live GET against the HubSpot API." You're actually doing this: Creating drift between canonical index and live portal. Every downstream agent will operate on a false map. Property-index reflects verified-live state only. Source: 2026-04-29 CAR.
Self-Correction skills/enforcement/vf-self-correction.md § Delegation Triggers New row — If you're doing this: "Replying to a request containing concrete bash commands with meta-prose like 'Now I'll spawn Ledger to execute these writes' instead of executing the commands via your own Bash tool." You're actually doing this: Self-delegation loop. The Ledger that received the request IS the executor. There is no further Ledger to spawn. Execute the commands directly. Source: 2026-04-29 CAR.
Self-Correction skills/enforcement/vf-self-correction.md § Architecture Triggers New row — If you're doing this: "Running node scripts/hubspot/api.js associate for a custom association label (anything created via the v4 schema API) without --category USER_DEFINED." You're actually doing this: The CLI defaults to HUBSPOT_DEFINED. The mismatch produces a misleading MEETING_EVENT=33251 is not valid error that masks the real cause. Read wiki/conventions.md § HubSpot data conventions for the canonical rule.
Conventions wiki/conventions.md § HubSpot data conventions Added in originating session — codifies typed-referral pattern, property-index discipline, --category default. (Already live; this CAR records it as the authoritative source.)
Ledger contract .claude/agents/ledger.md § Quality Bar Three new checklist items (test-before-claim, property-index discipline, execute-not-delegate). Hone-implemented.
Ledger contract .claude/agents/ledger.md § CLI Reference Annotate associate example with --category for both HubSpot-defined and custom labels. Hone-implemented.
CLI tooling scripts/hubspot/api.js --help Inline note about custom-label category requirement. Squire-implemented.

Detection Triggers

When Ledger is about to return any of the following, an internal check must fire:

  1. A platform-capability claim (does not (support|expose|allow), cannot, requires manual, API limitation) — same-turn evidence required: a Bash GET call against the relevant endpoint, response quoted in the return payload. If no such GET exists, halt and execute it first.
  2. A property-index file write (Edit/Write touching skills/hubspot/property-index/*.json) — same-turn evidence required: a Bash GET call whose response confirms the value being written. The verification GET response should be referenced in the file's commit message.
  3. A meta-prose execution response (spawn(ing)? Ledger, delegate to Ledger, have Ledger execute) when the input contained concrete bash commands and Ledger's tool-call log shows zero Bash invocations — halt and re-enter execution mode. The Ledger that received the request is the terminal executor.

These triggers are written for Hone to implement either as Startup Protocol additions to .claude/agents/ledger.md or as a validate-output.js hook check, whichever Hone judges more enforceable.

Verification Criteria — How We Know This Is Working

This is the section Trellis explicitly requested. Verification has three time horizons:

Immediate (next Ledger spawn):

  • A test write request against an unsupported-looking property does NOT produce a confident assertion — Ledger issues a GET first, quotes the response, then either proceeds or correctly states the limitation with evidence.
  • A test request containing concrete bash commands produces direct execution, not meta-prose about spawning.
  • A test custom-label association request produces a --category USER_DEFINED call, not a default-category call.

Short-term (30 days, monthly Tier 1 audit window):

  • Q's S-01 audit (docs/quality/audits/{date}-S-01-*.md, scheduled 2026-05-20) walks the Ledger contract end-to-end against three live writes. Verification score on docs/operating-procedures/hubspot-write-operations.md should rise from 68% (13/19) toward 80%+ as the new Quality Bar items get tested.
  • Echo's pattern memory shows zero new instances of "Ledger asserted limitation without testing" or "Ledger meta-prose instead of execute" in the incident log.
  • The next association involving a custom label routes correctly without MEETING_EVENT=33251 confusion.

Pattern-level (60-90 days):

  • Tuner runs an A/B comparison: Ledger spawns with the new Quality Bar items vs. without. The with-rules variant shows zero capability-claim assertions without same-turn GETs and zero self-delegation loops on concrete-command requests. If with-rules performance does not exceed without-rules performance, the rules are not effective and require revision (Q's responsibility per QMS framework Clause 9.1).
  • No CAR in this category (Confident Wrong / Execution Drop, internal-to-Ledger) is filed in the period. Recurrence counts as prevention failure.

Structural Fix Status

All three Governance Activation layers are at least specified in this CAR. Wiring status:

  • Agent runtime (Ledger's self-loaded definition): PENDING — Hone assigned the edits to .claude/agents/ledger.md. Until Hone lands the changes, Ledger's Startup Protocol does not yet self-load these rules. Trellis or any other leader spawning Ledger before that landing should pass the test-before-claim and execute-not-delegate rules in the spawn prompt as a temporary bridge.
  • Protocol (wiki/conventions.md § HubSpot data conventions): LIVE — added in the originating session.
  • Mechanical (validate-output hook or Startup Protocol gate): PROPOSED — Q recommends Hone implement as Startup Protocol additions; if that proves insufficient over 30 days, escalate to a validate-output.js check that scans Ledger's return payload for the trigger patterns. The pre-commit Dewey budget hook (Apr 25) is the proven precedent — mechanical hooks succeed where rules alone do not.

Two-of-three is indistinguishable from zero per the Apr 25 lesson. This CAR is not closed until all three layers are wired and verified by Q's next S-01 audit.


Lessons

A write gateway that produces confident-but-wrong outputs is more dangerous than no gateway at all. Bypass failures (Mar 14, Mar 16, Apr 15) corrupt data through wrong actors. Internal-to-gateway failures corrupt data through the right actor making wrong claims — and because the gateway carries the authority of the canonical write path, downstream agents trust its outputs. The fix is not a stronger gateway boundary (the boundary worked here); it is mandatory same-turn evidence for every capability claim, every index write, and every "I'll just delegate this further" instinct.

The execution drop is the more disturbing of the two failures because it is content-blind — it can happen on any clean task, not just edge-case schema work. The signal that prevented data harm in this incident was Trellis's verification of live state before claiming completion. That verification habit is what stood between the spawn loop and a downstream consumer reading false state. Every leader spawning Ledger must apply the same data-tier discipline — verify Tier 1 (live HubSpot) before reporting completion based on Ledger's return payload alone.

CLI defaults are part of the agent contract. --category HUBSPOT_DEFINED was a reasonable default in 2025 when the codebase had no custom labels. Once referred_via was created (typeId 675, USER_DEFINED), the default became a footgun. The fix is documentation in --help, agent-definition annotation, and a self-correction trigger — not changing the default, which would break every existing HubSpot-defined call.


Related Incidents

Date Incident Similarity / Difference
2026-03-14 All-Hands duplicate properties V bypassed Ledger via direct ToolSearch. Different — that was a bypass; this is internal-to-Ledger malfunction. Same — both produced wrong HubSpot state without same-turn verification.
2026-03-16 Ledger bypass across slash commands Slash commands contained inline write instructions; V followed them literally. Different — bypass via command literalism. Same — confident execution without checking the governance-correct path.
2026-04-15 V absorbed Ledger's work via CLI when MCP dropped V fell back to direct CLI calls. Different — bypass via fallback. Same — wrong actor, but the precedent for "Ledger CLI is the canonical write path" was established here, which is what made the present incident's --category flag the right place to add documentation.
2026-04-25 Dewey Registrar activation failure Documented owner role never wired into runtime/protocol/mechanical layers. Same shape — Governance Activation Failure. This CAR uses the four-part proven prevention pattern from that incident.
2026-04-09 Behavioral enforcement failure Rules existed in skills but lacked mechanical hooks. Same shape — written rule != activated rule. The validate-output hook proposal here addresses the same gap.

Echo Pattern Note

This is the first instance of internal-to-Ledger malfunction in the incident log. If a second instance appears (Ledger asserts a limitation without testing, OR drops execution on concrete commands), Echo should escalate as a confirmed pattern requiring Tuner A/B testing of the proposed Quality Bar additions.

Proven Prevention Pattern Applied

Per the Apr 25 Dewey CAR's four-part stack:

  1. Designated owner — Ledger's agent definition explicitly carries the new Quality Bar items (Hone-assigned).
  2. Written protocolwiki/conventions.md § HubSpot data conventions (LIVE) + skills/enforcement/vf-self-correction.md new triggers (PENDING Q).
  3. Mechanical hook — proposed Startup Protocol or validate-output.js check (Hone-assigned, escalation path defined).
  4. Scope clarification — none required (no dual-ownership conflict surfaced; Ledger is the single owner of HubSpot writes).

Three-of-four are specified; full closure requires Hone landing the agent-definition edits and either Hone or Q landing the mechanical hook before the 2026-05-20 S-01 audit.


Closure Conditions

This CAR is closed when:

  1. .claude/agents/ledger.md reflects the three new Quality Bar items and updated CLI reference (Hone).
  2. scripts/hubspot/api.js --help documents the --category USER_DEFINED requirement for custom labels (Squire).
  3. skills/enforcement/vf-self-correction.md carries the four new trigger rows (Q).
  4. The 2026-05-20 S-01 audit walks at least three live Ledger writes and finds zero capability-claim-without-evidence and zero execution-drop instances. Verification score on the S-01 operating procedure rises or holds.
  5. No new CAR in this category is filed in the 30-day window after closure.

If any of these conditions fails by 2026-05-30, the CAR is reopened and escalated to V (operations) for direct intervention.