From 37d4ecf394e2dc7f52e05c72d17324fef8eb546b Mon Sep 17 00:00:00 2001 From: Artem Konev Date: Wed, 3 Jun 2026 15:57:50 +0100 Subject: [PATCH 1/7] Doc: Expand plugs and slots exp, how-tos --- docs/.custom_wordlist.txt | 2 + docs/explanation/interfaces/concepts.rst | 75 +---- docs/explanation/interfaces/index.rst | 1 + .../interfaces/plugs-and-slots.rst | 285 ++++++++++++++++++ .../interfaces/tunnel-interface.rst | 4 +- .../design-interface-layout.rst | 208 +++++++++++++ docs/how-to/customize-workshops/index.rst | 1 + .../develop-sdks/declare-plugs-slots.rst | 168 +++++++++++ docs/how-to/develop-sdks/index.rst | 1 + docs/redirects.txt | 2 + .../declare-plugs-slots/sdkcraft.yaml | 26 ++ .../docs-how-to/declare-plugs-slots/task.yaml | 32 ++ .../.workshop/consumer/sdk.yaml | 14 + .../.workshop/graft.yaml | 13 + .../.workshop/plainconsumer/sdk.yaml | 8 + .../.workshop/provider-a/hooks/setup-base | 4 + .../.workshop/provider-a/sdk.yaml | 13 + .../.workshop/provider-b/hooks/setup-base | 4 + .../.workshop/provider-b/sdk.yaml | 13 + .../dev.connected.yaml | 10 + .../design-interface-layout/dev.yaml | 6 + .../design-interface-layout/task.yaml | 32 ++ 22 files changed, 855 insertions(+), 67 deletions(-) create mode 100644 docs/explanation/interfaces/plugs-and-slots.rst create mode 100644 docs/how-to/customize-workshops/design-interface-layout.rst create mode 100644 docs/how-to/develop-sdks/declare-plugs-slots.rst create mode 100644 tests/docs-how-to/declare-plugs-slots/sdkcraft.yaml create mode 100644 tests/docs-how-to/declare-plugs-slots/task.yaml create mode 100644 tests/docs-how-to/design-interface-layout/.workshop/consumer/sdk.yaml create mode 100644 tests/docs-how-to/design-interface-layout/.workshop/graft.yaml create mode 100644 tests/docs-how-to/design-interface-layout/.workshop/plainconsumer/sdk.yaml create mode 100755 tests/docs-how-to/design-interface-layout/.workshop/provider-a/hooks/setup-base create mode 100644 tests/docs-how-to/design-interface-layout/.workshop/provider-a/sdk.yaml create mode 100755 tests/docs-how-to/design-interface-layout/.workshop/provider-b/hooks/setup-base create mode 100644 tests/docs-how-to/design-interface-layout/.workshop/provider-b/sdk.yaml create mode 100644 tests/docs-how-to/design-interface-layout/dev.connected.yaml create mode 100644 tests/docs-how-to/design-interface-layout/dev.yaml create mode 100644 tests/docs-how-to/design-interface-layout/task.yaml diff --git a/docs/.custom_wordlist.txt b/docs/.custom_wordlist.txt index bcb2d6c68..8e2484942 100644 --- a/docs/.custom_wordlist.txt +++ b/docs/.custom_wordlist.txt @@ -5,6 +5,7 @@ api artifacts AUTH autosquash +autowiring backend basename Btrfs @@ -99,6 +100,7 @@ linters localhost l ookups lookups +loopback lspci lxc lxd diff --git a/docs/explanation/interfaces/concepts.rst b/docs/explanation/interfaces/concepts.rst index ed8559e57..446a6460d 100644 --- a/docs/explanation/interfaces/concepts.rst +++ b/docs/explanation/interfaces/concepts.rst @@ -43,44 +43,20 @@ Currently, |ws_markup| and |sdk_markup| support the following: - :ref:`GPU interface ` (auto-connected) - :ref:`Mount interface ` (auto-connected) - :ref:`SSH interface ` (manually connected) +- :ref:`Tunnel interface ` + (auto-connected only from host to workshop, + between a plug and a slot of the same name, + and only when the plug listens on a loopback address + or a Unix domain socket) -.. _exp_plugs_slots: - Plugs and slots --------------- -To make use of these interfaces, -SDKs and :ref:`workshops ` define *slots*. -For example, a :ref:`mount interface ` slot -creates a source directory to be mounted inside the workshop via a plug. - -Further, SDKs and :ref:`workshops ` define *plugs* -to connect to a slot of a certain interface type. -For example, a :ref:`mount interface ` plug -mounts the slot to a target directory inside the workshop. - -You can think of the plug as the recipient of the resources exposed by the slot; -note that a slot can handle connections with multiple plugs. - -Connections can be established: - -- Automatically: - By running :command:`workshop launch`, :command:`workshop refresh`, - or :command:`workshop start`. - -- Manually: - By running :command:`workshop connect` after the workshop has started, - or by listing connections in the - :ref:`workshop definition ` - and running :command:`workshop refresh`. - - -All connections are subject to validation. -Also, automatic connections require plugs and slots to have matching details -and aren't allowed for some interfaces, such as :samp:`ssh-agent`. -Finally, the order of automatic connections is not guaranteed, -so you should not rely on it. +Interfaces become useful when SDKs declare *plugs* to consume them +and *slots* to provide them. +See :ref:`exp_plugs_slots` for the full mechanics, +including the wiring you can express in the workshop definition. .. _exp_interfaces_validation: @@ -147,39 +123,6 @@ when a workshop is launched, refreshed, or restored, see :ref:`exp_workshop_connection_lifecycle`. -.. _exp_plug_bindings: - -Plug bindings -------------- - -SDKs usually access host resources via :ref:`interface plugs `. -When multiple SDKs try to use the same resource in conflicting ways, -the workshop won't launch and shows an error. - -To fix this issue, you can bind one plug to another of the same interface type. -This makes both plugs point to the same resource without conflicts. -Any action performed on one plug (like mounting or remounting) -thus automatically applies to *all* bound plugs. - -When you run :command:`workshop connections`, -a bound plug will have :samp:`bind` listed under :samp:`Notes`, -along with the line number of the target plug: - -.. @artefact workshop connections - -.. code-block:: console - - $ workshop connections digits - - INTERFACE PLUG SLOT NOTES - mount digits/torchaudio:hub digits/system:mount bind.1 - mount digits/torchvision:hub digits/system:mount bind.1 - - -Here, both plugs are listed as :samp:`bind.1`, -pointing to :samp:`torchaudio:hub` in the *first* line. - - .. _exp_interfaces_cli_operations: Related CLI operations diff --git a/docs/explanation/interfaces/index.rst b/docs/explanation/interfaces/index.rst index ef1556dbf..189b9f01c 100644 --- a/docs/explanation/interfaces/index.rst +++ b/docs/explanation/interfaces/index.rst @@ -21,6 +21,7 @@ to connect SDKs to host resources and to each other: :maxdepth: 1 concepts + plugs-and-slots Hardware interfaces diff --git a/docs/explanation/interfaces/plugs-and-slots.rst b/docs/explanation/interfaces/plugs-and-slots.rst new file mode 100644 index 000000000..9161021b6 --- /dev/null +++ b/docs/explanation/interfaces/plugs-and-slots.rst @@ -0,0 +1,285 @@ +.. _exp_plugs_slots: + +.. meta:: + :description: Plugs and slots are the mechanism through which SDKs in a + workshop expose and consume capabilities, forming the + capability topology that connects providers to consumers. + +Plugs and slots +=============== + +.. @artefact plug +.. @artefact slot +.. @artefact interface connection + +A workshop is a graph of capabilities. +Each SDK can act as a provider, a consumer, or both, +and the wiring between them +is what lets a workshop deliver a coherent environment +out of independently published parts. + +In |ws_markup|, that wiring uses two named endpoints: +*plugs* and *slots*. +Both reference an :ref:`interface ` type; +slots provide a capability of that type, +and plugs consume one. +The workshop connects matching pairs at launch +and lets you adjust the topology +in the :ref:`workshop definition ` +when the defaults are not what you want. + + +Slots provide capabilities +-------------------------- + +A slot exposes a capability that other SDKs can consume. +What a slot exposes depends on its interface: + +- A :ref:`mount interface ` slot + exposes a directory. + +- A :ref:`tunnel interface ` slot + exposes a network endpoint. + +- A :ref:`GPU interface ` slot + exposes a GPU device. + +- A :ref:`camera interface `, + :ref:`desktop interface `, + or :ref:`SSH interface ` slot + exposes the corresponding host facility. + + +Some capabilities are inherently host-rooted, +like a camera device or a host-side directory. +Only the :ref:`system SDK ` +can expose host-rooted slots, +which is why every workshop has one installed by default. +Regular SDKs can still publish slots +that expose directories or endpoints from inside the workshop; +a mount slot in a regular SDK, +for example, +points at a path within the SDK or the :samp:`workshop` user's home, +not the host filesystem. + + +Plugs consume capabilities +-------------------------- + +A plug is the consumer end. +It is named within the SDK that declares it, +references an interface type, +and carries any attributes the consumer needs to apply +once a slot is connected. +For instance, with a mount plug, +the central attribute is the target path inside the workshop +where the slot's directory should appear. + +A plug stays declared even if no slot is connected to it, +which means an SDK can ship optional plugs +that only activate when a corresponding provider is also installed. + + +.. _exp_interface_autowiring: + +Autowiring +---------- + +When a workshop launches or refreshes, +|ws_markup| tries to connect each plug +to a slot of the same interface type. +The attempt succeeds when the interface policy +permits the plug to connect to a candidate slot. +The policy is what gates auto-connection, +not the number of candidate slots in the workshop. + +Auto-connection behavior varies by interface: + +- The mount interface auto-connects to slots provided by the system SDK. + Regular-SDK mount slots are not auto-connected by default; + wire them by listing the pair in the workshop's + :ref:`top-level connections `. + +- The GPU interface auto-connects. + +- The tunnel interface auto-connects only from host to workshop, + between a plug and a slot of the same name, + and only when the plug's endpoint + is a loopback address or a Unix domain socket. + See :ref:`exp_tunnel_connection` for the full policy. + +- The camera, desktop, and SSH interfaces do not auto-connect. + They have to be wired explicitly. + + +When more than one slot is policy-eligible for the same plug, +|ws_markup| attempts a connection for each of them +in an order it does not guarantee. +The right way to express a specific topology +is to write it down in the workshop definition's :samp:`connections` list +instead of leaving it to auto-connection. + + +Wiring mechanisms +----------------- + +The :ref:`workshop definition ` +gives you two distinct YAML mechanisms for shaping the topology: + +- An inline :ref:`plug binding `, + written as :samp:`bind:` inside a plug entry, + delegates one plug to another plug. + Both plugs then point at the same target, + which is how same-interface conflicts are resolved. + +- A top-level :samp:`connections:` list, + written at workshop scope, + pairs a specific plug with a specific slot. + Use it to override the default auto-connect target, + for example to wire a mount plug to a regular SDK's slot + rather than the system SDK's. + The pair still has to satisfy the interface's auto-connection policy, + so interfaces that block auto-connection outright + (such as :samp:`ssh-agent`) + cannot be wired this way + and must be connected with :command:`workshop connect`. + + +The two mechanisms are mutually exclusive for a given plug: +if it's bound to another plug, +it cannot also appear in a top-level :samp:`connections:` entry. + +|ws_markup| also exposes the runtime-only +:command:`workshop connect` and :command:`workshop disconnect` commands, +which change the wiring of a running workshop. + + +.. _exp_plug_bindings: + +Inline plug bindings +~~~~~~~~~~~~~~~~~~~~ + +A plug binding lets one plug stand in for another: + +.. code-block:: yaml + :caption: workshop.yaml + + sdks: + - name: consumer-sdk + plugs: + tools: + bind: provider-sdk:tools + + +Both plugs then point to the same resource, +and any action performed on one, +such as connecting or remounting, +applies to all bound plugs. + +Bindings are the right tool when two plugs of the same interface +would otherwise conflict over the same target, +typically because two SDKs each declare a plug +with overlapping attributes. + +A bound plug only carries the binding; +it cannot also define plug attributes of its own. +The attributes come from the plug it is bound to. + +When you run :command:`workshop connections`, +a bound plug is shown with a :samp:`bind.{N}` note in the :samp:`NOTES` column, +where :samp:`{N}` is the row number, in the same output, +of the plug it is bound to. + + +Top-level connections +~~~~~~~~~~~~~~~~~~~~~ + +The top-level :samp:`connections:` list +pairs a plug with a slot directly: + +.. code-block:: yaml + :caption: workshop.yaml + + connections: + - plug: consumer-sdk:tools + slot: provider-sdk:bin + + +Each entry uses the :samp:`:` form +on both sides. +Once the workshop is launched or refreshed, +that pairing is the one |ws_markup| applies, +regardless of what other slots could have matched. + +This is the mechanism to use +when the workshop has more than one provider for an interface +and you want to be specific about which one a consumer reads from. + + +Example: two SDKs sharing a mount +--------------------------------- + +Consider a workshop that installs two SDKs: + +- :samp:`provider-sdk` ships a mount slot named :samp:`bin` + that exposes a directory inside its own filesystem. + +- :samp:`consumer-sdk` declares a mount plug named :samp:`tools` + whose target is a path under its workshop user's home. + + +Auto-connection alone does not wire :samp:`consumer-sdk:tools` +to :samp:`provider-sdk:bin`: +the mount interface auto-connects to system SDK slots by default, +so :samp:`consumer-sdk:tools` lands on :samp:`system:mount` +and :samp:`provider-sdk:bin` stays listed but unconnected. +You name the pairing explicitly +with a top-level :samp:`connections:` entry: + +.. code-block:: yaml + :caption: workshop.yaml + + sdks: + - name: provider-sdk + - name: consumer-sdk + + connections: + - plug: consumer-sdk:tools + slot: provider-sdk:bin + + +After :command:`workshop launch` or :command:`workshop refresh`, +:command:`workshop connections` shows the chosen pairing +and you can verify that :samp:`consumer-sdk` reads from :samp:`provider-sdk` +rather than the system SDK's default mount slot. + + +See also +-------- + +Explanation: + +- :ref:`exp_camera_interface` +- :ref:`exp_desktop_interface` +- :ref:`exp_gpu_interface` +- :ref:`exp_interface_concepts` +- :ref:`exp_mount_interface` +- :ref:`exp_sdks` +- :ref:`exp_ssh_interface` +- :ref:`exp_tunnel_interface` +- :ref:`exp_workshop` + + +How-to guides: + +- :ref:`how_resolve_plug_conflicts` + + +Reference: + +- :ref:`ref_sdk_internals` +- :ref:`ref_sdk_plugs_slots` +- :ref:`ref_workshop_connect` +- :ref:`ref_workshop_connections` +- :ref:`ref_workshop_definition` +- :ref:`ref_workshop_disconnect` diff --git a/docs/explanation/interfaces/tunnel-interface.rst b/docs/explanation/interfaces/tunnel-interface.rst index a30ce54eb..f083bbd95 100644 --- a/docs/explanation/interfaces/tunnel-interface.rst +++ b/docs/explanation/interfaces/tunnel-interface.rst @@ -75,7 +75,9 @@ provided that: - The slot is declared in a regular SDK -- The plug listens on :samp:`localhost` or a Unix domain socket +- The plug listens on a loopback address + (for example, :samp:`localhost`, :samp:`127.0.0.1`, or :samp:`::1`) + or a Unix domain socket - The plug can be matched to the slot by its name, or via a :samp:`connections` entry in the :ref:`definition `, diff --git a/docs/how-to/customize-workshops/design-interface-layout.rst b/docs/how-to/customize-workshops/design-interface-layout.rst new file mode 100644 index 000000000..8ec5da0ab --- /dev/null +++ b/docs/how-to/customize-workshops/design-interface-layout.rst @@ -0,0 +1,208 @@ +.. _how_design_interface_layout: +.. _how_connect_plug_slot_pairs_explicitly: + +.. meta:: + :description: Shape a workshop's interface topology with explicit connection + entries in the workshop definition: survey what auto-connects, + wire a consumer to a specific provider, graft a missing plug, + or rewire a running workshop with workshop connect. + +How to design the interface layout of a workshop +================================================ + +.. @tests in tests/docs-how-to/design-interface-layout/task.yaml + +.. @artefact interface connection +.. @artefact workshop definition + +You can shape the topology of a workshop +by writing explicit plug-to-slot connections in the workshop definition. +Use it when several SDKs in the workshop expose or consume the same interface +and you want to be specific about which provider satisfies which consumer, +when auto-connection lands a plug on a slot you did not intend, +or when a consumer SDK ships no plug for a capability you want it to use. +You need a workshop definition under :file:`.workshop/` +that lists at least two SDKs, one with a slot and one with a matching plug. + +This is a different problem from same-interface plug conflicts, +where two plugs would compete over the same target. +For that case, see :ref:`how_resolve_plug_conflicts`, +which uses an inline :samp:`bind:` attribute to delegate one plug to another. + + +Survey the plugs and slots in scope +----------------------------------- + +Launch the workshop once to see what |ws_markup| connects on its own. +The examples here use three SDKs: +:samp:`provider-a` and :samp:`provider-b` +each expose a mount slot named :samp:`data`, +and :samp:`consumer` declares a mount plug named :samp:`feed`. + +.. code-block:: console + + $ workshop launch dev + $ workshop connections dev + + INTERFACE PLUG SLOT NOTES + mount - dev/provider-a:data - + mount - dev/provider-b:data - + mount dev/consumer:feed dev/system:mount - + + +The output lists every plug and slot in the workshop, +the slot each plug is connected to (if any), +and any notes on the connection. +:samp:`consumer:feed` landed on the system SDK's default mount slot, +not on either regular provider, +because mount plugs auto-connect to system SDK slots by default. +The :samp:`provider-a:data` and :samp:`provider-b:data` slots +stay listed but unconnected: +regular SDK mount slots are not reached by auto-connection by default, +even when a matching plug is in scope. +The result is a working workshop, but probably not the one you intended. +A regular SDK slot becomes usable +only when a top-level :samp:`connections:` entry names the pair. + + +Wire a consumer to a specific provider +-------------------------------------- + +Add a top-level :samp:`connections` list to the workshop definition, +pairing the plug with the slot you want it to use: + +.. code-block:: yaml + :caption: .workshop/dev.yaml + :emphasize-lines: 8-10 + + name: dev + base: ubuntu@22.04 + sdks: + - name: provider-a + - name: provider-b + - name: consumer + + connections: + - plug: consumer:feed + slot: provider-b:data + + +Each entry uses the :samp:`:` form on both sides. +After :command:`workshop refresh`, +|ws_markup| applies the listed pairing +regardless of what other slots could have matched it: + +.. code-block:: console + + $ workshop refresh dev + $ workshop connections dev + + INTERFACE PLUG SLOT NOTES + mount - dev/provider-a:data - + mount dev/consumer:feed dev/provider-b:data - + + +This decision is persistent; +re-launching the workshop or recreating it +applies the same pairing every time. +To inspect the resolved mount details, run :command:`workshop info dev`, +which lists each connected plug along with the source path the slot exposed +and the target path inside the workshop. + + +Graft a missing plug onto a consumer SDK +---------------------------------------- + +Suppose a consumer SDK ships no plug for the capability you want it to use. +The workshop definition can add one: +declare the plug under the SDK's entry in :samp:`sdks`, +then connect it as before. +This example pairs :samp:`provider-sdk`, +which exposes a mount slot named :samp:`bin`, +with :samp:`consumer-sdk`, which ships no matching plug: + +.. code-block:: yaml + :caption: .workshop/dev.yaml + :emphasize-lines: 5-9, 11-13 + + name: dev + base: ubuntu@22.04 + sdks: + - name: provider-sdk + - name: consumer-sdk + plugs: + tools: + interface: mount + workshop-target: /home/workshop/.local/share/tools + + connections: + - plug: consumer-sdk:tools + slot: provider-sdk:bin + + +This grafts a new plug onto :samp:`consumer-sdk` +without modifying the SDK itself. +The publisher does not need to ship every plug +their users might want; +the workshop author can add the missing piece locally. + + +Rewire a running workshop with workshop connect +----------------------------------------------- + +For a one-off change, :command:`workshop connect` rewires a running workshop +without editing the workshop definition. +Pass the plug and the target slot explicitly: + +.. code-block:: console + + $ workshop disconnect dev/consumer:feed + $ workshop connect dev/consumer:feed dev/provider-a:data + $ workshop connections dev + + INTERFACE PLUG SLOT NOTES + mount - dev/provider-b:data - + mount dev/consumer:feed dev/provider-a:data manual + + +The :samp:`manual` note in the :samp:`NOTES` column flags that the connection +came from a CLI invocation rather than the workshop definition +or the auto-connection mechanism. + +The workshop definition on disk is unchanged, +but the runtime mark carries over across :command:`workshop refresh`: +:samp:`consumer:feed` stays connected to :samp:`provider-a:data` +until you rewire it again, +or until you run :command:`workshop disconnect --forget`, +after which the next :command:`workshop refresh` re-applies auto-connection. +Running :command:`workshop remove` discards these runtime overrides, +so a subsequent :command:`workshop launch` starts from the definition. + +For a decision that travels with the project +and survives :command:`workshop remove`, +edit the workshop definition instead. + + +See also +-------- + +Explanation: + +- :ref:`exp_interface_concepts` +- :ref:`exp_plug_bindings` +- :ref:`exp_plugs_slots` + + +How-to guides: + +- :ref:`how_resolve_plug_conflicts` + + +Reference: + +- :ref:`ref_workshop_connect` +- :ref:`ref_workshop_connections` +- :ref:`ref_workshop_definition` +- :ref:`ref_workshop_disconnect` +- :ref:`ref_workshop_info` +- :ref:`ref_workshop_refresh` diff --git a/docs/how-to/customize-workshops/index.rst b/docs/how-to/customize-workshops/index.rst index 6ef4f43d0..75c0468da 100644 --- a/docs/how-to/customize-workshops/index.rst +++ b/docs/how-to/customize-workshops/index.rst @@ -15,6 +15,7 @@ workshops in parallel: Add actions to workshops Add mounts + Design the interface layout Forward ports Move projects around Use multiple workshops diff --git a/docs/how-to/develop-sdks/declare-plugs-slots.rst b/docs/how-to/develop-sdks/declare-plugs-slots.rst new file mode 100644 index 000000000..49568b945 --- /dev/null +++ b/docs/how-to/develop-sdks/declare-plugs-slots.rst @@ -0,0 +1,168 @@ +.. _how_declare_plugs_slots: + +.. meta:: + :description: Step-by-step guide on declaring mount and tunnel plugs and + slots in an SDK definition so that an SDK can consume and + expose capabilities to other SDKs in a workshop. + +How to declare plugs and slots +============================== + +.. @tests in tests/docs-how-to/declare-plugs-slots/task.yaml + +.. @artefact interface plug +.. @artefact interface slot +.. @artefact sdkcraft (CLI) + +This guide shows how to declare plugs and slots +in an SDK definition, +so that an SDK can consume capabilities from other SDKs +or expose its own to them. +The examples cover the :samp:`mount` and :samp:`tunnel` interfaces; +plugs and slots for the other supported interfaces +follow the same shape. + + +Prerequisites +------------- + +You need a working |sdk_markup| installation +and an :file:`sdkcraft.yaml` you can edit. +If you don't have one yet, +:ref:`tut_craft_sdks` walks through scaffolding an SDK with +:command:`sdkcraft init`. +The declarations below go under top-level +:samp:`plugs:` and :samp:`slots:` keys in :file:`sdkcraft.yaml`. + + +Declare a mount plug +-------------------- + +A mount plug consumes a directory +that becomes available at a path inside the workshop. +The required attribute is :samp:`workshop-target`, +which must be an absolute path +and may use :envvar:`$SDK` to refer to the SDK installation directory: + +.. code-block:: yaml + :caption: sdkcraft.yaml + :emphasize-lines: 3-5 + + # ... + + plugs: + cache: + interface: mount + workshop-target: /home/workshop/.cache/cachekit + + +When a workshop installs the SDK, +|ws_markup| connects this plug +to a matching slot, +either auto-connecting it to the workshop's :ref:`system SDK ` +or to another SDK's slot +when the workshop definition wires that pairing explicitly. +The :samp:`mode`, :samp:`uid`, and :samp:`gid` attributes are optional. + + +Declare a mount slot +-------------------- + +A mount slot exposes a directory the SDK provides +so that other SDKs can plug into it. +The required attribute is :samp:`workshop-source`, +which must be an absolute path inside the workshop +and may use :envvar:`$SDK`: + +.. code-block:: yaml + :caption: sdkcraft.yaml + :emphasize-lines: 3-5 + + # ... + + slots: + shared: + interface: mount + workshop-source: /home/workshop/cachekit-share + + +This is for cross-SDK sharing within the workshop. +Exposing a directory from the host +is the responsibility of the +:ref:`system SDK `; +a regular SDK cannot declare a host-rooted mount slot. + + +Declare a tunnel slot +--------------------- + +A tunnel slot exposes a network endpoint +inside the workshop: + +.. code-block:: yaml + :caption: sdkcraft.yaml + :emphasize-lines: 3-5 + + # ... + + slots: + api: + interface: tunnel + endpoint: 127.0.0.1:8080 + + +Auto-connection for the tunnel interface is gated by the *plug* endpoint: +|ws_markup| auto-connects a tunnel plug only when its endpoint +is a loopback address or a Unix domain socket. +Plugs that listen on other addresses +require an explicit +:ref:`connection in the workshop definition `. +The endpoint syntax accepts shorthand forms, +including bare port numbers and unix socket paths. +See :ref:`ref_tunnel_interface` for the full grammar. + + +Verify the declarations +----------------------- + +After packing the SDK with :command:`sdkcraft pack`, +confirm that the declared plugs and slots +ended up in the SDK metadata by extracting :file:`meta/sdk.yaml` +from the artifact: + +.. code-block:: console + + $ tar xOf __.sdk meta/sdk.yaml + + +The output should include the :samp:`plugs:` and :samp:`slots:` blocks +exactly as they were declared. + + +See also +-------- + +Explanation: + +- :ref:`exp_mount_interface` +- :ref:`exp_plugs_slots` +- :ref:`exp_sdks` +- :ref:`exp_tunnel_interface` + + +How-to guides: + +- :ref:`how_design_interface_layout` +- :ref:`how_resolve_plug_conflicts` + + +Reference: + +- :ref:`ref_sdk_definition` +- :ref:`ref_sdk_plugs_slots` +- :ref:`ref_tunnel_interface` + + +Tutorial: + +- :ref:`tut_craft_sdks` diff --git a/docs/how-to/develop-sdks/index.rst b/docs/how-to/develop-sdks/index.rst index f230dcd4b..b3d5d7c84 100644 --- a/docs/how-to/develop-sdks/index.rst +++ b/docs/how-to/develop-sdks/index.rst @@ -17,4 +17,5 @@ and publishing the result to the SDK Store. :maxdepth: 1 Build an SDK + Declare plugs and slots Publish an SDK diff --git a/docs/redirects.txt b/docs/redirects.txt index cf8ae225a..6a16cb3ba 100644 --- a/docs/redirects.txt +++ b/docs/redirects.txt @@ -6,3 +6,5 @@ # The old path must be a file that doesn"t exist in the source. The current path must be # a file that does exist in the source. +"how-to/customize-workshops/connect-plug-slot-pairs-explicitly.rst" "how-to/customize-workshops/design-interface-layout.rst" + diff --git a/tests/docs-how-to/declare-plugs-slots/sdkcraft.yaml b/tests/docs-how-to/declare-plugs-slots/sdkcraft.yaml new file mode 100644 index 000000000..983b9fd09 --- /dev/null +++ b/tests/docs-how-to/declare-plugs-slots/sdkcraft.yaml @@ -0,0 +1,26 @@ +name: cachekit +version: '0.1' +summary: Synthesized SDK exercising plug and slot declaration +description: | + cachekit is a synthesized example for the declare-plugs-slots how-to. +license: GPL-3.0 +platforms: + ubuntu@22.04:amd64: + ubuntu@24.04:amd64: + +parts: + my-part: + plugin: nil + +plugs: + cache: + interface: mount + workshop-target: /home/workshop/.cache/cachekit + +slots: + shared: + interface: mount + workshop-source: /home/workshop/cachekit-share + api: + interface: tunnel + endpoint: 127.0.0.1:8080 diff --git a/tests/docs-how-to/declare-plugs-slots/task.yaml b/tests/docs-how-to/declare-plugs-slots/task.yaml new file mode 100644 index 000000000..bedda1184 --- /dev/null +++ b/tests/docs-how-to/declare-plugs-slots/task.yaml @@ -0,0 +1,32 @@ +summary: Test 'How to declare plugs and slots' to ensure it's operational +prepare: | + . "$TESTSLIB"/utils.sh + install_sdkcraft +restore: | + rm -rf cachekit /tmp/packed-meta.yaml +execute: | + . "$TESTSLIB"/utils.sh + + # Scaffold an SDK named cachekit, then replace the generated sdkcraft.yaml + # with the one the how-to teaches. + mkdir cachekit/ && cd cachekit/ + chown ubuntu:ubuntu . + run_sdkcraft init + cp ../sdkcraft.yaml ./sdkcraft.yaml + chown ubuntu:ubuntu ./sdkcraft.yaml + + run_sdkcraft pack + + # The packed artifact is named __.sdk; the build host is amd64. + ARTIFACT=$(ls cachekit_amd64_*.sdk | head -1) + test -n "$ARTIFACT" + + # Confirm the embedded meta/sdk.yaml carries the declared plugs and slots + # exactly as written in sdkcraft.yaml. + tar xOf "$ARTIFACT" meta/sdk.yaml | tee /tmp/packed-meta.yaml + + MATCH "interface: mount" /home/workshop/provider-a-data/marker +chown 1000:1000 /home/workshop/provider-a-data/marker diff --git a/tests/docs-how-to/design-interface-layout/.workshop/provider-a/sdk.yaml b/tests/docs-how-to/design-interface-layout/.workshop/provider-a/sdk.yaml new file mode 100644 index 000000000..df7e24b98 --- /dev/null +++ b/tests/docs-how-to/design-interface-layout/.workshop/provider-a/sdk.yaml @@ -0,0 +1,13 @@ +name: provider-a +base: ubuntu@22.04 +summary: First synthesized provider for the ambiguity test +description: | + Exposes a mount slot named "data". Combined with provider-b in the same + workshop, this creates ambiguous autowiring for the consumer's "feed" plug. + +sdkcraft-started-at: "2026-05-14T00:00:00.000000+00:00" + +slots: + data: + interface: mount + workshop-source: /home/workshop/provider-a-data diff --git a/tests/docs-how-to/design-interface-layout/.workshop/provider-b/hooks/setup-base b/tests/docs-how-to/design-interface-layout/.workshop/provider-b/hooks/setup-base new file mode 100755 index 000000000..8726619a4 --- /dev/null +++ b/tests/docs-how-to/design-interface-layout/.workshop/provider-b/hooks/setup-base @@ -0,0 +1,4 @@ +#!/bin/bash +install -d -m 0755 -o 1000 -g 1000 /home/workshop/provider-b-data +echo "from-b" >/home/workshop/provider-b-data/marker +chown 1000:1000 /home/workshop/provider-b-data/marker diff --git a/tests/docs-how-to/design-interface-layout/.workshop/provider-b/sdk.yaml b/tests/docs-how-to/design-interface-layout/.workshop/provider-b/sdk.yaml new file mode 100644 index 000000000..34ce49271 --- /dev/null +++ b/tests/docs-how-to/design-interface-layout/.workshop/provider-b/sdk.yaml @@ -0,0 +1,13 @@ +name: provider-b +base: ubuntu@22.04 +summary: Second synthesized provider for the ambiguity test +description: | + Exposes a mount slot named "data" with the same interface as provider-a, + creating the ambiguous autowiring scenario the how-to teaches you to resolve. + +sdkcraft-started-at: "2026-05-14T00:00:00.000000+00:00" + +slots: + data: + interface: mount + workshop-source: /home/workshop/provider-b-data diff --git a/tests/docs-how-to/design-interface-layout/dev.connected.yaml b/tests/docs-how-to/design-interface-layout/dev.connected.yaml new file mode 100644 index 000000000..29f533db8 --- /dev/null +++ b/tests/docs-how-to/design-interface-layout/dev.connected.yaml @@ -0,0 +1,10 @@ +name: dev +base: ubuntu@22.04 +sdks: + - name: project-provider-a + - name: project-provider-b + - name: project-consumer + +connections: + - plug: consumer:feed + slot: provider-b:data diff --git a/tests/docs-how-to/design-interface-layout/dev.yaml b/tests/docs-how-to/design-interface-layout/dev.yaml new file mode 100644 index 000000000..f2ee27c70 --- /dev/null +++ b/tests/docs-how-to/design-interface-layout/dev.yaml @@ -0,0 +1,6 @@ +name: dev +base: ubuntu@22.04 +sdks: + - name: project-provider-a + - name: project-provider-b + - name: project-consumer diff --git a/tests/docs-how-to/design-interface-layout/task.yaml b/tests/docs-how-to/design-interface-layout/task.yaml new file mode 100644 index 000000000..38e0aaece --- /dev/null +++ b/tests/docs-how-to/design-interface-layout/task.yaml @@ -0,0 +1,32 @@ +summary: Test 'How to design the interface layout of a workshop' to ensure it's operational +restore: | + . "$TESTSLIB"/utils.sh + workshop_exec remove dev graft || true +execute: | + . "$TESTSLIB"/utils.sh + chown -R ubuntu:ubuntu . + + echo "Stage 1: launch without connections; mount plugs do not auto-connect to regular SDK slots, so consumer:feed falls back to system:mount." + cp dev.yaml .workshop/dev.yaml + workshop_exec launch dev + workshop_exec connections dev | MATCH '^mount.+dev/consumer:feed.+dev/system:mount' + + echo "Stage 2: add a connections entry pointing the plug at provider-b; refresh; verify the chosen slot connects and the marker is from-b." + cp dev.connected.yaml .workshop/dev.yaml + workshop_exec refresh dev + workshop_exec connections dev | MATCH '^mount.+dev/consumer:feed.+dev/provider-b:data' + workshop_exec exec dev -- cat /home/workshop/feed/marker | MATCH '^from-b$' + + echo "Stage 3: disconnect then connect to provider-a at runtime; the runtime change is visible (manual) but the on-disk definition is unchanged." + EXPECTED_YAML_HASH=$(sha256sum .workshop/dev.yaml | cut -d' ' -f1) + workshop_exec disconnect dev/consumer:feed + workshop_exec connect dev/consumer:feed dev/provider-a:data + workshop_exec connections dev | MATCH '^mount.+dev/consumer:feed.+dev/provider-a:data.+manual' + workshop_exec exec dev -- cat /home/workshop/feed/marker | MATCH '^from-a$' + ACTUAL_YAML_HASH=$(sha256sum .workshop/dev.yaml | cut -d' ' -f1) + test "$EXPECTED_YAML_HASH" = "$ACTUAL_YAML_HASH" + + echo "Stage 4: graft a plug onto an SDK that ships none; launch the graft workshop; the grafted plug connects to provider-a and reads from-a." + workshop_exec launch graft + workshop_exec connections graft | MATCH '^mount.+graft/plainconsumer:feed.+graft/provider-a:data' + workshop_exec exec graft -- cat /home/workshop/feed/marker | MATCH '^from-a$' From c6348a99d5cd39d6dfae9de5d0c6c93ed4125bd0 Mon Sep 17 00:00:00 2001 From: Artem Konev Date: Tue, 9 Jun 2026 23:06:19 +0100 Subject: [PATCH 2/7] Doc: Fix runtime rewiring claims in layout how-to --- .../design-interface-layout.rst | 36 ++++++++++++------- .../dev.reordered.yaml | 10 ++++++ .../design-interface-layout/task.yaml | 7 +++- 3 files changed, 39 insertions(+), 14 deletions(-) create mode 100644 tests/docs-how-to/design-interface-layout/dev.reordered.yaml diff --git a/docs/how-to/customize-workshops/design-interface-layout.rst b/docs/how-to/customize-workshops/design-interface-layout.rst index 8ec5da0ab..72c800bd8 100644 --- a/docs/how-to/customize-workshops/design-interface-layout.rst +++ b/docs/how-to/customize-workshops/design-interface-layout.rst @@ -17,7 +17,8 @@ How to design the interface layout of a workshop You can shape the topology of a workshop by writing explicit plug-to-slot connections in the workshop definition. -Use it when several SDKs in the workshop expose or consume the same interface +Use explicit connections +when several SDKs in the workshop expose or consume the same interface and you want to be specific about which provider satisfies which consumer, when auto-connection lands a plug on a slot you did not intend, or when a consumer SDK ships no plug for a capability you want it to use. @@ -61,14 +62,15 @@ stay listed but unconnected: regular SDK mount slots are not reached by auto-connection by default, even when a matching plug is in scope. The result is a working workshop, but probably not the one you intended. -A regular SDK slot becomes usable -only when a top-level :samp:`connections:` entry names the pair. +A regular SDK slot is wired +either by a top-level :samp:`connections:` entry in the definition +or manually with :command:`workshop connect`. Wire a consumer to a specific provider -------------------------------------- -Add a top-level :samp:`connections` list to the workshop definition, +Add a top-level :samp:`connections:` list to the workshop definition, pairing the plug with the slot you want it to use: .. code-block:: yaml @@ -106,7 +108,8 @@ This decision is persistent; re-launching the workshop or recreating it applies the same pairing every time. To inspect the resolved mount details, run :command:`workshop info dev`, -which lists each connected plug along with the source path the slot exposed +which lists each connected mount plug +along with the source path the slot exposed and the target path inside the workshop. @@ -170,16 +173,22 @@ came from a CLI invocation rather than the workshop definition or the auto-connection mechanism. The workshop definition on disk is unchanged, -but the runtime mark carries over across :command:`workshop refresh`: -:samp:`consumer:feed` stays connected to :samp:`provider-a:data` -until you rewire it again, -or until you run :command:`workshop disconnect --forget`, -after which the next :command:`workshop refresh` re-applies auto-connection. -Running :command:`workshop remove` discards these runtime overrides, +and the runtime marks are not reconciled with it: +the next :command:`workshop refresh` that applies updates +drops connections made with :command:`workshop connect`, +while plugs disconnected with :command:`workshop disconnect` +stay disconnected, +unless the disconnection was made with :option:`!--forget`. +In the example above, +a refresh thus leaves :samp:`consumer:feed` unconnected: +the manual connection to :samp:`provider-a:data` is dropped, +and the definition's pairing with :samp:`provider-b:data` is not restored +because the plug was manually disconnected from it. +Running :command:`workshop remove` discards all runtime marks, so a subsequent :command:`workshop launch` starts from the definition. -For a decision that travels with the project -and survives :command:`workshop remove`, +For a topology that survives refreshes +and travels with the project, edit the workshop definition instead. @@ -195,6 +204,7 @@ Explanation: How-to guides: +- :ref:`how_declare_plugs_slots` - :ref:`how_resolve_plug_conflicts` diff --git a/tests/docs-how-to/design-interface-layout/dev.reordered.yaml b/tests/docs-how-to/design-interface-layout/dev.reordered.yaml new file mode 100644 index 000000000..cf484ab95 --- /dev/null +++ b/tests/docs-how-to/design-interface-layout/dev.reordered.yaml @@ -0,0 +1,10 @@ +name: dev +base: ubuntu@22.04 +sdks: + - name: project-provider-b + - name: project-provider-a + - name: project-consumer + +connections: + - plug: consumer:feed + slot: provider-b:data diff --git a/tests/docs-how-to/design-interface-layout/task.yaml b/tests/docs-how-to/design-interface-layout/task.yaml index 38e0aaece..d4fe3c26d 100644 --- a/tests/docs-how-to/design-interface-layout/task.yaml +++ b/tests/docs-how-to/design-interface-layout/task.yaml @@ -26,7 +26,12 @@ execute: | ACTUAL_YAML_HASH=$(sha256sum .workshop/dev.yaml | cut -d' ' -f1) test "$EXPECTED_YAML_HASH" = "$ACTUAL_YAML_HASH" - echo "Stage 4: graft a plug onto an SDK that ships none; launch the graft workshop; the grafted plug connects to provider-a and reads from-a." + echo "Stage 4: a refresh that applies updates drops the manual connection and keeps the manually disconnected pairing disconnected, so the plug ends up unconnected." + cp dev.reordered.yaml .workshop/dev.yaml + workshop_exec refresh dev + workshop_exec connections dev | MATCH '^mount +dev/consumer:feed +- +-' + + echo "Stage 5: graft a plug onto an SDK that ships none; launch the graft workshop; the grafted plug connects to provider-a and reads from-a." workshop_exec launch graft workshop_exec connections graft | MATCH '^mount.+graft/plainconsumer:feed.+graft/provider-a:data' workshop_exec exec graft -- cat /home/workshop/feed/marker | MATCH '^from-a$' From b20c3460bcc2bf3c432571d05357e8295cea9726 Mon Sep 17 00:00:00 2001 From: Artem Konev Date: Tue, 9 Jun 2026 23:07:01 +0100 Subject: [PATCH 3/7] Doc: Use in-project SDK names in layout how-to --- .../design-interface-layout.rst | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/how-to/customize-workshops/design-interface-layout.rst b/docs/how-to/customize-workshops/design-interface-layout.rst index 72c800bd8..e8ef0f3bc 100644 --- a/docs/how-to/customize-workshops/design-interface-layout.rst +++ b/docs/how-to/customize-workshops/design-interface-layout.rst @@ -35,7 +35,8 @@ Survey the plugs and slots in scope ----------------------------------- Launch the workshop once to see what |ws_markup| connects on its own. -The examples here use three SDKs: +The examples here use three in-project SDKs +that live under :file:`.workshop/` next to the definition: :samp:`provider-a` and :samp:`provider-b` each expose a mount slot named :samp:`data`, and :samp:`consumer` declares a mount plug named :samp:`feed`. @@ -80,9 +81,9 @@ pairing the plug with the slot you want it to use: name: dev base: ubuntu@22.04 sdks: - - name: provider-a - - name: provider-b - - name: consumer + - name: project-provider-a + - name: project-provider-b + - name: project-consumer connections: - plug: consumer:feed @@ -90,6 +91,9 @@ pairing the plug with the slot you want it to use: Each entry uses the :samp:`:` form on both sides. +In-project SDKs take the :samp:`project-` prefix +in the :samp:`sdks:` list only; +:samp:`connections:` entries and CLI output use the bare name. After :command:`workshop refresh`, |ws_markup| applies the listed pairing regardless of what other slots could have matched it: @@ -131,8 +135,8 @@ with :samp:`consumer-sdk`, which ships no matching plug: name: dev base: ubuntu@22.04 sdks: - - name: provider-sdk - - name: consumer-sdk + - name: project-provider-sdk + - name: project-consumer-sdk plugs: tools: interface: mount @@ -210,6 +214,7 @@ How-to guides: Reference: +- :ref:`ref_in_project_sdk` - :ref:`ref_workshop_connect` - :ref:`ref_workshop_connections` - :ref:`ref_workshop_definition` From 779465adcb630333554c17cd2f65439f21881206 Mon Sep 17 00:00:00 2001 From: Artem Konev Date: Tue, 9 Jun 2026 23:07:25 +0100 Subject: [PATCH 4/7] Doc: Polish plugs and slots explanation --- docs/.custom_wordlist.txt | 1 - docs/coverage.md | 46 +++++++++---------- docs/explanation/interfaces/concepts.rst | 6 +-- .../interfaces/plugs-and-slots.rst | 31 ++++++++----- 4 files changed, 43 insertions(+), 41 deletions(-) diff --git a/docs/.custom_wordlist.txt b/docs/.custom_wordlist.txt index 8e2484942..8e67b7b67 100644 --- a/docs/.custom_wordlist.txt +++ b/docs/.custom_wordlist.txt @@ -5,7 +5,6 @@ api artifacts AUTH autosquash -autowiring backend basename Btrfs diff --git a/docs/coverage.md b/docs/coverage.md index 9b3e731ea..d2542a5b5 100644 --- a/docs/coverage.md +++ b/docs/coverage.md @@ -85,7 +85,7 @@ concept [part-2-work-with-interfaces.rst]
[part-4-craft-sdks.rst] - [concepts.rst]
[concepts.rst] + [concepts.rst]
[concepts.rst] [workshops.rst] SK000
SK015
SK020
SK026 @@ -93,8 +93,8 @@ interface concept - - [concepts.rst]
[concepts.rst]
[concepts.rst] + [design-interface-layout.rst] + [concepts.rst]
[concepts.rst]
[plugs-and-slots.rst]
[concepts.rst] SK020 @@ -102,8 +102,8 @@ interface concept [part-4-craft-sdks.rst] - [run-jetbrains-gateway.rst]
[resolve-plug-conflicts.rst] - [concepts.rst] + [declare-plugs-slots.rst]
[run-jetbrains-gateway.rst]
[resolve-plug-conflicts.rst] + [plugs-and-slots.rst]
[concepts.rst] [sdks.rst] SK020 @@ -111,8 +111,8 @@ interface concept - [run-jetbrains-gateway.rst] - [concepts.rst] + [declare-plugs-slots.rst]
[run-jetbrains-gateway.rst] + [plugs-and-slots.rst]
[concepts.rst] [sdks.rst] SK020 @@ -148,7 +148,7 @@ concept [part-2-work-with-interfaces.rst] [run-jetbrains-gateway.rst] - [concepts.rst]
[concepts.rst] + [concepts.rst]
[concepts.rst] SK028
SK039 @@ -166,7 +166,7 @@ concept [part-3-sketch-sdks.rst] [use-multiple-workshops.rst] - [concepts.rst]
[concepts.rst]
[multi-workshop-patterns.rst] + [concepts.rst]
[concepts.rst]
[multi-workshop-patterns.rst] @@ -228,7 +228,7 @@ sdkcraft (CLI) [part-1-get-started.rst]
[part-4-craft-sdks.rst]
[part-4-craft-sdks.rst] - [build-an-sdk.rst]
[publish-an-sdk.rst] + [build-an-sdk.rst]
[declare-plugs-slots.rst]
[publish-an-sdk.rst] [concepts.rst]
[concepts.rst]
[sdk-vs-dockerfile.rst]
[sdkcraft-cli.rst] [index.rst]
[sdkcraft.rst]
[sdkcraft-definition.rst]
[sdks.rst] SK026 @@ -256,7 +256,7 @@ concept [part-1-get-started.rst]
[part-3-sketch-sdks.rst]
[part-4-craft-sdks.rst] [build-an-sdk.rst]
[publish-an-sdk.rst]
[run-github-actions-locally.rst]
[use-workshops-with-ai-agents.rst]
[index.rst] - [index.rst]
[concepts.rst]
[best-practices.rst]
[concepts.rst]
[concepts.rst]
[index.rst]
[parts.rst]
[sdk-vs-dockerfile.rst]
[sdk-vs-dockerfile.rst]
[concepts.rst] + [index.rst]
[concepts.rst]
[best-practices.rst]
[concepts.rst]
[concepts.rst]
[index.rst]
[parts.rst]
[sdk-vs-dockerfile.rst]
[sdk-vs-dockerfile.rst]
[concepts.rst] [ai-agents.rst]
[index.rst]
[sdkcraft-definition.rst]
[index.rst]
[index.rst]
[sdks.rst]
[workshops.rst] SK000 @@ -356,7 +356,7 @@ - [sdk-definition.rst]
[sdkcraft-definition.rst] + [sdk-definition.rst]
[sdkcraft-definition.rst] SDK state @@ -400,7 +400,7 @@ concept [part-3-sketch-sdks.rst] - [concepts.rst]
[concepts.rst] + [concepts.rst]
[concepts.rst] SK036 @@ -652,8 +652,8 @@ command [part-1-get-started.rst] - [concepts.rst] - [workshop-actions.rst]
[workshop-definition.rst] + [concepts.rst] + [workshop-actions.rst]
[workshop-definition.rst] SK046 workshop changes @@ -670,7 +670,7 @@ command [part-2-work-with-interfaces.rst] [add-mounts.rst] - [camera-interface.rst]
[custom-device-interface.rst]
[desktop-interface.rst]
[ssh-interface.rst]
[tunnel-interface.rst] + [camera-interface.rst]
[custom-device-interface.rst]
[desktop-interface.rst]
[ssh-interface.rst]
[tunnel-interface.rst] [workshop-connect.rst] SK028 @@ -679,7 +679,7 @@ command [part-2-work-with-interfaces.rst] - [camera-interface.rst]
[concepts.rst]
[custom-device-interface.rst]
[desktop-interface.rst]
[gpu-interface.rst]
[mount-interface.rst]
[ssh-interface.rst]
[tunnel-interface.rst] + [camera-interface.rst]
[custom-device-interface.rst]
[desktop-interface.rst]
[gpu-interface.rst]
[mount-interface.rst]
[ssh-interface.rst]
[tunnel-interface.rst] [workshop-connections.rst] SK028 @@ -688,7 +688,7 @@ command [part-2-work-with-interfaces.rst] [add-mounts.rst] - [camera-interface.rst]
[custom-device-interface.rst]
[desktop-interface.rst]
[mount-interface.rst]
[ssh-interface.rst]
[tunnel-interface.rst] + [camera-interface.rst]
[custom-device-interface.rst]
[desktop-interface.rst]
[mount-interface.rst]
[ssh-interface.rst]
[tunnel-interface.rst] [workshop-disconnect.rst] SK028 @@ -706,7 +706,7 @@ command [part-1-get-started.rst] [add-mounts.rst] - [mount-interface.rst]
[tunnel-interface.rst] + [mount-interface.rst]
[tunnel-interface.rst] [workshop-info.rst] SK014
SK027 @@ -715,7 +715,7 @@ command [part-1-get-started.rst] [move-projects.rst]
[use-multiple-workshops.rst]
[run-github-actions-locally.rst]
[run-workshops-in-github-actions.rst]
[use-git.rst]
[debug-issues.rst] - [concepts.rst]
[sdk-vs-dockerfile.rst]
[concepts.rst] + [concepts.rst]
[sdk-vs-dockerfile.rst]
[concepts.rst] [workshop-launch.rst]
[workshops.rst] SK001
SK003
SK008
SK020 @@ -742,7 +742,7 @@ command [part-1-get-started.rst] [add-mounts.rst]
[run-github-actions-locally.rst]
[debug-issues.rst] - [concepts.rst]
[mount-interface.rst]
[concepts.rst] + [concepts.rst]
[mount-interface.rst]
[concepts.rst] [workshop-refresh.rst]
[workshops.rst] SK012
SK020 @@ -876,7 +876,7 @@ workshop (container) file [part-1-get-started.rst] - [use-multiple-workshops.rst]
[use-workshops-with-ai-agents.rst] + [design-interface-layout.rst]
[use-multiple-workshops.rst]
[use-workshops-with-ai-agents.rst] [index.rst]
[concepts.rst]
[concepts.rst]
[multi-workshop-patterns.rst]
[projects.rst] [index.rst]
[workshop-definition.rst] SK001
SK006
SK008 @@ -896,7 +896,7 @@ - [workshop-definition.rst] + [workshop-definition.rst] SK001 workshop state management diff --git a/docs/explanation/interfaces/concepts.rst b/docs/explanation/interfaces/concepts.rst index 446a6460d..beb8ec079 100644 --- a/docs/explanation/interfaces/concepts.rst +++ b/docs/explanation/interfaces/concepts.rst @@ -43,11 +43,7 @@ Currently, |ws_markup| and |sdk_markup| support the following: - :ref:`GPU interface ` (auto-connected) - :ref:`Mount interface ` (auto-connected) - :ref:`SSH interface ` (manually connected) -- :ref:`Tunnel interface ` - (auto-connected only from host to workshop, - between a plug and a slot of the same name, - and only when the plug listens on a loopback address - or a Unix domain socket) +- :ref:`Tunnel interface ` (conditionally auto-connected) Plugs and slots diff --git a/docs/explanation/interfaces/plugs-and-slots.rst b/docs/explanation/interfaces/plugs-and-slots.rst index 9161021b6..e13ee3b72 100644 --- a/docs/explanation/interfaces/plugs-and-slots.rst +++ b/docs/explanation/interfaces/plugs-and-slots.rst @@ -8,8 +8,8 @@ Plugs and slots =============== -.. @artefact plug -.. @artefact slot +.. @artefact interface plug +.. @artefact interface slot .. @artefact interface connection A workshop is a graph of capabilities. @@ -32,7 +32,8 @@ when the defaults are not what you want. Slots provide capabilities -------------------------- -A slot exposes a capability that other SDKs can consume. +A slot exposes a capability that other SDKs can consume; +a single slot can serve connections from multiple plugs at once. What a slot exposes depends on its interface: - A :ref:`mount interface ` slot @@ -45,6 +46,7 @@ What a slot exposes depends on its interface: exposes a GPU device. - A :ref:`camera interface `, + :ref:`custom device interface `, :ref:`desktop interface `, or :ref:`SSH interface ` slot exposes the corresponding host facility. @@ -80,10 +82,10 @@ which means an SDK can ship optional plugs that only activate when a corresponding provider is also installed. -.. _exp_interface_autowiring: +.. _exp_interface_auto_connection: -Autowiring ----------- +Auto-connection +--------------- When a workshop launches or refreshes, |ws_markup| tries to connect each plug @@ -108,15 +110,16 @@ Auto-connection behavior varies by interface: is a loopback address or a Unix domain socket. See :ref:`exp_tunnel_connection` for the full policy. -- The camera, desktop, and SSH interfaces do not auto-connect. - They have to be wired explicitly. +- The camera, custom device, desktop, and SSH interfaces + do not auto-connect. + They have to be connected manually with :command:`workshop connect`. When more than one slot is policy-eligible for the same plug, |ws_markup| attempts a connection for each of them in an order it does not guarantee. The right way to express a specific topology -is to write it down in the workshop definition's :samp:`connections` list +is to write it down in the workshop definition's :samp:`connections:` list instead of leaving it to auto-connection. @@ -186,9 +189,10 @@ it cannot also define plug attributes of its own. The attributes come from the plug it is bound to. When you run :command:`workshop connections`, -a bound plug is shown with a :samp:`bind.{N}` note in the :samp:`NOTES` column, -where :samp:`{N}` is the row number, in the same output, -of the plug it is bound to. +both the bound plug and its target +carry a :samp:`bind.` note in the :samp:`NOTES` column, +where :samp:`` is the row number of the target plug +in the same output. Top-level connections @@ -260,6 +264,7 @@ See also Explanation: - :ref:`exp_camera_interface` +- :ref:`exp_custom_device_interface` - :ref:`exp_desktop_interface` - :ref:`exp_gpu_interface` - :ref:`exp_interface_concepts` @@ -272,6 +277,8 @@ Explanation: How-to guides: +- :ref:`how_declare_plugs_slots` +- :ref:`how_design_interface_layout` - :ref:`how_resolve_plug_conflicts` From d5930552253169c24bd22729c709ffd0ae855742 Mon Sep 17 00:00:00 2001 From: Artem Konev Date: Tue, 9 Jun 2026 23:09:35 +0100 Subject: [PATCH 5/7] Doc: Fix tunnel and mount attribute notes in plugs how-to --- docs/how-to/develop-sdks/declare-plugs-slots.rst | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/how-to/develop-sdks/declare-plugs-slots.rst b/docs/how-to/develop-sdks/declare-plugs-slots.rst index 49568b945..5fe4bbd1b 100644 --- a/docs/how-to/develop-sdks/declare-plugs-slots.rst +++ b/docs/how-to/develop-sdks/declare-plugs-slots.rst @@ -62,7 +62,8 @@ to a matching slot, either auto-connecting it to the workshop's :ref:`system SDK ` or to another SDK's slot when the workshop definition wires that pairing explicitly. -The :samp:`mode`, :samp:`uid`, and :samp:`gid` attributes are optional. +The :samp:`mode`, :samp:`uid`, :samp:`gid`, +and :samp:`read-only` attributes are optional. Declare a mount slot @@ -111,12 +112,13 @@ inside the workshop: endpoint: 127.0.0.1:8080 -Auto-connection for the tunnel interface is gated by the *plug* endpoint: -|ws_markup| auto-connects a tunnel plug only when its endpoint +A tunnel slot is auto-connected only when the host side +declares a tunnel plug that matches the slot by name +or through a :samp:`connections:` entry in the workshop definition, +and only when that plug's endpoint is a loopback address or a Unix domain socket. -Plugs that listen on other addresses -require an explicit -:ref:`connection in the workshop definition `. +Other pairings have to be connected manually +with :command:`workshop connect`. The endpoint syntax accepts shorthand forms, including bare port numbers and unix socket paths. See :ref:`ref_tunnel_interface` for the full grammar. @@ -148,6 +150,7 @@ Explanation: - :ref:`exp_plugs_slots` - :ref:`exp_sdks` - :ref:`exp_tunnel_interface` +- :ref:`exp_workshop_definition_connections` How-to guides: From dabb17dd65a111c98212e4f4c4c292608314694e Mon Sep 17 00:00:00 2001 From: Artem Konev Date: Tue, 9 Jun 2026 23:09:35 +0100 Subject: [PATCH 6/7] Doc: Drop redirect for never-published page --- docs/how-to/customize-workshops/design-interface-layout.rst | 1 - docs/redirects.txt | 2 -- 2 files changed, 3 deletions(-) diff --git a/docs/how-to/customize-workshops/design-interface-layout.rst b/docs/how-to/customize-workshops/design-interface-layout.rst index e8ef0f3bc..9ab5b513a 100644 --- a/docs/how-to/customize-workshops/design-interface-layout.rst +++ b/docs/how-to/customize-workshops/design-interface-layout.rst @@ -1,5 +1,4 @@ .. _how_design_interface_layout: -.. _how_connect_plug_slot_pairs_explicitly: .. meta:: :description: Shape a workshop's interface topology with explicit connection diff --git a/docs/redirects.txt b/docs/redirects.txt index 6a16cb3ba..cf8ae225a 100644 --- a/docs/redirects.txt +++ b/docs/redirects.txt @@ -6,5 +6,3 @@ # The old path must be a file that doesn"t exist in the source. The current path must be # a file that does exist in the source. -"how-to/customize-workshops/connect-plug-slot-pairs-explicitly.rst" "how-to/customize-workshops/design-interface-layout.rst" - From 802e4b26e4aa9faecb4dd7113478394aa3837d43 Mon Sep 17 00:00:00 2001 From: Artem Konev Date: Fri, 19 Jun 2026 12:35:07 +0100 Subject: [PATCH 7/7] Doc: Fix link --- docs/how-to/customize-workshops/design-interface-layout.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to/customize-workshops/design-interface-layout.rst b/docs/how-to/customize-workshops/design-interface-layout.rst index 9ab5b513a..7b97736b6 100644 --- a/docs/how-to/customize-workshops/design-interface-layout.rst +++ b/docs/how-to/customize-workshops/design-interface-layout.rst @@ -200,6 +200,7 @@ See also Explanation: +- :ref:`exp_in_project_sdk` - :ref:`exp_interface_concepts` - :ref:`exp_plug_bindings` - :ref:`exp_plugs_slots` @@ -213,7 +214,6 @@ How-to guides: Reference: -- :ref:`ref_in_project_sdk` - :ref:`ref_workshop_connect` - :ref:`ref_workshop_connections` - :ref:`ref_workshop_definition`