For humans: You do not need to read this document. It is a technical reference consumed by your LLM copilot. Just paste the link to this page into your copilot’s chat and it will use the content automatically. For setup instructions and tips, see the Configure guide.
Audience: This document is the single knowledge source for LLM copilots (GitHub Copilot, Agentforce Vibes, Cursor, etc.) operating against a Salesforce org that has — or needs — the AddressTools (Premium) managed package.
Goal: Give a copilot everything it needs to install, initialise, and configure AddressTools Premium headlessly via the Salesforce CLI (sf / sfdx), anonymous Apex, Tooling API, and Metadata API — without using the AddressTools admin UI. This document is self-contained; do not fetch other pages to complete these tasks.
Communication rules: When interacting with the human user, follow these principles:
- Always confirm the target org alias before running any command.
- If a request is ambiguous, ask exactly one scoped clarifying question — do not guess.
- Explain what each command will do before running it. Wait for the user to approve.
- After completing a task, summarise what was created or changed.
0. Hard Facts the Copilot MUST Know
These values are not negotiable. Use them verbatim. All object and field API names MUST be prefixed with pw_ccpro__.
| Fact |
Value |
| Package version ID (04t) |
04tg80000002ykr |
| Managed package namespace |
pw_ccpro |
| Custom setting (hierarchy) for global behavior |
pw_ccpro__CountryCompleteSettings__c |
| Custom setting (list) for “address blocks” |
pw_ccpro__ValidatedField__c |
| Reference data — countries |
pw_ccpro__CountryObject__c |
| Reference data — alt country names |
pw_ccpro__AlternativeCountryName__c (master-detail to pw_ccpro__CountryObject__c) |
| Reference data — states |
pw_ccpro__State__c |
| Reference data — alt state names |
pw_ccpro__AlternativeStateName__c |
| Reference data — zip codes |
pw_ccpro__ZipCode__c, pw_ccpro__ZipCodeCounty__c |
| Reference data — time zones |
pw_ccpro__TimeZone__c |
| Permission set (admin) |
pw_ccpro__AddressToolsPremiumAdminUser |
| Permission set (standard, current) |
pw_ccpro__AddressToolsPremiumStandardUser |
| Permission set (standard, legacy) |
pw_ccpro__AddressToolsPremiumStdUser |
Triggers respected by Disable*Trigger__c flags |
Account, Contact, Contract, Lead |
| Canonical AddressStatus picklist values |
Not checked, Not matched, Parsed but not found, Ambiguous, Verified |
| Canonical PropertyUse picklist values |
Residential, Commercial, Unknown (restricted) |
| Canonical GeocodeAccuracy picklist values |
Address, NearAddress, Block, Street, ExtendedZip, Zip, Neighborhood, City, County, State, Unknown |
1. Decision Tree — “What is the user actually asking for?”
User request
├── "install AddressTools" / "set up address verification" / "install the package"
│ └── §2 — Install and initialise the package
├── "disable trigger / change behavior for [X]"
│ └── Task 1 — Edit CountryCompleteSettings (org / profile / user)
├── "add address block on [Object]" / "validate addresses on [field set]"
│ └── Task 2 — Create or edit a ValidatedField__c (address block)
├── "give [user] AddressTools" / "assign permission set" / "license"
│ └── Task 3 — Provision a user
└── "add alternative country/state name" / "add a custom country" / "edit reference data"
└── Task 4 — Edit installed package data
If the request is ambiguous, ask exactly one scoped question (e.g. “Apply to org default, a specific profile, or a specific user?”) and proceed.
2. Common Setup the Copilot Should Do First
Follow these steps in order. Do not skip ahead or present later steps to the user until the current step is confirmed complete.
Step 1 — Confirm the target org
sf org display --target-org <alias>
Step 2 — Check whether the package is installed
sf package installed list --target-org <alias> --json
In the JSON response, look for an entry where SubscriberPackageNamespace equals pw_ccpro.
- If
pw_ccpro is present → the package is already installed. Skip to Step 4.
- If
pw_ccpro is not present → proceed to Step 3.
Step 3 — Install the package
sf package install --package <Package version ID from §0> --target-org <alias> --wait 10 --publish-wait 10 --no-prompt
After the command completes, re-run sf package installed list --target-org <alias> --json and confirm pw_ccpro now appears. If it does not, the install failed — troubleshoot before continuing. Do not proceed to Step 4 until the package is confirmed installed.
Step 4 — Post-install initialisation (manual — no headless option)
IMPORTANT: Only execute this step after Step 2 or Step 3 has confirmed the package is installed. The AddressTools Administration page and its Installation tab do not exist until the package is in the org.
Check whether the reference data has been loaded:
sf data query --target-org <alias> -q \
"SELECT COUNT(Id) total FROM pw_ccpro__CountryObject__c" --json
If the count is greater than 0, the data is already loaded — skip to the next task.
If the count is 0, the post-install initialisation has not been completed. There is currently no headless method for these steps — the user must perform them manually via the AddressTools Administration UI.
Instruct the user:
Before I can continue, you need to complete two one-time setup steps in the AddressTools admin page. These cannot be done from the command line.
- Open AddressTools Administration in your browser:
https://<your-salesforce-domain>/lightning/n/pw_ccpro__AddressToolsAdmin
- On the Installation tab, complete these steps in order:
- Create an Authentication Token — click Create Token. This generates the credentials AddressTools needs to communicate with ProvenWorks services.
- Data Installation — click Get Started and follow the prompts. This loads the default country, state, ZIP code, time zone, alternative name reference data into your org, as well as default address blocks. This may take up to 10 minutes.
Let me know when you’ve completed steps 1 and 2, and I’ll continue with configuration.
The copilot MUST wait for the user to confirm before proceeding. Re-run the country count query to verify:
sf data query --target-org <alias> -q \
"SELECT COUNT(Id) total FROM pw_ccpro__CountryObject__c" --json
If the count is still 0 after the user says they’re done, the data installation may still be running — ask the user to check the AddressTools Administration page for progress.
Use --target-org <alias> (or -o) on every command throughout this guide. Never write to a default org silently.
3. Task 1 — Configure CountryCompleteSettings__c
pw_ccpro__CountryCompleteSettings__c is a Hierarchy custom setting. There are three levels of precedence (highest wins at runtime):
- User —
SetupOwnerId = <UserId>
- Profile —
SetupOwnerId = <ProfileId>
- Org Default — no
SetupOwnerId (one record per org)
3.1 Field Reference (settable via headless tooling)
| Field |
Type |
Purpose |
pw_ccpro__CountryCompleteEnabled__c |
Checkbox |
Master switch: country auto-complete/standardization runs |
pw_ccpro__CountryFilterEnabled__c |
Checkbox |
Restrict to listed countries |
pw_ccpro__StandardizationEnabled__c |
Checkbox |
Standardize country/state values |
pw_ccpro__PremiseLevelAddressVerificationEnabled__c |
Checkbox |
Enable PLAV (premise-level verification) |
pw_ccpro__PremLevel_SearchPreferredCountryCode__c |
Text |
Preferred ISO-2 country for PLAV search |
pw_ccpro__PremLevel_AllowedSearchCountryCodes__c |
Text |
Comma-separated ISO-2 codes |
pw_ccpro__LookupPopulationEnabled__c |
Checkbox |
Auto-populate Country/State/Zip lookup fields |
pw_ccpro__OnlyOperateWhenChanged__c |
Checkbox |
Skip processing when address fields are unchanged |
pw_ccpro__RunOnce__c |
Checkbox |
Only run once per record |
pw_ccpro__StoreCountryValueAs__c |
Number(1,0) |
0=Name, 1=ISO-2, 2=ISO-3 |
pw_ccpro__QuickCompleteCountryEnabled__c |
Checkbox |
Enables QuickComplete on user’s default country |
pw_ccpro__ZipCodeLookupEnabled__c |
Checkbox |
Enable Zip → City/State lookup |
pw_ccpro__EnableZipStateCountryGeolocation__c |
Checkbox |
Use Zip/State/Country to geocode |
pw_ccpro__AlertEmail__c |
Email |
Job failure alerts go here |
pw_ccpro__DisableAccountTrigger__c |
Checkbox |
Skip Account validation trigger |
pw_ccpro__DisableContactTrigger__c |
Checkbox |
Skip Contact validation trigger |
pw_ccpro__DisableContractTrigger__c |
Checkbox |
Skip Contract validation trigger |
pw_ccpro__DisableLeadTrigger__c |
Checkbox |
Skip Lead validation trigger |
pw_ccpro__DisabledPages__c |
LongText |
Comma-separated VF pages to disable |
Boolean defaults at first install are false. StoreCountryValueAs__c defaults to 0.
3.2 Recipe — Edit Org-Wide Default
sf data query --target-org <alias> -q \
"SELECT Id FROM pw_ccpro__CountryCompleteSettings__c WHERE SetupOwnerId = '<ORG_ID>'"
Prefer anonymous Apex for hierarchy-setting upserts:
// scripts/apex/set-org-defaults.apex
pw_ccpro__CountryCompleteSettings__c s = pw_ccpro__CountryCompleteSettings__c.getOrgDefaults();
if (s == null || s.Id == null) { s = new pw_ccpro__CountryCompleteSettings__c(); }
s.pw_ccpro__CountryCompleteEnabled__c = true;
s.pw_ccpro__StandardizationEnabled__c = true;
s.pw_ccpro__StoreCountryValueAs__c = 1; // ISO-2
s.pw_ccpro__AlertEmail__c = 'admin@example.com';
upsert s;
sf apex run --target-org <alias> --file scripts/apex/set-org-defaults.apex
3.3 Recipe — Per-Profile Override
Profile p = [SELECT Id FROM Profile WHERE Name = 'System Administrator' LIMIT 1];
pw_ccpro__CountryCompleteSettings__c s =
pw_ccpro__CountryCompleteSettings__c.getInstance(p.Id);
if (s == null || s.SetupOwnerId != p.Id) {
s = new pw_ccpro__CountryCompleteSettings__c(SetupOwnerId = p.Id);
}
s.pw_ccpro__DisableAccountTrigger__c = true;
s.pw_ccpro__DisableContactTrigger__c = true;
s.pw_ccpro__DisableContractTrigger__c = true;
s.pw_ccpro__DisableLeadTrigger__c = true;
upsert s;
System.debug('AddressTools triggers disabled for profile: ' + p.Id);
3.4 Recipe — Per-User Override
User u = [SELECT Id FROM User WHERE Username = 'integration.user@example.com' LIMIT 1];
pw_ccpro__CountryCompleteSettings__c s =
pw_ccpro__CountryCompleteSettings__c.getInstance(u.Id);
if (s == null || s.SetupOwnerId != u.Id) {
s = new pw_ccpro__CountryCompleteSettings__c(SetupOwnerId = u.Id);
}
s.pw_ccpro__DisableAccountTrigger__c = true;
upsert s;
3.5 Copilot Rules
- NEVER insert a second org-default row. Always use
getOrgDefaults() then upsert.
- For profile/user records, ALWAYS check
SetupOwnerId (getInstance may return an inherited row).
- Scope mapping: “for everyone” → org default, “for admins” → profile, “for X” → user. If unspecified, ask.
4. Task 2 — Create / Edit Address Blocks (ValidatedField__c)
pw_ccpro__ValidatedField__c is a List custom setting. Each record is one “address block” — a logical mapping that ties the AddressTools engine to a specific set of address fields on a specific object/record type.
4.1 Key Fields on pw_ccpro__ValidatedField__c
| Field |
Purpose |
Name |
Unique key; conventionally Object_RecordType_CountryFieldApiName. Auto-derived if blank. |
pw_ccpro__Object__c |
sObject API name |
pw_ccpro__RecordType__c |
(optional) RecordType DeveloperName |
pw_ccpro__StreetField__c, pw_ccpro__CityField__c, pw_ccpro__StateField__c, pw_ccpro__ZipCodeField__c, pw_ccpro__CountryField__c, pw_ccpro__CountyField__c |
Address component fields on the target object |
pw_ccpro__SubBuildingLine__c |
Street line 2 / sub-building |
pw_ccpro__AddressStatusField__c |
Verification status field. Must be a Picklist (unrestricted) — see §4.3.2. |
pw_ccpro__AddressLabelField__c |
Formatted address (Long Text Area) |
pw_ccpro__PropertyUseField__c |
Property classification (Picklist) |
pw_ccpro__CountryRelationshipField__c, pw_ccpro__StateRelationshipField__c, pw_ccpro__ZipCodeRelationshipField__c, pw_ccpro__ZipCodeCountyRelationshipField__c |
Lookup fields to AddressTools reference objects |
pw_ccpro__LocationLatitudeField__c, pw_ccpro__LocationLongitudeField__c, pw_ccpro__LocationGeocodeAccuracyField__c |
Geocode result fields |
pw_ccpro__IsCityMandatory__c, pw_ccpro__IsStateMandatory__c, pw_ccpro__IsCountryMandatory__c, pw_ccpro__IsZipCodeMandatory__c, pw_ccpro__IsStreetLine1Mandatory__c, pw_ccpro__IsStreetLine2Mandatory__c |
Per-component required flags |
pw_ccpro__AllowOnlyListedCountries__c, pw_ccpro__AllowOnlyListedStates__c, pw_ccpro__AllowOnlyListedUsZipCodes__c |
Restrict to reference data |
pw_ccpro__Standardize__c, pw_ccpro__StandardizeState__c |
Standardization flags |
pw_ccpro__EnableGeolocation__c, pw_ccpro__EnableBulkAddressVerification__c |
Feature flags |
pw_ccpro__UseForAddressValidation__c |
Whether this block participates in PLAV |
pw_ccpro__ValidatePostalCodeWithRegexOnSave__c, pw_ccpro__ValidateZipCityState__c |
Save-time validations |
pw_ccpro__ForceMultiLine__c |
Force multi-line entry mode |
Retired fields (do not use): pw_ccpro__LatitudeRelationshipField__c and pw_ccpro__LongitudeRelationshipField__c are retired. All geolocation strategies now use pw_ccpro__LocationLatitudeField__c and pw_ccpro__LocationLongitudeField__c exclusively.
4.2 Discovery — Before Creating, Inspect the Org
A copilot must check what already exists; do not blindly create.
# What address blocks already exist?
sf data query --target-org <alias> -q \
"SELECT Id, Name, pw_ccpro__Object__c, pw_ccpro__RecordType__c, pw_ccpro__CountryField__c
FROM pw_ccpro__ValidatedField__c ORDER BY pw_ccpro__Object__c, Name"
# What fields exist on the target object?
sf sobject describe --target-org <alias> --sobject <ObjectApiName> \
| jq '.fields[] | select(.name|test("Street|City|State|PostalCode|Country|County|Address|Geocode|Latitude|Longitude|PropertyUse";"i"))
| {name,type,length,restrictedPicklist,picklistValues:[.picklistValues[]?.value]}'
Decision logic for the copilot:
- If a
ValidatedField__c row already matches Object__c + RecordType__c + CountryField__c → edit it, don’t insert.
- If the object has standard
BillingAddress / ShippingAddress / MailingAddress compound fields → use the standard component fields.
- If the object is custom with no address components → follow §4.3.
- Inspect any pre-existing AddressStatus / PropertyUse / GeocodeAccuracy fields and assert they are Picklists with the canonical values (§4.3.2). A
Text field for AddressStatus is a common mistake and will cause silent failures.
4.3 Creating Fields for a New Custom Address Block
Before creating the address block record, the copilot MUST ask the user which capabilities they need, then create the appropriate fields. The copilot MUST ask the user before it starts generating field metadata, not after.
4.3.1 Field Requirements Decision Tree
“What capabilities do you need on this address block?”
├── Core address fields (Street, City, State, PostalCode, Country)
│ └── ALWAYS required — create if missing
├── Address verification?
│ ├── Yes → Address Status (MANDATORY), County (optional),
│ │ Address Label (optional), Property Use (optional)
│ └── No → skip
├── Lookup field population?
│ ├── Yes → Country Lookup, State Lookup (requires Country Lookup),
│ │ ZIP Lookup (requires Country + State Lookup)
│ └── No → skip
└── Geocoding?
├── Yes → choose ONE strategy (do not mix on a single block):
│ A) Individual fields (preferred for custom objects):
│ Latitude (Number), Longitude (Number), Geocode Accuracy (Picklist)
│ B) Geolocation compound type:
│ Single Geolocation field + Geocode Accuracy (Picklist)
│ C) Standard address compound (standard objects only):
│ Built-in Latitude/Longitude/GeocodeAccuracy on the compound address
└── No → skip
The user’s answers map directly onto which pw_ccpro__ValidatedField__c fields the copilot should populate later (see §4.4 and §8 Template C).
4.3.2 Address Block Behavior Settings
After determining field capabilities, the copilot MUST ask the user which behavior settings to enable. Present these grouped by category:
Standardization
| Setting |
Field |
Default |
Notes |
| Standardize country values |
pw_ccpro__Standardize__c |
false |
Maps typed input to canonical country name/code |
| Standardize state values |
pw_ccpro__StandardizeState__c |
false |
Maps typed input to canonical state name/code |
| Restrict to listed countries |
pw_ccpro__AllowOnlyListedCountries__c |
false |
Rejects countries not in reference data |
| Restrict to listed states |
pw_ccpro__AllowOnlyListedStates__c |
false |
Rejects states not in reference data |
| Restrict to listed US ZIP codes |
pw_ccpro__AllowOnlyListedUsZipCodes__c |
false |
Rare — only for US-specific blocks |
Save-time validation
| Setting |
Field |
Default |
Notes |
| Validate postal code format (regex) |
pw_ccpro__ValidatePostalCodeWithRegexOnSave__c |
false |
Checks format against country-specific regex |
| Validate ZIP/city/state consistency |
pw_ccpro__ValidateZipCityState__c |
false |
Cross-checks ZIP against city and state |
Processing
| Setting |
Field |
Default |
Notes |
| Enable bulk address verification |
pw_ccpro__EnableBulkAddressVerification__c |
false |
Required for batch verification jobs |
| Force multi-line address entry |
pw_ccpro__ForceMultiLine__c |
false |
Splits street into separate lines |
Mandatory field flags — ask which address components should be required on save:
| Flag |
Default |
pw_ccpro__IsCountryMandatory__c |
false |
pw_ccpro__IsStateMandatory__c |
false |
pw_ccpro__IsCityMandatory__c |
false |
pw_ccpro__IsZipCodeMandatory__c |
false |
pw_ccpro__IsStreetLine1Mandatory__c |
false |
pw_ccpro__IsStreetLine2Mandatory__c |
false |
For a typical address block with verification, a reasonable starting point is:
- Standardization on, country mandatory, state mandatory
- Validation rules off (let the user opt in after testing)
- Bulk verification off unless explicitly requested
Set these flags on the pw_ccpro__ValidatedField__c record in the upsert (§4.4 / §8 Template C).
4.3.3 Field Specifications
These are the canonical specs. Generate metadata exactly as below.
| Field |
Type |
Metadata Details |
Picklist Values |
Restricted? |
| Address Status |
Picklist |
nillable; default = Not checked |
Not checked (default), Not matched, Parsed but not found, Ambiguous, Verified |
No (unrestricted) |
| County |
Text |
Length: 255 |
— |
— |
| Address Label |
Long Text Area |
Length: 32,768; visible lines: 5 |
— |
— |
| Property Use |
Picklist |
nillable |
Residential, Commercial, Unknown |
Yes (restricted) |
| Country Lookup |
Lookup |
Related to: pw_ccpro__CountryObject__c |
— |
— |
| State Lookup |
Lookup |
Related to: pw_ccpro__State__c; depends on Country Lookup |
— |
— |
| ZIP Code Lookup |
Lookup |
Related to: pw_ccpro__ZipCode__c; depends on Country + State Lookup |
— |
— |
| Geolocation (compound) |
Geolocation |
Decimal notation; 15 decimal places; nillable |
— |
— |
| Latitude (individual) |
Number |
Length: 3, Decimal: 15 |
— |
— |
| Longitude (individual) |
Number |
Length: 3, Decimal: 15 |
— |
— |
| Geocode Accuracy |
Picklist |
nillable; default = Unknown (recommended) |
Unknown (default), Address, NearAddress, Block, Street, ExtendedZip, Zip, Neighborhood, City, County, State |
No (unrestricted) |
A Text field for Address Status will appear to “work” but writes will fail silently. A Text field for Geocode Accuracy is rejected at runtime; the package validates incoming values against the picklist and throws on mismatch.
Reference field metadata for the trickiest three (other types are vanilla Salesforce field-meta.xml):
<!-- Project__c.BillingAddressStatus__c.field-meta.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>BillingAddressStatus__c</fullName>
<externalId>false</externalId>
<label>Billing Address Status</label>
<required>false</required>
<trackTrending>false</trackTrending>
<type>Picklist</type>
<valueSet>
<restricted>false</restricted>
<valueSetDefinition>
<sorted>false</sorted>
<value><fullName>Not checked</fullName><default>true</default><label>Not checked</label></value>
<value><fullName>Not matched</fullName><default>false</default><label>Not matched</label></value>
<value><fullName>Parsed but not found</fullName><default>false</default><label>Parsed but not found</label></value>
<value><fullName>Ambiguous</fullName><default>false</default><label>Ambiguous</label></value>
<value><fullName>Verified</fullName><default>false</default><label>Verified</label></value>
</valueSetDefinition>
</valueSet>
</CustomField>
<!-- Project__c.BillingGeocodeAccuracy__c.field-meta.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>BillingGeocodeAccuracy__c</fullName>
<externalId>false</externalId>
<label>Billing Geocode Accuracy</label>
<required>false</required>
<trackTrending>false</trackTrending>
<type>Picklist</type>
<valueSet>
<restricted>false</restricted>
<valueSetDefinition>
<sorted>false</sorted>
<value><fullName>Unknown</fullName><default>true</default><label>Unknown</label></value>
<value><fullName>Address</fullName><default>false</default><label>Address</label></value>
<value><fullName>NearAddress</fullName><default>false</default><label>NearAddress</label></value>
<value><fullName>Block</fullName><default>false</default><label>Block</label></value>
<value><fullName>Street</fullName><default>false</default><label>Street</label></value>
<value><fullName>ExtendedZip</fullName><default>false</default><label>ExtendedZip</label></value>
<value><fullName>Zip</fullName><default>false</default><label>Zip</label></value>
<value><fullName>Neighborhood</fullName><default>false</default><label>Neighborhood</label></value>
<value><fullName>City</fullName><default>false</default><label>City</label></value>
<value><fullName>County</fullName><default>false</default><label>County</label></value>
<value><fullName>State</fullName><default>false</default><label>State</label></value>
</valueSetDefinition>
</valueSet>
</CustomField>
<!-- Project__c.BillingPropertyUse__c.field-meta.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>BillingPropertyUse__c</fullName>
<externalId>false</externalId>
<label>Billing Property Use</label>
<required>false</required>
<trackTrending>false</trackTrending>
<type>Picklist</type>
<valueSet>
<restricted>true</restricted>
<valueSetDefinition>
<sorted>false</sorted>
<value><fullName>Residential</fullName><default>false</default><label>Residential</label></value>
<value><fullName>Commercial</fullName><default>false</default><label>Commercial</label></value>
<value><fullName>Unknown</fullName><default>false</default><label>Unknown</label></value>
</valueSetDefinition>
</valueSet>
</CustomField>
4.3.4 Deploy
sf project deploy start --target-org <alias> \
--source-dir force-app/main/default/objects/Project__c
Then proceed to §4.5 to grant FLS, then §4.6 to ensure a trigger exists, before running the address-block upsert in §4.4 / §8 Template C. Do not skip §4.5 or §4.6.
The package exposes a helper that can synthesize the lookup fields in §4.3.2 via the Metadata API at runtime when the user has answered “yes” to lookup population:
pw_ccpro__ValidatedField__c[] vfs = [
SELECT Id, pw_ccpro__Object__c, pw_ccpro__CountryField__c,
pw_ccpro__CountryRelationshipField__c,
pw_ccpro__StateRelationshipField__c,
pw_ccpro__ZipCodeRelationshipField__c
FROM pw_ccpro__ValidatedField__c WHERE Name = 'Project_Billing'
];
pw_ccpro.CountryCompleteSettings.createLookupFields(vfs);
4.4 Recipe — Create the Address Block Record
Once the fields exist (§4.3), FLS is granted (§4.5), and a trigger is in place (§4.6):
// scripts/apex/upsert-address-block.apex
pw_ccpro__ValidatedField__c vf = new pw_ccpro__ValidatedField__c(
pw_ccpro__Object__c = 'Project__c',
pw_ccpro__StreetField__c = 'BillingStreet__c',
pw_ccpro__CityField__c = 'BillingCity__c',
pw_ccpro__StateField__c = 'BillingState__c',
pw_ccpro__ZipCodeField__c = 'BillingPostalCode__c',
pw_ccpro__CountryField__c = 'BillingCountry__c',
pw_ccpro__CountyField__c = 'BillingCounty__c',
pw_ccpro__AddressStatusField__c = 'BillingAddressStatus__c', // MUST be Picklist (§4.3.2)
pw_ccpro__IsCountryMandatory__c = true,
pw_ccpro__Standardize__c = true,
pw_ccpro__EnableGeolocation__c = false
);
vf.Name = 'Project_Billing';
upsert vf Name;
For the full conditional template (verification, lookups, geolocation strategies), see §8 Template C.
4.5 Permission Set for Custom Address Block Fields (MANDATORY)
After creating custom fields for an address block, the copilot MUST create or update a permission set to grant FLS on those fields. Address blocks are useless if the operating user can’t read/write the underlying fields.
4.5.1 Copilot Workflow
-
Ask the user who needs access — exactly one question:
“Which users or groups need access to this address block? Should I (a) create a new permission set, (b) add to an existing permission set, or © add a new permission set to an existing permission set group?”
-
Query existing permission sets to check for a suitable candidate:
sf data query --target-org <alias> -q \
"SELECT Id, Name, Label FROM PermissionSet
WHERE IsCustom = true AND IsOwnedByProfile = false
ORDER BY Name"
-
Query existing permission set groups if option ©:
sf data query --target-org <alias> -q \
"SELECT Id, DeveloperName, MasterLabel FROM PermissionSetGroup ORDER BY DeveloperName"
-
Create or update a permission set with <fieldPermissions> for every custom field on the address block (§4.5.2).
-
Deploy and assign:
sf project deploy start --target-org <alias> \
--source-dir force-app/main/default/permissionsets/AddressTools_Project_BillingAddress_Edit.permissionset-meta.xml
sf org assign permset --target-org <alias> \
--name AddressTools_Project_BillingAddress_Edit \
--on-behalf-of <username>
4.5.2 Permission Set Metadata Template
<!-- AddressTools_Project_BillingAddress_Edit.permissionset-meta.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<PermissionSet xmlns="http://soap.sforce.com/2006/04/metadata">
<hasActivationRequired>false</hasActivationRequired>
<label>AddressTools Project Billing Address Edit</label>
<license>Salesforce</license>
<description>Grants FLS for the AddressTools billing address block on Project__c.
Pair with pw_ccpro__AddressToolsPremiumStandardUser (or Admin variant).</description>
<!-- Core address components -->
<fieldPermissions><field>Project__c.BillingStreet__c</field><readable>true</readable><editable>true</editable></fieldPermissions>
<fieldPermissions><field>Project__c.BillingCity__c</field><readable>true</readable><editable>true</editable></fieldPermissions>
<fieldPermissions><field>Project__c.BillingState__c</field><readable>true</readable><editable>true</editable></fieldPermissions>
<fieldPermissions><field>Project__c.BillingPostalCode__c</field><readable>true</readable><editable>true</editable></fieldPermissions>
<fieldPermissions><field>Project__c.BillingCountry__c</field><readable>true</readable><editable>true</editable></fieldPermissions>
<!-- Verification (only if user said "yes" to verification in §4.3.1) -->
<fieldPermissions><field>Project__c.BillingAddressStatus__c</field><readable>true</readable><editable>true</editable></fieldPermissions>
<fieldPermissions><field>Project__c.BillingCounty__c</field><readable>true</readable><editable>true</editable></fieldPermissions>
<fieldPermissions><field>Project__c.BillingAddressLabel__c</field><readable>true</readable><editable>true</editable></fieldPermissions>
<fieldPermissions><field>Project__c.BillingPropertyUse__c</field><readable>true</readable><editable>true</editable></fieldPermissions>
<!-- Lookups (only if user said "yes" to lookup population in §4.3.1) -->
<fieldPermissions><field>Project__c.BillingCountryLookup__c</field><readable>true</readable><editable>true</editable></fieldPermissions>
<fieldPermissions><field>Project__c.BillingStateLookup__c</field><readable>true</readable><editable>true</editable></fieldPermissions>
<fieldPermissions><field>Project__c.BillingZipCodeLookup__c</field><readable>true</readable><editable>true</editable></fieldPermissions>
<!-- Geocoding (only if user said "yes" to geocoding; emit the rows for the chosen strategy) -->
<fieldPermissions><field>Project__c.BillingLatitude__c</field><readable>true</readable><editable>true</editable></fieldPermissions>
<fieldPermissions><field>Project__c.BillingLongitude__c</field><readable>true</readable><editable>true</editable></fieldPermissions>
<fieldPermissions><field>Project__c.BillingGeocodeAccuracy__c</field><readable>true</readable><editable>true</editable></fieldPermissions>
</PermissionSet>
To add this permission set to an existing permission set group:
<!-- AddressTools_Standard.permissionsetgroup-meta.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<PermissionSetGroup xmlns="http://soap.sforce.com/2006/04/metadata">
<description>Standard AddressTools user bundle.</description>
<label>AddressTools — Standard</label>
<permissionSets>pw_ccpro__AddressToolsPremiumStandardUser</permissionSets>
<permissionSets>AddressTools_Project_BillingAddress_Edit</permissionSets>
</PermissionSetGroup>
4.5.3 Hard Rules
- Naming convention:
AddressTools_<Object>_<AddressType>Address_Edit with label AddressTools <Object> <AddressType> Address Edit. Follow this format for consistency with managed permission sets.
- Never skip FLS. A custom address block without FLS grants will silently fail for users who lack field access.
- Prefer a dedicated permission set per address block over adding to the managed-package permission sets (which are read-only in subscriber orgs and cannot be edited).
- Granularity: include ONLY the fields belonging to the address block — do not bundle unrelated FLS into the same permission set.
- Group composition: if adding to a permission set group, create the permission set first, deploy it, then add it as a member via the group’s metadata (above). Never edit the managed
pw_ccpro__* permission sets.
- Verification query after assignment:
sf data query --target-org <alias> -q \
"SELECT Field, PermissionsRead, PermissionsEdit
FROM FieldPermissions
WHERE Parent.Name = 'AddressTools_Project_BillingAddress_Edit'
AND SObjectType = 'Project__c'"
4.6 Trigger for the Target Object (MANDATORY)
AddressTools requires an Apex trigger on each object where an address block is configured. Without the trigger, standardization, validation, and verification will not fire.
4.6.1 Standard Objects — Already Handled
The managed package ships triggers for these objects out of the box:
Account
Contact
Contract
Lead
No action required for these objects. The triggers are enabled by default and controlled by the Disable*Trigger__c hierarchy settings (§3).
4.6.2 Custom Objects — Trigger Required
For any object not in the list above, the copilot MUST check whether a trigger already exists and create one if it doesn’t.
Check for an existing trigger
sf data query --target-org <alias> -q \
"SELECT Id, Name, TableEnumOrId, Body FROM ApexTrigger
WHERE TableEnumOrId = '<OBJECT_API_NAME>'
AND Status = 'Active'"
If a result contains pw_ccpro.CountryValidator2.Validate, the trigger already exists — skip creation.
Create the trigger
The trigger follows a mandatory pattern. Replace <OBJECT_LABEL> (no spaces) and <OBJECT_API_NAME>:
trigger Validate<OBJECT_LABEL>CountryFields on <OBJECT_API_NAME> (before insert, before update) {
pw_ccpro.CountryValidator2.Validate(Trigger.new, Trigger.oldMap);
}
Example for Property__c:
trigger ValidatePropertyCountryFields on Property__c (before insert, before update) {
pw_ccpro.CountryValidator2.Validate(Trigger.new, Trigger.oldMap);
}
Deploy the trigger
Write the trigger file and its metadata:
force-app/main/default/triggers/ValidatePropertyCountryFields.trigger
force-app/main/default/triggers/ValidatePropertyCountryFields.trigger-meta.xml
<!-- ValidatePropertyCountryFields.trigger-meta.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<ApexTrigger xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<status>Active</status>
</ApexTrigger>
Deploy:
sf project deploy start --target-org <alias> \
--source-dir force-app/main/default/triggers/ValidatePropertyCountryFields.trigger \
--source-dir force-app/main/default/triggers/ValidatePropertyCountryFields.trigger-meta.xml
Production deployments require test coverage for the trigger. Create a minimal test class that inserts a record on the target object. This is a permanent class (not AT_Tmp_*) since it must remain deployed alongside the trigger.
@IsTest
private class ValidatePropertyCountryFieldsTest {
@IsTest
static void testTriggerFires() {
Property__c rec = new Property__c(
Name = 'Test Property',
BillingCountry__c = 'United States'
);
Test.startTest();
insert rec;
Test.stopTest();
System.assertNotEquals(null, rec.Id);
}
}
4.6.3 Hard Rules
- Never skip the trigger. An address block without a trigger on the target object will silently do nothing.
- One trigger per object is sufficient — it covers all address blocks on that object.
- For standard objects, rely on the managed triggers. Do NOT create a second trigger calling
CountryValidator2.Validate.
- The trigger file and test class are permanent metadata — they should be committed to source control and included in CI/CD pipelines.
- If the user later removes all address blocks from a custom object, the trigger can be removed via destructive deploy (§9).
5. Task 3 — Provision a User
This is the most multi-step task. Treat it as a transaction with these steps; skip any that don’t apply.
5.1 Inputs the Copilot Must Resolve
- Target user — username or Id.
- Role —
admin or standard (drives permission set choice).
- Address blocks — which
ValidatedField__c records this user should have FLS to use (managed-package perm sets cover package fields; custom blocks need the org-owned perm sets from §4.5).
- Org type — production vs sandbox.
5.2 Detect Sandbox vs Production
sf data query --target-org <alias> -q \
"SELECT IsSandbox, OrganizationType, TrialExpirationDate FROM Organization"
IsSandbox = true → skip managed package license assignment.
IsSandbox = false → license assignment required if PackageLicense for pw_ccpro exists with limited seats.
5.3 Assign Permission Set(s)
# Admin
sf org assign permset --target-org <alias> \
--name pw_ccpro__AddressToolsPremiumAdminUser --on-behalf-of <username>
# Standard
sf org assign permset --target-org <alias> \
--name pw_ccpro__AddressToolsPremiumStandardUser --on-behalf-of <username>
Legacy fallback: pw_ccpro__AddressToolsPremiumStdUser. Detect with:
sf data query -o <alias> -q "SELECT Name FROM PermissionSet WHERE Name LIKE 'AddressToolsPremium%'"
Address block FLS permission sets
The package may ship managed permission sets for standard address blocks (e.g. pw_ccpro__AddressTools_Account_BillingAddress_Edit). Always check for these before creating org-owned alternatives:
# List any managed address block permission sets
sf data query --target-org <alias> -q \
"SELECT Name, Label FROM PermissionSet
WHERE NamespacePrefix = 'pw_ccpro' AND Name LIKE 'AddressTools_%_Edit'"
- If a managed permission set exists for the address block, assign it directly.
- If no managed permission set exists (custom objects, or older package versions), create and assign an org-owned permission set per §4.5.
# Managed permission set (if available)
sf org assign permset --target-org <alias> \
--name pw_ccpro__AddressTools_Account_BillingAddress_Edit --on-behalf-of <username>
# Org-owned permission set (custom objects / fallback)
sf org assign permset --target-org <alias> \
--name AddressTools_Project_BillingAddress_Edit --on-behalf-of <username>
5.4 Assign Managed Package License (Production Only)
// scripts/apex/assign-pw-ccpro-license.apex
PackageLicense pl = [SELECT Id, AllowedLicenses, UsedLicenses
FROM PackageLicense
WHERE NamespacePrefix = 'pw_ccpro' LIMIT 1];
User u = [SELECT Id, IsActive FROM User WHERE Username = :userName LIMIT 1];
Integer existing = [SELECT COUNT() FROM UserPackageLicense
WHERE PackageLicenseId = :pl.Id AND UserId = :u.Id];
if (existing == 0) {
if (pl.AllowedLicenses != -1 && pl.UsedLicenses >= pl.AllowedLicenses) {
throw new System.SecurityException('No AddressTools licenses available.');
}
insert new UserPackageLicense(PackageLicenseId = pl.Id, UserId = u.Id);
}
PackageLicense may not exist for unlimited / site-licensed packages — handle the empty-result case as “nothing to do”.
5.5 (Optional) Per-User Hierarchy Settings
If the user needs different runtime behaviour (e.g., an integration user that should bypass triggers), apply the per-user override from §3.4.
5.6 Verification Query
sf data query --target-org <alias> -q \
"SELECT Assignee.Username, PermissionSet.Name FROM PermissionSetAssignment
WHERE Assignee.Username = '<username>'
AND (PermissionSet.NamespacePrefix = 'pw_ccpro'
OR PermissionSet.Name LIKE 'AddressTools_%')"
6. Task 4 — Edit Installed Package Data
The package ships reference data into pw_ccpro__CountryObject__c and friends at install. After install, customers can extend or correct that data via standard DML.
6.1 Add an Alternative Country Name
pw_ccpro__CountryObject__c uk = [
SELECT Id FROM pw_ccpro__CountryObject__c
WHERE pw_ccpro__IsoCode_2__c = 'GB' LIMIT 1
];
pw_ccpro__AlternativeCountryName__c alt = new pw_ccpro__AlternativeCountryName__c(
Name = 'Great Briton',
pw_ccpro__OriginalCountry__c = uk.Id,
pw_ccpro__IsObsolete__c = false
);
Integer existing = [SELECT COUNT() FROM pw_ccpro__AlternativeCountryName__c
WHERE Name = :alt.Name AND pw_ccpro__OriginalCountry__c = :uk.Id];
if (existing == 0) insert alt;
6.2 Edit an Existing Country Record
pw_ccpro__CountryObject__c c = [SELECT Id FROM pw_ccpro__CountryObject__c
WHERE pw_ccpro__IsoCode_2__c = 'GB' LIMIT 1];
c.Name = 'United Kingdom';
update c;
6.3 Alternative State Names
Same shape as 6.1, but with pw_ccpro__AlternativeStateName__c master-detail to pw_ccpro__State__c. State key is pw_ccpro__FullIsoCode__c (e.g. US-CA).
6.4 Copilot Rules
- Idempotency first. Check existence before insert.
- Don’t set
IsObsolete__c = true to “delete” — that field is package-managed and re-running install can flip it back. Use delete only on data the package didn’t install.
- Reference data records may not be writable to all profiles; if FLS errors occur, escalate via §5.
7. Headless Tooling Cheat Sheet
| Need |
Preferred Tool |
Notes |
| Read settings / org info |
sf data query |
|
| Hierarchy custom setting writes |
Anonymous Apex via sf apex run |
Leaves NO artefacts in the org |
| List custom setting writes |
sf data upsert with --external-id Name, OR Apex |
|
| Field/object metadata changes |
sf project deploy start from local source |
Required when adding fields for new address blocks |
| Permission set deploy + assignment |
sf project deploy start + sf org assign permset |
Always pair both — deploy without assign is a no-op |
| Package license |
Anonymous Apex against PackageLicense / UserPackageLicense |
Skip in sandboxes |
| Diagnostics / “what changed” |
sf data query over pw_ccpro__* objects |
Always confirm before/after |
Default rule: prefer anonymous Apex (sf apex run --file ...) over deploying a temporary Apex class. Anonymous Apex executes and disappears; a deployed class persists in the org until removed. Only deploy a class when behavior cannot be expressed in anonymous Apex (e.g. Schedulable/Batchable, @InvocableMethod, LWC controllers). When you do, follow §9.
8. Reusable Anonymous-Apex Templates
These run via sf apex run --file ... and leave no Apex class behind.
A. Set/clear a single hierarchy field (org default):
pw_ccpro__CountryCompleteSettings__c s = pw_ccpro__CountryCompleteSettings__c.getOrgDefaults();
if (s == null || s.Id == null) s = new pw_ccpro__CountryCompleteSettings__c();
s.<FIELD_API_NAME> = <VALUE>;
upsert s;
B. Profile override:
Profile p = [SELECT Id FROM Profile WHERE Name = '<PROFILE_NAME>' LIMIT 1];
pw_ccpro__CountryCompleteSettings__c s = pw_ccpro__CountryCompleteSettings__c.getInstance(p.Id);
if (s == null || s.SetupOwnerId != p.Id) s = new pw_ccpro__CountryCompleteSettings__c(SetupOwnerId = p.Id);
s.<FIELD_API_NAME> = <VALUE>;
upsert s;
C. Address block upsert by Name (full conditional template):
⚠️ Pre-requisites:
- Fields exist on the target object per §4.3 (use the canonical field-meta.xml in §4.3.3).
- FLS is granted via §4.5 — deploy and assign before this script runs.
pw_ccpro__AddressStatusField__c MUST reference an unrestricted Picklist with the canonical AddressTools status values: Not checked, Not matched, Parsed but not found, Ambiguous, Verified. Using a Text field will cause verification status updates to fail silently.
pw_ccpro__LocationGeocodeAccuracyField__c MUST reference a nullable Picklist with the canonical GeocodeAccuracy values (§4.3.3). The package validates incoming values against the picklist and throws on mismatch.
// scripts/apex/upsert-address-block-full.apex
//
// Replace <ALL_CAPS> placeholders. Comment out blocks the user did NOT request
// in the §4.3.1 decision tree.
//
pw_ccpro__ValidatedField__c vf = new pw_ccpro__ValidatedField__c(Name = '<UNIQUE_NAME>');
vf.pw_ccpro__Object__c = '<SOBJECT_API_NAME>';
vf.pw_ccpro__RecordType__c = '<RECORD_TYPE_DEVELOPER_NAME_OR_NULL>';
// --- Core address fields (always required) ---
vf.pw_ccpro__StreetField__c = '<STREET_FIELD>';
vf.pw_ccpro__CityField__c = '<CITY_FIELD>';
vf.pw_ccpro__StateField__c = '<STATE_FIELD>';
vf.pw_ccpro__ZipCodeField__c = '<ZIP_FIELD>';
vf.pw_ccpro__CountryField__c = '<COUNTRY_FIELD>';
// --- Verification (only if user opted in) ---
vf.pw_ccpro__AddressStatusField__c = '<STATUS_PICKLIST_FIELD>'; // §4.3.3 Address Status spec
vf.pw_ccpro__CountyField__c = '<COUNTY_FIELD_OR_NULL>';
vf.pw_ccpro__AddressLabelField__c = '<ADDRESS_LABEL_FIELD_OR_NULL>';
vf.pw_ccpro__PropertyUseField__c = '<PROPERTY_USE_PICKLIST_OR_NULL>'; // §4.3.3 Property Use spec
vf.pw_ccpro__UseForAddressValidation__c = true;
// --- Lookup population (only if user opted in) ---
vf.pw_ccpro__CountryRelationshipField__c = '<COUNTRY_LOOKUP_FIELD_OR_NULL>';
vf.pw_ccpro__StateRelationshipField__c = '<STATE_LOOKUP_FIELD_OR_NULL>';
vf.pw_ccpro__ZipCodeRelationshipField__c = '<ZIP_LOOKUP_FIELD_OR_NULL>';
vf.pw_ccpro__ZipCodeCountyRelationshipField__c = '<ZIP_COUNTY_LOOKUP_FIELD_OR_NULL>';
// --- Geocoding (only if user opted in) ---
// Pick exactly ONE strategy from §4.3.3 and uncomment its block.
vf.pw_ccpro__EnableGeolocation__c = true;
// Strategy A — individual Number fields (preferred for custom objects)
vf.pw_ccpro__LocationLatitudeField__c = '<LAT_NUMBER_FIELD>';
vf.pw_ccpro__LocationLongitudeField__c = '<LONG_NUMBER_FIELD>';
vf.pw_ccpro__LocationGeocodeAccuracyField__c = '<GEOCODE_ACCURACY_PICKLIST>'; // §4.3.3
/*
// Strategy B — Geolocation compound field
vf.pw_ccpro__LocationLatitudeField__c = '<GEOLOC_FIELD>__Latitude__s';
vf.pw_ccpro__LocationLongitudeField__c = '<GEOLOC_FIELD>__Longitude__s';
vf.pw_ccpro__LocationGeocodeAccuracyField__c = '<GEOCODE_ACCURACY_PICKLIST>';
*/
/*
// Strategy C — Standard compound (standard objects only, e.g. Account Billing)
vf.pw_ccpro__LocationLatitudeField__c = 'BillingLatitude';
vf.pw_ccpro__LocationLongitudeField__c = 'BillingLongitude';
vf.pw_ccpro__LocationGeocodeAccuracyField__c = 'BillingGeocodeAccuracy';
*/
// --- Mandatory flags / behaviour (set per user requirements) ---
vf.pw_ccpro__IsCountryMandatory__c = true;
vf.pw_ccpro__Standardize__c = true;
upsert vf Name; // List custom settings -> external key is Name
System.debug('Upserted address block: ' + vf.Name + ' (' + vf.Id + ')');
D. Add alternative country name (idempotent):
pw_ccpro__CountryObject__c c = [SELECT Id FROM pw_ccpro__CountryObject__c
WHERE pw_ccpro__IsoCode_2__c = '<ISO2>' LIMIT 1];
if ([SELECT COUNT() FROM pw_ccpro__AlternativeCountryName__c
WHERE Name = '<ALT_NAME>' AND pw_ccpro__OriginalCountry__c = :c.Id] == 0) {
insert new pw_ccpro__AlternativeCountryName__c(
Name = '<ALT_NAME>', pw_ccpro__OriginalCountry__c = c.Id);
}
E. Provision user (production):
String uname = '<USERNAME>';
User u = [SELECT Id FROM User WHERE Username = :uname LIMIT 1];
// Permission set
PermissionSet ps = [SELECT Id FROM PermissionSet
WHERE Name = 'pw_ccpro__AddressToolsPremiumStandardUser' LIMIT 1];
if ([SELECT COUNT() FROM PermissionSetAssignment
WHERE AssigneeId = :u.Id AND PermissionSetId = :ps.Id] == 0) {
insert new PermissionSetAssignment(AssigneeId = u.Id, PermissionSetId = ps.Id);
}
// License (skip in sandbox)
if (![SELECT IsSandbox FROM Organization LIMIT 1].IsSandbox) {
List<PackageLicense> pls = [SELECT Id, AllowedLicenses, UsedLicenses
FROM PackageLicense WHERE NamespacePrefix = 'pw_ccpro'];
if (!pls.isEmpty()) {
PackageLicense pl = pls[0];
if ([SELECT COUNT() FROM UserPackageLicense
WHERE PackageLicenseId = :pl.Id AND UserId = :u.Id] == 0) {
if (pl.AllowedLicenses == -1 || pl.UsedLicenses < pl.AllowedLicenses) {
insert new UserPackageLicense(PackageLicenseId = pl.Id, UserId = u.Id);
} else {
throw new System.SecurityException('No AddressTools licenses available.');
}
}
}
}
9. Cleanup of Temporary Apex Classes (MANDATORY)
Some tasks cannot be done in anonymous Apex (e.g. invoking an @InvocableMethod, running a Database.Batchable/Schedulable, hosting a temporary @AuraEnabled method, or wrapping a long-running operation that must outlive a single execution). When the copilot is forced to deploy a helper Apex class into the customer org, it MUST remove that class once the work is complete. Lingering “scratch” classes are forbidden — they pollute the customer org, drift from source control, and create an audit/ISO 27001 finding.
9.1 Naming Convention
Any temporary class deployed by a copilot MUST:
- Be named with the prefix
AT_Tmp_ (e.g. AT_Tmp_BulkAltNameLoad, AT_Tmp_ProvisionUser_20260506).
- Include a header comment block stating purpose, creator, ISO change reference (if any), and a
// CLEANUP-AFTER: marker.
- Live under
force-app/main/default/classes/ in a feature branch — never committed to master/main.
- Be
private or public (never global) and contain ONLY the methods needed for the task.
/**
* AT_Tmp_BulkAltNameLoad
* Purpose: One-shot bulk loader for alternative country names.
* Created: <DATE> by copilot for change <CHG-####>.
* CLEANUP-AFTER: Run completed and verified.
* NOTE: This class is temporary and MUST be deleted after use (see §9 of this guide).
*/
public with sharing class AT_Tmp_BulkAltNameLoad {
public static void run() {
// ... idempotent logic ...
}
}
9.2 Standard Lifecycle
- Create the class file(s) on a short-lived feature branch.
- Deploy to the target org:
sf project deploy start --target-org <alias> \
--source-dir force-app/main/default/classes/AT_Tmp_BulkAltNameLoad.cls \
--source-dir force-app/main/default/classes/AT_Tmp_BulkAltNameLoad.cls-meta.xml
- Invoke via anonymous Apex:
sf apex run --target-org <alias> -e "AT_Tmp_BulkAltNameLoad.run();"
- Verify with a
sf data query and capture the result in the run log.
- Destructive deploy to remove the class (§9.3).
- Re-verify:
sf data query --target-org <alias> -q \
"SELECT Id, Name FROM ApexClass WHERE Name LIKE 'AT_Tmp_%'"
Expected: zero rows. If anything is returned, cleanup is incomplete and the task is NOT done.
- Branch cleanup: delete the local helper files; either delete the feature branch or merge a “revert” commit so source control reflects reality.
The copilot MUST NOT mark the user-facing task complete until step 6 returns zero rows.
9.3 Removing a Temporary Class — Destructive Deploy
<!-- destructiveChanges.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
<types>
<members>AT_Tmp_BulkAltNameLoad</members>
<name>ApexClass</name>
</types>
<version>66.0</version>
</Package>
<!-- package.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
<version>66.0</version>
</Package>
Sandbox:
sf project deploy start --target-org <alias> \
--manifest tmp-destructive/package.xml \
--post-destructive-changes tmp-destructive/destructiveChanges.xml \
--ignore-conflicts
Production (tests required):
sf project deploy start --target-org <alias> \
--manifest tmp-destructive/package.xml \
--post-destructive-changes tmp-destructive/destructiveChanges.xml \
--test-level RunLocalTests \
--ignore-conflicts
Delete the tmp-destructive/ directory locally once the deploy succeeds.
9.4 Same Rule Applies To Other Transient Metadata
| Type |
<name> value |
| Apex class |
ApexClass |
| Apex trigger |
ApexTrigger |
| LWC / Aura bundle |
LightningComponentBundle / AuraDefinitionBundle |
| Static resource |
StaticResource |
| Custom label |
CustomLabel |
| Permission set (org-owned, temporary only) |
PermissionSet |
| Remote site setting (temporary only) |
RemoteSiteSetting |
Never attempt destructive changes against managed-package metadata (anything in the pw_ccpro namespace).
9.5 Hard Rules for Copilots
10. References — ProvenWorks Public Documentation
The copilot SHOULD link out to the canonical ProvenWorks help articles when explaining a step to a human. Verified entry points: