Service capability discovery and organization-member permissions
Status: proposed M7 standard, revision 1, 2026-10-03; adoption is incomplete.
Use this contract when a receiving service interprets User.m7 organization Management membership and explicitly selected service permissions. API User manages organization membership, tags, and disclosure policy; the receiving service publishes its own action catalogue and enforces access to its own resources. Discovery alone does not grant permission.
The naming, versioned selections, and default-resolution rules are proposed
service integration requirements. Existing organization and publication access
checks continue independently. The approved readonly rule grants reads and
allows separately selected writes. Service-specific profile contents belong in
each service's own documentation; the catalogue example here is illustrative.
The standalone PHP runtime consumer described below is implemented in source;
its local resolution does not implement the wider host authorization contract.
Naming contract
| Identifier | Meaning | Boundary |
|---|---|---|
role.owner, role.admin, role.member |
Reserved bundle identifiers associated with User.m7 organization Management roles. Each service publishes their explicit default capability sets. | The names do not establish membership, change a Management role, grant ownership, or bypass ownership checks. |
preset.* |
Service-defined explicit capability bundles, for example preset.editor, preset.writer, and preset.publisher. |
Selection is independent of the caller's Management role. Editor and Writer are service profiles, not additional User.m7 Management roles. |
readonly |
Reserved global bundle name expanding to all capabilities classified as read by the receiving service at the approved revision. | Additive, not a write ceiling. Selecting readonly and post.save grants their union, subject to resource and operation checks. |
<family>.read, <family>.write |
Broad family bundles, for example post.read, post.write, media.read, and media.write. |
Reserved for bundles. Individual actions must not use these IDs. |
| Exact capabilities | Stable individual actions, for example post.save, post.publish, and publication.create. |
Authorization evaluates an exact ID and its operation/resource policy. |
The asterisk in preset.* and the placeholder in <family> describe naming
patterns. They are not runtime wildcards. Reject selections such as preset.*,
post.*, or *; a family bundle resolves only to its published explicit list.
Do not classify an action by a label or verb alone: a service must declare its
read/write operation. For example, a status-changing action can be a write.
Catalogue display names, descriptions, and labels are presentation text;
structured capability IDs govern resolution. Blog's current host convention
uses each verified Management tag's name as a capability/profile ID. SSO
preserves its dots and case, and Blog matches it case-insensitively against the
current SQL catalogue. Renaming such a tag changes the selected identifier.
Tag slugs are normalized separately and may serve generic tag purposes;
neither slugs nor arbitrary tag data supply Blog capability grants.
Registration bindings and approved revisions belong to the proposed approval
contract below. A role.owner selection can request the receiving service's
published pack; it cannot make a member an organization or resource owner.
Public discovery catalogue
Every participating service publishes an unauthenticated HTTPS JSON catalogue
at a URL bound to its receiving-service registration. The endpoint must be
usable without a caller token. It contains exact capabilities, named presets,
family bundles, the reserved readonly bundle, role-default bundles, and
role-default mappings. Anonymous discovery must expose no organization,
membership, selection, or credential data.
Every bundle has an explicit array of exact capability IDs; references to other bundles and wildcard expressions are not expansions. All IDs in an expansion must exist in that catalogue revision. IDs are unique, and exact actions must not collide with bundle IDs or reserved naming forms.
Bind catalogue retrieval to a trusted registration rather than a caller's arbitrary URL. Record the catalogue schema version, catalogue revision, and expansion revision of each bundle. A catalogue update cannot silently enlarge an approved selection. Preserve immutable expansion revisions or retain the approved exact-action snapshot. A changed expansion requires a new revision and deliberate approval before the added actions become effective.
The same rule applies to role fallbacks: resolve the role mapping and expansion at the service's approved catalogue revision, not whatever latest revision is available. An owner bundle may contain every action in that revision without implicitly including future actions. New exact IDs remain ungranted until an approved selection or approved default revision includes them. Changes to an action's security meaning also require deliberate review; retaining an ID must not become an expansion mechanism.
Illustrative catalogue JSON
This complete small catalogue illustrates the proposed data model. It is not the existing Blog wire format, an API User payload, or a required platform-wide Owner/Admin profile. Services choose the contents of those default bundles. All UUIDs are examples. Schema field names below are proposed standard fields; they are not claims that current tag APIs accept this document.
{
"schema_version": 1,
"catalogue_version": 1,
"service": {
"id": "example.service.m7",
"registration_id": "11111111-1111-4111-8111-111111111111",
"client_id": "example-service-receiver",
"name": "Example service"
},
"capabilities": [
{"id": "post.list", "name": "List posts", "family": "post", "operation": "read"},
{"id": "post.save", "name": "Save posts", "family": "post", "operation": "write"},
{"id": "post.publish", "name": "Publish posts", "family": "post", "operation": "write"},
{"id": "media.list", "name": "List media", "family": "media", "operation": "read"},
{"id": "media.save", "name": "Save media", "family": "media", "operation": "write"},
{"id": "publication.create", "name": "Create publication", "family": "publication", "operation": "write"}
],
"bundles": [
{"id": "role.owner", "kind": "role_default", "expansion_version": 1, "capabilities": ["post.list", "post.save", "post.publish", "media.list", "media.save", "publication.create"]},
{"id": "role.admin", "kind": "role_default", "expansion_version": 1, "capabilities": ["post.list", "post.save", "post.publish", "media.list", "media.save"]},
{"id": "role.member", "kind": "role_default", "expansion_version": 1, "capabilities": []},
{"id": "preset.editor", "kind": "preset", "expansion_version": 1, "capabilities": ["post.list", "post.save", "post.publish", "media.list", "media.save"]},
{"id": "preset.writer", "kind": "preset", "expansion_version": 1, "capabilities": ["post.list", "post.save", "media.list", "media.save"]},
{"id": "readonly", "kind": "global", "expansion_version": 1, "capabilities": ["post.list", "media.list"]},
{"id": "post.read", "kind": "family", "expansion_version": 1, "capabilities": ["post.list"]},
{"id": "post.write", "kind": "family", "expansion_version": 1, "capabilities": ["post.save", "post.publish"]},
{"id": "media.read", "kind": "family", "expansion_version": 1, "capabilities": ["media.list"]},
{"id": "media.write", "kind": "family", "expansion_version": 1, "capabilities": ["media.save"]},
{"id": "publication.read", "kind": "family", "expansion_version": 1, "capabilities": []},
{"id": "publication.write", "kind": "family", "expansion_version": 1, "capabilities": ["publication.create"]}
],
"role_defaults": {
"source": "direct_organization_management_membership",
"apply_when": "verified_empty_service_capability_selection",
"explicit_selection": "replace_defaults",
"mappings": {
"owner": {"id": "role.owner", "expansion_version": 1},
"admin": {"id": "role.admin", "expansion_version": 1},
"member": null
}
},
"readonly_policy": "additive"
}
role.member is advertised for the role namespace, but does not grant anything
automatically. Members receive service permissions through explicit presets,
family bundles, or exact capabilities. A service must not turn its published
member profile into a new implicit fallback.
Illustrative approved selection JSON
This is a proposed normalized approval record, not a caller-supplied request
that grants access or Blog's implemented input format. A future trusted adapter
must bind it to live direct Management membership, the receiving registration,
and the approved definition revision. Current Blog consumes verified
management_groups[].tags[].name; it does not require a capability list in tag
data. The structured registration/version approval machinery below remains
separate from that current name-based host convention.
{
"organization_id": "22222222-2222-4222-8222-222222222222",
"management_membership_id": "33333333-3333-4333-8333-333333333333",
"service_registration_id": "11111111-1111-4111-8111-111111111111",
"catalogue_version": 1,
"selection": [
{"kind": "bundle", "id": "readonly", "expansion_version": 1},
{"kind": "capability", "id": "post.save"}
],
"approved_exact_capabilities": ["post.list", "media.list", "post.save"]
}
Selections belong to this receiving registration and catalogue. Identical capability strings from another service do not confer access here. Replacing a registration does not transfer old approvals merely because its public name or Client ID was reused. The approved exact-action snapshot must agree with the selected immutable expansions; mismatches are invalid authorization data.
Runtime capability use service
platform\service\capability is a standalone runtime consumer. Its constructor
receives one configuration array containing supplied definitions, profile,
and an optional fallback role. It resolves selections in memory and builds
an allowed hash for check(). It is separate from the catalogue builder/editor:
it does not publish discovery, edit a catalogue, persist approvals, or install
storage. Defining an item does not select it.
Native families and presets
Preferred native terminology is families and presets. A native master
contains a families hash whose values are hashes of primitive capability IDs,
and a presets hash of named capability packs. Each primitive declares
operation: read|write; each preset has a capabilities list. Either top-level
hash may be omitted, but at least one is required. Display labels and
descriptions do not affect grants.
With the PHP platform autoloader configured:
use platform\service\capability;
$master = [
'families' => [
'post' => [
'post.list' => ['operation' => 'read'],
'post.save' => ['operation' => 'write'],
'post.publish' => ['operation' => 'write'],
],
'media' => ['media.list' => ['operation' => 'read']],
],
'presets' => [
'preset.writer' => ['capabilities' => ['post.list', 'post.save']],
'role.owner' => ['capabilities' => ['post.list', 'post.save', 'post.publish', 'media.list']],
'readonly' => ['capabilities' => ['post.list', 'media.list']],
'post.read' => ['capabilities' => ['post.list']],
],
];
$gate = new capability([
'autoload' => 'master',
'master' => $master,
'profile' => ['PRESET.WRITER', 'unknown.name'],
'role' => 'role.owner',
]);
$gate->check('POST.SAVE'); // true: case-insensitive primitive check.
$gate->check('post.publish'); // false: explicit profile replaces role fallback.
$gate->loadProfile(['unknown.name']);
$gate->check('post.publish'); // false: filtering never activates Owner fallback.
$gate->loadProfile([]);
$gate->check('post.publish'); // true: original empty profile uses role.owner.
Autoload and discovery
autoload accepts discovery, master, sql, or false for manual loading.
The default true infers SQL first when sql or sql_loader is supplied, then
discovery, then master; with no source it starts empty. Sources are not
combined by the constructor. Select the source explicitly when supplying more
than one. The definition keys are discovery and master, not a generic
definitions payload key.
loadDiscovery($document, $mode = 'replace') directly accepts a supplied JSON
string or decoded discovery document. It performs no URL fetch. It accepts
standard capabilities/bundles lists or Blog-format capabilities/presets
lists, translating external bundles into native presets. Do not supply both
bundles and presets. Primitives use family or group; conflicting values
are invalid. Each primitive requires a read/write operation.
The illustrative discovery JSON earlier in this guide can be consumed directly:
$gate = new capability([
'autoload' => 'discovery',
'discovery' => $discoveryJson, // The supplied example JSON or decoded array.
'profile' => ['readonly', 'post.save'],
'role' => 'owner',
]);
$gate->check('media.list'); // true: Readonly contributes reads.
$gate->check('post.save'); // true: explicit writes remain allowed.
$gate->check('post.publish'); // false: nonempty profile does not use Owner fallback.
$gate->loadCapability(['id' => 'post.new_read', 'family' => 'post', 'operation' => 'read']);
$gate->check('post.new_read'); // false: published Readonly expansion stays as supplied.
$gate->loadProfile(['post.read']);
$gate->check('post.new_read'); // false: published post.read expansion also stays fixed.
loadMaster($master, $mode = 'replace') consumes a decoded native master.
Published role_defaults.mappings (ID hashes or null) and
role_defaults.presets (ID strings or null) are supported by both document
loaders. An owner → role.owner mapping makes role: owner use that preset;
a null mapping grants nothing. Without a mapping, role names a supplied
primitive or preset directly (including a family pack). Roles are not a
built-in enum, ownership check, or privilege
ceiling. Revision fields on the document or mapping do not make the consumer
select or authenticate a revision; the host supplies the approved definitions.
SQL reader and shared loaders
loadSql($mode = 'replace') calls the host's
sql_loader($sql, $config), with sql optional. The reader returns either a
discovery document or native master, decoded or as JSON. SQL JSON is decoded
before being passed to loadDiscovery() or loadMaster(). loadFromSql() is
an alias for the same operation. The service contains no hardcoded queries,
tables, SQL writes, or schema installation.
$gate = new capability([
'autoload' => 'sql',
'sql' => $connection,
'sql_loader' => $readDefinitions, // Host callable accepting ($sql, $config).
'profile' => ['preset.writer'],
'role' => 'owner',
]);
$gate->loadSql('upsert'); // Explicitly invoke the reader again and apply its result.
$connection and $readDefinitions are supplied by the host; the example does
not prescribe a database, query, table, or repository method. check(),
loadProfile(), and memory imports do not invoke the SQL reader.
All document loaders share loadFamily($family, $items),
loadCapability($primitive), and loadPreset($preset). A family item hash
uses primitive IDs as keys; standalone primitive hashes have id,
family/group, and operation. Preset hashes have id and capabilities.
import($list) consumes a list of primitive/preset hashes in memory, and
importSingle($hash) consumes one through that same import path. After each
complete load the current profile is reapplied; imports do not persist edits.
Load modes and reset scope
| Operation | Default | replace scope |
|---|---|---|
loadDiscovery(), loadMaster(), loadSql() / loadFromSql() |
replace |
Reset all definitions and role-default mappings, then consume the supplied snapshot. |
import() and importSingle() |
upsert |
Reset all definitions and mappings before importing, even for importSingle(). |
loadFamily() |
upsert |
Reset only that family's primitive set; retain other families, existing preset definitions, and mappings. |
loadCapability() |
upsert |
Replace that single primitive definition. |
loadPreset() |
upsert |
Replace that single preset's entire capability list. |
For every supplied scope, append preserves matching definitions and inserts
new IDs; it does not merge extra actions into an existing preset. upsert
inserts new IDs and replaces matching definitions while retaining absent ones.
Document-level modes also apply to supplied role-default mappings. Family
append adds new primitive IDs only; family upsert replaces matching primitive
records and retains the family's other primitives. Family replacement is not a
whole-master reset and does not delete preset definitions.
Direct definition loads validate complete batches atomically; malformed input
leaves the previous valid state unchanged. The SQL reader path additionally
marks the consumer unavailable and clears its allowed hash on a reader or
result-loading failure, throwing RuntimeException with code 503. A later
profile change cannot restore grants while unavailable; a successful definition
load can. A successful empty whole-document replacement removes prior
primitives, presets, and role mappings.
Profile resolution and checks
A profile is a list of primitive or preset names, including family packs.
Identifiers and checks are case-insensitive; surrounding whitespace is not
silently trimmed. Known selections expand and deduplicate; unknown names are
discarded. Role fallback applies only when the original profile is empty,
not when filtering removes every selection. Omitted or null constructor
profiles normalize to an empty list; loadProfile() takes an array and replaces
the current selection. The host must distinguish verified emptiness from
missing authorization data before constructing the consumer.
check($name) uses the resolved allowed hash without IO or expansion. In
addition to allowed primitives, that hash recognizes a preset when its complete
nonempty expansion is covered, even if the preset was not explicitly selected.
Empty packs, unknown names, and wildcard strings deny. A successful preset check
is not evidence of membership, ownership, or explicit selection of that preset.
The consumer can flatten references between supplied presets with cycle
protection; unknown references are discarded. This also applies to presets
imported from discovery. The publication contract above requires flattened
exact-action lists; the host must enforce it before loading.
Readonly is additive. Explicit published family/Readonly presets take precedence
over automatically derived packs, so added primitives do not silently enlarge
those supplied expansions. Such presets must stay within their declared family
and read/write operation. When an explicit preset is absent, full-family,
family.read, family.write, and readonly packs derive from the loaded
primitives and are recalculated after definition changes. The host must supply
explicit approved snapshots when fixed expansions are required.
Profile transport, definition revision selection, identity verification, Management membership, receiving-registration binding, and resource ownership remain outside this tool. The platform authorization rules elsewhere in this guide are host responsibilities: the consumer's unknown-name filtering does not validate a transported approval or enforce those rules. Use one instance for the supplied caller and scope. Format compatibility with Blog discovery is not Blog integration or deployment of this service.
Resolution order
The following order is the receiving host's authorization contract. The standalone consumer only resolves supplied definitions and names: it discards unknown names and normalizes omitted/null constructor profiles to empty. The host must validate transported approval data and verified emptiness before passing a profile, role, and approved definitions to that consumer. The registration/revision approval checks below are proposed host requirements. Current Blog uses verified tag names and discards unknown names; the table's strict invalid-approval rejection must not be read as Blog's name-filtering rule.
- Authenticate the incoming caller token and validate its issuer, subject,
audience, lifetime, live state, and sender bindings under the service's
token policy. Require
orgsfor organization lookup. Keep the actual actor identity before any effective-organization mapping. - Authenticate the receiving service to backend organization introspection. The submitted access token still identifies the caller. Bind the response to both the expected caller subject and authenticated recipient.
- Verify the caller's current direct Management membership in the selected organization and the organization's owner-controlled permission to disclose those facts to this receiving registration. Both are necessary. An absent or denied organization cannot supply a fallback role.
- Load and validate the service-scoped selection and approved catalogue/default revision. Distinguish a verified explicit empty array from absent, malformed, incomplete, unavailable, unknown-version, or wrong-recipient data. Reject invalid data; do not normalize it to an empty selection or a partial grant.
- For a valid nonempty selection, expand only its approved bundles and exact
IDs, union and deduplicate their exact actions, and use that result instead
of role defaults. Invalid entries never trigger a role fallback.
readonlycontributes its read list to this union and does not remove explicit writes. - Only for a verified direct membership with a verified empty service selection,
use
owner → role.owner,admin → role.admin, andmember → no automatic capability grant. Pin fallback expansions to the approved catalogue/default revision. Membership alone does not prove empty selection data. - Resolve the organization's existing ownership or membership in the target publication/resource. Bound the proposed action set by that resource access, then apply lifecycle, ownership, membership-management, final-owner, and other action-specific checks. For creation, check the service's creation entitlement and operation rules instead of inventing an existing resource.
- Execute only an allowed exact action and attribute it to the real actor, retaining the effective organization principal separately for resource scope.
| Verified direct membership and selection | Candidate capability result |
|---|---|
Owner, verified [] |
Approved role.owner expansion |
Admin, verified [] |
Approved role.admin expansion |
Member, verified [] |
Empty action set |
| Any role, valid nonempty explicit selection | Explicit expansion only; replaces fallback |
Admin, explicit readonly |
Approved read set only; no automatic Admin writes |
Member, explicit readonly plus post.save |
Read set plus post.save, if resource and action checks permit |
| Owner, missing selection field or failed lookup | Deny organization capability resolution; no Owner fallback |
| Any role, unknown action/bundle/revision or receiver mismatch | Deny; no partial grant or role fallback |
| Valid selection, organization lacks resource access | Deny that resource operation |
For a resource R, an action must belong to both the caller's resolved
organization capability set and the organization's permitted actions on R.
This intersection is necessary but not sufficient: independent invariants can
still deny it. A selected Owner bundle does not elevate the organization's
resource role or the caller's Management role.
Authorization boundaries
Caller and receiving service are different identities
The owner disclosure decision targets the receiving service registration, not the caller's machine app. An app's own Management membership identifies its direct authority as caller. Its creator, registration owner, tenant directory, or inclusion in an org's app inventory does not supply another identity's membership. Allowing disclosure to the caller's app does not allow disclosure to every service that app contacts.
Current source implements a recipient-authenticated POST /introspect/orgs
at M7 SSO. Receiver authentication is separate from the submitted caller
token; caller_client_id, when asserted, must agree with the token and is
not a recipient selector. Validate the returned sub and receiver_client_id
and the applicable signed/encrypted response policy. Caller tokens require
orgs; recipient credentials alone cannot confer caller membership. Token
audience and DPoP/certificate requirements still apply.
/userinfo/orgs is caller-oriented discovery. Its organization list must not
be reused as proof that disclosure to a different receiving service is allowed.
Use SSO organization discovery
for caller-oriented lookup. Backend organization operations must preserve the
separate authenticated recipient boundary described here.
One level of delegation
Resolve real caller → directly joined organization → that organization's existing resource access. Stop there. If Organization A is a member of
Organization B, a caller's membership in A does not confer B's identity,
memberships, or resources. Do not recursively traverse organizations, import
another org's roles, or synthesize additional effective principals.
Keep the verified user/app actor and effective organization principal as separate fields. Audit, created-by, updated-by, and revision attribution refer to the real actor; resource ownership and membership checks use the effective principal where the operation allows organization delegation.
Independent protections
Capability selection does not create membership, assign an owner/admin role, change resource ownership, authorize otherwise forbidden member-management operations, or remove final-owner protection. Check the stored live role, target, state, and operation rules even when the selected bundle contains the corresponding action. Any separately authorized self-service route retains its own documented policy; it is not implicitly added to these bundles.
Cookies and workspace selectors are request context. Display names, org lists, app inventories, tag labels, and local roster/import references are descriptive or lookup data. None is an authorization grant. A historical snapshot cannot replace the required current checks. Lookup, storage, validation, and catalogue failures must not become successful empty permission data.
Current support and adoption gaps
API User currently manages organization Management roles, member public data,
organization-specific scope tags, and owner-controlled disclosure. Tag data is
service-defined JSON; a displayed label is not a capability grant. The proposed
registration-bound, versioned selection document above is not an existing
/org/manage/set_tags request or a new supported management endpoint. Follow
the current tag API contract for actual
requests.
Recipient-authenticated POST https://sso.user.m7.org/introspect/orgs is present
in current source, separate from caller-oriented /userinfo/orgs. The receiving
service authenticates as the recipient while the submitted token identifies the
caller and requires orgs. Current-source support does not establish production
deployment or inclusion in an immutable SDK release; verify those boundaries
for the environment being integrated.
The standalone runtime consumer resolves supplied definitions and profiles
in memory. It does not transport or validate organization approvals, select
an immutable definition revision, or intersect grants with resource access.
The separate catalogue builder/editor supports saved immutable revisions in
source. Receiving hosts still need a validated registration/catalogue
selection adapter and approved expansion/default revisions bound to actual
organization resource access. A catalogue, fallback mapping, or successful
local check() does not prove these integration checks are active.
Blog's current discovery reference is
its capability catalogue.
Current source uses the SQL capability manager for discovery and runtime
definitions, with role.*, preset.*, Readonly and family identifiers.
The recovery/import document supplies 62 primitives and 39 presets, including
seven publication-policy packs; it is not loaded automatically on a read.
Current organization tag-name resolution and publication intersection are
implemented in source, as described below. This does not establish deployment,
an imported production snapshot, or registration/version approval support.
Its publication-local capability checks are a distinct layer. In particular,
current post.save can modify published posts; omitting publishing actions from
a Writer bundle does not enforce draft-only editing. Strict draft-only Writer
behavior requires an additional API rule. Keep those Blog-specific profiles and
limitations separate from the generic contract above.
Blog organization tag names (source verified)
Blog consumes current direct organization Management tags returned by
recipient-authenticated SSO. The capability/profile identifier is each tag's
name, including dots and case. SSO does not replace it with the slug.
For example, these tag fields can arrive together (other response fields omitted):
{"name": "test.me", "slug": "testme", "data": null}
The shared tag service trims outer whitespace from a name while preserving its
case and dots; slug normalization separately lowercases and removes the dot.
Slugs retain their generic tag uses. Blog reads name only; neither slug nor
arbitrary tag data is an alternative capability input. If test.me is absent
from the catalogue, this tag grants no actions, regardless of its slug or data.
Names may identify primitives, families, or presets, for example post.save,
post.read, or preset.writer. Blog resolves them case-insensitively through
the current SQL capability catalogue. Unknown names are discarded; recognized
names expand to exact primitives. Any nonempty tag list replaces role defaults,
including a list containing only unknown names, which grants nothing.
Only a verified empty tag list permits the current Owner/Admin SQL mappings
(role.owner / role.admin in the baseline). Member receives no automatic
capabilities. Missing Management groups or tag lists, inconsistent roles, and
missing, blank, or non-string names fail closed; they are not verified emptiness.
These are current direct membership facts, not caller-supplied permission JSON.
Resolved org grants are intersected with the org's actual publication permissions. Membership, ownership, recipient disclosure, lifecycle, membership-management, and final-owner checks remain independent. The real actor remains separate from the effective organization principal. A selected Owner pack cannot grant publication ownership or inherit the actor's personal publication access.
This describes inspected source on 2026-10-04. The adapter uses current SQL definitions; it does not implement the proposed structured receiving-registration and approved-revision selection record. Source verification does not prove production deployment or a live catalogue revision. See Blog authorization for the service-specific permission boundary.
Before adopting this standard, verify empty versus missing authorization data, explicit replacement of defaults, additive Readonly plus writes, receiving registration mismatch, unknown catalogue versions, unchanged prior approvals after new actions are added, recipient disclosure denial, direct-only delegation, resource access, independent owner protections, and actor attribution. These are adoption requirements, not claims that every current service already enforces them.