MiCyte

← The wiki

92 — Ports: type, extension, binding, grant

BuiltRendered from 92-ports-as-four-layers.md in the public repository. Edits belong there.

A port is a seam through which an instance uses an outside service (email, SMS, an AI provider, site hosting, payments, a package source). Whether a port works on an instance is four separate facts, each in its own module:

#LayerQuestionModule
1TypeWhich ports can exist.micyte/ports/port_catalog/contracts.py
2ExtensionWhich installed package can fill a port.micyte/ports/tool_package/contracts.py (PortFill)
3BindingWhich extension this instance chose for a port.micyte/ports/port_binding/contracts.py
4GrantWhether the bound calls may be made.micyte/ports/external_call_policy/

A port works when all four hold.

1. Type

port_types() returns the declared port types, in display order:

PortKind
email_providerMail aliases, forwarding, held delivery.
sms_providerText messages.
ai_providerModel messages.
site_hostingA published website.
commerce_offeringA storefront. Its operations are the payment vendor's (for example paypal:order.capture), so PortType.names_its_operations is false.
package_sourceWhere packages are pulled from (93).
payment_instrumentA stored means of payment.

The list is written in the module, not derived from the micyte/ports/ directory. NOT_BINDABLE lists the port modules that are not bindable, each with a reason (for example datum_write_policy and port_binding, which are the policies a binding is judged by). fnd_app/tests/unit/test_port_catalog.py asserts the two lists together cover the directory exactly. Each type's operations are imported from its port's contract module. port_type() raises UnknownPortType for an id that is not declared.

2. Extension

An extension is a package whose manifest declares PortFill entries: which port it fills and the functions it performs. It installs no tools. eligible_fills(port_id, packages) returns the installed packages that can fill a port, and raises ToolPackageError for an unknown port.

3. Binding

A PortBinding records which extension fills which port for which msn. Its calls are built from the extension's declared functions; the operator does not type them. declared_call_requests(binding) and declared_write_requests(binding) turn a binding into the requests layer 4 and the datum write policy check. The Ports surface writes bindings through fnd_app/instances/_shared/runtime/port_binding_write_runtime.py (bind_extension, unbind_port). Neither form writes a grant.

4. Grant

require_external_call checks an ExternalCallRequest against the instance's ExternalCallGrant entries and raises ExternalCallDenied when none matches. There is no default grant: every actor is denied until an external_call_grants entry is added to the instance's configuration. fnd_app/scripts/check_external_call_grants.py prints the entry a binding needs.

Datum writes differ: their policy derives a default from the store (the operator may write the instance's own sandboxes). Nothing in the store implies a right to call an outside service, so external calls have no default.

The host derives the actor for each call site. A grant written for a different actor than the one a call site resolves to matches nothing and the call is refused.

Binding and granting are separate writes, so a port can be bound and not granted. The Ports surface shows that state as waiting, not as a fault.

The instance diagram

The Utilities surface opens on This instance, drawn by micyte/tools/instance_diagram.py:

PositionShowsSelecting it opens
TopEach app installed on this msnInstall and update, at that app
RightEach sandbox the instance holds, linked to the app that owns itThe compendium, filtered to that sandbox
Left edgeA socket for each port an installed app usesPorts
Beyond the socketsEach installed extension that fills or could fill a socketPorts
BottomThe msn_profile that sign-in resolves through—

Socket states:

StateLabelLayers
open"nothing fills it yet"1–2: no binding
refused"waiting to be allowed"3 without 4
working"working"3 and 4 for every bound function

Only a binding is drawn as a line. With static=True the diagram has no links and nothing focusable; accounts that cannot change their instance see this form (96).