12 Commits
Author SHA1 Message Date
james.maes 1ee64c95fe Merge remote-tracking branch 'origin/develop' into feature/measurement-rollup
# Conflicts:
#	README.md
2026-10-14 11:57:48 -05:00
james.maes 1c0fa34e7c Merge remote-tracking branch 'origin/develop' into feature/crm-table-names
# Conflicts:
#	CHANGELOG.md
2026-10-14 11:52:54 -05:00
james.maes 0e37be1070 Merge remote-tracking branch 'origin/develop' into feature/measurement-rollup 2026-10-14 11:50:26 -05:00
james.maesandClaude Opus 5.5 30182b4805 feat(measurement): daily results rollup and an outcome importer SPI (#4)
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>
2026-10-14 11:50:25 -05:00
james.maesandClaude Opus 5.5 5dd68eb20b refactor(crm): crm_* table names only, no legacy fallback
qbit-crm 0.2.0 renames its tables to crm_* with no compatibility shims
(owner decision via the coordinator), so the gateway uses the snake_case
names directly and drops the camelCase fallback and LEGACY_* constants.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 11:46:24 -05:00
james.maesandClaude Opus 5.5 d732cef626 feat(crm): resolve qbit-crm's snake_case table names, legacy fallback
qbit-crm is renaming its tables to crm_* snake_case. QbitCrmGateway now
resolves each table from the instance at call time, preferring the
snake_case name and falling back to the legacy camelCase one, so either
qbit-crm generation works. Tests run the gateway against both schemes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 11:40:48 -05:00
james.maesandClaude Opus 5.5 0088e3b77b build: assistant provider SPI RC.12 and parent 2.1.2, no allow-lists
Move to qbit-assistant-provider-api 0.1.0-RC.12, whose types now live in
com.kingsrook.qbits.assistant.spi (ModelPricing included), and to
qbit-build-parent 2.1.2, which allows that package by default. Drop the
per-repository guardrail allow-lists. qbit-assistant-provider-openai is
only used by tests, so it becomes test scope; hosts that register it add
it themselves.

Fix the stale README status (RCs are on Central; hosted in Voyage#113).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 11:35:23 -05:00
james.maesandClaude Opus 5.5 3ec8776c2a refactor(providers): depend only on qbit-assistant-provider-api and provider-openai
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>
2026-10-14 08:00:04 -05:00
james.maesandClaude Opus 5.5 368f40e062 docs(spec): record owner decisions, provider defaults and AI-content policy
Owner decisions (2026-10-12): all recommendations accepted. Records the
provider defaults (Claude via qbit-assistant, SES email from qrun.io, no
social transports), the AI-content channel policy (owned channels with
disclosure, release notes allowed, community channels not allowed) and a
USD 25 monthly cost limit; the starter data carries the same values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 06:19:09 -05:00
james.maesandClaude Opus 5.5 9e71b2956d fix: close the gaps found in code review
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>
2026-10-12 15:59:56 -05:00
james.maesandClaude Opus 5.5 ed62add2de docs: README, design, operations and starter data
- 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>
2026-10-12 15:46:24 -05:00
james.maesandClaude Opus 5.5 b26f832fc9 chore: bootstrap qbit-marketing with the QRun qbit repository standard
Maven scaffold (qbit-build-parent 2.0.0, com.kingsrook.qbits, <revision>
versioning, qqq 4.1.0-RC.4 BOM, JaCoCo gate), QBit config and producer,
CircleCI qqq-orb pipeline, and the standard repository files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:41:30 -05:00