EPMS·Data Model Explorer
seco_Pass 3

Portal access model

Power Pages security runs on contact and account. Every portal-facing table declares a path to one of them, or the portal cannot scope it at all. There are three distinct ways an external person reaches a record, and conflating them is how portals leak.

Branch on `seco_code`, never on the row GUID.

Lookup row GUIDs are generated per environment and differ between Dev, Test and Prod. A plugin or flow that branches on a GUID passes every test in Dev and fails silently in Prod, because the row it matched no longer exists under that id. Resolve the lookup, read seco_code, branch on the code. This applies to all 55 vocabulary tables, both tiers.

The three chains

Chain A

Customer

The signed-in contact's parent account is the customer on the project. The customer sees its own projects and the delivery records that hang off them.

Customer account boundary
contact
signed-in portal identity
parentcustomerid
contact → account
seco_account
Account scope on the project
seco_project
Read
seco_deliverable · seco_projectgate · seco_actionlog
Parent scope via seco_project, Read
Chain B

Partner

The signed-in contact's parent account is the partner's own firm, never the customer's. The partner reaches the project through a participant row, so the customer list is never in scope.

Partner's own account boundary
contact
signed-in portal identity
parentcustomerid
contact → partner's own account
seco_projectparticipant
Account scope on seco_account, Read
seco_project
Parent scope via the participant row, Read
Chain C

Per-action

The finest grain, and the only place an external person writes. One share row per contact per action; the share row is the grant.

No account boundary — the grant is per contact
contact
signed-in portal identity
seco_actionshare
Contact scope on seco_contact, Read
seco_action
Parent scope via seco_action, Read + Write

Table permissions

TableScopeColumn Power Pages readsPrivilegesNote
accountSelfReadPower Pages reads identity from contact and its parentcustomerid; the account is the boundary itself, read-only and never wider than the signed-in contact's own account.
contactAccountparentcustomeridReadPower Pages reads identity from contact and its parentcustomerid. A contact sees contacts within its own account only.
seco_actionParentseco_action → seco_actionshareRead + WriteChain C. The external user writes to the action only because a share row exists naming them. Without the share row there is no path to the action at all. This is what prevents privilege escalation on the externally-writable surface.
seco_actionlogParentseco_project → seco_projectReadChain A. Inherits the project's account scope. The log header is readable; individual actions are not reachable from here without a share row.
seco_actionshareContactseco_contactReadChain C. One row per contact per action; this row is the grant.
seco_deliverableParentseco_project → seco_projectReadChain A. Inherits the project's account scope; never granted directly, so a deliverable can never be reached without its project.
seco_projectAccountseco_accountReadChain A. The customer's own projects: the signed-in contact's parent account is the customer on the project.
seco_projectParentseco_project → seco_projectparticipantReadChain B. A partner reaches the project only through a participant row naming its own account. Recorded as a second permission; Power Pages unions the two.
seco_projectgateParentseco_project → seco_projectReadChain A. Inherits the project's account scope. Read only: gate decisions are internal.
seco_projectparticipantAccountseco_accountReadChain B. For a partner this account is the partner's own account, never the customer's. That is what stops a partner seeing the customer list.

Explicitly no portal path (40 tables)

  • seco_agreementNo portal path exists by design. Signed agreement terms are held internally and shared out of band.
  • seco_approvalchainEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_approvalstepEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_bdcountrypoolEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_capacityentryEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_changerequestEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_clearanceEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_commercialapproverpolicyEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_configauditEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_configsettingEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_costlineNo portal path exists by design. A customer or partner never reaches cost, budget, rate or margin.
  • seco_costrateNo portal path exists by design. A customer or partner never reaches cost, budget, rate or margin.
  • seco_countryteammembershipEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_crmfieldmapEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_delegationEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_documentrequirementEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_eventNo portal path exists by design. Events are internal delivery telemetry, not a customer-facing timeline.
  • seco_externalaccessEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_externalaccesspolicyEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_featureflagEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_feedbackNo portal path exists by design. Feedback is collected through a dedicated flow, not exposed as a readable table.
  • seco_gatetemplateEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_gatetemplatestepEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_invoicemilestoneNo portal path exists by design. A customer or partner never reaches cost, budget, rate or margin.
  • seco_notificationtemplateEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_projectaccessgrantEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_projectbudgetNo portal path exists by design. A customer or partner never reaches cost, budget, rate or margin.
  • seco_proposalNo portal path exists by design. Proposals are negotiated through the account team, not read from the portal.
  • seco_reportingaudienceEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_requestraisergroupEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_requiredfieldruleEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_resourceskillEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_restrictionhandlingEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_retentionpolicyEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_routingruleEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_scopeprofileEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_timeentryNo portal path exists by design. A customer or partner never reaches cost, budget, rate or margin.
  • seco_tutorialEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_userprofileEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.
  • seco_viewonlybucketEvery config, security and vocabulary table is scope None: none of them carries a path to an account or a contact, and the portal must never resolve behaviour, permissions or option lists directly.

Leak check — every column with portalExposed: true

CRM-sourced columns are exactly where a leak would happen, so they are marked. Any column not on this list does not reach the portal.

seco_project

  • seco_nameTextread-only
  • seco_projectnumberAutonumberread-only
  • seco_accountLookupread-only
  • seco_stageLookupread-only
  • seco_startdateDate Onlyread-only
  • seco_targetenddateDate Onlyread-only

seco_documenttype

  • seco_sharewithcustomerYes/Noread-only
  • seco_sharewithpartnerYes/Noread-only

Cross-environment

EPMS and D365 are separate Dataverse environments. account and contact are replicated into the EPMS environment as real rows. The portal reads the EPMS copies, not the CRM — Power Pages table permissions resolve against contact.parentcustomerid on the replicated records. Replication must run before a project can reference a customer.

A customer who exists in D365 but has not yet replicated cannot be selected on a request. Intake must handle that case rather than assume the account is present.

See the materialisation map →

Table permissions are environment configuration

Power Pages table permissions and web roles are environment configuration, not solution components in the same way tables are. They must be recreated or exported deliberately per environment; importing the EPMS solution does not bring them along.