Skip to content

fix(lead): declare ai.requiresConfirmation on convert_lead - #1589

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-1578-convert-lead-requires-confirmation
Sep 5, 2026
Merged

fix(lead): declare ai.requiresConfirmation on convert_lead#1589
os-steve merged 1 commit into
mainfrom
claude/issue-1578-convert-lead-requires-confirmation

Conversation

@os-steve

@os-steve os-steve commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

Fixes #1578

convert_lead shipped an ai.description promising "Irreversible — requires human approval before it runs" while list_actions reported requiresConfirmation: false. The ai block declared exposed and description and no requiresConfirmation; a code comment attributed the AI gate to confirmText. This adds the key the runtime actually reads and rewrites the comment.

The premise, re-verified against the installed packages

The card cited runtime 17.2.0; this repo pins and installs 17.3.0. Re-measured there rather than taken from the card:

// node_modules/@objectstack/runtime/dist/index.js
function actionLooksDestructive(_deps, action) {
  if (action?.ai?.requiresConfirmation !== void 0) return Boolean(action.ai.requiresConfirmation);
  return Boolean(action?.mode === "delete" || action?.variant === "danger");
}

convert_lead is neither mode: 'delete' nor variant: 'danger', so with no ai.requiresConfirmation the fallback returned false.

confirmText occurs zero times in the entire shipped runtime bundle — it never reaches the AI path at all. @objectstack/spec 17.3.0 agrees in its own authoring guidance: "the AI human-in-the-loop override lives under ai — write ai: { requiresConfirmation: true }. confirmText is the separate UI confirm prompt." Its alias table maps confirm / requireConfirmation / hitl / humanInTheLoop onto requiresConfirmation (the card named three of those four).

Premise holds. requiresConfirmation was 0 hits repo-wide before this change; confirmText returns 37 at this base from the same invocation shape as the working control.

Evidence the fix is load-bearing

A probe applies the shipped predicate — extracted verbatim out of node_modules, never hand-copied — to the real action metadata. Ablation was run from the committed state, restore proven by blob hash equality and an empty git diff HEAD:

key present  -> convert_lead: ai.requiresConfirmation=true  confirmText=true  => requiresConfirmation = true
key removed  -> convert_lead: ai.requiresConfirmation=undefined confirmText=true  => requiresConfirmation = false
key restored -> convert_lead: ai.requiresConfirmation=true  confirmText=true  => requiresConfirmation = true

The middle line is the point: with confirmText still present and only the new key gone, the verdict is false. That is the defect reproduced, and it is direct proof confirmText does not gate.

schedule_followup rides along as a negative control and stays false throughout — it is additive and reversible and declares no gate, so the predicate is not simply returning true.

End-to-end, the key survives pnpm build into dist/objectstack.json at all three registration sites (objects[].actions, actions[], and the page component that embeds it) — that artifact is what list_actions reads.

The comment

Rewritten to name the key the AI path reads and to stop attributing the gate to confirmText, which remains the console's confirm-dialog string for a human click.

It deliberately does not restate the retired "lands in the HITL queue" claim. Measured on the same bundle: requiresConfirmation appears at exactly two sites — the read above and the list_actions projection. approvalQueue and destructiveHint are 0 occurrences; the single HITL occurrence is a comment calling the flag a hint. run_action checks exposure, permission, activation and params, then dispatches — no server-side pause. So the comment says surfaced, not enforced.

That non-enforcement is a platform gap, not this repo's to work around. Deduped with a working control leg and filed at destination as objectstack#15942, unassigned and unlabelled for their triage.

Scope

src/actions/lead.actions.ts plus the changeset — the dispatched file surface, not exceeded. No locale pack is touched: the translated surface for this action is label / confirmText / successMessage, and requiresConfirmation is a boolean, so the fix needed nothing held by #1144. content/docs/reference/faq* untouched, so nothing held by #1402 is involved either.

Verification

Every step of pnpm verify run individually, exit code captured before any pipe:

step exit
pnpm validate 0
pnpm typecheck 0
pnpm lint 0
pnpm lint:i18n-gate 0
pnpm hygiene 0
pnpm hygiene:tokens 0
pnpm build 0
pnpm test 0 — 159 files, 3380 passed, 1 skipped

typecheck green is itself the contract check: the Action type from @objectstack/spec/ui accepts ai.requiresConfirmation. Token ratchet stays clean — business semantics ~84,026 of a ~85,000 ceiling. The ratchet is comment-stripped, so the comment rewrite costs it nothing; only the one key moves the number.

Validate/build warnings are pre-existing (flow try_catch advisories on unrelated flows) and are unchanged by this diff.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DuzfS5chho38Yx1jxx9DEj


Generated by Claude Code

The action's AI description promises "Irreversible — requires human
approval before it runs", while list_actions reported
requiresConfirmation: false. The ai block declared exposed and
description but no requiresConfirmation, and a comment attributed the
AI gate to confirmText.

Measured against the pinned @objectstack/runtime 17.3.0:
actionLooksDestructive reads action.ai.requiresConfirmation and
otherwise falls back to mode === 'delete' || variant === 'danger'.
confirmText occurs zero times in the whole shipped runtime bundle — it
is the console's confirm-dialog string for a human click, unrelated to
the AI path.

Rewrite the comment to name the key the AI path actually reads, and to
state that the flag is surfaced in list_actions rather than enforced:
run_action dispatches the flow with no server-side pause, so the
retired "lands in the HITL queue" claim is not restated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DuzfS5chho38Yx1jxx9DEj
@vercel

vercel Bot commented Sep 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
hotcrm Ignored Ignored Sep 5, 2026 3:32pm UTC

Request Review

@github-actions github-actions Bot added the backend Server-side behaviour — hooks, flows, actions label Sep 5, 2026
@os-steve
os-steve marked this pull request as ready for review September 5, 2026 15:36
@os-steve
os-steve added this pull request to the merge queue Sep 5, 2026
Merged via the queue into main with commit ccfe8e2 Sep 5, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend Server-side behaviour — hooks, flows, actions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

convert_lead: confirmText does not gate AI invocation — list_actions reports requiresConfirmation: false

2 participants