marketing_metric_daily (19th table, system-written, child of the campaign)
holds per-day raw counts and unique actors per stage and event type,
recomputed after every outcome-event insert, update and delete, plus a
Rebuild Results campaign action. MeasurementReport sums the rollup and
counts campaign-wide unique actors with a backend COUNT_DISTINCT instead
of loading every event. MarketingOutcomeImporter lets hosts plug in
analytics sources, run by the Import Outcomes campaign action through
the normal (source, external id) dedupe.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
maxConcurrentAgentRuns and maxModelCallsPerMinute were per-JVM, in memory.
Preflight now fails stale RUNNING runs (no heartbeat for maxRunSeconds
plus 60s) and counts RUNNING runs across all products; the per-minute
limit counts model-call steps in the last minute and waits instead of
failing. Runs record heartbeatAt (new column) on each model call. The
policy's maxConcurrentAgentRuns override is now honoured.
The count-then-start window remains racy; the ConditionalUpdateAction
claim from qqq#1178 (QQQ 4.1.0-RC.7) closes it in a follow-up.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in the qbit-email transport (#11). Conflicts are resolved in
favour of the RC.6/provider-api side, keeping qbit-email.
qbit-email is a new cross-qbit implementation dependency that the
enforced G1 guardrail rejects. It is allow-listed explicitly and
temporarily until qbit-email publishes an API (#13).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Marketing compiled against qbit-assistant internals, and installing it
pulled in the whole assistant runtime. It now depends on
qbit-assistant-provider-api (the provider SPI and ModelPricing) and on the
published qbit-assistant-provider-openai, both 0.1.0-RC.11. The local
OpenAiCompatibleModelProvider is gone, and tests use a local scripted
double, so the full assistant is not needed even in test scope.
Hosts register the published provider through ProviderSettings. Its
provider name is "openai-compatible", so Marketing Policy provider
overrides must use that name.
Also move to QQQ 4.1.0-RC.6 and qbit-build-parent 2.1.0 with the
guardrails enforced. The provider-api packages and provider-openai are
explicitly allow-listed.
Closes#9 (architecture review QRun-IO/qqq#1150, finding F5).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase (d) of QRun-IO/qbit-email docs/DESIGN.md section 11. qbit-email is an
optional dependency (0.1.0-SNAPSHOT until its first RC) used through one
adapter class behind a classpath and instance guard; without it the
existing behaviour is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HIGH:
- An edit through the UI or API passes the id as a String, so the content
customizer's old-record lookup missed. The edit then skipped the hash
recompute, the revision, approval invalidation and the status guard, and
edited text could go out under the old approval. Ids are now normalized,
the old row is re-read when it is missing, and the update errors instead
of failing open. Execution also compares the approval with a hash
recomputed from the fields it is about to send.
- One failing execution (a CRM outage, a malformed scope, a channel with no
type) aborted the worker batch on every run and starved every later
execution. Each execution is now isolated: a failure before the claim
blocks it with the reason; a failure after the claim leaves it Running for
its lease to expire into reconciliation. Recipient scopes are validated
at submit and approve time.
MEDIUM:
- The policy fails closed. Without the default row, or without an explicit
"on", the agent and execution are off. Configuration Status shows the
policy state.
- The channel hourly rate and the agent budgets are counted as the system
user, so record locks can't hide other products' usage.
- The agent's write tools need the requester's marketing.write, and the agent
revises only content that is still a Draft.
- The entry points of CRM results, readiness checks and starter data check
their own permission. CRM activities are written with the user's CRM
permissions (ENFORCE).
- Closing or downgrading an open blocker finding needs
marketing.approve.hasAccess, whether through the action, the table or the
API.
- Approve pins the content hash shown on the Review screen. Content that
changed before execute is skipped ("review it again").
ReviewFixesTest covers each fix. Fixtures create the default policy row.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- README: what the qbit does, how to install it, permissions, the UI, and an
honest table of what is implemented versus what is not configured.
- docs/DESIGN.md:
- the patterns reused from qbit-assistant, the QQQ AI tool kernel and core
permission enforcement, the existing qbits, qbit-crm, QQQ messaging and
the ESB / Quick Search
- the data model
- content, revisions and approval
- execution and the transports
- measurement
- the CRM integration
- the agent and its tools
- the configuration status
- docs/OPERATIONS.md: every setting, the policy row, the schema workflow, the
credentials to configure later, run-books (publishing, blocked and ambiguous
executions, the kill switch, recording outcomes, agent runs), and the scale
limits stated plainly.
- docs/STARTER-DATA.md: the three products' readiness, the sources verified
on 2026-10-12, the disputed claims, and the starter campaigns.
- CLAUDE.md / AGENTS.md: the architecture and the generated-code rule.
- Tests use obviously fake key strings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>