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}