Response

The sequence of channel responses the client answers with, what ends it, what a finish with nothing before it means, and what the server does with the answer: the byte 0 followed by the secrets’ first values is fed to the container before the id, and anything else is the run’s error.

[0][values JSON …]   deployed: these are the secrets' first values
[1][error JSON …]    not deployed
  • The sequence. Exactly one channel response, followed by the channel response finish. The byte 0 lets the run go on to its feed and its id. The byte 1, followed by exactly one JSON value in the form defined on the volumes::list response page, is the run’s error, in the client’s words: the server stops the container and sends the error as the scope’s one response. A channel response finish that no channel response precedes is the run’s error too.
  • The values. The bytes after the byte 0 are a JSON object whose every member is a secret’s name, as the declaration carried it, to its value, any JSON value, in the order declared: every name the program declared, and no other. The server relays the object verbatim on exactly one secrets channel of the begin scope, reads none of it, and sends the id only after the proxy has answered Fed.
  • Malformed. A channel response whose first byte is neither 0 nor 1, or whose bytes after 0 do not parse as the values, is the run’s error.

The channel response is defined by diverge-sdk-rs/src/shared/containers/registration/response/frame.rs:

//! What a caller answers on a registration channel.

use std::fmt;

use super::super::super::secrets::Values;
use crate::wire::decode::Decode;
use crate::wire::encode::{Encode, Writer};
use crate::shared::error::Error;

/// Deployed, or not.
///
/// The whole answer to a [`Request`](super::super::request::Request),
/// and one frame is all there is — this is not a stream, and a
/// channel carrying one of these finishes immediately after.
///
/// # The layout
///
/// ```text
/// [0][values JSON …]   deployed: every dependency runs, and these are the secrets' first values
/// [1][error JSON …]    not deployed, and why
/// ```
///
/// A deploy carries the first [`Values`] — every name the program
/// declared, each to the value locked or read for it — which the
/// provider feeds to the container before it sends the id, and
/// nothing else: the provider needs no id of a tool container, since
/// the caller wires them. An empty map is a program that declared no
/// secret. A refusal carries the caller's own words — a dependency
/// that did not deploy, a name the template does not declare, a key
/// with no free value — which the run's error carries on, and never
/// a value.
#[derive(Debug, Clone, PartialEq)]
pub enum Frame {
    /// Every dependency runs, and the secrets have these values. Tag
    /// `0`.
    Deployed(Values),
    /// Something did not, and this says why. Tag `1`. See
    /// [`shared::error::Error`](crate::shared::error::Error) for why
    /// it says so little.
    Error(Error),
}

/// Tag for [`Frame::Deployed`].
const DEPLOYED: u8 = 0;

/// Tag for [`Frame::Error`].
const ERROR: u8 = 1;

impl Encode for Frame {
    /// The ordinary JSON failure, from either variant.
    type Error = serde_json::Error;

    // Spelled out rather than `Self::Error`: this enum has a variant
    // called `Error`, so the associated type is ambiguous by that name.
    fn encode(&self, out: &mut Writer<'_>) -> Result<(), serde_json::Error> {
        match self {
            Frame::Deployed(values) => {
                out.extend_from_slice(&[DEPLOYED]);
                serde_json::to_writer(out, values)
            }
            Frame::Error(error) => {
                out.extend_from_slice(&[ERROR]);
                error.encode(out)
            }
        }
    }
}

impl Decode<'_> for Frame {
    /// Four ways to fail, and two of them are JSON.
    type Error = FrameError;

    // Spelled out for the same reason as `encode` above.
    fn decode(bytes: &[u8]) -> Result<Self, FrameError> {
        let (tag, rest) = bytes.split_first().ok_or(FrameError::Empty)?;
        match *tag {
            DEPLOYED => serde_json::from_slice(rest).map(Frame::Deployed).map_err(FrameError::Deployed),
            ERROR => Error::decode(rest).map(Frame::Error).map_err(FrameError::Error),
            tag => Err(FrameError::UnknownTag(tag)),
        }
    }
}

/// A registration answer that could not be read.
#[derive(Debug)]
pub enum FrameError {
    /// No bytes at all, so not even a tag.
    Empty,
    /// A tag that is neither of this frame's two.
    UnknownTag(u8),
    /// The values did not parse. The message is the parser's, which
    /// quotes no value.
    Deployed(serde_json::Error),
    /// The error did not parse.
    Error(serde_json::Error),
}

impl fmt::Display for FrameError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            FrameError::Empty => f.write_str("registration answer frame is empty"),
            FrameError::UnknownTag(tag) => write!(f, "unknown registration answer frame tag {tag}"),
            FrameError::Deployed(error) => write!(f, "registration values did not parse: {error}"),
            FrameError::Error(error) => write!(f, "registration error did not parse: {error}"),
        }
    }
}

impl std::error::Error for FrameError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            FrameError::Deployed(error) | FrameError::Error(error) => Some(error),
            FrameError::Empty | FrameError::UnknownTag(_) => None,
        }
    }
}

and by diverge-sdk-rs/src/shared/containers/secrets/values.rs:

//! The secrets' values, by name.

use indexmap::IndexMap;
use serde_json::Value;

/// Secrets by name to value, any JSON: what the caller feeds a
/// container, and what the program hands back. Fed, it carries every
/// name the program declared, in the order declared — a `fixed` one
/// a value read from its key, a `rotating` one the value locked to
/// the container. Collected at an inactive, it carries every
/// `rotating` name the program declared, each with the value the
/// program leaves in it; a `fixed` name in it is ignored, and a
/// `rotating` name missing from it is the collect's error, never a
/// write-back. A value is never read by anything between the vault
/// and the program.
pub type Values = IndexMap<String, Value>;