PII: every personal field is classified DataClassification.PII. A field-name regression test catches any unclassified email, phone, name, address or LinkedIn field.
Tenancy (optional, off by default):
withTenantSecurityKeyType / withTenantAllAccessKeyName add a tenantKey record lock to main and settings tables, and join-chain locks to sub-tables.
The columns live in a separate crm-tenancy Liquibase changelog.
Isolation tests cover tenant A/B and all-access.
Contingent dropdowns: the deal's primary contact is limited to the deal's company. Stage and outcome are also validated server-side.
Search and API: main tables have search fields. When withApiName is set, main and settings tables are published through the QQQ API middleware, with an API-to-admin field parity test.
ESB: deferred. RC.6 cannot exclude classified values, so configuring it fails with a clear message.
Help: a 391-entry help/qbit-crm.yaml bundle in the qqq#1198 format, with a STAND-IN(core=QRun-IO/qqq#1198, removeBy=4.1.0-RC.9) loader and a completeness test.
Health: checks for transport readiness, sequence lag and scoring freshness (STAND-IN(core=QRun-IO/qqq#1199, removeBy=4.1.0-RC.9)).
GDPR: Erase Personal Data now covers contacts, web leads, list members, email messages, activities, consent text, external mappings, enrollments and core audit copies. A test asserts that no original PII string remains.
Bulk load: starter profiles for contacts, companies and deals, seeded into the host's saved-profile table when it exists.
Host-adoption fixes found while installing on kof22 (WB#224):
the app is a plain QAppMetaData, so it can be attached as a child app
home table columns have no fixed widths
emails are 320 characters and UTM fields 500
lead source names taken from UTM are capped at 100
Codex implemented this; Claude reviewed it and wrote the host-adoption fixes.
Test plan
./mvnw verify: 454 tests, 0 failures; JaCoCo 92%; checkstyle and guardrails pass, including G4 stand-in markers
## Summary
P4 part B of the qbit-crm 0.2.0 design:
- **PII:** every personal field is classified `DataClassification.PII`. A field-name regression test catches any unclassified email, phone, name, address or LinkedIn field.
- **Tenancy (optional, off by default):**
- `withTenantSecurityKeyType` / `withTenantAllAccessKeyName` add a `tenantKey` record lock to main and settings tables, and join-chain locks to sub-tables.
- The columns live in a separate `crm-tenancy` Liquibase changelog.
- Isolation tests cover tenant A/B and all-access.
- **Contingent dropdowns:** the deal's primary contact is limited to the deal's company. Stage and outcome are also validated server-side.
- **Search and API:** main tables have search fields. When `withApiName` is set, main and settings tables are published through the QQQ API middleware, with an API-to-admin field parity test.
- **ESB:** deferred. RC.6 cannot exclude classified values, so configuring it fails with a clear message.
- **Help:** a 391-entry `help/qbit-crm.yaml` bundle in the qqq#1198 format, with a `STAND-IN(core=QRun-IO/qqq#1198, removeBy=4.1.0-RC.9)` loader and a completeness test.
- **Health:** checks for transport readiness, sequence lag and scoring freshness (`STAND-IN(core=QRun-IO/qqq#1199, removeBy=4.1.0-RC.9)`).
- **GDPR:** Erase Personal Data now covers contacts, web leads, list members, email messages, activities, consent text, external mappings, enrollments and core audit copies. A test asserts that no original PII string remains.
- **Bulk load:** starter profiles for contacts, companies and deals, seeded into the host's saved-profile table when it exists.
- **Host-adoption fixes found while installing on kof22 (WB#224):**
- the app is a plain `QAppMetaData`, so it can be attached as a child app
- home table columns have no fixed widths
- emails are 320 characters and UTM fields 500
- lead source names taken from UTM are capped at 100
Codex implemented this; Claude reviewed it and wrote the host-adoption fixes.
## Test plan
- [x] `./mvnw verify`: 454 tests, 0 failures; JaCoCo 92%; checkstyle and guardrails pass, including G4 stand-in markers
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
P4 part B of the qbit-crm 0.2.0 design:
DataClassification.PII. A field-name regression test catches any unclassified email, phone, name, address or LinkedIn field.withTenantSecurityKeyType/withTenantAllAccessKeyNameadd atenantKeyrecord lock to main and settings tables, and join-chain locks to sub-tables.crm-tenancyLiquibase changelog.withApiNameis set, main and settings tables are published through the QQQ API middleware, with an API-to-admin field parity test.help/qbit-crm.yamlbundle in the qqq#1198 format, with aSTAND-IN(core=QRun-IO/qqq#1198, removeBy=4.1.0-RC.9)loader and a completeness test.STAND-IN(core=QRun-IO/qqq#1199, removeBy=4.1.0-RC.9)).QAppMetaData, so it can be attached as a child appCodex implemented this; Claude reviewed it and wrote the host-adoption fixes.
Test plan
./mvnw verify: 454 tests, 0 failures; JaCoCo 92%; checkstyle and guardrails pass, including G4 stand-in markers🤖 Generated with Claude Code