Everything built in ca-dcc-dev-2, in the order it has to move. Twelve phases, hard-sequenced — each assumes the ones above it are done. Written for a target org with no Axsy or Public Sector packages installed.
How the work splits
Roughly two thirds is standard Salesforce metadata and moves with whatever deployment tool you use. The rest is package configuration, Axsy-specific artefacts and record data. Packages land first, because the entire build sits on Public Sector objects that do not otherwise exist.
00 Prerequisites Gates. If one fails, stop — later phases will appear to succeed and then misbehave.
The build uses PSS standard objects as its data spine — Account as business entity, BusinessLicense, RegulatoryAuthorizationType under RegulatoryAuthority, BusinessLicenseApplication, DocumentChecklistItem. There are no parallel custom licensing objects, so without PSS almost nothing in phase 02 deploys.
Smart Forms backs phase 07, Axsy Mobile backs phase 09, and the Business Rules Engine backs the expression set and decision matrices in phase 08. Record the version numbers — a version skew against the source org breaks the Smart Form import and the mobile config JSON schema.
Person Accounts are enabled in the source org. With them on, any permission set granting Account object permissions must grant Contact at a matching level, or the permission-set validator rejects the deploy outright. The phase-06 permission sets are written on that assumption.
A fresh org has no axsy_mobile__AppConfigVersion__c baseline. Config tooling can only edit a Draft on top of an existing Published version, so the first version must be created by hand in the Axsy Config Tool web app against the target org. Phase 09 is blocked until this exists.
Field-level security and record-type visibility are enforced independently of Modify All Data, so even a System Administrator gets "No such column" or "record type isn't valid for the user" against freshly-deployed components until granted. Every phase that adds fields, objects or record types must also grant FLS and record-type visibility — to a permission set, and to the user doing the seeding in phases 01 and 10.
01 Reference data Record-level, not metadata. The schema in phase 02 has required lookups pointing at these.
One RegulatoryAuthority record — "California Department of Cannabis Control" in the source org. Everything else in this phase hangs off it.
RegulatoryAuthorizationType records under that authority. These are the licence types the whole configuration model versions.
BusinessLicenseApplication.LicenseTypeId → RegulatoryAuthorizationType is a required lookup, so any permission set, flow or seed that creates an application also needs Read on RegulatoryAuthorizationType.
02 Schema — objects and fields 10 custom objects and 100 custom fields across 16 objects. The largest phase, and the one with the most dangerous failure mode.
Objects before fields, and both before any layout that references them. The manifest's capability groups list every object and field by API name.
This bit the source build twice. Deploys returned success: true — confirmed by DeployRequest, Setup Audit Trail and real component Ids — for 17 compliance-model fields that never became visible to describe, SOQL or Tooling, 45 minutes later and via a raw sf sobject describe that bypassed the tooling entirely.
So after deploying fields, run a live SELECT naming the new columns. Re-deploy anything missing one at a time, then re-check. Do not trust the deploy JSON on its own.
If a field was absent at the moment a permission set deployed, its fieldPermissions entry can fail silently. Once 2.2 is clean, re-deploy the phase-06 permission sets and confirm the grants took.
03 Record types & picklists 2 record types, 2 standard value sets, 2 global value sets, 1 business process.
BusinessLicense.Status is a StandardValueSet named BusinessLicenseStatus — extend it as a StandardValueSet component, not as a picklist on the field. The source org ships Draft/Verified/Inactive/Revoked and this build adds Active, Expired and Suspended. CaseStatus is extended the same way.
Account.Cannabis_Business_Entity, Case.Compliance_Case_Process, and the License_Owner_Role__gvs / DCC_Review_Division__gvs value sets. Grant record-type visibility as you go — see 0.5.
04 Flows 8 flows. They must exist before the quick actions in phase 05, which reference them.
Do not compare version numbers to the source org — versions restart on deploy. Confirm each one is Active in the target instead.
In-flow file upload does not work. The flowruntime:fileUpload screen component failed to deploy in the source org, so Manage_Documents_Upload creates the document record only and the user attaches the file afterwards via the record's Files panel. Confirm whether forceContent:fileUpload deploys in your target before assuming otherwise.
Document sorting is by Id, not label. Flow Get Records rejects relationship dot-traversal on a custom lookup at deploy time, so Manage_Documents sorts by DocumentCategoryId rather than the category label. Displayed labels are still correct.
05 Actions, layouts, tabs & list views 8 quick actions, 18 layouts, 5 custom tabs, 5 list views. The phase with the most deploy-time traps.
A Flow-type quick action is rejected from a layout's quickActionList ("You can't add QuickActionType Flow to a QuickActionList"). Use the modern platformActionList instead, with actionListContext of Record or Global.
The app supports only object-specific Flow quick actions placed in a page layout's Mobile & Lightning Actions section. That is why New_Cannabis_Licence_Application exists as a record-level action on the BusinessLicenseApplication layout rather than as a global one. If you deploy the orphaned global action, it will not be reachable on a device.
A layout carrying a <relatedLists> entry for a brand-new custom relationship fails with "Cannot find related list" for roughly 15–30 minutes after the relationship is created. It is a metadata-cache propagation delay, not a mistake. Field-only layout deploys succeed immediately.
Two ways through: deploy the layouts without their related lists first and add them in a second pass, or wait and re-deploy. Also note the format is <ChildObject>.<RelationshipField> — for example License_Config_Version__c.Regulatory_Authorization_Type__c — not the relationship name alone.
Removing the <platformActionList> block from a layout does not clear the one already in the org — the stale list survives, and can block deleting a quick action it still references. To actually clear it, deploy an explicit empty block: the element present, with an actionListContext and zero items.
06 Security & sharing 7 permission sets, 1 profile, 1 custom permission, 1 public group, 2 criteria-based sharing rules — and one org-wide default change.
The five role sets — DCC_Licensing_Staff, DCC_Compliance_Staff, DCC_Supervisor, DCC_Applicant, DCC_Consultant — are the durable access model; the remaining sets cover the licence-config admin and DOJ clearance.
Formula fields take readable=true / editable=false. A required field or a master-detail field cannot carry FLS at all — Salesforce rejects the deploy — and is implicitly accessible instead.
DOJ document security cannot be a Restriction Rule: those key only on Profile or user type, never on a custom permission. It is enforced instead by OWD Private plus a public group and a criteria-based rule sharing DOJ_Special_Clearance_Required__c = true records to that group.
Because OWD is now Private, non-DOJ documents also become invisible to internal staff unless the second criteria-based rule — sharing = false records to internal staff — is in place. Deploy both, or document management appears empty for everyone.
A deploy carries the permission sets, not who holds them. Assign the role sets to your target-org users, and put DCC_DOJ_Cleared on whoever should see DOJ-flagged documents. Assign a licensing set to the user who will run phases 10 and 11 — see 0.5.
07 Smart Form One form — Cannabis Licence Application.
A Form Version import creates a static snapshot the app renders from — not Element, Section or Picklist records. So there is no separate picklist migration, and the target is not a form-authoring environment: form changes are made in the source org and re-exported.
Workbench against ca-dcc-dev-2, then Utilities › REST Explorer › GET /services/apexrest/axsy_forms/form/a0dQH000009Dcv7YAC. Execute, Show Raw Response, save the JSON body without headers. Confirm the JSON contains a versionId — if it does not, you exported a draft.
Workbench against <TARGET-ORG> → POST /services/apexrest/axsy_forms/form?upsert, JSON in the body. Verify the form name matches the source exactly — the launcher resolves it by name, and a rename breaks routing silently.
When a Smart Form is launched from a flow, formula evaluation runs with strict mode unconditionally off — an unresolved reference returns null silently. So a dependent lookup filtered by another element (@licence_type and similar) returns "No matches found" at runtime even with correct syntax and correct data, and a required one blocks submission entirely.
This form is built to avoid that: the flow resolves licence type, active config, disclosure and owner requirement up front and passes them in as flowPlugin_ inputs. Keep it that way. Static-literal filters are fine.
08 Business rules One expression set and its decision matrices.
DCC_Licence_Application_Rules and activate itAn expression set is inert until activated in the target org.
Two CalculationMatrix records back the rules. Rows must go in through Salesforce's CSV upload process — a raw row insert leaves them unhashed and the matrix version silently broken, with no error at load time.
09 Axsy Mobile configuration Blocked until 0.4 is done. Nothing reaches a device before this.
The licence-config and application objects, plus the axsy_forms object graph so the form renders offline, and write-back on axsy_forms__Form_Response__c.
The review-routing objects (Application_Review_Assignment__c, Review_Deficiency__c, Deficiency_Notice__c) and their flows are not in the sync scope.
Document management is not either — DocumentChecklistItem, DocumentCategory, DocumentType, ContentVersion and ContentDocument are all absent. The whole feature returns empty on-device until they are added.
Both were deliberate for a desktop-only scope. Add them here if the target needs field access, and republish.
Nothing is active until the config version is published.
10 Seed data Record-level. Depends on 0.5 and phase 06.
Accounts on the Cannabis_Business_Entity record type, their BusinessLicense records, and a BusinessLicenseApplication.
Expiration_Rule__c offers "Fixed Calendar Date", but there is no field to hold that date — only Expiration_Term_Months__c. The launcher flow computes expiration only for the Issue Date + Term rule and shows a non-blocking notice otherwise. Seed accordingly, or add a Fixed_Expiration_Date__c field first.
Give each the appropriate role permission set from phase 06, plus Division and Region values — the analyst selectors filter on those.
11 Manual Setup steps Four things the Metadata API cannot do. None are optional if you want the build to behave correctly.
Deficiency Notice layout → Edit Layout → Related Lists → drag Review Deficiencies on → Save. The Metadata API cannot create this placement from scratch for a brand-new custom lookup; after one manual placement it becomes API-manageable. Until then the child records still appear on the record's Lightning Related tab, so it is functionally present either way.
DocumentChecklistItem.ParentRecordId is polymorphic, and classic page layout metadata cannot reference it. To show documents inline on an application record, author a Lightning record page with a Dynamic Related List. The BusinessLicense side works normally, because it uses an ordinary custom lookup (Business_License__c).
BusinessLicenseApplication has a second layout tied to a PSS managed record type which is not retrievable via the Metadata API. The quick actions were placed only on the standard layout. If you use the Reopening record type, add its actions by hand in Setup.
Open the component manifest beside the target org and confirm each capability is present. Phase 02's verification step is the one that matters most — a field that reported success and never landed is invisible until something tries to use it.