For Salesforce developers, admins and release managers putting AI agents to work. The model itself is explained in Intent → Spec → Test: Linking Must-Nevers to Hidden Tests. This post maps it onto Salesforce orgs, metadata and tools.
TL;DR
- Each layer maps onto something you already have: intent in the story and a small file in the repo, a spec that names metadata by API name, and tests that are Apex tests, Flow tests and SOQL access assertions.
- Each environment has one job: the agent works in its own sandbox, a fresh CI scratch org runs every check, a QA sandbox catches changes that collide, UAT confirms the change is the right one, and only the CI user deploys to production.
- Hidden tests run only in CI, deployed from a repository the agent cannot see.
- Protect the riskiest metadata by folder: permission sets, profiles, sharing and site settings go through the strictest path automatically.
- Configuration gets weaker checks than code. Flow tests and access assertions help, but business acceptance in UAT carries more of the weight.
What You'll Learn
- Where intent, spec and tests live in a Salesforce project
- A starter list of Salesforce must-nevers
- Which checks run in which org, from the agent's sandbox to production
- How to route risky metadata automatically, and what changes for admin-led teams
The Problem
The Intent → Spec → Test model was written in general terms: must-nevers, rules, visible and hidden tests, CI. A Salesforce team has to answer more specific questions before it can use it. Where does a must-never live? What is a "test" for a permission set? Which org runs the hidden tests, and what stops someone deploying around all of it?
Salesforce also has a twist most software does not: a large share of every change is configuration. Flows, permission sets, sharing rules and page layouts change behaviour without a line of Apex.
Common Questions This Article Answers:
- Where do I keep must-nevers and specs in a Salesforce project?
- How do I test a permission or sharing change, not just Apex?
- Which of my sandboxes should run which checks?
Quick Answer
Keep the intent in the story and a short intent/ file in the repo, and write each rule against real metadata API names. Prove rules with Apex tests, Flow tests, and SOQL access assertions on ObjectPermissions and FieldPermissions, and keep the hidden tests in a repository only CI deploys. Let the agent work in its own sandbox or scratch org. Have CI build a fresh scratch org per change to run everything, merge into a QA sandbox to catch changes that collide, accept in a UAT copy with realistic data, and deploy to production only through the CI user, validating first and then quick deploying. Send permission sets, profiles, sharing and site settings through the strictest path by folder.
A few words you will see
| Term | What it means here |
|---|---|
| Must-never | A line the business says can never be crossed, such as "a guest user must never read Contact records" |
| Hidden test | A test the agent never sees, run only in CI, like exam questions a student has not seen |
| Access assertion | A SOQL query in CI that checks a permission or sharing setting is what the rule says |
| Scratch org | A short-lived, empty org built from your project, ideal for a clean test run per change |
| Validate, then quick deploy | Running a production deployment's tests without saving, then deploying that validated job later |
| Flow test | A test for a Flow, stored as FlowTest metadata and run with sf flow run test |
Where Each Layer Lives
| Layer | Where it lives on Salesforce | Example |
|---|---|---|
| Intent | The story in your tracker, plus a short file in the repo with the must-nevers and their IDs | intent/portal-security.md: MN-001 |
| Spec | Rules in the repo that name metadata by API name | R-014: after a change to the guest profile, sharing or site, the guest user has no access to Contact |
| Visible tests | Apex tests and Flow tests in force-app, where the agent works |
GuestSharingRuleTest, CaseDefaults.flowtest |
| Hidden tests | A separate repository only CI checks out and deploys | GuestContactIsolationTest, running as the guest user |
| Access assertions | SOQL queries the pipeline runs after deploying | the guest profile has no read on Contact |
An access assertion checks a setting; a hidden test checks the outcome. Both are worth having for a high-risk rule:
-- Access assertion for R-014: should return no rows
SELECT Parent.Name, PermissionsRead
FROM ObjectPermissions
WHERE SobjectType = 'Contact'
AND PermissionsRead = true
AND Parent.Profile.UserType = 'Guest'
The query catches a permission someone switched on. It cannot catch a sharing rule or another route that still exposes Contacts, which is why the hidden test runs as the guest user and checks what it can actually read. ObjectPermissions, FieldPermissions, PermissionSetAssignment and SetupEntityAccess are all queryable, so the same pattern covers field access, who holds a permission set, and access to Apex classes and pages.
A Starter List of Salesforce Must-Nevers
Adapt these to your org; each one should get its own ID, rule and at least one hidden test.
- A guest (unauthenticated) user must never read or edit internal records such as Contacts, Cases or Accounts.
- A change must never widen sharing on a sensitive object without a named approver.
- The integration user must never hold Modify All Data, View All Data or delete rights it does not need.
- No one must ever gain a sensitive permission (Modify All Data, Author Apex, Customize Application) through a permission set that was not reviewed.
- Automation must never delete or overwrite records outside the object and fields its rule names.
- Nothing must ever reach production except through the pipeline's CI user.
Which Checks Run Where
Same layers, five environments
Switch between an Apex change and a configuration change, and point at each hop to see which checks run there and which do not.
The hidden tests and access assertions run only in CI, never where the agent works.
| Environment | Its job | What runs there |
|---|---|---|
| Agent's sandbox or scratch org | Where the agent builds | Visible Apex tests, Flow tests, Code Analyzer |
| CI scratch org, one per change | The full gate, from a clean start | Everything above, plus hidden tests, access assertions and the traceability check |
| QA sandbox, after merge | Catch changes that pass alone but fail together | Characterization tests from the archaeology step, hidden tests again |
| UAT, full or partial copy | Real data volumes and real sharing | Business acceptance: is this the right change? |
| Production | Receive validated changes only | sf project deploy validate, then sf project deploy quick, by the CI user |
A few details that matter:
- Tests create their own data. Apex tests cannot see existing org data unless they use
@IsTest(SeeAllData=true), which is one reason a fresh scratch org per change works: the tests carry everything they need. User and Profile records are still visible, so a test can find the guest user. - Validate and quick deploy are for production.
sf project deploy validateruns the tests against production without saving, andsf project deploy quick --job-iddeploys that validated job within 10 days. For sandboxes, Salesforce's CLI documentation points tosf project deploy start --dry-runinstead. - Code Analyzer v5 reads Flows.
sf code-analyzer runincludes a Flow engine as well as PMD and ESLint, so Flow rules can be gated alongside Apex.
Route Risky Metadata Automatically
Do not rely on people remembering which changes are risky. Put the metadata folders that decide access under stricter review, so any change touching them takes the strictest path:
# CODEOWNERS (GitHub)
/force-app/main/default/permissionsets/ @your-org/security-reviewers
/force-app/main/default/profiles/ @your-org/security-reviewers
/force-app/main/default/sharingRules/ @your-org/security-reviewers
/force-app/main/default/sites/ @your-org/security-reviewers
/force-app/main/default/namedCredentials/ @your-org/security-reviewers
The same paths can tell CI to require the full tier 3 checks: must-nevers, hidden tests and access assertions. Everything else can take a lighter path, as the risk tiers describe.
For Admin-Led Teams
If most of your changes are configuration and you release through DevOps Center, the model still applies, with the weight shifted:
- Intent and spec pay off at once. Write the must-nevers on the work item before building. They need no code.
- DevOps Center pipelines map to the environments above. Each stage is an org, ending with production, and quality gates can run Apex tests, Flow tests and Code Analyzer before promotion.
- Flow tests are your main automated check for record-triggered and autolaunched Flows.
- Business acceptance matters more. With weaker automated checks for configuration, the UAT step is where a wrong change is most likely to be caught. Plan time for it.
Frequently Asked Questions
Q: Do I need a scratch org per change?
A: It is the cleanest way to run the full gate, because every run starts from your project with no leftovers. A dedicated CI sandbox works too, but changes from earlier runs can hide or cause failures, so refresh it often.
Q: Can I test a permission set change without Apex?
A: Yes. An access assertion is a SOQL query on ObjectPermissions, FieldPermissions or PermissionSetAssignment that the pipeline runs after deploying. It checks the setting. For high-risk rules, add a hidden Apex test that runs as the affected user and checks the outcome, because sharing and other routes can grant access the setting does not show.
Q: Does the guest user test really work in Apex?
A: It follows documented behaviour: System.runAs enforces the chosen user's sharing and permissions, and User records are visible in tests without SeeAllData. Running as a site guest user specifically is not called out in the documentation, so prove it in a scratch org before relying on it.
Q: What about changes made directly in production Setup?
A: Make them impossible or visible. Limit who can change production configuration, run a regular comparison between production and your repository, and treat any difference as an incident: it is a change that skipped every check.
Key Takeaways
- Intent, spec and tests map onto stories, metadata API names, Apex tests, Flow tests and SOQL access assertions.
- Give every environment one job, and run the hidden tests only in a CI scratch org.
- Route permission sets, profiles, sharing and site settings to the strictest path by folder.
- Configuration has weaker automated checks, so acceptance in UAT carries more of the weight.
What's Next?
Recommended Reading:
- Archaeological Specs: Using AI Agents Safely on a Salesforce Org You Didn't Build, the step before any of this in an existing org
- Intent → Spec → Test: Linking Must-Nevers to Hidden Tests, and When Not To, the model in general terms
- Deployable permission sets for a delivery team, building the CI deploy user
Action Items:
- Write three must-nevers for your highest-risk area and give each an ID.
- Add one access assertion and one hidden test for the first must-never to your pipeline.
- Add CODEOWNERS entries for your permission set, profile and sharing folders.
Resources & References
- Salesforce CLI deploy and retrieve commands (source and docs)
- Salesforce CLI: flow run test
- Salesforce Metadata API: FlowTest
- Salesforce Apex: Using the runAs method
- Salesforce Object Reference: UserRecordAccess
- Salesforce Code Analyzer on GitHub
- Trailhead: Improve release quality with DevOps Center testing
Responses
Checking your session.
Loading responses.