@emilia-protocol/gate 0.15.2 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -0
- package/README.md +31 -0
- package/authority-allocation.js +2 -0
- package/autonomy-control-plane-profile.js +2 -0
- package/consequence-actuator.js +3 -0
- package/discovery-permit-resolver.js +3 -0
- package/dist/authority-allocation.d.ts +200 -0
- package/dist/authority-allocation.d.ts.map +1 -0
- package/dist/authority-allocation.js +984 -0
- package/dist/authority-allocation.js.map +1 -0
- package/dist/autonomy-control-plane-profile.d.ts +18 -0
- package/dist/autonomy-control-plane-profile.d.ts.map +1 -0
- package/dist/autonomy-control-plane-profile.js +352 -0
- package/dist/autonomy-control-plane-profile.js.map +1 -0
- package/dist/consequence-actuator.d.ts +226 -0
- package/dist/consequence-actuator.d.ts.map +1 -0
- package/dist/consequence-actuator.js +685 -0
- package/dist/consequence-actuator.js.map +1 -0
- package/dist/discovery-permit-resolver.d.ts +46 -0
- package/dist/discovery-permit-resolver.d.ts.map +1 -0
- package/dist/discovery-permit-resolver.js +428 -0
- package/dist/discovery-permit-resolver.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/package.json +22 -2
- package/src/authority-allocation.ts +1268 -0
- package/src/autonomy-control-plane-profile.ts +371 -0
- package/src/consequence-actuator.ts +1122 -0
- package/src/discovery-permit-resolver.ts +496 -0
- package/src/index.ts +4 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Closed composition profile for bounded autonomous execution.
|
|
4
|
+
*
|
|
5
|
+
* This is a compiler/validator over existing EMILIA primitives. It is not a
|
|
6
|
+
* scheduler, goal generator, natural-language entailment oracle, receipt
|
|
7
|
+
* format, or second Trust Program engine.
|
|
8
|
+
*/
|
|
9
|
+
import { hashCanonical } from './execution-binding.js';
|
|
10
|
+
import { TRUST_PROGRAM_VERSION } from './trust-program.js';
|
|
11
|
+
|
|
12
|
+
export const AUTONOMY_CONTROL_PLANE_VERSION = 'EP-GATE-AUTONOMY-CONTROL-PLANE-PROFILE-v1';
|
|
13
|
+
export const AUTONOMY_ROOT_EVIDENCE_TYPE = 'ep-root-objective';
|
|
14
|
+
export const AUTONOMY_FITNESS_EVIDENCE_TYPE = 'agent-fitness-report';
|
|
15
|
+
|
|
16
|
+
const DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
17
|
+
const CAID = /^caid:1:[a-z][a-z0-9.-]*\.[1-9][0-9]*:jcs-sha256:[A-Za-z0-9_-]{43}$/;
|
|
18
|
+
const ID = /^[A-Za-z0-9][A-Za-z0-9:_.@/-]{0,255}$/;
|
|
19
|
+
const PATH = /^(?!\/)(?!.*(?:^|\/)\.\.(?:\/|$))[^\u0000-\u001f\u007f]{1,1024}$/;
|
|
20
|
+
const HUMAN_EVIDENCE = new Set(['ep-class-a-signoff', 'ep-quorum']);
|
|
21
|
+
const PHASES = new Set(['execute', 'canary', 'promote', 'rollback']);
|
|
22
|
+
const TOP_KEYS = new Set(['@version', 'control_plane_id', 'version', 'valid_from', 'expires_at', 'root', 'status', 'children']);
|
|
23
|
+
const ROOT_KEYS = new Set(['objective_id', 'root_caid', 'action_digest', 'actions', 'audiences', 'budget', 'expires_at', 'initiator_id', 'authorization']);
|
|
24
|
+
const AUTH_KEYS = new Set(['evidence_type', 'verifier_profile', 'policy_digest', 'max_age_sec', 'require_initiator_exclusion']);
|
|
25
|
+
const STATUS_KEYS = new Set(['verifier_profile', 'policy_digest', 'max_age_sec']);
|
|
26
|
+
const CHILD_KEYS = new Set(['goal_id', 'parent_goal_id', 'phase', 'caid', 'action_digest', 'action_type', 'audience', 'budget', 'expires_at', 'capability_template_digest', 'proposer_id', 'evaluator_id', 'executor_id', 'change', 'fitness', 'canary', 'rollback']);
|
|
27
|
+
const BUDGET_KEYS = new Set(['cents', 'calls']);
|
|
28
|
+
const CHANGE_KEYS = new Set(['before_digest', 'after_digest', 'changed_paths']);
|
|
29
|
+
const FITNESS_KEYS = new Set(['suite_digest', 'environment_digest', 'policy_digest', 'max_age_sec']);
|
|
30
|
+
const CANARY_KEYS = new Set(['exposure_percent', 'max_exposure_percent', 'policy_digest', 'max_age_sec']);
|
|
31
|
+
const PROMOTION_CANARY_KEYS = new Set(['goal_id', 'exposure_percent', 'max_exposure_percent', 'policy_digest', 'max_age_sec']);
|
|
32
|
+
const ROLLBACK_KEYS = new Set(['original_caid', 'authorization_policy_digest']);
|
|
33
|
+
|
|
34
|
+
type JsonRecord = Record<string, any>;
|
|
35
|
+
|
|
36
|
+
export class AutonomyControlPlaneValidationError extends TypeError {
|
|
37
|
+
readonly code: string;
|
|
38
|
+
constructor(code: string, message: string) {
|
|
39
|
+
super(message);
|
|
40
|
+
this.name = 'AutonomyControlPlaneValidationError';
|
|
41
|
+
this.code = code;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface CompiledAutonomyControlPlane {
|
|
46
|
+
version: typeof AUTONOMY_CONTROL_PLANE_VERSION;
|
|
47
|
+
profile_digest: string;
|
|
48
|
+
control_plane_id: string;
|
|
49
|
+
programs: JsonRecord[];
|
|
50
|
+
claim_boundary: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function refuse(code: string, message: string): never {
|
|
54
|
+
throw new AutonomyControlPlaneValidationError(code, message);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isRecord(value: unknown): value is JsonRecord {
|
|
58
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
59
|
+
const prototype = Object.getPrototypeOf(value);
|
|
60
|
+
return prototype === Object.prototype || prototype === null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function exact(value: unknown, keys: Set<string>): value is JsonRecord {
|
|
64
|
+
return isRecord(value)
|
|
65
|
+
&& Reflect.ownKeys(value).every((key) => typeof key === 'string')
|
|
66
|
+
&& Object.keys(value).length === keys.size
|
|
67
|
+
&& Object.keys(value).every((key) => keys.has(key));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function identifier(value: unknown): value is string {
|
|
71
|
+
return typeof value === 'string' && ID.test(value);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function digest(value: unknown): value is string {
|
|
75
|
+
return typeof value === 'string' && DIGEST.test(value);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function caid(value: unknown): value is string {
|
|
79
|
+
return typeof value === 'string' && CAID.test(value);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function instant(value: unknown): number {
|
|
83
|
+
if (typeof value !== 'string'
|
|
84
|
+
|| !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z$/.test(value)) return NaN;
|
|
85
|
+
return Date.parse(value);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function exactIdentifiers(value: unknown): value is string[] {
|
|
89
|
+
return Array.isArray(value) && value.length > 0 && value.length <= 256
|
|
90
|
+
&& value.every(identifier) && new Set(value).size === value.length;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function validBudget(value: unknown): value is { cents: number; calls: number } {
|
|
94
|
+
return exact(value, BUDGET_KEYS)
|
|
95
|
+
&& Number.isSafeInteger(value.cents) && value.cents >= 0
|
|
96
|
+
&& Number.isSafeInteger(value.calls) && value.calls >= 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function validFreshness(value: unknown): value is number {
|
|
100
|
+
return Number.isSafeInteger(value) && (value as number) >= 1 && (value as number) <= 31_536_000;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function canonicalDigest(value: unknown): string {
|
|
104
|
+
return `sha256:${hashCanonical(value)}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function validateShape(profile: unknown): asserts profile is JsonRecord {
|
|
108
|
+
if (!exact(profile, TOP_KEYS)
|
|
109
|
+
|| profile['@version'] !== AUTONOMY_CONTROL_PLANE_VERSION
|
|
110
|
+
|| !identifier(profile.control_plane_id)
|
|
111
|
+
|| !Number.isSafeInteger(profile.version) || profile.version < 1
|
|
112
|
+
|| !Number.isFinite(instant(profile.valid_from))
|
|
113
|
+
|| !Number.isFinite(instant(profile.expires_at))
|
|
114
|
+
|| instant(profile.expires_at) <= instant(profile.valid_from)
|
|
115
|
+
|| !exact(profile.root, ROOT_KEYS)
|
|
116
|
+
|| !exact(profile.root.authorization, AUTH_KEYS)
|
|
117
|
+
|| !exact(profile.root.budget, BUDGET_KEYS)
|
|
118
|
+
|| !exact(profile.status, STATUS_KEYS)
|
|
119
|
+
|| !Array.isArray(profile.children) || profile.children.length < 1 || profile.children.length > 64
|
|
120
|
+
|| profile.children.some((child: unknown) => !exact(child, CHILD_KEYS))) {
|
|
121
|
+
refuse('profile_shape_invalid', 'autonomy profile is not a closed, bounded v1 object');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function validateRoot(profile: JsonRecord): void {
|
|
126
|
+
const root = profile.root;
|
|
127
|
+
const authorization = root.authorization;
|
|
128
|
+
if (!identifier(root.objective_id) || !caid(root.root_caid) || !digest(root.action_digest)
|
|
129
|
+
|| !exactIdentifiers(root.actions) || !exactIdentifiers(root.audiences)
|
|
130
|
+
|| !validBudget(root.budget) || !identifier(root.initiator_id)
|
|
131
|
+
|| !Number.isFinite(instant(root.expires_at))
|
|
132
|
+
|| instant(root.expires_at) > instant(profile.expires_at)) {
|
|
133
|
+
refuse('root_authority_invalid', 'root objective authority is invalid');
|
|
134
|
+
}
|
|
135
|
+
if (!HUMAN_EVIDENCE.has(authorization.evidence_type)
|
|
136
|
+
|| !identifier(authorization.verifier_profile) || !digest(authorization.policy_digest)
|
|
137
|
+
|| !validFreshness(authorization.max_age_sec)
|
|
138
|
+
|| typeof authorization.require_initiator_exclusion !== 'boolean'
|
|
139
|
+
|| (authorization.evidence_type === 'ep-quorum' && authorization.require_initiator_exclusion !== true)) {
|
|
140
|
+
refuse('root_human_authorization_required', 'root objective requires Class-A or initiator-excluding quorum human evidence');
|
|
141
|
+
}
|
|
142
|
+
if (!identifier(profile.status.verifier_profile) || !digest(profile.status.policy_digest)
|
|
143
|
+
|| !validFreshness(profile.status.max_age_sec)) {
|
|
144
|
+
refuse('status_policy_invalid', 'a fresh suspension and revocation status policy is required');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function validateChildShape(child: JsonRecord): void {
|
|
149
|
+
if (!identifier(child.goal_id) || !identifier(child.parent_goal_id) || !PHASES.has(child.phase)
|
|
150
|
+
|| !caid(child.caid) || !digest(child.action_digest) || !identifier(child.action_type)
|
|
151
|
+
|| !identifier(child.audience) || !validBudget(child.budget)
|
|
152
|
+
|| !Number.isFinite(instant(child.expires_at)) || !digest(child.capability_template_digest)
|
|
153
|
+
|| !identifier(child.proposer_id) || !identifier(child.evaluator_id) || !identifier(child.executor_id)) {
|
|
154
|
+
refuse('child_shape_invalid', `child ${String(child.goal_id)} is invalid`);
|
|
155
|
+
}
|
|
156
|
+
if (new Set([child.proposer_id, child.evaluator_id, child.executor_id]).size !== 3) {
|
|
157
|
+
refuse('independent_roles_required', `child ${child.goal_id} collapses proposer, evaluator, or executor`);
|
|
158
|
+
}
|
|
159
|
+
if (!exact(child.change, CHANGE_KEYS) || !digest(child.change.before_digest)
|
|
160
|
+
|| !digest(child.change.after_digest) || child.change.before_digest === child.change.after_digest
|
|
161
|
+
|| !Array.isArray(child.change.changed_paths) || child.change.changed_paths.length < 1
|
|
162
|
+
|| child.change.changed_paths.length > 512 || !child.change.changed_paths.every((entry: unknown) => typeof entry === 'string' && PATH.test(entry))
|
|
163
|
+
|| new Set(child.change.changed_paths).size !== child.change.changed_paths.length
|
|
164
|
+
|| !child.change.changed_paths.every((entry: string, index: number) => index === 0 || child.change.changed_paths[index - 1] < entry)) {
|
|
165
|
+
refuse('change_binding_invalid', `child ${child.goal_id} requires exact before, after, and sorted changed-path bindings`);
|
|
166
|
+
}
|
|
167
|
+
if (!exact(child.fitness, FITNESS_KEYS) || !digest(child.fitness.suite_digest)
|
|
168
|
+
|| !digest(child.fitness.environment_digest) || !digest(child.fitness.policy_digest)
|
|
169
|
+
|| !validFreshness(child.fitness.max_age_sec)) {
|
|
170
|
+
refuse('fitness_policy_invalid', `child ${child.goal_id} requires pinned, freshness-bounded fitness evidence`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function validateCanary(child: JsonRecord, childrenById: Map<string, JsonRecord>): void {
|
|
175
|
+
if (child.phase === 'promote') {
|
|
176
|
+
if (!exact(child.canary, PROMOTION_CANARY_KEYS) || !identifier(child.canary.goal_id)) {
|
|
177
|
+
refuse('promotion_requires_canary', `promotion ${child.goal_id} has no pinned canary predecessor`);
|
|
178
|
+
}
|
|
179
|
+
const predecessor = childrenById.get(child.canary.goal_id);
|
|
180
|
+
if (!predecessor || predecessor.phase !== 'canary'
|
|
181
|
+
|| predecessor.change.before_digest !== child.change.before_digest
|
|
182
|
+
|| predecessor.change.after_digest !== child.change.after_digest
|
|
183
|
+
|| JSON.stringify(predecessor.change.changed_paths) !== JSON.stringify(child.change.changed_paths)) {
|
|
184
|
+
refuse('promotion_requires_canary', `promotion ${child.goal_id} does not bind the same change as a canary child`);
|
|
185
|
+
}
|
|
186
|
+
} else if (child.phase === 'canary') {
|
|
187
|
+
if (!exact(child.canary, CANARY_KEYS)) {
|
|
188
|
+
refuse('canary_policy_invalid', `canary ${child.goal_id} requires a closed exposure policy`);
|
|
189
|
+
}
|
|
190
|
+
} else if (child.canary !== null) {
|
|
191
|
+
refuse('canary_policy_invalid', `non-canary child ${child.goal_id} must not carry canary authority`);
|
|
192
|
+
}
|
|
193
|
+
if (child.canary !== null) {
|
|
194
|
+
if (!Number.isInteger(child.canary.exposure_percent) || child.canary.exposure_percent < 1
|
|
195
|
+
|| child.canary.exposure_percent > 100
|
|
196
|
+
|| !Number.isInteger(child.canary.max_exposure_percent) || child.canary.max_exposure_percent < 1
|
|
197
|
+
|| child.canary.max_exposure_percent > 100
|
|
198
|
+
|| child.canary.exposure_percent > child.canary.max_exposure_percent
|
|
199
|
+
|| !digest(child.canary.policy_digest) || !validFreshness(child.canary.max_age_sec)) {
|
|
200
|
+
refuse('canary_exposure_exceeded', `canary ${child.goal_id} exceeds its pinned exposure ceiling`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function validateGraph(profile: JsonRecord): Map<string, JsonRecord> {
|
|
206
|
+
const children = profile.children as JsonRecord[];
|
|
207
|
+
const byId = new Map<string, JsonRecord>();
|
|
208
|
+
const caids = new Set<string>();
|
|
209
|
+
for (const child of children) {
|
|
210
|
+
validateChildShape(child);
|
|
211
|
+
if (byId.has(child.goal_id) || caids.has(child.caid)) {
|
|
212
|
+
refuse('child_identity_replayed', 'child goal ids and CAIDs must be unique');
|
|
213
|
+
}
|
|
214
|
+
byId.set(child.goal_id, child); caids.add(child.caid);
|
|
215
|
+
}
|
|
216
|
+
const visiting = new Set<string>();
|
|
217
|
+
const visited = new Set<string>();
|
|
218
|
+
const visit = (id: string): void => {
|
|
219
|
+
if (visiting.has(id)) refuse('child_goal_cycle', 'child goal derivation must be acyclic');
|
|
220
|
+
if (visited.has(id)) return;
|
|
221
|
+
const child = byId.get(id);
|
|
222
|
+
if (!child) refuse('child_parent_missing', `unknown child goal ${id}`);
|
|
223
|
+
visiting.add(id);
|
|
224
|
+
if (child.parent_goal_id !== 'root') {
|
|
225
|
+
if (!byId.has(child.parent_goal_id)) refuse('child_parent_missing', `unknown parent ${child.parent_goal_id}`);
|
|
226
|
+
visit(child.parent_goal_id);
|
|
227
|
+
}
|
|
228
|
+
visiting.delete(id); visited.add(id);
|
|
229
|
+
};
|
|
230
|
+
for (const id of byId.keys()) visit(id);
|
|
231
|
+
return byId;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function validateAuthority(profile: JsonRecord, byId: Map<string, JsonRecord>): void {
|
|
235
|
+
const root = profile.root;
|
|
236
|
+
const groups = new Map<string, JsonRecord[]>();
|
|
237
|
+
for (const child of byId.values()) {
|
|
238
|
+
const parent = child.parent_goal_id === 'root' ? root : byId.get(child.parent_goal_id) as JsonRecord;
|
|
239
|
+
const parentActions = child.parent_goal_id === 'root' ? parent.actions : [parent.action_type];
|
|
240
|
+
const parentAudiences = child.parent_goal_id === 'root' ? parent.audiences : [parent.audience];
|
|
241
|
+
if (!parentActions.includes(child.action_type) || !parentAudiences.includes(child.audience)
|
|
242
|
+
|| child.budget.cents > parent.budget.cents || child.budget.calls > parent.budget.calls
|
|
243
|
+
|| instant(child.expires_at) > instant(parent.expires_at)) {
|
|
244
|
+
refuse('child_authority_widening', `child ${child.goal_id} exceeds parent authority`);
|
|
245
|
+
}
|
|
246
|
+
const siblings = groups.get(child.parent_goal_id) ?? [];
|
|
247
|
+
siblings.push(child); groups.set(child.parent_goal_id, siblings);
|
|
248
|
+
}
|
|
249
|
+
for (const [parentId, siblings] of groups) {
|
|
250
|
+
const parent = parentId === 'root' ? root : byId.get(parentId) as JsonRecord;
|
|
251
|
+
const cents = siblings.reduce((sum, child) => sum + child.budget.cents, 0);
|
|
252
|
+
const calls = siblings.reduce((sum, child) => sum + child.budget.calls, 0);
|
|
253
|
+
if (!Number.isSafeInteger(cents) || !Number.isSafeInteger(calls)
|
|
254
|
+
|| cents > parent.budget.cents || calls > parent.budget.calls) {
|
|
255
|
+
refuse('aggregate_sibling_budget_exceeded', `children of ${parentId} exceed aggregate authority`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function validatePhases(profile: JsonRecord, byId: Map<string, JsonRecord>): void {
|
|
261
|
+
for (const child of byId.values()) {
|
|
262
|
+
validateCanary(child, byId);
|
|
263
|
+
if (child.phase === 'rollback') {
|
|
264
|
+
if (!exact(child.rollback, ROLLBACK_KEYS) || !caid(child.rollback.original_caid)
|
|
265
|
+
|| !digest(child.rollback.authorization_policy_digest)
|
|
266
|
+
|| child.rollback.original_caid === child.caid
|
|
267
|
+
|| ![...byId.values()].some((candidate) => candidate.caid === child.rollback.original_caid)) {
|
|
268
|
+
refuse('rollback_requires_new_authority', `rollback ${child.goal_id} requires a new CAID and authorization policy`);
|
|
269
|
+
}
|
|
270
|
+
} else if (child.rollback !== null) {
|
|
271
|
+
refuse('rollback_requires_new_authority', `non-rollback child ${child.goal_id} must not carry rollback semantics`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function requirement(requirementId: string, evidenceType: string, verifierProfile: string, policyDigest: string, maxAgeSec: number, revocationRequired: boolean): JsonRecord {
|
|
277
|
+
return { requirement_id: requirementId, evidence_type: evidenceType, verifier_profile: verifierProfile, policy_digest: policyDigest, max_age_sec: maxAgeSec, revocation_required: revocationRequired };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function compileProgram(profile: JsonRecord, child: JsonRecord, index: number): JsonRecord {
|
|
281
|
+
const rootPolicy = canonicalDigest({
|
|
282
|
+
version: AUTONOMY_CONTROL_PLANE_VERSION,
|
|
283
|
+
objective_id: profile.root.objective_id,
|
|
284
|
+
root_caid: profile.root.root_caid,
|
|
285
|
+
root_action_digest: profile.root.action_digest,
|
|
286
|
+
child_goal_id: child.goal_id,
|
|
287
|
+
child_caid: child.caid,
|
|
288
|
+
child_action_digest: child.action_digest,
|
|
289
|
+
authorization_policy_digest: profile.root.authorization.policy_digest,
|
|
290
|
+
require_initiator_exclusion: profile.root.authorization.require_initiator_exclusion,
|
|
291
|
+
});
|
|
292
|
+
const allocationPolicy = canonicalDigest({
|
|
293
|
+
authority_head_required: true, authority_epoch_required: true,
|
|
294
|
+
parent_goal_id: child.parent_goal_id, action_type: child.action_type, audience: child.audience,
|
|
295
|
+
budget: child.budget, expires_at: child.expires_at, capability_template_digest: child.capability_template_digest,
|
|
296
|
+
});
|
|
297
|
+
const stages: JsonRecord[] = [
|
|
298
|
+
{
|
|
299
|
+
stage_id: 'root-objective', depends_on: [],
|
|
300
|
+
rule: { mode: 'all', distinct_subjects: false, distinct_keys: false },
|
|
301
|
+
requirements: [requirement('human-root-objective', AUTONOMY_ROOT_EVIDENCE_TYPE, profile.root.authorization.verifier_profile, rootPolicy, profile.root.authorization.max_age_sec, true)],
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
stage_id: 'authority-containment', depends_on: ['root-objective'],
|
|
305
|
+
rule: { mode: 'all', distinct_subjects: false, distinct_keys: false },
|
|
306
|
+
requirements: [requirement('bounded-child-authority', 'ep-authority-allocation', 'ep-authority-allocation:v1', allocationPolicy, profile.status.max_age_sec, true)],
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
stage_id: 'exact-change', depends_on: ['authority-containment'],
|
|
310
|
+
rule: { mode: 'all', distinct_subjects: false, distinct_keys: false },
|
|
311
|
+
requirements: [requirement('exact-before-after-diff', 'ep-execution-binding', 'ep-execution-binding:v1', canonicalDigest(child.change), profile.status.max_age_sec, false)],
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
stage_id: 'fitness', depends_on: ['exact-change'],
|
|
315
|
+
rule: { mode: 'all', distinct_subjects: false, distinct_keys: false },
|
|
316
|
+
requirements: [requirement('task-fitness', AUTONOMY_FITNESS_EVIDENCE_TYPE, 'agent-fitness-bench:v1', child.fitness.policy_digest, child.fitness.max_age_sec, true)],
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
stage_id: 'status-current', depends_on: ['fitness'],
|
|
320
|
+
rule: { mode: 'all', distinct_subjects: false, distinct_keys: false },
|
|
321
|
+
requirements: [requirement('not-suspended-or-revoked', 'ep-autonomy-status', profile.status.verifier_profile, profile.status.policy_digest, profile.status.max_age_sec, true)],
|
|
322
|
+
},
|
|
323
|
+
];
|
|
324
|
+
let terminal = 'status-current';
|
|
325
|
+
if (child.canary !== null) {
|
|
326
|
+
stages.push({
|
|
327
|
+
stage_id: 'canary-evidence', depends_on: [terminal],
|
|
328
|
+
rule: { mode: 'all', distinct_subjects: false, distinct_keys: false },
|
|
329
|
+
requirements: [requirement('bounded-canary', 'ep-canary-result', 'ep-canary-result:v1', child.canary.policy_digest, child.canary.max_age_sec, true)],
|
|
330
|
+
});
|
|
331
|
+
terminal = 'canary-evidence';
|
|
332
|
+
}
|
|
333
|
+
if (child.phase === 'rollback') {
|
|
334
|
+
stages.push({
|
|
335
|
+
stage_id: 'rollback-authorization', depends_on: [terminal],
|
|
336
|
+
rule: { mode: 'all', distinct_subjects: true, distinct_keys: true },
|
|
337
|
+
requirements: [requirement('new-rollback-authorization', 'ep-class-a-or-quorum', 'ep-human-authorization:v1', child.rollback.authorization_policy_digest, profile.root.authorization.max_age_sec, true)],
|
|
338
|
+
});
|
|
339
|
+
terminal = 'rollback-authorization';
|
|
340
|
+
}
|
|
341
|
+
return {
|
|
342
|
+
'@version': TRUST_PROGRAM_VERSION,
|
|
343
|
+
program_id: `acp.${profile.control_plane_id}.${String(index + 1).padStart(2, '0')}`,
|
|
344
|
+
version: profile.version,
|
|
345
|
+
root_caid: child.caid,
|
|
346
|
+
action_digest: child.action_digest,
|
|
347
|
+
valid_from: profile.valid_from,
|
|
348
|
+
expires_at: child.expires_at,
|
|
349
|
+
stages,
|
|
350
|
+
execution: {
|
|
351
|
+
depends_on: [terminal], consequence_mode: 'receipt-program',
|
|
352
|
+
capability_template_digest: child.capability_template_digest, escrow_profile_digest: null,
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export function compileAutonomyControlPlaneProfile(value: unknown): CompiledAutonomyControlPlane {
|
|
358
|
+
validateShape(value);
|
|
359
|
+
validateRoot(value);
|
|
360
|
+
const byId = validateGraph(value);
|
|
361
|
+
validateAuthority(value, byId);
|
|
362
|
+
validatePhases(value, byId);
|
|
363
|
+
const programs = (value.children as JsonRecord[]).map((child, index) => compileProgram(value, child, index));
|
|
364
|
+
return {
|
|
365
|
+
version: AUTONOMY_CONTROL_PLANE_VERSION,
|
|
366
|
+
profile_digest: canonicalDigest(value),
|
|
367
|
+
control_plane_id: value.control_plane_id,
|
|
368
|
+
programs,
|
|
369
|
+
claim_boundary: 'Typed action, audience, budget, expiry, diff, role, fitness, canary, status, and rollback bindings are validated. Natural-language goal entailment, human understanding, provider truth, and deployment completeness are not established.',
|
|
370
|
+
};
|
|
371
|
+
}
|