Skip to main content

packc/
agent_pack.rs

1//! AgenticWorker (`dw-application`) pack emission: designer-faithful sidecars
2//! and the store describe document. All output mirrors greentic-designer's
3//! `orchestrate::pack_via_packc` / `orchestrate::dw_publish` byte-for-byte.
4
5use std::collections::BTreeMap;
6
7use anyhow::{Context, Result};
8use greentic_types::secrets::SecretRequirement;
9use serde::Serialize;
10use serde_json::{Value, json};
11
12/// Serialize the `agents:` map to the `dw-agents.json` sidecar bytes
13/// (bare `{ "<agent_id>": <AgentConfig> }` JSON). Returns `None` when the map is
14/// empty so non-agent packs produce byte-identical archives.
15pub fn dw_agents_sidecar_bytes(
16    agents: &BTreeMap<String, serde_json::Value>,
17) -> Result<Option<Vec<u8>>> {
18    if agents.is_empty() {
19        return Ok(None);
20    }
21    let bytes = serde_json::to_vec(agents).context("serialize dw-agents.json")?;
22    Ok(Some(bytes))
23}
24
25/// Mirror of greentic-designer `store::secrets_policy::SecretSharePolicy`.
26///
27/// Uses `#[serde(tag = "policy", rename_all = "kebab-case")]` to emit the
28/// policy as an inlined tag field, matching the designer's wire format exactly.
29/// Only `Serialize` is derived — packc emits this sidecar but never reads it back.
30#[derive(Debug, Clone, Serialize)]
31#[serde(tag = "policy", rename_all = "kebab-case")]
32pub enum SecretSharePolicy {
33    /// Installer must supply their own value; no publisher value ever ships.
34    ByoRequired,
35    /// Publisher may supply an overridable default (referenced, never inlined).
36    DefaultOverridable {
37        /// Reference to a publisher secret marked shareable.
38        #[serde(default, skip_serializing_if = "Option::is_none")]
39        default_ref: Option<String>,
40    },
41}
42
43/// One requirement: the canonical `SecretRequirement` flattened with its share
44/// policy tag. Mirrors `SecretPolicyEntry` in the designer's `store::secrets_policy`.
45///
46/// `#[serde(flatten)]` on both fields produces the designer's byte-faithful wire
47/// format: `{"key":"...","required":true,"policy":"byo-required"}`.
48#[derive(Debug, Clone, Serialize)]
49pub struct SecretPolicyEntry {
50    /// Canonical requirement — key/required and optional description/scope/format/schema/examples.
51    #[serde(flatten)]
52    pub requirement: SecretRequirement,
53    /// Hybrid share policy tag (inlined via `#[serde(tag)]` on the enum).
54    #[serde(flatten)]
55    pub share: SecretSharePolicy,
56}
57
58/// The `secrets-policy.json` document embedded in a published `dw-application` `.gtpack`.
59#[derive(Debug, Clone, Default, Serialize)]
60pub struct AgenticWorkerSecretsPolicy {
61    /// One entry per secret the worker needs.
62    pub requirements: Vec<SecretPolicyEntry>,
63}
64
65/// Build the `secrets-policy.json` sidecar bytes from the canonical secret
66/// requirements the pack declares. All entries are assigned the `byo-required`
67/// policy — no publisher value ever ships. Returns `None` when `requirements`
68/// is empty so packs with no secrets produce a byte-identical archive.
69pub fn secrets_policy_sidecar_bytes(requirements: &[SecretRequirement]) -> Result<Option<Vec<u8>>> {
70    if requirements.is_empty() {
71        return Ok(None);
72    }
73    let policy = AgenticWorkerSecretsPolicy {
74        requirements: requirements
75            .iter()
76            .map(|req| SecretPolicyEntry {
77                requirement: req.clone(),
78                share: SecretSharePolicy::ByoRequired,
79            })
80            .collect(),
81    };
82    let bytes = serde_json::to_vec(&policy).context("serialize secrets-policy.json")?;
83    Ok(Some(bytes))
84}
85
86/// Inputs for the store describe document. `manifest_sha256` is the
87/// lowercase-hex SHA-256 of the FINAL `.gtpack` bytes (== `artifactSha256`).
88pub struct DescribeMeta {
89    pub id: String,
90    pub name: String,
91    pub version: String,
92    pub summary: String,
93    pub manifest_sha256: String,
94}
95
96/// Author a `describe-v2` document for an Agentic Worker. Byte-faithful to
97/// greentic-designer `orchestrate::dw_publish::agentic_worker_describe`.
98pub fn agentic_worker_describe(meta: &DescribeMeta) -> Value {
99    json!({
100        "apiVersion": "greentic.ai/v2",
101        "kind": "AgenticWorker",
102        "compat": {
103            "min_designer_version": ">=1.2.0",
104            "min_runner_version": "^0.12.0",
105            "contract_version": "1.2.0"
106        },
107        "metadata": {
108            "id": meta.id,
109            "name": meta.name,
110            "version": meta.version,
111            "summary": meta.summary,
112            "author": { "name": "Greentic" },
113            "license": "MIT"
114        },
115        "engine": { "greenticDesigner": ">=1.2.0", "extRuntime": "^1.2.0" },
116        "capabilities": { "offered": [], "required": [] },
117        "runtime": {
118            "memoryLimitMB": 32,
119            "permissions": { "network": [], "secrets": [], "callExtensionKinds": [] },
120            "components": {
121                "worker": {
122                    "gtpack": {
123                        "file": "worker.wasm",
124                        "sha256": "0000000000000000000000000000000000000000000000000000000000000000",
125                        "pack_id": meta.id,
126                        "component_version": meta.version
127                    },
128                    "sha256": "0000000000000000000000000000000000000000000000000000000000000000",
129                    "world": format!("greentic:{}/extension@{}", meta.id, meta.version)
130                }
131            }
132        },
133        "contributions": {},
134        "manifestSha256": meta.manifest_sha256
135    })
136}