Controlled document · Living standard
Microsoft 365 & Power Platform
Development Standards & Architecture Guide
The authoritative record of how this team designs, builds, secures, and ships solutions across Microsoft 365 and the Power Platform — and, just as importantly, why. Every standard in this document carries its reasoning: the tradeoff it accepts, the constraint it solves, or the failure it prevents.
Contents
- I · Foundations
- §00How to use this document
- II · Platform-wide standards
- §01Naming conventions
- §02Identity & Entra ID
- §03Service principals & service accounts
- §04Security & permissions
- §05Compliance & data governance
- §06Licensing: premium vs. non-premium
- III · Power Platform
- §07Data access patterns
- §08Connector strategy
- §09Flow design patterns
- §10Canvas app structure
- IV · Microsoft 365 services
- §11SharePoint & OneDrive
- §12Teams
- §13Exchange Online
- §14Microsoft Graph & PowerShell automation
- V · Delivery & practice
- §15Testing & deployment
- §16Team collaboration practices
- Appendices
- ADecision record template
- BChange log
- CGlossary
- DReferences
I · Foundations
§00How to use this documentReading, updating, and the rule that governs everything below▶
This is a living document. It is deliberately incomplete: it contains decided standards, drafts awaiting confirmation, and open questions you still need to answer. The empty space is the point — fill it in as decisions get made, and record the date and reasoning when you do.
The one rule this document enforces
How a decision gets recorded here
Propose it (in writing, with the why) → discuss it with whoever it affects → decide it → record it using the template in Appendix A → log the edit in Appendix B. Decisions get an ID (DA-001, NC-002, …) so flows, app comments, and wiki pages can cite them.
Revisiting decisions
Every decision record has a Revisit when field. Decisions are made against constraints — licensing terms, tenant policy, platform features — and constraints change. Naming the trigger for re-evaluation up front prevents both blind loyalty to stale rules and constant relitigation of settled ones.
II · Platform-wide standards
§01Naming conventionsNames are documentation you can’t avoid reading▶
Naming is the cheapest form of documentation and the only one guaranteed to be seen at the moment of use. The goal of a convention is not tidiness — it is that any team member can open an unfamiliar app or flow and predict what a thing is, who owns it, and what it touches, from its name alone.
DraftProposed defaults — confirm or revise
| Object | Proposed pattern | Example | Why this shape |
|---|---|---|---|
| Canvas controls | 3–4 letter type prefix + PascalCase purpose | btnSubmitRequest, galApprovalQueue, conHeaderBar | The prefix makes formulas self-describing; you can read galApprovalQueue.Selected without opening the tree view. |
| Global variables | gbl + PascalCase | gblCurrentUser | Scope is the first thing you need to know when debugging a variable; encode it where you can’t miss it. |
| Context variables | loc + PascalCase | locShowConfirmDialog | Same reason — and it exposes accidental scope-mixing at a glance. |
| Collections | col + PascalCase | colMyRequests | Collections behave differently from variables and data sources; the prefix prevents category errors in formulas. |
| Flows | [AppCode] - [Verb][Entity] - [Qualifier] | PCARD - GetRequests - ByApprover | App code groups flows in a crowded environment; verb-first tells you read vs. write before you open it. |
| SharePoint lists | PascalCase, singular or plural — pick one, no spaces at creation | PCardRequests | The internal name is frozen at creation and haunts every OData filter forever; spaces become _x0020_. |
| SharePoint columns | PascalCase internal name created before any rename | ApprovalStage | Same freeze rule as lists. Create with the final internal name, then re-label for display. |
| Screens | Accessibility convention — see NC-001 | Request Detail Screen | Screen names are announced by screen readers, which makes screen naming an accessibility decision, not a style one. Recorded as NC-001 below. |
BaselineMicrosoft’s positions — from the canvas app standards white paper
Microsoft’s Power Apps canvas app coding standards and guidelines white paper (Appendix D) is the closest thing to an official baseline, and the table above already agrees with most of it: camelCase controls with short type prefixes, gbl/loc/col prefixes, PascalCase data sources. Its additions worth deciding on:
- Why the variable prefixes exist at all: Power Fx allows a context variable and a global variable to share a name, and formulas silently resolve to the context one unless disambiguated. The
gbl/locprefixes aren’t taste — they make that collision impossible. Worth writing into the standard so the rule survives its author. - Reused controls carry a screen suffix: control names must be unique app-wide, so a control repeated across screens gets a short screen tag (their example: a nav gallery suffixed for the home screen). Decide whether to adopt this or forbid cross-screen duplication outright in favor of components (§10) — components didn’t exist when the paper was written.
- Custom connector naming happens before import: the OpenAPI
titleattribute becomes the data source name with spaces stripped, so set a clean PascalCase title first — it can’t be fixed afterward. Feeds §08’s custom connector policy. - Type prefixes for data-typed variables (e.g.,
str,b) are explicitly left to team preference by the paper. Decide once below.
Screen names use the accessibility convention: plain words with spaces, ending in “Screen”
Screens are named like Home Screen and Request Detail Screen — real words, spaces, no abbreviations, no scr prefix. This supersedes the prefixed pattern originally drafted in the table above.
Screen readers announce screen names as users navigate. A prefixed, compressed name is announced as noise; “Request Detail Screen” is announced as meaning. In a public-sector environment accessibility is an obligation, and this is one of the cheapest accessibility wins the platform offers — the Microsoft baseline is emphatic on it for exactly this reason.
Breaks the “every object gets a type prefix” symmetry and loses prefix-search for screens. Accepted because an app has a handful of screens — all visible at the top of the tree view — and thousands of controls; the prefix earns its keep on controls, not screens.
OpenQuestions you need to answer
- What is the official app-code registry (e.g.,
PCARD,TRNRM) and who assigns codes? - Singular or plural list names — which one, permanently?
- Do environment names carry a suffix convention (
-DEV/-TEST/-PROD), and do solution and flow names repeat it or rely on the environment context? - What is the naming pattern for service accounts and app registrations? (Feeds §03.)
- Are flow action names inside a flow renamed to describe intent, and is that enforced in peer review?Why it matters: “Apply to each 4” is where debugging goes to die.
- What naming marks something as deprecated but not yet deleted (e.g.,
zzOLD_prefix)?
§02Identity & Entra IDThe substrate every other section resolves to▶
Every rule in this document eventually resolves to an Entra ID object. Who can open an app, whose rows a flow returns, who may approve, who gets the notification, who can run the script — each is a lookup against a user, a group, or a service identity. Get identity wrong and the standards above it are decoration.
DraftProposed practices — each with its why
| Area | Practice | Why |
|---|---|---|
| Groups over individuals | Every access grant — app share, run-only, list permission, license, approver role — targets a security group, never a named user. | Individual grants are invisible to review and survive the person who left. A group is one object an auditor can read and a joiner/leaver process can touch. |
| Group naming | Groups carry a purpose-encoded name that matches §01’s conventions and states the app or function they serve. | A group called Test Group 2 will outlive everyone who knew what it was for. Names are the only documentation a group reliably carries. |
| Dynamic vs. assigned | Decide per audience, and record which. Dynamic groups for HR-shaped audiences (department, title); assigned for exception-shaped ones (project teams, approvers). | Dynamic rules stay true automatically but inherit every error in the source attributes. Assigned groups are explicit and rot the moment someone forgets. The failure modes are opposite — pick the one you can live with per case. |
| Self-service group creation | Decide explicitly whether users may create groups and Teams. Record the answer even if the answer is the platform default. | Left at default, anyone can create groups, which quietly creates SharePoint sites and mailboxes nobody governs. An undocumented default is still a decision — just one nobody made. |
| Privileged access | No standing admin roles on daily-driver accounts. Elevation is just-in-time via PIM, time-bound, justified, and reviewed. | Standing admin is the finding every assessment writes first, and the blast radius of one phished session. JIT converts a permanent risk into a logged, bounded event. |
| Conditional Access | Know which CA policies apply to the Power Platform, SharePoint, and Graph service principals — and test app and flow behavior against them before release. | CA is where identity decisions become runtime behavior. A policy that blocks unmanaged devices will break an app for field staff and the error surfaces as “the app is broken,” not “policy denied.” |
| Licensing by group | License assignment via group membership, not per-user clicks. | Makes entitlement auditable in the same place as access, and stops the “why does this person have a premium license” archaeology in §06. |
| Break-glass accounts | Emergency access accounts exist, are excluded from CA, are stored offline, and are tested on a schedule. | The one scenario where you cannot log in to fix the thing preventing you from logging in. An untested break-glass account is a story you tell yourself. |
| Guests | Guest posture decided per workload with an expiry/review path — not left to whatever the tenant default is. | Guests accumulate silently and appear in access reviews years later attached to data nobody expected them to reach. |
No standing privileged roles — admin access is activated just-in-time
No account, human or service, carries a standing privileged role. Admin-level access is activated per task through PIM with a justification and an expiry. Service accounts (§03) are explicitly in scope: they get the permissions their solution needs and nothing role-shaped.
- A standing admin role is a permanent attack surface bought for an occasional need. It is also the first finding every security assessment writes, and the hardest one to argue with.
- JIT converts a permanent grant into an expiring, approved request, so a compromised session is measured in hours rather than indefinitely.
- The activation records are the audit trail. Reviewers under our frameworks (§05) ask who held privilege and when; this answers it without anyone assembling evidence by hand.
Activation friction during incidents — which is why a break-glass path must exist and be rehearsed (§03), not improvised at 2 AM · PIM carries licensing requirements; confirm entitlement before committing · approvals only work if someone actually reviews them, otherwise you have bought friction and no security.
OpenQuestions you need to answer
- What is the group naming standard, who may create groups, and is self-service creation on or off today?Why it matters: this one setting determines whether §11’s site architecture is a plan or a suggestion.
- For app audiences — dynamic or assigned? If dynamic, which attributes are trustworthy enough to drive access, and who owns their accuracy?
- Which roles are PIM-eligible versus standing, what is the maximum activation window, and who approves activation?
- Which Conditional Access policies touch Power Platform, SharePoint, and Graph — and does anyone test releases against them before users do?
- What is the guest access posture per workload, and what expires an unused guest?
- How many break-glass accounts exist, where are the credentials, and when were they last tested?
- What does the government-cloud environment change here — which identity features are unavailable or behave differently, and where is that recorded so it isn’t rediscovered quarterly?
- What is the access review cadence for groups, and does completing it produce evidence an auditor would accept?
§03Service principals & service accountsWho the platform thinks is doing the work▶
DA-001 makes identity an architectural feature: flows deliberately run as something other than the end user. That “something” must be chosen, secured, documented, and survivable when people leave. Two distinct instruments exist and should not be conflated: service accounts (licensed user identities that own connections and flows) and service principals / app registrations (non-user identities for API-level access, e.g., Microsoft Graph or SharePoint app-only calls).
Production connections are owned by dedicated service accounts, not personal accounts
Each production solution (or solution family) runs its flows under a named service account (e.g., svc-pp-pcard) that owns the connections and is a co-owner of the flows. Personal accounts may own connections only in dev.
Continuity (survives offboarding), least-privilege clarity (the account’s permissions are the solution’s permissions — auditable in one place), and it is the identity mechanism that makes DA-001’s row-level security enforceable rather than accidental.
License cost per service account · credential custody overhead (MFA, password vaulting, sign-in monitoring) · one more identity per solution to include in access reviews.
OpenQuestions you need to answer
- One service account per app, per department, or per environment tier? What is the blast radius you are willing to accept if one credential is compromised?
- Where are service account credentials stored, who can retrieve them, and what is the break-glass procedure?
- What is the secret/credential rotation cadence, and who owns the calendar reminder that actually makes it happen?
- When is a true service principal (app registration) required instead of a service account — e.g., Graph API calls, app-only SharePoint access — and who approves new app registrations?
- Certificates or client secrets for app registrations, and what expiry ceiling is allowed?Why it matters: the default temptation is a 24-month secret created in a hurry; the incident happens in month 25.
- What Entra ID roles/permissions is a service account allowed to hold, and what is categorically forbidden (e.g., no standing admin roles — use PIM)?
- How are service accounts and app registrations inventoried and reviewed — and does that review satisfy your compliance frameworks (e.g., CJIS/HIPAA-adjacent audit expectations)?
§04Security & permissionsThe permission model DA-001 promises only exists if these settings are right▶
DA-001 claims row-level security. That claim is only true when three layers agree: the SharePoint lists are locked down, the flows run under the service identity for everyone, and the app is shared to the right people. Get any one wrong and the model silently degrades to “everyone can see everything, just less conveniently.”
App-facing flows are shared run-only, configured to use the owner’s connection — and backing lists grant end users no direct access
Every DA-001 flow is shared to app users as run-only, with connections set to “Use this connection” (the owner’s / service account’s), never “Provided by run-only user.” The SharePoint lists behind the app grant end users no permissions at all; only the service account (and admins) can touch them.
- If run-only users provide their own connection, the flow executes as them — recreating exactly the direct-connector identity problem DA-001 exists to eliminate, and failing for any user without list access.
- If users retain direct list permissions, the flow’s filtering is theater: anyone can open the list URL, use built-in search, or query via Graph and see every row and column. Server-side filtering is only security when the server is the only path to the data.
The service identity becomes high-value — its credential custody (§03) is now a security control, not housekeeping · out-of-app experiences (list views, exports, Power BI direct) require deliberate alternatives · misconfiguration is invisible in the app UI, so it must be checked, not assumed (deployment checklist, §15).
DraftFurther proposed practices
| Area | Practice | Why |
|---|---|---|
| App sharing | Share apps to security groups, never individual users; group membership is the access request process. | Individual shares rot instantly and are invisible to access reviews; groups make joiner/leaver processes real. |
| Authorization data | Role assignments (who approves what, who sees which department) live in an admin-managed SharePoint list read by flows — not hard-coded in formulas. | Authorization changes weekly; app deployments shouldn’t. Data-driven roles also give auditors a single artifact. |
| Environment roles | Maker/admin roles in each environment reviewed on a schedule; production maker access restricted to the team. | Every maker in prod is a person who can edit a live flow at 4:55 PM on Friday. |
| Admin elevation | Admin-level work happens under PIM-style just-in-time elevation, not standing rights. | Standing admin on daily-driver accounts is the finding every security assessment writes first. |
| Data classification | Every solution declares its data classification at intake; classification selects the control set (encryption expectations, audit depth, retention). | CJIS/HIPAA-class data cannot be a surprise discovered in an incident review. Classify first, then build. |
OpenQuestions you need to answer
- Which compliance frameworks formally apply to which data classes here, and what specific controls do they demand from Power Platform solutions?
- What audit evidence must solutions produce (who did what, when), and is flow-written stamping (§11) sufficient or is centralized logging required?
- Who performs periodic access reviews of app shares, group memberships, run-only lists, and service identities — and how is completion evidenced?
- What is the incident procedure if a service account credential is exposed — rotation steps, connection re-auth, and communication?
- What is the exception process when someone requests direct list access “just for reporting”?Why it matters: this is the single most common way the SEC-001 model gets quietly broken.
- Are guest/external identities ever in scope for these apps, and what changes if so?
§05Compliance & data governanceRetention, labels, audit, and the two different things called DLP▶
Solutions built here hold public-sector data, some of it regulated. That makes retention, classification, auditability, and discoverability design inputs — not paperwork applied afterward. This section is where those obligations get named, so §04 can select controls and §07 can size its data model against them.
DraftProposed practices — each with its why
| Area | Practice | Why |
|---|---|---|
| Classify at intake | Every solution declares a data classification before build starts; the classification selects the control set (audit depth, retention, encryption expectations, environment rules). | Classification discovered during an incident is discovered too late. It is also the cheapest gate in the process — one question, asked once. |
| Retention decided per list | Each system-of-record list maps to a retention schedule and a disposition path, recorded with the schema (§11). | Retention is a legal obligation with a clock on it; “we never decided” is not a defensible disposition. |
| Both DLPs are configured and documented | Power Platform connector DLP and Purview content DLP each have a recorded state, reviewed together. | See the callout above. Whichever one you skipped is the one the auditor asks about. |
| Sensitivity labels | Decide whether labels apply to app-generated documents and container-level (site/team) labels, and what each label actually enforces. | Labels that carry no enforcement train people to ignore labels — worse than not labeling. |
| Audit log | Know the tenant’s audit retention period and confirm it meets the longest obligation you carry, not the shortest. | Audit retention is only discovered to be too short at the moment you need the record. |
| Discoverability | App data must be findable under eDiscovery/records request; if a store isn’t discoverable, that is a design flaw to record, not a feature. | Public records requests reach app data. A store nobody can search is a liability with a deadline attached. |
| No regulated data in lower environments | Test data is synthetic or scrubbed; production data does not travel to Dev/Test. | A copy in a dev environment is a copy outside the control set — a compliance incident nobody planned and everybody could have predicted. |
Every solution declares a data classification before development starts
No solution enters development without a recorded data classification. The classification selects the control set: retention, audit depth, permitted environments, whether flow-mediated access is sufficient, and what may exist in lower environments.
- Classification discovered late is discovered during an incident review. It is the input that drives §04’s controls, §07’s data model, §11’s site architecture, and §16’s test-data rules — every one of which is expensive to change after build.
- One question at intake costs minutes. Retrofitting regulated-grade auditing onto a shipped SharePoint app costs a rebuild.
Intake friction · it requires a named person empowered to say what the data actually is — without that, the field gets guessed · over-classification is its own failure: a tool wrapped in controls it does not need trains people to route around them.
OpenQuestions you need to answer
- Which frameworks formally apply to which data classes — CJIS, HIPAA, state records law, PCI-adjacent for payment workflows — and who is the authority that answers this, not guesses at it?Why it matters: the P-Card workflow touches purchasing data; the answer shapes §04’s control set for it.
- What is the records retention schedule for app-held data, and who is the records custodian for a SharePoint list?
- What is the current state of Purview DLP, and does it inspect anything in the app-backing sites?
- Are sensitivity labels in use, and do they enforce anything or merely mark?
- What is the audit log retention period today, and what is the longest retention obligation any solution carries?
- Who signs off that a solution is compliant before it ships, and what artifact records that sign-off?
- What is the breach/incident procedure specific to app data, and how does it differ by classification?
§06Licensing: premium vs. non-premiumA cost-and-capability framework, not a reflex▶
“Never go premium” and “just buy premium” are both reflexes. The standard here is a framework: premium is a purchase of specific capabilities (Dataverse’s relational integrity and security roles, premium connectors, higher limits) at a specific multiplication factor (per user? per app? per flow?). The decision is legitimate only when the capability gap and the multiplication factor are both written down.
Decision framework — work these in order
| # | Question | Why it’s asked here |
|---|---|---|
| 1 | Can standard connectors + flow-mediated access (DA-001) meet the requirement at all? | Most form/approval/reporting workloads can. Exhaust the free architecture before pricing the paid one. |
| 2 | If not, what is the specific capability gap — relational integrity, security roles, volume limits, a particular connector? | “Dataverse is more professional” is not a gap. Named gaps can be priced and challenged; vibes cannot. |
| 3 | What is the multiplication factor — per-user × how many users, per-app, or per-flow? | This converts the gap into dollars. It is also where flow containment often collapses the cost by an order of magnitude. |
| 4 | What does the SharePoint-based alternative cost in engineering effort and accepted limitations? | Premium avoided is not free — it’s paid in workarounds. Compare honestly in both directions. |
| 5 | Who signs off, and where is the decision recorded with its expiry/revisit trigger? | Licensing terms change more often than architectures. Every premium decision needs a revisit date. |
Precedent worth recording: the P-Card approvals app was re-platformed from Dataverse to SharePoint lists after a licensing review — a live example of question 3 changing the answer. Write up that case below while the details are fresh; institutional memory of why is exactly what this document exists to hold.
OpenQuestions you need to answer
- What licenses does the organization actually hold today (seeded plans, per-app, per-user, process/flow-level), and where is that inventory kept current?
- What is the approval threshold — can a maker decide premium for a 5-user tool, or does every premium decision route through the same gate?
- At what point does an app’s scale or data sensitivity force the Dataverse conversation regardless of cost (security roles, auditing, relational integrity)?
- How do you monitor for accidental premium — a maker dropping a premium connector into an app and flagging every user?Why it matters: the platform will happily let this happen silently; the license true-up will not be silent.
- What is the standing answer for AI Builder / Copilot Studio credits, which carry their own consumption model?
III · Power Platform
§07Data access patternsHow apps read and write data — the decision everything else hangs on▶
Data access is the highest-leverage architectural decision in a Power Platform solution. It determines licensing exposure, the security model, performance characteristics, and how much of your logic lives server-side versus in the app. It is decided first because most other sections inherit constraints from it.
Power Automate flows are the primary method for all data read and write operations to SharePoint lists
Canvas apps do not query or patch SharePoint lists through direct SharePoint connectors with lookups. Instead, apps call Power Automate flows (Power Apps V2 trigger) that perform the read/write server-side and return only the data the calling user is entitled to see.
- Licensing containment. It prevents the app from becoming premium if any upstream connectors are premium. When the app itself only ever touches the flow boundary, premium connector usage is isolated to the flow layer instead of flagging every app user for premium licensing. The premium cost surface stays at the flow, where it can be evaluated and licensed deliberately, rather than silently multiplying across the entire user base of the app.
- Server-side row-level security. Flows allow server-side filtering that enforces row-level security without exposing the entire list to end users. The flow evaluates who is asking and returns only their rows and only the columns the app needs. Direct SharePoint connectors force the logged-in user’s identity and expose all columns and rows the user can reach — which breaks granular permission models. SharePoint list permissions are too coarse to express “managers see their team’s records; submitters see their own,” but a flow can express exactly that.
- Latency. A flow round-trip is slower than a direct connector call. Acceptable for form-and-approval workloads; design galleries around explicit “load” moments, not keystroke-by-keystroke queries.
- No delegation via galleries. The app receives a JSON payload, not a delegable data source. Pagination, sorting, and filtering must be designed into the flow contract instead of assumed from the connector.
- Connection ownership matters. The flow runs under its connection owner’s identity, not the end user’s. This is precisely what makes row-level security possible — and precisely why connection ownership must be disciplined (see §03 and §04).
- Audit trail shifts. SharePoint’s
Created By/Modified Bywill show the connection identity. The actual acting user must be stamped into dedicated columns by the flow. - More moving parts. Every data operation is now app + flow + list. The flow envelope standard (DA-002) and naming conventions (§01) exist to keep this navigable.
Standard response envelope with explicit success/error contract (DA-002) · service-account-owned connections with run-only sharing configured to use the owner’s connection (§04) · user-stamping columns written by the flow · pagination parameters built into every read flow that can return >100 rows.
Define the exceptions explicitly so this rule doesn’t get eroded case-by-case. Candidates: small, non-sensitive reference/choice lists where every user may legitimately see every row; personal productivity apps with a single user. Confirm and list the sanctioned exceptions below.
External corroboration: Microsoft’s own canvas app standards white paper (Appendix D) recommends this same layering for SQL back ends — routing data operations through a flow that calls stored procedures — for performance, for decoupling the app from schema changes, and for tighter security via a restricted execute-only identity. DA-001 applies that reasoning to SharePoint, with licensing containment added on top.
Every app-facing flow returns a standard response envelope
All flows triggered from canvas apps end in a single Respond to a Power App action returning, at minimum: success (boolean), message (text, human-readable), and data (JSON text, parsed in-app with a typed schema).
Because DA-001 routes everything through flows, the app–flow boundary becomes the most-crossed interface in every solution. If each flow invents its own return shape, every screen needs bespoke error handling and nothing is reusable. One envelope means one error-handling pattern in the app, one testing pattern for flows, and new team members learn the contract once.
Exact field names and casing · whether data is a JSON string parsed with ParseJSON() or multiple typed output fields · standard error codes, if any.
OpenQuestions you need to answer
- What is the pagination contract for read flows — page size, page token or skip count, and who enforces the cap?Why it matters: without a contract, someone will eventually return 5,000 rows in one response and blame the platform for the timeout.
- What is the app-side caching policy — which datasets get loaded into collections once per session, and which are re-fetched on every screen visit?
- What is the timeout and retry behavior when a flow call fails mid-session? Silent retry, user-facing notification, or both?
- How do you handle concurrent edits — last-write-wins, a version/ETag check in the update flow, or record locking via a status column?Why it matters: approval apps invite two approvers acting on the same record within seconds of each other.
- How are attachments and images handled, given the flow-mediated model? (Attachments often tempt teams back to direct connectors — decide the pattern before that happens.)
- What payload size is “too big” for a single Respond action, and what is the fallback pattern when you hit it?
- Do read flows and write flows live in the same flow per entity, or separate flows per operation (Get / Create / Update / Delete)?
§08Connector strategyEvery connector is a dependency, a license question, and a data-exfiltration path▶
A connector is three things at once: a capability, a licensing trigger, and a hole in your data boundary. Strategy means deciding all three deliberately — which connectors are sanctioned, how new ones get approved, and how DLP policy encodes those answers so the platform enforces them instead of relying on memory.
OpenQuestions you need to answer
- What is the sanctioned connector list for app-layer use vs. flow-layer use? (Under DA-001 these are different lists — the app layer should be close to “SharePoint via flows, Office 365 Users, and little else.”)
- What do your DLP policies actually say today — which connectors sit in Business vs. Non-Business vs. Blocked, and does that match this document or contradict it?Why it matters: an undocumented DLP policy is a standard someone else wrote for you.
- Who approves adding a connector to the sanctioned list, and what evidence is required (data touched, auth model, license class, vendor posture)?
- What is the policy on the HTTP connector and custom connectors — banned, approval-gated, or free? Who reviews the endpoints they call?
- Are consumer-grade connectors (personal cloud storage, social) blocked tenant-wide, and is that documented here with the reasoning?
- How do government-cloud constraints affect connector availability, and where do you record “not available in our cloud” findings so they aren’t rediscovered quarterly?
- What happens when a needed connector is premium — what is the escalation path into the §06 decision framework?
§09Flow design patternsUnder DA-001, flows are your API layer — build them like one▶
Because DA-001 routes all data operations through flows, flow quality is solution quality. These patterns treat each app-facing flow as a small API endpoint: typed inputs, predictable outputs, explicit error handling, and no silent failure.
DraftProposed patterns — each with its why
| Pattern | Practice | Why |
|---|---|---|
| Typed trigger inputs | Power Apps V2 trigger with named, typed parameters; never parse a single concatenated string. | Typed inputs are self-documenting, validated at the boundary, and don’t shatter when a value contains your delimiter. |
| Try / Catch / Finally | Three scopes: main logic in Try; Catch runs on failure (configure run-after) and returns the standard envelope with success:false; Finally for cleanup/logging. | An unhandled flow failure looks like an infinite spinner to the app user. Catch converts every failure into a message the app can render and a person can report. |
| Single exit | Exactly one Respond to a Power App per outcome path, always returning the DA-002 envelope. | Multiple ad-hoc respond actions with different shapes make the app’s parsing brittle and reviews unreliable. |
| Renamed actions | Every action renamed to intent: Get requests for approver, not Send an HTTP request 2. | Run history is your production debugger; its readability is set at design time. |
| Child flows for shared logic | Reused logic (audit logging, notification formatting, permission checks) extracted into child flows inside the same solution. | Copy-pasted logic across flows drifts; the third copy is where the bug lives. |
| Deliberate retry policy | Retry policy reviewed on every external action; writes that aren’t idempotent get retries disabled or an idempotency check. | Default retries on a non-idempotent create can produce duplicate records that look like user error. |
| Concurrency awareness | Loop concurrency set consciously; ordered operations forced sequential and labeled as such. | Parallel “Apply to each” is a free speedup right up until ordering mattered. |
| Comments at decision points | Notes on any action embodying a business rule, citing the rule’s source or decision ID. | Business rules encoded silently in a condition are unrecoverable when the author leaves. |
OpenQuestions you need to answer
- What is the synchronous budget — how long may an app-facing flow run before it must be redesigned as async (queue + notify) instead of making the user wait?
- Where do flow errors go besides the envelope — a logging list, email to owners, Teams channel? Who is on the hook to look?
- Are solutions mandatory for all flows (enabling connection references and environment variables), including quick utility flows?
- What is the standard for environment variables — which values must never be hard-coded (site URLs, list names, recipient groups)?Why it matters: hard-coded URLs are the #1 reason a “tested” flow fails on the first day in production.
- Do you require an idempotency strategy for create operations (dedupe key, existence check), and what is the default?
- What is your policy on nested loops and per-item lookups vs. Filter Query — when is a design sent back in review for performance?
§10Canvas app structurePredictable internals: any maker can open any app and know where things live▶
Canvas apps have no enforced architecture — every app is a folder of screens and formulas arranged however its author was feeling. Structure standards exist so the team’s apps converge on one internal shape: where state lives, how screens talk to each other, how errors surface, and what the user sees while data loads.
DraftProposed structure — each with its why
| Area | Practice | Why |
|---|---|---|
| Startup | Prefer App.Formulas (named formulas) over piling logic into App.OnStart; use App.StartScreen, not a Navigate in OnStart. | Named formulas are lazy, cacheable, and declarative — faster startup and no “did OnStart finish yet?” race conditions. |
| State discipline | Global variables for session-wide truth, context variables for screen-local UI state, collections for tabular working data — never interchangeably. | Most “mystery bugs” in canvas apps are state written from an unexpected place; scope discipline shrinks the search space. |
| Data loading | Explicit load pattern per screen: loading spinner state, flow call (per DA-001), IfError handling, empty-state UI. | Flow-mediated access makes loading visible; designing the load moment beats pretending it’s instant. |
| Error surface | All flow-call failures route through one reusable notification pattern (component or helper formula) showing the envelope’s message. | One error pattern means users report consistent messages and makers debug from one place. |
| Componentization | Shared chrome (headers, nav, dialogs) built as canvas components — in a component library once used by 2+ apps. | Copy-pasted headers drift within a month; a library makes consistency the default instead of a chore. |
| Theming | Modern (Fluent 2) controls with a defined theme object/tokens; no hex values scattered in properties. | Central tokens make rebrands and accessibility fixes one change, not three hundred. |
| Responsiveness | Containers (horizontal/vertical) with explicit min sizes; decide per app whether it must survive phone widths. | Bolting responsiveness on later is a rebuild; deciding “desktop-only, and here’s why” is also a valid recorded decision. |
| Accessibility | AccessibleLabel on interactive controls, tab order verified, contrast checked against tokens. | Public-sector tools carry accessibility obligations; retrofits are expensive and morale-sapping. |
BaselineMicrosoft’s white paper positions — proposed stance on each
The same Microsoft white paper (Appendix D) covers app construction — but its feature coverage stops at late 2018, before named formulas, modern controls, or components matured. That makes it a perfect exercise for this document’s philosophy: a baseline is an input, not an authority. Proposed stances below; confirm each and record divergences.
| Paper’s position | Stance | Why |
|---|---|---|
Initialize apps in OnStart (routing, globals, run-once code) | Supersede | The paper predates App.Formulas and App.StartScreen, which solve the exact pain it warns about — that debugging OnStart requires closing and reopening the app. Our startup row above stands. The paper’s own preface predicts this: it says its practices will age as the platform evolves. |
Encapsulate screens — pass what a screen needs as context via Navigate; the screen loads its own data | Adopt | Screens with multiple entry paths don’t break, and they stay reusable. Reaching across screens to a gallery’s .Selected couples screens invisibly and fails the moment a second path to the screen exists. |
| Minimize control count — one control with logic over stacked visibility-toggled variants | Adopt | Every control is package weight and tree-view noise; logic in one property beats archaeology across four overlapping controls. |
| Comment heavily — comments are stripped from the client package | Adopt | Zero runtime cost removes the only argument against commenting. §09 already requires comments at decision points; extend the habit into app formulas. |
| Unlinked, maker-only documentation screens listing variables and collections | Adapt | Useful as an inventory that travels inside the app itself — but it cannot replace the runbook (§16); an app screen can’t hold escalation paths or environment maps. |
| Hidden configuration screen for app settings | Supersede / Adapt | For environment-shaped values, solution environment variables (§15) win — the paper itself lists the fatal downside (republish to change a value). Keep the hidden-screen idea only as a maker-gated debug panel, with visibility checked against maker/admin identity. |
Concurrent() for independent data calls | Adopt | Sequential calls serialize load time; parallelizing independent ones is the cheapest startup win available, and it retires the old hidden-timer workarounds the paper describes. |
| Cache small, frequently-read datasets in local collections | Adopt | Pairs naturally with DA-001: flow responses land in collections once per session. §07’s caching question decides which datasets qualify. |
| Package hygiene — prefer SVG over PNG, delete unused media and screens, split admin app from user app | Adopt | Load time and blast radius: a separate admin app means user-app changes need a smaller test pass, and vice versa. |
Formula hygiene — one UpdateContext per event, ClearCollect over Clear;Collect, CountIf over Count(Filter()) | Adopt | Small individual wins that compound; enforce through the §16 review checklist rather than memory. |
| Build one form factor, finalize it, then convert to the other | Adopt when both needed | Keeps controls, expressions, and variables identically named across phone and tablet variants, halving the support surface. |
SaveData / LoadData for caching | Caution | Works only in the mobile player — silently unavailable in browsers. Feeds the offline-stance question below; never let a browser-first app depend on it. |
| Every control on a screen belongs to a group | Adopt — via containers | Groups make intent legible in the tree view and let you move or hide a whole cluster at once. Modern layout containers now serve that purpose and handle responsiveness (see the row above), so treat containers as the current implementation of the paper’s grouping intent rather than a competing idea. |
Click targets — lay a transparent rectangle over a control group and use its OnSelect | Adopt | The alternative the paper rejects — putting logic in one member control and having its siblings call Select() — breaks whenever the group’s membership changes. A rectangle decouples the clickable region from whatever sits under it and lets you shape the target deliberately. |
| Relative styling — base one control’s color, fill, and position on another’s | Adapt | Right instinct, wrong altitude for color. Chaining control-to-control builds a brittle dependency web that breaks silently when the parent is deleted. Use relative styling for position and size within a screen; use theme tokens for color, where one change reaches everything. |
| Galleries for anything repetitive or linear — including a Gallery as a read-only view form instead of a Display Form | Adopt | Hand-placed control rows are faster to build once and miserable to change forever. The Table()-driven gallery view form is genuinely quicker to restyle than editing separate data cards. |
| Forms for repetitive data entry fields | Adapt — see the constraint | Forms buy grouping, validation, and relative styling, but under DA-001 they cannot submit: SubmitForm() writes through the data source connector we deliberately removed from the app. Use forms for layout, Valid, and Updates — then pass those values into the flow call. Any maker reaching for SubmitForm has left the architecture without noticing. |
Avoid nesting — unnecessary DataCards and canvases, nested galleries, nested ForAll | Adopt | Each level multiplies evaluation work and hides where a formula actually runs. Note the paper corrects itself here: nested galleries were never deprecated — they’re permitted, just costly. |
| Use Format text; comments and whitespace are stripped from the client package | Adopt | Removes the last excuse for unformatted formulas and thin comments — readability is free at runtime. |
| Periodically republish apps to pick up platform optimizations | Adopt — fold into §15 | Some platform improvements apply only to apps published at or after a given version. An app untouched for two years is still running on the runtime it was born with. This belongs in the release cadence, not in anyone’s memory. |
| Preview and experimental app settings (delayed load, explicit column selection, non-blocking OnStart) | Case-by-case — never silently | These move real performance numbers, but they change runtime behavior, and the paper’s own warning is to test thoroughly. A preview feature enabled in production is a decision record here, not a checkbox someone flipped on a Friday. |
DraftDebugging & diagnostics
The white paper devotes a full section to debug affordances built into the app itself: a semi-transparent canvas panel showing live variable state, toggle controls that let a maker force an error condition and watch the UI react, and an identity check that keeps all of it invisible to end users. That instinct matters more under DA-001, not less — when data arrives as a JSON payload from a flow rather than a bound data source, “what did the app actually receive?” becomes a question you ask constantly, and a debug panel answers it without a republish.
Two of its three patterns still hold. The debug panel (a maker-only overlay listing globals, collection counts, and the last flow envelope) is worth building into the house screen template. The toggle-control error pattern is superseded: it was a workaround from an era before formula-level error handling, and IfError / IsError now express the same thing directly, closer to the failure. The third — how you decide who sees any of it — needs a decision, because the paper’s answer conflicts with a standard already recorded in this document.
Debug affordances are gated on an Entra ID security group — never a hard-coded email list
Apps may ship a maker-only debug panel (global variable state, last flow response envelope, environment label, app version). Its visibility is driven by membership in a named security group, resolved once at startup into a global. Not by a list of email addresses in a formula.
- The paper’s example hard-codes three email addresses into
OnStart. That collides head-on with §15’s rule against hard-coded configuration: adding one person means an edit, a republish, and a deploy through every environment — and removing someone who left requires remembering the formula exists at all. A group check moves the list to data and inherits the joiner/leaver process for free, exactly as §04 does for app sharing. - The paper’s cleaner alternative — the Power Apps for Makers connector’s
GetAppRoleAssignments— ties debug rights to app-edit rights. That’s the wrong coupling under §04, where production maker access is deliberately restricted: the person on support duty may legitimately need diagnostics without the ability to edit a live app.
Confirm the license class of the Power Apps for Makers connector in our tenant before anyone adds it to an app. If it is premium, dropping it into the app layer for debug convenience would flag every end user for premium licensing — precisely the accidental-premium failure DA-001 and §06 exist to prevent, arriving through the back door of a diagnostics feature. This is a live worked example of §06’s monitoring question, not a hypothetical.
One more group to provision and review · a membership check costs one call at startup (cache it in a global; don’t re-check per screen) · debug UI ships inside the production package, so it must be genuinely inert for everyone else — hidden, not merely small.
OpenQuestions you need to answer
- What is the screen inventory convention — is there a required set (Home, Detail, Admin, About/Version) and a maximum before an app should split?
- When does the component library become mandatory, and who curates it?
- What is the versioning display standard — do apps show a version and environment label to users, and where?Why it matters: “which version are you on?” is the first support question; the answer should be on screen.
- What is the performance budget (startup time, screen-load time) and how is it measured before release?
- What is the offline stance — explicitly unsupported, or designed for where field work demands it?
- Which formula patterns are banned or discouraged in review (e.g., heavy logic in visible/OnChange chains, N+1 lookups inside galleries)?
- Which security group gates debug affordances — one per app family, or one across all Power Platform solutions?Why it matters: one group is easier to run and leaks diagnostics across unrelated apps; per-app groups are precise and multiply the review burden.
- Is the Power Apps for Makers connector premium in our tenant, and does that alone disqualify it from the app layer? (Feeds §08’s sanctioned list and §06’s accidental-premium monitoring.)
- May makers enable preview or experimental app settings freely in dev, and what evidence is required to carry one into production?
- Does the house screen template ship the debug panel by default, or is it added per app on request?
IV · Microsoft 365 services
§11SharePoint & OneDriveTreat lists as a database you happen to get for free — design them like one▶
DA-001 makes SharePoint the system of record, accessed through flows. That shifts where SharePoint’s sharp edges cut: delegation warnings in the app matter less, but list design, throughput limits, and site permissions matter more — because the flow layer inherits them all.
DraftProposed practices — each with its why
| Area | Practice | Why |
|---|---|---|
| Site architecture | One dedicated site (or site collection) per solution family; app lists never live in a collaboration site people also use for documents. | Permissions boundaries follow sites. Mixing app data with human collaboration makes §04’s lockdown impossible to reason about. |
| Column creation | Create every column with its final internal name (PascalCase, no spaces), then set display names; record the schema in the solution docs. | Internal names are immutable and appear in every OData filter; Approval_x0020_Stage is a lifelong tax on readability. |
| Indexing | Index every column used in flow Filter Queries before the list grows; document which queries each index serves. | See callout above — the failure mode is delayed and production-shaped. |
| Lookups | Prefer storing keys (text/number) resolved by flows over native lookup columns for high-volume lists; keep native lookups under the per-view limits. | Lookup columns count against strict view limits and complicate OData; flow-resolved keys keep the schema portable. |
| User stamping | Dedicated columns (SubmittedBy, ActionedBy, timestamps) written by flows, since built-in Created/Modified By show the service identity. | Restores a true audit trail under DA-001’s identity model; auditors will ask. |
| Choice vs. reference list | Volatile or reportable value sets live in a reference list read by flows; only truly static sets use Choice columns. | Choice edits are a schema change requiring a deploy mindset; reference lists make it data. |
| Versioning & retention | List versioning on for systems of record; retention/trim policy decided per list, not defaulted. | Versioning is your cheapest undo and your audit evidence — but unlimited versions on a hot list is a quiet storage leak. |
| Attachments | Prefer a document library with metadata linking back to the list item over native list attachments, when files matter. | Native attachments are hard to secure granularly, hard to query, and invisible to most governance tooling. |
OpenQuestions you need to answer
- Who provisions app sites — makers, or an admin gate — and from what template?
- What is the archival strategy when a list approaches size thresholds (yearly archive lists, a rollover flow, or deletion policy)?
- Where does the canonical schema documentation live for each list, and is it generated or hand-maintained?
- What is the standard for calculated columns and Power Automate-maintained denormalized fields — allowed, and who owns keeping them true?
- How are list-level backups/restores handled beyond versioning — is Recycle Bin + versioning officially “enough,” and for which data classes is it not?Why it matters: the honest answer differs for a room-booking list vs. anything touching regulated data.
§12TeamsWhere approvals actually get seen — and a site architecture risk hiding in a checkbox▶
Teams matters here for two reasons: it is the surface where flow-driven approvals reach a human, and it silently provisions the SharePoint sites §11 tries to govern. Both need decisions.
DraftProposed practices — each with its why
| Area | Practice | Why |
|---|---|---|
| Approval delivery | Pick one primary channel for approval requests — adaptive card in Teams, email, or in-app queue — and make the others secondary, consistent across solutions. | Approvers learn one habit. Three delivery patterns across three apps means every app gets its own training and its own “I never saw it.” |
| Adaptive cards carry state honestly | A card that has been actioned updates to show the outcome; cards never leave a stale “Approve / Reject” pair sitting in a chat. | A stale actionable card invites a second approval on a record already decided — the concurrency problem from §07 arriving through the UI. |
| Cards link, apps decide | The card triggers the same flow the app would; approval logic lives in the flow, never duplicated in the card handler. | Two implementations of one business rule drift. The card is a doorway, not a second front door with its own lock. |
| Team provisioning | Team creation is gated and templated, and the resulting site is inventoried under §11. | Ungated Teams creation is ungated SharePoint site creation. See §02’s self-service question — same decision, different admin center. |
| Private channels | Decide: permitted, restricted, or off — and record the site-collection consequence in the reasoning. | See the callout above. The default is “on,” which is a decision you inherit rather than make. |
| Lifecycle | Group expiration and an archival path decided for app-associated teams. | Teams accumulate faster than any other object in the tenant, each one dragging a site and a mailbox behind it. |
| Apps embedded as tabs | If a canvas app ships as a Teams tab, decide it up front — the layout, auth context, and deep-link behavior differ from the browser. | Retro-fitting an app into a tab surfaces every hard-coded width and every assumption about the URL. |
Teams-connected sites are never app data stores
App lists live in a dedicated SharePoint site with no Team attached. Where a solution also wants a Team for conversation, the Team is a separate artifact from the site holding the data.
- Team membership grants access to the Team’s underlying site. So adding a colleague to a chat silently grants them list access — and SEC-001’s premise is that end users hold no direct list permissions. The row-level security this entire architecture rests on can be undone by someone being helpful in Teams.
- Worse, it is undone by a Teams action rather than a SharePoint one, so it never crosses an administrator’s desk and appears in no permissions review until someone goes looking.
Two artifacts to provision and explain instead of one · users find it counterintuitive that the app’s Team does not hold the app’s data, so the runbook (§16) has to say why.
OpenQuestions you need to answer
- Are approvals delivered by adaptive card, email, or the app itself — and what is the fallback when the primary channel fails?
- Are private channels permitted? If so, how do their site collections get inventoried and secured under §11 and §04?
- Who can create teams today, and does that match §02’s answer on self-service group creation?
- What is the team naming and expiration policy, and who handles the expiry notifications when the owner has left?
- Do any solutions ship as Teams tabs, and is that a supported pattern here or an exception?
- Are shared channels (external participants) in scope, and what does that change for §04 and §05?
§13Exchange OnlineWho the notification appears to come from, and why it matters▶
Under §07, flows run as a service identity — which means that by default, every notification an app sends appears to come from a service account nobody recognizes, or worse, from whoever happened to own the connection. Mail is the most visible artifact most solutions produce. It deserves a deliberate answer.
svc-pp-pcard@… looks like phishing and gets ignored or reported. A message from a person’s account makes that person the support desk forever, and breaks the day they leave. Neither has a reply path — and users will reply, because a message from a human invites a reply to a human. A shared mailbox gives the message a recognizable identity, a monitored reply address, and continuity independent of any employee.DraftProposed practices — each with its why
| Area | Practice | Why |
|---|---|---|
| Send from a shared mailbox | App notifications send as a solution-specific shared mailbox, with the service account granted explicit Send As permission. | See the callout above. It also puts the sending identity in the same governed inventory as everything else in §03. |
| Send As, not Send On Behalf | Choose deliberately and record it. Send As shows only the mailbox; Send On Behalf exposes the service account in the header. | “Sent on behalf of svc-pp-pcard” reintroduces exactly the phishing-shaped confusion the shared mailbox removed. |
| Reply-to is monitored | Every app-sent message has a reply path that reaches a human, or explicitly states that replies aren’t read and where to go instead. | Unmonitored mailboxes turn user replies into silence, and silence into a support ticket about a different thing entirely. |
| Security groups, not distribution lists | App audiences resolve to the §02 security groups. Distribution lists are for mail, not authorization. | Two parallel membership lists always diverge, and the DL is the one nobody reviews. |
| Mail is not a system of record | Notifications inform; SharePoint holds the record. Nothing important exists only in an inbox. | Mailboxes get deleted, filtered, and retained on schedules that have nothing to do with your app’s obligations (§05). |
| Throttling is a design constraint | Know the per-connection send limits before designing a flow that mails a large audience; batch or use a distribution mechanism built for volume. | Throttling surfaces as random partial failures across a run — the hardest class of bug to reproduce and the easiest to prevent. |
System mail sends from a named shared mailbox with a real owner
Flow-generated mail sends from a named shared mailbox per solution family, with Send As granted to the service identity. Not from a personal account, and not from an address nobody reads.
- The From line is the system’s public identity, and mail is the most visible artifact most solutions produce. Sending as a person makes the system unattributable and breaks the week that person is offboarded.
- An unmonitored no-reply address does not stop people replying — it just means their reply lands nowhere, and they conclude the system is broken. Users will always answer the thing that emailed them.
- A shared mailbox is the only option where the identity is stable across staff changes, replies reach a human, and the audit trail names the system rather than an employee.
One more mailbox to provision and include in access reviews · Send As must be granted to the service identity and reviewed (§03) · “monitored” is a claim that requires a named owner — without one it is simply a no-reply address with better branding.
OpenQuestions you need to answer
- Which shared mailbox belongs to which solution, and who provisions and monitors it?
- Send As or Send On Behalf — and is the permission grant documented alongside the service account in §03?
- May app mail reach external recipients, and what changes for §05 when it does?
- Do any mail flow rules or connectors silently modify or quarantine app-generated mail?Why it matters: a transport rule written for a different reason is a classic cause of “the flow ran but nobody got the email.”
- What is the largest audience any single flow mails, and does that sit inside the connector’s send limits?
- Are notification templates standardized (branding, tone, footer, unsubscribe/where-to-go-for-help), and where do they live?
§14Microsoft Graph & PowerShell automationThe ungoverned corner of most M365 shops▶
Scripts are where governance quietly stops. Apps get reviewed, flows live in solutions, and meanwhile a PowerShell file on somebody’s desktop holds an application-permission token that can read every mailbox in the tenant, runs on a scheduled task nobody documented, and is understood by exactly one person. This section exists to make scripts first-class: sourced, scoped, reviewed, and survivable.
Sites.Read.All means all, forever, with no one in the loop. That is not a bigger version of the same thing; it is a different category of risk, and it should cost a conversation.DraftProposed practices — each with its why
| Area | Practice | Why |
|---|---|---|
| Scripts live in source control | Every script that touches production is in a repository with history — not on a desktop, not in a mailbox, not in a chat message. | A script without history is a black box: you cannot see what changed, when, or why, and you cannot recover it when the laptop dies. This is the same argument §15 makes for solutions. |
| Delegated first, application by exception | Prefer delegated permissions. Application permissions require named approval and a recorded justification. | See the callout above. The default should be the one bounded by a human. |
| Least-privilege scopes, documented | Each script declares the exact scopes it needs and why, in its header. .All scopes are challenged in review, not waved through. | Scopes are granted once in a hurry and inherited forever. The header is what a reviewer reads two years later. |
| Certificates over client secrets | Unattended auth uses certificate credentials; secrets only where certificates aren’t supported, with a short expiry and a rotation owner. | Secrets leak in transcripts, config files, and screenshots, and expire on a date nobody diarized. Certificates raise the floor on both problems. |
| One app registration per purpose | Scripts don’t share a single do-everything registration; scope and lifecycle are per function. | A shared registration means the union of every permission any script ever needed — and one compromise reaches all of it. Cf. §03’s blast-radius question. |
| Writes are idempotent and support a dry run | Anything that changes state supports -WhatIf (or an explicit dry-run switch) and can be re-run without duplicating its effect. | Bulk scripts are run under pressure against production. A dry run is the cheapest possible confidence, and idempotency makes “did it half-finish?” a survivable question. |
| Standard header | Purpose · author · permissions required · identity it runs as · where it runs · last verified date. | Six lines that answer every question the next person will have, including whether the thing still works. |
| Module currency | Track which modules a script depends on and whether they’re still supported; pin versions for unattended runs. | The legacy MSOnline and AzureAD modules are retired in favor of the Graph SDK — scripts written against them are on borrowed time, and they fail silently-then-suddenly. Pinning stops an unattended job from breaking on someone else’s release day. |
Automation authenticates app-only with certificate credentials
Scheduled and unattended automation authenticates as a registered application using certificate credentials, with Graph permissions scoped to the task. Never as a delegated user, and never with a client secret pasted into the script.
- Delegated auth inherits a human’s entire permission set — far more than the job needs — and stops working the week that person leaves. The failure surfaces in production, during offboarding, with no obvious cause.
- App-only lets permissions be scoped to exactly the job, and the consent record becomes a reviewable artifact rather than tribal knowledge.
- Certificates over secrets: a secret in a script is exfiltrated the moment the file is shared or committed, and its expiry becomes an unplanned outage the day it lapses. Certificate auth keeps the private key out of the script entirely.
Certificate lifecycle is real work and must be owned (§03) · app-only Graph permissions are tenant-wide by default, so scoping needs deliberate constraint rather than the convenient wildcard · local development is genuinely more awkward than delegated auth, which is the pressure that breaks this standard if nobody plans for it.
OpenQuestions you need to answer
- Where do scripts live today — and honestly, how many production-affecting scripts are running from a personal machine on a scheduled task right now?Why it matters: this is the single most common ungoverned-automation finding, and the inventory is usually shorter than feared and scarier than expected. Find it before an offboarding does.
- Where should they run — Azure Automation, Functions, a managed jump host, a pipeline? What is the cost and the approval path?
- Who approves an application permission, and what evidence is required?
- One app registration per script, per function, or per team? What blast radius is acceptable?
- Certificates or secrets, and what is the maximum expiry allowed for each?
- Who reviews scripts before they run against production, and against what checklist?
- What is the inventory of existing app registrations and their granted scopes — and when was it last reconciled against what’s actually in use?
- Are any scripts still on retired modules, and what is the migration plan and deadline?
V · Delivery & practice
§15Testing & deploymentA solution isn’t done when it works — it’s done when it moves safely▶
Deployment is where good builds go to embarrass their authors. The standard here is boring on purpose: separate environments, solution-based packaging, externalized configuration, and a checklist that verifies the things the platform will not verify for you — especially the SEC-001 settings that silently define your security model.
DraftProposed baseline
| Area | Practice | Why |
|---|---|---|
| Environments | Minimum Dev → Test → Prod for anything with real users; personal/default environment is for throwaways only. | The default environment is a shared junk drawer with no promotion path; real solutions need a boundary between “being edited” and “being used.” |
| Packaging | All components (app, flows, connection references, environment variables, custom connectors) travel in one solution; managed in Test/Prod. | Managed solutions prevent drive-by edits in production and make “what version is live?” answerable. |
| Configuration | Zero hard-coded environment-specific values; everything environment-shaped is an environment variable. | Every hard-coded URL is a future deployment failure with your name on it. |
| Testing tiers | Maker self-test against a written test script → peer review (§16) → UAT with named business testers and sign-off → deploy. | Each tier catches a different failure class: logic, convention/security, and “that’s not how the process actually works.” |
| Deployment checklist | A per-release checklist including: connection references bound to service account · run-only sharing verified as “Use this connection” · list permissions verified locked · environment variables set · smoke test as a real end user. | SEC-001’s settings don’t survive export/import automatically and fail invisibly. A checklist is the only tool that reliably remembers. |
| Rollback | Previous managed version retained and restorable; app versions restorable; rollback rehearsed at least once. | A rollback plan that has never been executed is a hypothesis, not a plan. |
OpenQuestions you need to answer
- Manual export/import or pipeline-based ALM — and if pipelines, which tooling is actually available and approved in your cloud/tenant?
- Where do test scripts live, and what is the minimum test coverage for an app-facing flow (happy path + which failure paths)?
- Who owns UAT sign-off per solution, and is sign-off recorded somewhere durable?
- What is the release cadence and change-window policy — can production deploys happen any time, or scheduled windows only?
- How is test data handled — synthetic only, scrubbed copies, or (decide explicitly) never production data in lower environments?Why it matters: regulated data in a dev environment is a compliance incident nobody planned.
- What is monitored post-deploy (flow failure rates, app errors) and for how long before a release is “settled”?
§16Team collaboration practicesHow the team builds together without stepping on each other — or leaving with the knowledge▶
Standards fail socially before they fail technically. This section covers how work is shared, reviewed, documented, and handed off — the practices that make the other ten sections survive personnel changes, deadline pressure, and the fact that canvas apps still effectively allow one editor at a time.
DraftProposed practices
| Area | Practice | Why |
|---|---|---|
| Single-editor reality | Canvas app work is claimed visibly (task board or channel post) before opening the app in edit mode; flows can be divided by ownership. | Two people editing one canvas app produces lost work and resentment; a 10-second claim ritual prevents both. |
| Peer review | Every production-bound app/flow gets a second set of eyes against a short checklist: naming (§01), envelope (DA-002), error scopes (§09), SEC-001 settings, no hard-coded config (§15). | A checklist keeps review consistent across reviewers and makes “passed review” mean something specific. |
| Documentation home | One canonical location for solution docs (this guide, per-solution runbooks, schemas); links from app About screens. | Documentation that must be hunted for is documentation that doesn’t exist at 2 AM. |
| Decision hygiene | Architectural choices made mid-build get written into this document (or the solution’s decision log) within the week, with their why. | The why evaporates in about seven days; after that you’re reverse-engineering your own intent. |
| Knowledge spread | Regular internal show-and-tell / training rotation so every solution has at least two people who can support it; pair new makers with a reviewer. | Bus-factor-one solutions turn vacations into incidents. Teaching is also the fastest way to harden a standard. |
| Support handoff | Every production solution ships with a runbook: owner, service identities, environment map, known failure modes, escalation path. | The person on support duty should never need the original author online to do triage. |
OpenQuestions you need to answer
- Who are the named reviewers, and what turnaround is promised so review never becomes the excuse to skip review?
- Where does the claim board / work log live, and is it actually lightweight enough that people use it?
- What is the training and certification plan per team member, and how is time for it protected?
- What is the intake process for new app requests — how do ideas get sized, prioritized, and assigned against this standard from day one?
- How are citizen developers outside the core team handled — free rein in a sandboxed environment, mentorship track, or gated intake?Why it matters: this decision determines whether governance feels like enablement or like a wall.
- What is the cadence for reviewing this document itself, and who runs that meeting?
Appendices
ADecision record templateCopy, fill, and file under the relevant section▶
Copy the block below (in the HTML source it’s marked with TEMPLATE — COPY FROM HERE) into the relevant section. Set the class to dr decided, dr draft, or dr openq, and update the stamp text to match. Every field is mandatory except Mitigations — if you can’t write the Why, the decision isn’t ready.
One-sentence statement of the decision
What we will do, stated so a new team member could follow it without asking.
The tradeoff this accepts, the constraint it solves, or the failure it prevents. This field is the point of the record.
What this costs us, honestly stated.
How the costs above are contained (optional).
What was rejected and the one-line reason each lost.
BChange logEvery edit to this document, dated and attributed▶
| Date | Rev | Author | Change |
|---|---|---|---|
| 2026-07-14 | 0.1 | John | Initial draft. Ratified DA-001 (flow-mediated SharePoint data access). Seeded drafts DA-002, SP-001, SEC-001 and open-question sets for all sections. |
| 2026-07-14 | 0.2 | John | Added full-text search. Incorporated the Microsoft canvas app coding standards white paper as baseline input (§07 corroboration note, §01 baseline + NC-001, §10 stance table); added Appendix D (References). |
| 2026-07-14 | 0.3 | John | Second pass over the Microsoft white paper: extended the §10 stance table to 21 positions (click targets, grouping, relative styling, galleries, forms, nesting, republishing, preview settings). Flagged the Forms/SubmitForm conflict with DA-001. Added §10 debugging & diagnostics with draft decision AS-001. |
| 2026-07-14 | 0.4 | John | Scope widened from Power Platform to Microsoft 365 (doc ID now M365-STD-001). Restructured into five parts and renumbered; all cross-references updated. New sections: §02 Identity & Entra ID, §05 Compliance & data governance, §12 Teams, §13 Exchange Online, §14 Graph & PowerShell automation. §11 widened to SharePoint & OneDrive. Added theme picker (8 themes) with light/dark/auto. |
| 2026-07-15 | 0.5 | John | Repaired section numbering: header numbers had drifted out of step with the contents and ids (§15 and §16 were each used twice; §01 and §06 were unused). Contents, anchors and cross-references were already correct and are unchanged. Default theme set to Jade; Graphite renders at zero saturation and is now opt-in. Seeded anchor decisions ID-001, CMP-001, TM-001, MX-001, PS-001 in the sections added at 0.4. |
| ____ | |||
| ____ | |||
| ____ |
CGlossaryShared vocabulary, so the standards mean the same thing to everyone▶
| Term | Meaning here |
|---|---|
| ALM | Application Lifecycle Management — how solutions move Dev → Test → Prod with versioning and rollback. |
| DLP policy | Data Loss Prevention policy — tenant rules grouping connectors into Business / Non-Business / Blocked so they can’t be combined dangerously. |
| Envelope | The standard flow response shape defined in DA-002 (success, message, data). |
| Flow-mediated access | The DA-001 pattern: apps never touch lists directly; flows perform all reads/writes server-side. |
| Run-only user | Someone granted permission to execute a flow without editing it; their connection setting is the linchpin of SEC-001. |
| RLS | Row-level security — users see only the records they’re entitled to, enforced here in the flow layer. |
| Service account | A licensed, non-personal user identity that owns production connections and flows. |
| Service principal | A non-user Entra ID identity (app registration) used for API-level access such as Microsoft Graph. |
DReferencesExternal baselines this standard draws on — and how to treat them▶
| Reference | Informs | Notes |
|---|---|---|
| Power Apps canvas app coding standards and guidelines — Microsoft white paper (Baginski & Dunn, with Microsoft IT contributors) | §07 corroboration · §01 naming baseline · §10 construction stances · NC-001 | Feature coverage stops at Dec 2018 (© 2019); predates named formulas, modern controls, and components. Treat as input, not authority — see the stance table in §10. Source PDF |
| Microsoft Learn — Power Apps and Power Automate documentation | Current platform behavior; supersedes aged guidance anywhere it conflicts | Check publication dates on everything — the platform moves quarterly. |
| ____ | ____ | ____ |
| ____ | ____ | ____ |