@victframework/runtime 0.1.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/dist/agent-governance.d.ts +285 -0
- package/dist/agent-governance.js +438 -0
- package/dist/agent-governance.js.map +1 -0
- package/dist/agent-registry.d.ts +103 -0
- package/dist/agent-registry.js +758 -0
- package/dist/agent-registry.js.map +1 -0
- package/dist/agent-types.d.ts +433 -0
- package/dist/agent-types.js +480 -0
- package/dist/agent-types.js.map +1 -0
- package/dist/authority.d.ts +83 -0
- package/dist/authority.js +170 -0
- package/dist/authority.js.map +1 -0
- package/dist/boundary-conformance.d.ts +51 -0
- package/dist/boundary-conformance.js +364 -0
- package/dist/boundary-conformance.js.map +1 -0
- package/dist/control-conformance.d.ts +68 -0
- package/dist/control-conformance.js +1131 -0
- package/dist/control-conformance.js.map +1 -0
- package/dist/control-in-memory.d.ts +257 -0
- package/dist/control-in-memory.js +886 -0
- package/dist/control-in-memory.js.map +1 -0
- package/dist/control-types.d.ts +1017 -0
- package/dist/control-types.js +779 -0
- package/dist/control-types.js.map +1 -0
- package/dist/durable-run.d.ts +73 -0
- package/dist/durable-run.js +262 -0
- package/dist/durable-run.js.map +1 -0
- package/dist/effect-policy.d.ts +22 -0
- package/dist/effect-policy.js +45 -0
- package/dist/effect-policy.js.map +1 -0
- package/dist/errors.d.ts +36 -0
- package/dist/errors.js +71 -0
- package/dist/errors.js.map +1 -0
- package/dist/in-memory-stores.d.ts +16 -0
- package/dist/in-memory-stores.js +522 -0
- package/dist/in-memory-stores.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/operator-config.d.ts +141 -0
- package/dist/operator-config.js +289 -0
- package/dist/operator-config.js.map +1 -0
- package/dist/orchestration-activation.d.ts +116 -0
- package/dist/orchestration-activation.js +223 -0
- package/dist/orchestration-activation.js.map +1 -0
- package/dist/orchestration-canary-conformance.d.ts +10 -0
- package/dist/orchestration-canary-conformance.js +726 -0
- package/dist/orchestration-canary-conformance.js.map +1 -0
- package/dist/orchestration-commands.d.ts +47 -0
- package/dist/orchestration-commands.js +538 -0
- package/dist/orchestration-commands.js.map +1 -0
- package/dist/orchestration-conformance.d.ts +59 -0
- package/dist/orchestration-conformance.js +594 -0
- package/dist/orchestration-conformance.js.map +1 -0
- package/dist/orchestration-driver-types.d.ts +163 -0
- package/dist/orchestration-driver-types.js +21 -0
- package/dist/orchestration-driver-types.js.map +1 -0
- package/dist/orchestration-driver.d.ts +48 -0
- package/dist/orchestration-driver.js +1030 -0
- package/dist/orchestration-driver.js.map +1 -0
- package/dist/orchestration-in-memory.d.ts +18 -0
- package/dist/orchestration-in-memory.js +1313 -0
- package/dist/orchestration-in-memory.js.map +1 -0
- package/dist/orchestration-join-conformance.d.ts +20 -0
- package/dist/orchestration-join-conformance.js +542 -0
- package/dist/orchestration-join-conformance.js.map +1 -0
- package/dist/orchestration-plan.d.ts +52 -0
- package/dist/orchestration-plan.js +265 -0
- package/dist/orchestration-plan.js.map +1 -0
- package/dist/orchestration-race-conformance.d.ts +35 -0
- package/dist/orchestration-race-conformance.js +1265 -0
- package/dist/orchestration-race-conformance.js.map +1 -0
- package/dist/orchestration-remediation-conformance.d.ts +54 -0
- package/dist/orchestration-remediation-conformance.js +632 -0
- package/dist/orchestration-remediation-conformance.js.map +1 -0
- package/dist/orchestration-store-types.d.ts +568 -0
- package/dist/orchestration-store-types.js +2 -0
- package/dist/orchestration-store-types.js.map +1 -0
- package/dist/pack-conformance.d.ts +59 -0
- package/dist/pack-conformance.js +222 -0
- package/dist/pack-conformance.js.map +1 -0
- package/dist/pack-install.d.ts +44 -0
- package/dist/pack-install.js +126 -0
- package/dist/pack-install.js.map +1 -0
- package/dist/registry.d.ts +107 -0
- package/dist/registry.js +481 -0
- package/dist/registry.js.map +1 -0
- package/dist/runtime.d.ts +158 -0
- package/dist/runtime.js +1341 -0
- package/dist/runtime.js.map +1 -0
- package/dist/serialization.d.ts +26 -0
- package/dist/serialization.js +162 -0
- package/dist/serialization.js.map +1 -0
- package/dist/store-conformance.d.ts +45 -0
- package/dist/store-conformance.js +876 -0
- package/dist/store-conformance.js.map +1 -0
- package/dist/store-errors.d.ts +49 -0
- package/dist/store-errors.js +48 -0
- package/dist/store-errors.js.map +1 -0
- package/dist/store-types.d.ts +331 -0
- package/dist/store-types.js +14 -0
- package/dist/store-types.js.map +1 -0
- package/dist/store-validation.d.ts +63 -0
- package/dist/store-validation.js +164 -0
- package/dist/store-validation.js.map +1 -0
- package/dist/stream-hub.d.ts +151 -0
- package/dist/stream-hub.js +402 -0
- package/dist/stream-hub.js.map +1 -0
- package/dist/testing.d.ts +25 -0
- package/dist/testing.js +19 -0
- package/dist/testing.js.map +1 -0
- package/dist/types.d.ts +221 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
import { AGENT_ACTIVATION_IDENTITY_SCHEMA, validateAgentActivationRecord } from './agent-types.js';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { canonicalJson, compileAgentProfile } from '@victframework/kernel';
|
|
4
|
+
import { VictRuntimeError } from './errors.js';
|
|
5
|
+
/**
|
|
6
|
+
* Stage 06A — agent-profile registry, artifact registry, and immutable
|
|
7
|
+
* activation snapshots.
|
|
8
|
+
*
|
|
9
|
+
* Registration discipline (established atomic-staging pattern):
|
|
10
|
+
* - direct registration is atomic — a failed registration leaves the
|
|
11
|
+
* registries semantically unchanged;
|
|
12
|
+
* - duplicates fail; intentional replacement goes through an explicit
|
|
13
|
+
* `replaceProfile` that pins the expected previous revision;
|
|
14
|
+
* - caller objects are deep-captured at registration: later mutation of an
|
|
15
|
+
* original definition has no effect;
|
|
16
|
+
* - activation resolves EVERY revisioned component to its EXACT revision
|
|
17
|
+
* and deep-captures an immutable VICT-owned snapshot; missing or
|
|
18
|
+
* mismatched artifacts fail closed — a current definition never silently
|
|
19
|
+
* substitutes for a pinned older revision;
|
|
20
|
+
* - registry maps are never exposed; consumers receive frozen descriptors
|
|
21
|
+
* or activation snapshots.
|
|
22
|
+
*
|
|
23
|
+
* Function references (helper-tool executes, processor transforms,
|
|
24
|
+
* guardrail checks) are captured by reference at activation and are never
|
|
25
|
+
* hashed or serialized. Their bodies never participate in identity.
|
|
26
|
+
*/
|
|
27
|
+
const MAX_TEXT_LENGTH = 64 * 1024;
|
|
28
|
+
const WORKING_MEMORY_FIELDS = new Set(['enabled', 'template']);
|
|
29
|
+
/** Bounded pattern for author-declared guardrail failure codes. */
|
|
30
|
+
const GUARDRAIL_CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,31}$/;
|
|
31
|
+
const MAX_GUARDRAIL_CODES = 16;
|
|
32
|
+
/** Frozen deep copy of plain canonical data (validated before capture). */
|
|
33
|
+
function frozenCopy(value) {
|
|
34
|
+
if (Array.isArray(value)) {
|
|
35
|
+
return Object.freeze(value.map(frozenCopy));
|
|
36
|
+
}
|
|
37
|
+
if (typeof value === 'object' && value !== null) {
|
|
38
|
+
const out = {};
|
|
39
|
+
for (const key of Object.keys(value)) {
|
|
40
|
+
out[key] = frozenCopy(value[key]);
|
|
41
|
+
}
|
|
42
|
+
return Object.freeze(out);
|
|
43
|
+
}
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Defensive descriptor of an artifact: data is deep-copied and frozen;
|
|
48
|
+
* function references (helper executes, processor transforms, guardrail
|
|
49
|
+
* checks, contract parsers) are preserved BY REFERENCE and never copied or
|
|
50
|
+
* serialized. Every resolution returns a FRESH descriptor, so mutating a
|
|
51
|
+
* previously resolved value can never affect a later resolution or an
|
|
52
|
+
* activation, and the registry's internal objects are never exposed.
|
|
53
|
+
*/
|
|
54
|
+
function defensiveArtifactDescriptor(artifact) {
|
|
55
|
+
return frozenCopy(artifact);
|
|
56
|
+
}
|
|
57
|
+
function requireId(id, what) {
|
|
58
|
+
if (typeof id !== 'string' || id.trim().length === 0 || id.length > 128) {
|
|
59
|
+
throw new VictRuntimeError('VICT_AGENT_INVALID_ARTIFACT', `${what} requires a non-empty id of at most 128 characters.`);
|
|
60
|
+
}
|
|
61
|
+
return id;
|
|
62
|
+
}
|
|
63
|
+
function requireRevision(revision, what) {
|
|
64
|
+
if (typeof revision !== 'string' || revision.trim().length === 0 || revision.length > 128) {
|
|
65
|
+
throw new VictRuntimeError('VICT_AGENT_INVALID_ARTIFACT', `${what} requires a non-empty revision of at most 128 characters.`);
|
|
66
|
+
}
|
|
67
|
+
return revision;
|
|
68
|
+
}
|
|
69
|
+
/** The strict closed field set of a helper-tool definition. */
|
|
70
|
+
const HELPER_TOOL_FIELDS = new Set([
|
|
71
|
+
'id',
|
|
72
|
+
'revision',
|
|
73
|
+
'description',
|
|
74
|
+
'effect',
|
|
75
|
+
'input',
|
|
76
|
+
'output',
|
|
77
|
+
'execute',
|
|
78
|
+
]);
|
|
79
|
+
const HELPER_IO_FIELDS = new Set(['id', 'revision', 'jsonSchema', 'parse']);
|
|
80
|
+
/** Validate a helper-tool definition at the public registration boundary. */
|
|
81
|
+
function validateHelperToolDefinition(artifactId, artifactRevision, definition) {
|
|
82
|
+
requireId(definition.id, `Helper tool`);
|
|
83
|
+
requireRevision(definition.revision, `Helper tool '${String(definition.id)}'`);
|
|
84
|
+
// The artifact wrapper's id/revision must agree with the definition's
|
|
85
|
+
// own identity: a disagreement means the declared reference does not
|
|
86
|
+
// name the implementation that would execute.
|
|
87
|
+
if (definition.id !== artifactId || definition.revision !== artifactRevision) {
|
|
88
|
+
throw new VictRuntimeError('VICT_AGENT_INVALID_ARTIFACT', `Helper tool artifact declares outer identity '${artifactId}' (revision '${artifactRevision}') but its definition declares '${definition.id}' (revision '${definition.revision}'); the outer id/revision must agree with the definition.`, { artifactId });
|
|
89
|
+
}
|
|
90
|
+
for (const key of Object.keys(definition)) {
|
|
91
|
+
if (!HELPER_TOOL_FIELDS.has(key)) {
|
|
92
|
+
throw new VictRuntimeError('VICT_AGENT_UNKNOWN_ARTIFACT_FIELD', `Helper tool '${definition.id}' declares unknown field '${key}'; the helper-tool schema is closed.`, { helperToolId: definition.id, field: key });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (typeof definition.description !== 'string' ||
|
|
96
|
+
definition.description.length === 0 ||
|
|
97
|
+
definition.description.length > 512) {
|
|
98
|
+
throw new VictRuntimeError('VICT_AGENT_INVALID_ARTIFACT', `Helper tool '${definition.id}' requires a non-empty description of at most 512 characters.`, { helperToolId: definition.id });
|
|
99
|
+
}
|
|
100
|
+
// §6.5: a helper is allowed only when it is genuinely pure. Any other
|
|
101
|
+
// declared effect — including the runtime effect vocabulary — is rejected
|
|
102
|
+
// BEFORE activation. Effectful work belongs to VICT capabilities.
|
|
103
|
+
if (definition.effect !== 'pure') {
|
|
104
|
+
throw new VictRuntimeError('VICT_AGENT_HELPER_TOOL_NOT_PURE', `Helper tool '${definition.id}' declares effect '${String(definition.effect)}'; adapter-native helper tools must be exactly 'pure' (deterministic formatting/computation/transformation with no external or durable effect). Effectful work must cross the VICT capability boundary.`, { helperToolId: definition.id, effect: String(definition.effect) });
|
|
105
|
+
}
|
|
106
|
+
for (const role of ['input', 'output']) {
|
|
107
|
+
const io = definition[role];
|
|
108
|
+
if (typeof io !== 'object' || io === null) {
|
|
109
|
+
throw new VictRuntimeError('VICT_AGENT_INVALID_ARTIFACT', `Helper tool '${definition.id}' must declare a neutral ${role} contract.`, { helperToolId: definition.id, role });
|
|
110
|
+
}
|
|
111
|
+
const record = io;
|
|
112
|
+
for (const key of Object.keys(record)) {
|
|
113
|
+
if (!HELPER_IO_FIELDS.has(key)) {
|
|
114
|
+
throw new VictRuntimeError('VICT_AGENT_UNKNOWN_ARTIFACT_FIELD', `Helper tool '${definition.id}' declares unknown field '${key}' in its ${role} contract; the contract binding is closed.`, { helperToolId: definition.id, field: key });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
requireId(record.id, `Helper tool '${definition.id}' ${role} contract`);
|
|
118
|
+
requireRevision(record.revision, `Helper tool '${definition.id}' ${role} contract`);
|
|
119
|
+
if (typeof record.parse !== 'function') {
|
|
120
|
+
throw new VictRuntimeError('VICT_AGENT_INVALID_ARTIFACT', `Helper tool '${definition.id}' ${role} contract must provide a parse callable.`, { helperToolId: definition.id, role });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (typeof definition.execute !== 'function') {
|
|
124
|
+
throw new VictRuntimeError('VICT_AGENT_INVALID_ARTIFACT', `Helper tool '${definition.id}' must provide an execute function.`, { helperToolId: definition.id });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** Validate a memory-policy config at the public registration boundary. */
|
|
128
|
+
function validateMemoryPolicyConfig(config, id) {
|
|
129
|
+
if (typeof config !== 'object' || config === null || Array.isArray(config)) {
|
|
130
|
+
throw new VictRuntimeError('VICT_AGENT_INVALID_ARTIFACT', `Memory policy '${id}' requires a plain config object.`, { memoryPolicyId: id });
|
|
131
|
+
}
|
|
132
|
+
const record = config;
|
|
133
|
+
const allowed = new Set(['lastMessages', 'workingMemory', 'semanticRecall']);
|
|
134
|
+
for (const key of Object.keys(record)) {
|
|
135
|
+
if (!allowed.has(key)) {
|
|
136
|
+
throw new VictRuntimeError('VICT_AGENT_UNKNOWN_ARTIFACT_FIELD', `Memory policy '${id}' declares unknown field '${key}'; the memory-policy config schema is closed.`, { memoryPolicyId: id, field: key });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const lastMessages = record.lastMessages;
|
|
140
|
+
if (lastMessages === undefined) {
|
|
141
|
+
throw new VictRuntimeError('VICT_AGENT_INVALID_ARTIFACT', `Memory policy '${id}' must declare 'lastMessages' explicitly (a positive integer or exactly false).`, { memoryPolicyId: id });
|
|
142
|
+
}
|
|
143
|
+
if (lastMessages !== false &&
|
|
144
|
+
(typeof lastMessages !== 'number' ||
|
|
145
|
+
!Number.isInteger(lastMessages) ||
|
|
146
|
+
lastMessages < 1 ||
|
|
147
|
+
lastMessages > 1000)) {
|
|
148
|
+
throw new VictRuntimeError('VICT_AGENT_INVALID_ARTIFACT', `Memory policy '${id}' declares an invalid 'lastMessages' window; use an integer within [1, 1000] or exactly false.`, { memoryPolicyId: id });
|
|
149
|
+
}
|
|
150
|
+
if (record.workingMemory === undefined ||
|
|
151
|
+
typeof record.workingMemory !== 'object' ||
|
|
152
|
+
record.workingMemory === null) {
|
|
153
|
+
throw new VictRuntimeError('VICT_AGENT_INVALID_ARTIFACT', `Memory policy '${id}' must declare 'workingMemory' explicitly.`, { memoryPolicyId: id });
|
|
154
|
+
}
|
|
155
|
+
const workingMemory = record.workingMemory;
|
|
156
|
+
for (const key of Object.keys(workingMemory)) {
|
|
157
|
+
if (!WORKING_MEMORY_FIELDS.has(key)) {
|
|
158
|
+
throw new VictRuntimeError('VICT_AGENT_UNKNOWN_ARTIFACT_FIELD', `Memory policy '${id}' declares unknown working-memory field '${key}'.`, { memoryPolicyId: id, field: key });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (typeof workingMemory.enabled !== 'boolean') {
|
|
162
|
+
throw new VictRuntimeError('VICT_AGENT_INVALID_ARTIFACT', `Memory policy '${id}' must declare 'workingMemory.enabled' as a boolean.`, { memoryPolicyId: id });
|
|
163
|
+
}
|
|
164
|
+
if (workingMemory.template !== undefined &&
|
|
165
|
+
(typeof workingMemory.template !== 'string' || workingMemory.template.length > MAX_TEXT_LENGTH)) {
|
|
166
|
+
throw new VictRuntimeError('VICT_AGENT_INVALID_ARTIFACT', `Memory policy '${id}' declares an invalid working-memory template (non-empty string of at most ${MAX_TEXT_LENGTH} characters when enabled).`, { memoryPolicyId: id });
|
|
167
|
+
}
|
|
168
|
+
if (record.semanticRecall !== false) {
|
|
169
|
+
throw new VictRuntimeError('VICT_AGENT_INVALID_ARTIFACT', `Memory policy '${id}' must declare 'semanticRecall: false' explicitly in this adapter revision; enabling semantic recall requires an embedding model, which the pinned offline profile does not provide.`, { memoryPolicyId: id });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Owns registered agent artifacts and compiled agent profiles for one
|
|
174
|
+
* runtime composition. Not a global registry: every composition carries
|
|
175
|
+
* its own.
|
|
176
|
+
*/
|
|
177
|
+
export class AgentProfileRegistry {
|
|
178
|
+
#artifacts = new Map();
|
|
179
|
+
#profiles = new Map();
|
|
180
|
+
#compiled = new Map();
|
|
181
|
+
#options;
|
|
182
|
+
constructor(options = {}) {
|
|
183
|
+
this.#options = options;
|
|
184
|
+
}
|
|
185
|
+
// ---- Artifacts -----------------------------------------------------------
|
|
186
|
+
/**
|
|
187
|
+
* Validate one artifact and produce the normalized frozen internal
|
|
188
|
+
* capture — WITHOUT touching the registry. Throws on any invalid input.
|
|
189
|
+
*/
|
|
190
|
+
#stage(artifact) {
|
|
191
|
+
switch (artifact.kind) {
|
|
192
|
+
case 'instructions': {
|
|
193
|
+
requireId(artifact.id, 'Instructions artifact');
|
|
194
|
+
requireRevision(artifact.revision, `Instructions '${artifact.id}'`);
|
|
195
|
+
if (typeof artifact.text !== 'string' ||
|
|
196
|
+
artifact.text.length === 0 ||
|
|
197
|
+
artifact.text.length > MAX_TEXT_LENGTH) {
|
|
198
|
+
throw new VictRuntimeError('VICT_AGENT_INVALID_ARTIFACT', `Instructions '${artifact.id}' require non-empty text of at most ${MAX_TEXT_LENGTH} characters.`, { artifactId: artifact.id });
|
|
199
|
+
}
|
|
200
|
+
return Object.freeze({
|
|
201
|
+
kind: 'instructions',
|
|
202
|
+
id: artifact.id,
|
|
203
|
+
revision: artifact.revision,
|
|
204
|
+
text: artifact.text,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
case 'memory-policy': {
|
|
208
|
+
requireId(artifact.id, 'Memory-policy artifact');
|
|
209
|
+
requireRevision(artifact.revision, `Memory policy '${artifact.id}'`);
|
|
210
|
+
validateMemoryPolicyConfig(artifact.config, artifact.id);
|
|
211
|
+
return Object.freeze({
|
|
212
|
+
kind: 'memory-policy',
|
|
213
|
+
id: artifact.id,
|
|
214
|
+
revision: artifact.revision,
|
|
215
|
+
config: frozenCopy(artifact.config),
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
case 'helper-tool': {
|
|
219
|
+
requireId(artifact.id, 'Helper-tool artifact');
|
|
220
|
+
requireRevision(artifact.revision, `Helper tool '${artifact.id}'`);
|
|
221
|
+
validateHelperToolDefinition(artifact.id, artifact.revision, artifact.definition);
|
|
222
|
+
return Object.freeze({
|
|
223
|
+
kind: 'helper-tool',
|
|
224
|
+
id: artifact.id,
|
|
225
|
+
revision: artifact.revision,
|
|
226
|
+
definition: frozenCopy(artifact.definition),
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
case 'processor': {
|
|
230
|
+
requireId(artifact.id, 'Processor artifact');
|
|
231
|
+
requireRevision(artifact.revision, `Processor '${artifact.id}'`);
|
|
232
|
+
if (typeof artifact.transform !== 'function') {
|
|
233
|
+
throw new VictRuntimeError('VICT_AGENT_INVALID_ARTIFACT', `Processor '${artifact.id}' must provide a pure transform function.`, { artifactId: artifact.id });
|
|
234
|
+
}
|
|
235
|
+
return Object.freeze({
|
|
236
|
+
kind: 'processor',
|
|
237
|
+
id: artifact.id,
|
|
238
|
+
revision: artifact.revision,
|
|
239
|
+
transform: artifact.transform,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
case 'guardrail': {
|
|
243
|
+
requireId(artifact.id, 'Guardrail artifact');
|
|
244
|
+
requireRevision(artifact.revision, `Guardrail '${artifact.id}'`);
|
|
245
|
+
if (typeof artifact.check !== 'function') {
|
|
246
|
+
throw new VictRuntimeError('VICT_AGENT_INVALID_ARTIFACT', `Guardrail '${artifact.id}' must provide a pure check function.`, { artifactId: artifact.id });
|
|
247
|
+
}
|
|
248
|
+
if (artifact.failureCodes !== undefined) {
|
|
249
|
+
if (!Array.isArray(artifact.failureCodes) ||
|
|
250
|
+
artifact.failureCodes.length > MAX_GUARDRAIL_CODES) {
|
|
251
|
+
throw new VictRuntimeError('VICT_AGENT_INVALID_ARTIFACT', `Guardrail '${artifact.id}' declares an invalid failureCodes set (at most ${MAX_GUARDRAIL_CODES} codes).`, { artifactId: artifact.id });
|
|
252
|
+
}
|
|
253
|
+
const seen = new Set();
|
|
254
|
+
for (const code of artifact.failureCodes) {
|
|
255
|
+
if (typeof code !== 'string' || !GUARDRAIL_CODE_PATTERN.test(code) || seen.has(code)) {
|
|
256
|
+
throw new VictRuntimeError('VICT_AGENT_INVALID_ARTIFACT', `Guardrail '${artifact.id}' declares an invalid or duplicate failure code; codes must match ^[A-Z][A-Z0-9_]{0,31}$ and must not repeat.`, { artifactId: artifact.id });
|
|
257
|
+
}
|
|
258
|
+
seen.add(code);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return Object.freeze({
|
|
262
|
+
kind: 'guardrail',
|
|
263
|
+
id: artifact.id,
|
|
264
|
+
revision: artifact.revision,
|
|
265
|
+
check: artifact.check,
|
|
266
|
+
...(artifact.failureCodes !== undefined
|
|
267
|
+
? { failureCodes: Object.freeze([...artifact.failureCodes]) }
|
|
268
|
+
: {}),
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
case 'structured-output-contract': {
|
|
272
|
+
requireId(artifact.id, 'Structured-output contract artifact');
|
|
273
|
+
requireRevision(artifact.revision, `Structured-output contract '${artifact.id}'`);
|
|
274
|
+
if (typeof artifact.description !== 'string' ||
|
|
275
|
+
artifact.description.length === 0 ||
|
|
276
|
+
artifact.description.length > 512) {
|
|
277
|
+
throw new VictRuntimeError('VICT_AGENT_INVALID_ARTIFACT', `Structured-output contract '${artifact.id}' requires a non-empty description of at most 512 characters.`, { artifactId: artifact.id });
|
|
278
|
+
}
|
|
279
|
+
if (typeof artifact.parse !== 'function') {
|
|
280
|
+
throw new VictRuntimeError('VICT_AGENT_INVALID_ARTIFACT', `Structured-output contract '${artifact.id}' must provide a parse callable.`, { artifactId: artifact.id });
|
|
281
|
+
}
|
|
282
|
+
return Object.freeze({
|
|
283
|
+
kind: 'structured-output-contract',
|
|
284
|
+
id: artifact.id,
|
|
285
|
+
revision: artifact.revision,
|
|
286
|
+
description: artifact.description,
|
|
287
|
+
parse: artifact.parse,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
case 'workflow': {
|
|
291
|
+
requireId(artifact.id, 'Workflow artifact');
|
|
292
|
+
requireRevision(artifact.revision, `Workflow '${artifact.id}'`);
|
|
293
|
+
if (typeof artifact.description !== 'string' || artifact.description.length === 0) {
|
|
294
|
+
throw new VictRuntimeError('VICT_AGENT_INVALID_ARTIFACT', `Workflow '${artifact.id}' requires a non-empty description.`, { artifactId: artifact.id });
|
|
295
|
+
}
|
|
296
|
+
return Object.freeze({
|
|
297
|
+
kind: 'workflow',
|
|
298
|
+
id: artifact.id,
|
|
299
|
+
revision: artifact.revision,
|
|
300
|
+
description: artifact.description,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
default: {
|
|
304
|
+
throw new VictRuntimeError('VICT_AGENT_INVALID_ARTIFACT', 'An artifact requires a supported kind.');
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/** Register one artifact atomically; duplicates of the same (id, revision) fail. */
|
|
309
|
+
registerArtifact(artifact) {
|
|
310
|
+
const staged = this.#stage(artifact);
|
|
311
|
+
if (this.#artifacts.has(artifactKey(staged))) {
|
|
312
|
+
throw new VictRuntimeError('VICT_AGENT_DUPLICATE_ARTIFACT', `A ${staged.kind} artifact '${staged.id}' (revision '${staged.revision}') is already registered; use an intentional replacement API instead of re-registering.`, { artifactId: staged.id, revision: staged.revision });
|
|
313
|
+
}
|
|
314
|
+
this.#artifacts.set(artifactKey(staged), staged);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Register many artifacts atomically: ALL or NOTHING.
|
|
318
|
+
*
|
|
319
|
+
* Preflight (before anything becomes observable):
|
|
320
|
+
* 1. every artifact is validated and staged;
|
|
321
|
+
* 2. every staged key is checked against the EXISTING registry content;
|
|
322
|
+
* 3. every staged key is checked against the OTHER staged entries
|
|
323
|
+
* (intra-batch duplicates).
|
|
324
|
+
*
|
|
325
|
+
* If any artifact is invalid or conflicting, nothing from the batch is
|
|
326
|
+
* committed — the registry is byte-for-byte unchanged, and a corrected
|
|
327
|
+
* batch can be retried.
|
|
328
|
+
*/
|
|
329
|
+
installArtifacts(artifacts) {
|
|
330
|
+
const staged = [];
|
|
331
|
+
for (const artifact of artifacts) {
|
|
332
|
+
staged.push(this.#stage(artifact));
|
|
333
|
+
}
|
|
334
|
+
const seen = new Set();
|
|
335
|
+
for (const artifact of staged) {
|
|
336
|
+
const key = artifactKey(artifact);
|
|
337
|
+
if (this.#artifacts.has(key)) {
|
|
338
|
+
throw new VictRuntimeError('VICT_AGENT_DUPLICATE_ARTIFACT', `A ${artifact.kind} artifact '${artifact.id}' (revision '${artifact.revision}') is already registered; the batch was rejected atomically and nothing from it was installed.`, { artifactId: artifact.id, revision: artifact.revision });
|
|
339
|
+
}
|
|
340
|
+
if (seen.has(key)) {
|
|
341
|
+
throw new VictRuntimeError('VICT_AGENT_DUPLICATE_ARTIFACT', `The batch declares ${artifact.kind} '${artifact.id}' (revision '${artifact.revision}') more than once; the batch was rejected atomically and nothing from it was installed.`, { artifactId: artifact.id, revision: artifact.revision });
|
|
342
|
+
}
|
|
343
|
+
seen.add(key);
|
|
344
|
+
}
|
|
345
|
+
for (const artifact of staged) {
|
|
346
|
+
this.#artifacts.set(artifactKey(artifact), artifact);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Resolve one artifact by kind + exact revision. The result is a FRESH
|
|
351
|
+
* defensive descriptor: data members are deep-copied and frozen, function
|
|
352
|
+
* references are preserved by reference, and the registry's internal
|
|
353
|
+
* objects are never exposed. Mutating a resolved value cannot alter a
|
|
354
|
+
* later resolution or an activation.
|
|
355
|
+
*/
|
|
356
|
+
resolveArtifact(kind, id, revision) {
|
|
357
|
+
const internal = this.#artifacts.get(`${kind}\u0000${id}\u0000${revision}`);
|
|
358
|
+
return internal === undefined ? undefined : defensiveArtifactDescriptor(internal);
|
|
359
|
+
}
|
|
360
|
+
// ---- Profiles -------------------------------------------------------------
|
|
361
|
+
/**
|
|
362
|
+
* Register a compiled agent profile atomically. Duplicate registration
|
|
363
|
+
* of the same (id, revision) fails; a NEW revision for the same id adds
|
|
364
|
+
* a resolvable revision for exact pinned restoration.
|
|
365
|
+
*/
|
|
366
|
+
registerProfile(profile) {
|
|
367
|
+
const compiled = compileAgentProfile(profile);
|
|
368
|
+
if (!compiled.ok) {
|
|
369
|
+
throw new VictRuntimeError('VICT_AGENT_INVALID_PROFILE', `Agent profile '${String(profile.id)}' failed compilation with ${compiled.issues.length} issue(s); the first issue is ${compiled.issues[0]?.code} at '${compiled.issues[0]?.path ?? '(profile)'}'.`);
|
|
370
|
+
}
|
|
371
|
+
return this.#commitProfile(compiled.value);
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Intentional replacement: registers a new profile revision and REPLACES
|
|
375
|
+
* the current pointer only when the caller pins the expected previous
|
|
376
|
+
* revision. Affects only later activations — never an active snapshot.
|
|
377
|
+
*/
|
|
378
|
+
replaceProfile(options) {
|
|
379
|
+
const { profile } = options;
|
|
380
|
+
const compiled = compileAgentProfile(profile);
|
|
381
|
+
if (!compiled.ok) {
|
|
382
|
+
throw new VictRuntimeError('VICT_AGENT_INVALID_PROFILE', `Agent profile replacement failed compilation with ${compiled.issues.length} issue(s).`);
|
|
383
|
+
}
|
|
384
|
+
const id = compiled.value.profile.id;
|
|
385
|
+
const previous = this.#currentRevision(id);
|
|
386
|
+
if (previous !== options.expectedPreviousRevision) {
|
|
387
|
+
throw new VictRuntimeError('VICT_AGENT_REPLACE_CONFLICT', `Agent profile '${id}' current revision is '${String(previous)}', not the expected '${options.expectedPreviousRevision}'; refusing the replace.`, { agentId: id });
|
|
388
|
+
}
|
|
389
|
+
return this.#commitProfile(compiled.value);
|
|
390
|
+
}
|
|
391
|
+
#currentRevision(id) {
|
|
392
|
+
const revisions = this.#profiles.get(id);
|
|
393
|
+
if (revisions === undefined || revisions.size === 0) {
|
|
394
|
+
return undefined;
|
|
395
|
+
}
|
|
396
|
+
// Registration order is preserved by Map; the last registered revision
|
|
397
|
+
// is the current one.
|
|
398
|
+
return [...revisions.keys()].at(-1);
|
|
399
|
+
}
|
|
400
|
+
#commitProfile(compiled) {
|
|
401
|
+
const id = compiled.profile.id;
|
|
402
|
+
const revision = compiled.profile.revision;
|
|
403
|
+
let revisions = this.#profiles.get(id);
|
|
404
|
+
if (revisions !== undefined && revisions.has(revision)) {
|
|
405
|
+
throw new VictRuntimeError('VICT_AGENT_DUPLICATE_PROFILE', `Agent profile '${id}' (revision '${revision}') is already registered; use replaceProfile for an intentional replacement.`, { agentId: id, revision });
|
|
406
|
+
}
|
|
407
|
+
if (revisions === undefined) {
|
|
408
|
+
revisions = new Map();
|
|
409
|
+
this.#profiles.set(id, revisions);
|
|
410
|
+
this.#compiled.set(id, new Map());
|
|
411
|
+
}
|
|
412
|
+
revisions.set(revision, compiled.profile);
|
|
413
|
+
this.#compiled.get(id).set(revision, compiled);
|
|
414
|
+
return { agentProfileVersion: compiled.agentProfileVersion };
|
|
415
|
+
}
|
|
416
|
+
/** Resolve a compiled profile by id + exact revision (default: current). */
|
|
417
|
+
resolveProfile(id, revision) {
|
|
418
|
+
const revisions = this.#compiled.get(id);
|
|
419
|
+
if (revisions === undefined) {
|
|
420
|
+
return undefined;
|
|
421
|
+
}
|
|
422
|
+
const target = revision ?? this.#currentRevision(id);
|
|
423
|
+
if (target === undefined) {
|
|
424
|
+
return undefined;
|
|
425
|
+
}
|
|
426
|
+
return revisions.get(target);
|
|
427
|
+
}
|
|
428
|
+
// ---- Activation -------------------------------------------------------------
|
|
429
|
+
/**
|
|
430
|
+
* Activate an agent profile: resolve EVERY revisioned component to its
|
|
431
|
+
* exact revision — including a declared structured-output contract —
|
|
432
|
+
* verify the capability envelope (fail closed when a resolver is missing),
|
|
433
|
+
* and capture an immutable snapshot. Missing or mismatched artifacts fail
|
|
434
|
+
* closed; identity-only references are never treated as executable
|
|
435
|
+
* bindings.
|
|
436
|
+
*/
|
|
437
|
+
activateAgentProfile(options) {
|
|
438
|
+
const compiled = this.resolveProfile(options.id, options.revision);
|
|
439
|
+
if (compiled === undefined) {
|
|
440
|
+
throw new VictRuntimeError('VICT_AGENT_PROFILE_NOT_FOUND', `Agent profile '${options.id}' (revision '${String(options.revision ?? 'current')}') is not registered; activation fails closed.`, { agentId: options.id, revision: options.revision });
|
|
441
|
+
}
|
|
442
|
+
const profile = compiled.profile;
|
|
443
|
+
const clock = this.#options.clock ?? (() => Date.now());
|
|
444
|
+
const instructions = this.#resolveExact('instructions', profile.instructions);
|
|
445
|
+
const memoryPolicy = this.#resolveExact('memory-policy', profile.memoryPolicy);
|
|
446
|
+
const helperTools = (profile.helperTools ?? []).map((reference) => this.#resolveExact('helper-tool', reference));
|
|
447
|
+
const processors = (profile.processors ?? []).map((reference) => this.#resolveExact('processor', reference));
|
|
448
|
+
const guardrails = (profile.guardrails ?? []).map((reference) => this.#resolveExact('guardrail', reference));
|
|
449
|
+
const workflows = (profile.workflows ?? []).map((reference) => this.#resolveExact('workflow', reference));
|
|
450
|
+
// A declared structured-output contract is an executable binding: it
|
|
451
|
+
// must resolve at its exact id + revision, and its actual parser
|
|
452
|
+
// semantics are captured by reference. A nonexistent or mismatched
|
|
453
|
+
// contract fails activation — the identity reference alone is never
|
|
454
|
+
// treated as a binding.
|
|
455
|
+
const structuredOutput = profile.structuredOutput
|
|
456
|
+
? this.#resolveExact('structured-output-contract', profile.structuredOutput.contract)
|
|
457
|
+
: undefined;
|
|
458
|
+
// Sub-agents resolve to other registered agent profiles (exact revision).
|
|
459
|
+
// The RESOLVED child profile identity (its computed agentProfileVersion)
|
|
460
|
+
// becomes part of the authoritative activation binding below: a stored
|
|
461
|
+
// activation pins not only which child revision it referenced but what
|
|
462
|
+
// that child resolved to. A differently-resolved child profile can never
|
|
463
|
+
// silently restore under the same activation identity.
|
|
464
|
+
const subagents = (profile.subagents ?? []).map((reference) => {
|
|
465
|
+
const sub = this.resolveProfile(reference.id, reference.revision);
|
|
466
|
+
if (sub === undefined) {
|
|
467
|
+
throw new VictRuntimeError('VICT_AGENT_ARTIFACT_MISSING', `Sub-agent '${reference.id}' (revision '${reference.revision}') is not registered; activation fails closed.`, { agentId: reference.id, revision: reference.revision });
|
|
468
|
+
}
|
|
469
|
+
return { reference, agentProfileVersion: sub.agentProfileVersion };
|
|
470
|
+
});
|
|
471
|
+
// Capability envelope: exact-revision existence, fail closed. A profile
|
|
472
|
+
// that DECLARES capability references but is activated WITHOUT an
|
|
473
|
+
// exact-revision resolver cannot prove its authority envelope and
|
|
474
|
+
// fails closed — the envelope is never silently accepted.
|
|
475
|
+
const declaredCapabilities = profile.capabilities ?? [];
|
|
476
|
+
if (declaredCapabilities.length > 0 && this.#options.resolveCapabilityRevision === undefined) {
|
|
477
|
+
throw new VictRuntimeError('VICT_AGENT_CAPABILITY_RESOLVER_MISSING', `The profile declares ${declaredCapabilities.length} capability reference(s) but no exact-revision capability resolver is configured; the authority envelope cannot be proven and activation fails closed.`, { agentId: profile.id });
|
|
478
|
+
}
|
|
479
|
+
for (const reference of declaredCapabilities) {
|
|
480
|
+
const resolver = this.#options.resolveCapabilityRevision;
|
|
481
|
+
if (resolver !== undefined && !resolver(reference.id, reference.revision)) {
|
|
482
|
+
throw new VictRuntimeError('VICT_AGENT_CAPABILITY_MISSING', `Capability '${reference.id}' (revision '${reference.revision}') is not registered; the pinned authority envelope cannot be resolved.`, { capabilityId: reference.id, revision: reference.revision });
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
helperTools.sort((a, b) => a.reference.id < b.reference.id ? -1 : a.reference.id > b.reference.id ? 1 : 0);
|
|
486
|
+
workflows.sort((a, b) => a.reference.id < b.reference.id ? -1 : a.reference.id > b.reference.id ? 1 : 0);
|
|
487
|
+
// Adapter compatibility: the exact marker the activation was resolved
|
|
488
|
+
// under (defensive canonical copy of the compiled profile's marker).
|
|
489
|
+
const adapterCompatibility = frozenCopy({
|
|
490
|
+
id: profile.adapter.id,
|
|
491
|
+
revision: profile.adapter.revision,
|
|
492
|
+
runtimePackages: { ...profile.adapter.runtimePackages },
|
|
493
|
+
});
|
|
494
|
+
// Canonical ACTIVATION manifest (distinct from the profile manifest):
|
|
495
|
+
// covers the exact resolved executable activation — the profile
|
|
496
|
+
// identity, the adapter compatibility metadata, every resolved
|
|
497
|
+
// artifact binding (including the structured-output contract,
|
|
498
|
+
// sub-agents, and the capability envelope), AND the resolved identity
|
|
499
|
+
// of every referenced sub-agent profile. Canonically sorted;
|
|
500
|
+
// insertion order never matters.
|
|
501
|
+
const subagentIdentities = subagents
|
|
502
|
+
.map((binding) => ({
|
|
503
|
+
id: binding.reference.id,
|
|
504
|
+
revision: binding.reference.revision,
|
|
505
|
+
agentProfileVersion: binding.agentProfileVersion,
|
|
506
|
+
}))
|
|
507
|
+
.sort((a, b) => a.id < b.id
|
|
508
|
+
? -1
|
|
509
|
+
: a.id > b.id
|
|
510
|
+
? 1
|
|
511
|
+
: a.revision < b.revision
|
|
512
|
+
? -1
|
|
513
|
+
: a.revision > b.revision
|
|
514
|
+
? 1
|
|
515
|
+
: 0);
|
|
516
|
+
const artifactList = [
|
|
517
|
+
{
|
|
518
|
+
kind: 'instructions',
|
|
519
|
+
id: instructions.reference.id,
|
|
520
|
+
revision: instructions.reference.revision,
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
kind: 'memory-policy',
|
|
524
|
+
id: memoryPolicy.reference.id,
|
|
525
|
+
revision: memoryPolicy.reference.revision,
|
|
526
|
+
},
|
|
527
|
+
...(structuredOutput !== undefined
|
|
528
|
+
? [
|
|
529
|
+
{
|
|
530
|
+
kind: 'structured-output-contract',
|
|
531
|
+
id: structuredOutput.reference.id,
|
|
532
|
+
revision: structuredOutput.reference.revision,
|
|
533
|
+
},
|
|
534
|
+
]
|
|
535
|
+
: []),
|
|
536
|
+
...helperTools.map((binding) => ({
|
|
537
|
+
kind: 'helper-tool',
|
|
538
|
+
id: binding.reference.id,
|
|
539
|
+
revision: binding.reference.revision,
|
|
540
|
+
})),
|
|
541
|
+
...processors.map((binding) => ({
|
|
542
|
+
kind: 'processor',
|
|
543
|
+
id: binding.reference.id,
|
|
544
|
+
revision: binding.reference.revision,
|
|
545
|
+
})),
|
|
546
|
+
...guardrails.map((binding) => ({
|
|
547
|
+
kind: 'guardrail',
|
|
548
|
+
id: binding.reference.id,
|
|
549
|
+
revision: binding.reference.revision,
|
|
550
|
+
})),
|
|
551
|
+
...workflows.map((binding) => ({
|
|
552
|
+
kind: 'workflow',
|
|
553
|
+
id: binding.reference.id,
|
|
554
|
+
revision: binding.reference.revision,
|
|
555
|
+
})),
|
|
556
|
+
...subagents.map((binding) => ({
|
|
557
|
+
kind: 'subagent',
|
|
558
|
+
id: binding.reference.id,
|
|
559
|
+
revision: binding.reference.revision,
|
|
560
|
+
})),
|
|
561
|
+
...declaredCapabilities.map((reference) => ({
|
|
562
|
+
kind: 'capability',
|
|
563
|
+
id: reference.id,
|
|
564
|
+
revision: reference.revision,
|
|
565
|
+
})),
|
|
566
|
+
].sort(compareArtifactEntries);
|
|
567
|
+
const manifest = {
|
|
568
|
+
schema: AGENT_ACTIVATION_IDENTITY_SCHEMA,
|
|
569
|
+
agentProfileVersion: compiled.agentProfileVersion,
|
|
570
|
+
adapter: adapterCompatibility,
|
|
571
|
+
artifacts: artifactList,
|
|
572
|
+
subagents: subagentIdentities,
|
|
573
|
+
};
|
|
574
|
+
const manifestJson = canonicalJson(manifest);
|
|
575
|
+
const activationVersion = `v1_${sha256Hex(manifestJson)}`;
|
|
576
|
+
const activation = frozenCopy({
|
|
577
|
+
activationVersion,
|
|
578
|
+
agentProfileVersion: compiled.agentProfileVersion,
|
|
579
|
+
profile: compiled,
|
|
580
|
+
instructions: { reference: profile.instructions, artifact: instructions.artifact },
|
|
581
|
+
memoryPolicy: { reference: profile.memoryPolicy, artifact: memoryPolicy.artifact },
|
|
582
|
+
helperTools,
|
|
583
|
+
processors,
|
|
584
|
+
guardrails,
|
|
585
|
+
...(structuredOutput !== undefined ? { structuredOutput } : {}),
|
|
586
|
+
workflows,
|
|
587
|
+
subagents,
|
|
588
|
+
capabilities: frozenCopy(declaredCapabilities.map((reference) => ({
|
|
589
|
+
id: reference.id,
|
|
590
|
+
revision: reference.revision,
|
|
591
|
+
}))),
|
|
592
|
+
adapterCompatibility,
|
|
593
|
+
canonicalManifestJson: manifestJson,
|
|
594
|
+
artifactList,
|
|
595
|
+
createdAt: clock(),
|
|
596
|
+
});
|
|
597
|
+
// Frozen copy preserves function references (frozenCopy passes
|
|
598
|
+
// functions through) — execute/transform/check/parse stay bound while
|
|
599
|
+
// all data becomes immutable.
|
|
600
|
+
return activation;
|
|
601
|
+
}
|
|
602
|
+
#resolveExact(kind, reference) {
|
|
603
|
+
const artifact = this.resolveArtifact(kind, reference.id, reference.revision);
|
|
604
|
+
if (artifact === undefined) {
|
|
605
|
+
const currentExists = this.#hasAnyRevision(kind, reference.id);
|
|
606
|
+
throw new VictRuntimeError(currentExists ? 'VICT_AGENT_ARTIFACT_REVISION_MISMATCH' : 'VICT_AGENT_ARTIFACT_MISSING', currentExists
|
|
607
|
+
? `A ${kind} artifact '${reference.id}' is registered but revision '${reference.revision}' is not; a current definition never substitutes for a pinned older revision.`
|
|
608
|
+
: `A ${kind} artifact '${reference.id}' (revision '${reference.revision}') is not registered; activation fails closed.`, { artifactId: reference.id, revision: reference.revision });
|
|
609
|
+
}
|
|
610
|
+
return { reference: { id: reference.id, revision: reference.revision }, artifact };
|
|
611
|
+
}
|
|
612
|
+
#hasAnyRevision(kind, id) {
|
|
613
|
+
for (const key of this.#artifacts.keys()) {
|
|
614
|
+
const [existingKind, existingId] = key.split('\u0000');
|
|
615
|
+
if (existingKind === kind && existingId === id) {
|
|
616
|
+
return true;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
// ---- Restart restoration (Stage 02 model) ---------------------------------
|
|
622
|
+
/**
|
|
623
|
+
* Restore an activation from its persisted identity record: the record is
|
|
624
|
+
* identity, NOT executable code, and it is NEVER trusted on identity
|
|
625
|
+
* strings alone. Restoration:
|
|
626
|
+
*
|
|
627
|
+
* 1. structurally validates the record (closed field sets, canonical
|
|
628
|
+
* version forms, well-formed artifact entries — extra or
|
|
629
|
+
* secret-bearing injected fields fail);
|
|
630
|
+
* 2. re-activates from the exact pinned revision (re-resolving EVERY
|
|
631
|
+
* artifact — a newer live definition never substitutes);
|
|
632
|
+
* 3. recomputes the canonical activation manifest (including the resolved
|
|
633
|
+
* sub-agent identities) and compares the stored manifest BYTES exactly
|
|
634
|
+
* (tampered manifests, and activations whose resolved sub-agent
|
|
635
|
+
* profiles differ from the pinned ones, fail);
|
|
636
|
+
* 4. compares both derived identities (profile + activation versions);
|
|
637
|
+
* 5. compares the stored artifact list against the reconstructed
|
|
638
|
+
* activation for exact kind/id/revision equality in canonical order
|
|
639
|
+
* (missing, additional, reordered, or inconsistent entries fail);
|
|
640
|
+
* 6. preserves the stored activation `createdAt`: the restored snapshot
|
|
641
|
+
* carries the PERSISTED creation time, never the current clock of the
|
|
642
|
+
* restoring process.
|
|
643
|
+
*/
|
|
644
|
+
restoreActivation(record) {
|
|
645
|
+
const structural = validateAgentActivationRecord(record);
|
|
646
|
+
if (!structural.ok) {
|
|
647
|
+
return {
|
|
648
|
+
ok: false,
|
|
649
|
+
code: 'AGENT_ACTIVATION_CORRUPT_RECORD',
|
|
650
|
+
message: `The persisted agent-activation record is malformed: ${structural.reason}`,
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
const compiled = this.#compiled.get(record.agentId)?.get(record.agentRevision);
|
|
654
|
+
if (compiled === undefined) {
|
|
655
|
+
return {
|
|
656
|
+
ok: false,
|
|
657
|
+
code: 'AGENT_ACTIVATION_PROFILE_MISMATCH',
|
|
658
|
+
message: `The persisted activation pins agent profile '${record.agentId}' (revision '${record.agentRevision}'), which is not registered in this process; refusing to substitute.`,
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
if (compiled.agentProfileVersion !== record.agentProfileVersion) {
|
|
662
|
+
return {
|
|
663
|
+
ok: false,
|
|
664
|
+
code: 'AGENT_ACTIVATION_PROFILE_MISMATCH',
|
|
665
|
+
message: 'The persisted activation pins a different agentProfileVersion than the registered profile; refusing to substitute.',
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
// Re-activate from the exact pinned revision, then verify identity.
|
|
669
|
+
let activation;
|
|
670
|
+
try {
|
|
671
|
+
activation = this.activateAgentProfile({
|
|
672
|
+
id: record.agentId,
|
|
673
|
+
revision: record.agentRevision,
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
catch (error) {
|
|
677
|
+
const code = error instanceof VictRuntimeError ? error.code : undefined;
|
|
678
|
+
return {
|
|
679
|
+
ok: false,
|
|
680
|
+
code: code === 'VICT_AGENT_ARTIFACT_REVISION_MISMATCH'
|
|
681
|
+
? 'AGENT_ACTIVATION_ARTIFACT_REVISION_MISMATCH'
|
|
682
|
+
: 'AGENT_ACTIVATION_ARTIFACT_MISSING',
|
|
683
|
+
message: code === 'VICT_AGENT_ARTIFACT_REVISION_MISMATCH'
|
|
684
|
+
? 'The persisted activation pins an artifact revision that is not registered in this process; refusing to substitute.'
|
|
685
|
+
: 'The persisted activation could not restore its exact pinned artifacts in this process.',
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
if (activation.activationVersion !== record.activationVersion) {
|
|
689
|
+
return {
|
|
690
|
+
ok: false,
|
|
691
|
+
code: 'AGENT_ACTIVATION_ARTIFACT_REVISION_MISMATCH',
|
|
692
|
+
message: 'The restored activation identity does not match the persisted record; refusing to substitute.',
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
// Tampered canonical bytes: the stored manifest must equal the
|
|
696
|
+
// recomputed manifest BYTE FOR BYTE.
|
|
697
|
+
if (activation.canonicalManifestJson !== record.canonicalManifest) {
|
|
698
|
+
return {
|
|
699
|
+
ok: false,
|
|
700
|
+
code: 'AGENT_ACTIVATION_CORRUPT_RECORD',
|
|
701
|
+
message: 'The stored canonical activation manifest does not match the reconstructed activation; refusing the record.',
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
// Exact artifact-list correspondence: kind/id/revision equality in the
|
|
705
|
+
// canonical order. Missing, additional, reordered-when-semantic, or
|
|
706
|
+
// otherwise inconsistent stored lists are rejected.
|
|
707
|
+
const stored = record.artifacts;
|
|
708
|
+
const reconstructed = activation.artifactList;
|
|
709
|
+
if (stored.length !== reconstructed.length) {
|
|
710
|
+
return {
|
|
711
|
+
ok: false,
|
|
712
|
+
code: 'AGENT_ACTIVATION_CORRUPT_RECORD',
|
|
713
|
+
message: 'The stored artifact list does not cover exactly the reconstructed activation artifacts; refusing the record.',
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
for (let index = 0; index < reconstructed.length; index += 1) {
|
|
717
|
+
const expected = reconstructed[index];
|
|
718
|
+
const found = stored[index];
|
|
719
|
+
if (found.kind !== expected.kind ||
|
|
720
|
+
found.id !== expected.id ||
|
|
721
|
+
found.revision !== expected.revision) {
|
|
722
|
+
return {
|
|
723
|
+
ok: false,
|
|
724
|
+
code: 'AGENT_ACTIVATION_ARTIFACT_REVISION_MISMATCH',
|
|
725
|
+
message: 'The stored artifact list is inconsistent with the reconstructed activation (kind, id, revision, or order differs); refusing the record.',
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
// The restored snapshot carries the PERSISTED creation time: a
|
|
730
|
+
// restoration through a changed clock never rewrites when the
|
|
731
|
+
// activation was originally created.
|
|
732
|
+
const restored = activation.createdAt === record.createdAt
|
|
733
|
+
? activation
|
|
734
|
+
: Object.freeze({ ...activation, createdAt: record.createdAt });
|
|
735
|
+
return { ok: true, activation: restored };
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
function sha256Hex(payload) {
|
|
739
|
+
return createHash('sha256').update(payload, 'utf8').digest('hex');
|
|
740
|
+
}
|
|
741
|
+
/** The registry-internal dedup key of one artifact (kind + id + revision). */
|
|
742
|
+
function artifactKey(artifact) {
|
|
743
|
+
return `${artifact.kind}\u0000${artifact.id}\u0000${artifact.revision}`;
|
|
744
|
+
}
|
|
745
|
+
/** Canonical order of activation-manifest artifact entries. */
|
|
746
|
+
function compareArtifactEntries(a, b) {
|
|
747
|
+
if (a.kind !== b.kind) {
|
|
748
|
+
return a.kind < b.kind ? -1 : 1;
|
|
749
|
+
}
|
|
750
|
+
if (a.id !== b.id) {
|
|
751
|
+
return a.id < b.id ? -1 : 1;
|
|
752
|
+
}
|
|
753
|
+
if (a.revision === b.revision) {
|
|
754
|
+
return 0;
|
|
755
|
+
}
|
|
756
|
+
return a.revision < b.revision ? -1 : 1;
|
|
757
|
+
}
|
|
758
|
+
//# sourceMappingURL=agent-registry.js.map
|