1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/*
* Copyright 2025-2026 Colliery Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//! Secret resolution side channel (CLOACI-I-0133 / T-0858, design D-1).
//!
//! A task/constructor reads a resolved secret through [`Context::secret`] — a
//! dedicated accessor on the execution scope that is **structurally distinct**
//! from the durable [`Context`](crate::Context) data. The resolved plaintext is
//! *returned* to the task; it is never inserted into the context's serialized
//! `data` map, so it can never land in `schedules.params`, the fires log, audit
//! rows, or execution history (NFR-001).
//!
//! This module defines only the trait + error types that live in the authoring
//! crate. The concrete backend (which decrypts against the tenant-scoped
//! `SecretStore`) lives in the `cloacina` runtime crate as `SecretStoreResolver`
//! and is threaded onto the `Context` by the executor at fire time.
use async_trait;
use BTreeMap;
use Error;
/// Reserved `Context` data key holding the instance's `{"$secret": name}` binding
/// map (CLOACI-I-0133 / T-0859, design D-4).
///
/// At fire time `merge_instance_params` recognizes a `{"$secret": "name"}` param
/// value, keeps the **resolved value** out of the context entirely, and records
/// only the non-sensitive `local_binding_name -> secret_name` alias here. The map
/// carries NAMES ONLY (never values), so it is safe to serialize into the durable
/// context; it survives the fire → persist → execute boundary and lets
/// [`Context::secret`](crate::Context::secret) resolve a task's declared local
/// binding name to the concrete secret the instance chose.
pub const SECRET_REFS_KEY: &str = "__cloacina_secret_refs__";
/// Error returned by a [`SecretResolver`] backend implementation.
/// Error surfaced to a task body by the [`Context`](crate::Context) secret
/// accessor.
/// A backend that resolves a named secret into its plaintext `{field: value}`
/// map at fire time.
///
/// Implementations decrypt at the last possible moment and return the fields to
/// the caller; they MUST NOT persist or log the plaintext. The runtime attaches
/// a resolver to the [`Context`](crate::Context) via a non-serialized handle
/// (see [`Context::set_secret_resolver`](crate::Context::set_secret_resolver)),
/// which is what keeps resolution structurally separate from the durable
/// context.
/// In-memory resolver over already-resolved secret values, keyed by concrete
/// secret name (CLOACI-T-0895).
///
/// The packaged-task bridge uses this on the PLUGIN side: the host resolves
/// every `{"$secret"}`-referenced secret through its real backend before the
/// plugin call and ships the values across the boundary in the
/// `TaskExecutionRequest`; the plugin shell rebuilds the execution scope with
/// this resolver so `context.secret(...)` works identically inside the
/// package. Values live only in this object for the duration of one task
/// invocation — never serialized into the durable context (NFR-001).
// Values must never appear in logs; a manual Debug keeps names only.