@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.
Files changed (115) hide show
  1. package/dist/agent-governance.d.ts +285 -0
  2. package/dist/agent-governance.js +438 -0
  3. package/dist/agent-governance.js.map +1 -0
  4. package/dist/agent-registry.d.ts +103 -0
  5. package/dist/agent-registry.js +758 -0
  6. package/dist/agent-registry.js.map +1 -0
  7. package/dist/agent-types.d.ts +433 -0
  8. package/dist/agent-types.js +480 -0
  9. package/dist/agent-types.js.map +1 -0
  10. package/dist/authority.d.ts +83 -0
  11. package/dist/authority.js +170 -0
  12. package/dist/authority.js.map +1 -0
  13. package/dist/boundary-conformance.d.ts +51 -0
  14. package/dist/boundary-conformance.js +364 -0
  15. package/dist/boundary-conformance.js.map +1 -0
  16. package/dist/control-conformance.d.ts +68 -0
  17. package/dist/control-conformance.js +1131 -0
  18. package/dist/control-conformance.js.map +1 -0
  19. package/dist/control-in-memory.d.ts +257 -0
  20. package/dist/control-in-memory.js +886 -0
  21. package/dist/control-in-memory.js.map +1 -0
  22. package/dist/control-types.d.ts +1017 -0
  23. package/dist/control-types.js +779 -0
  24. package/dist/control-types.js.map +1 -0
  25. package/dist/durable-run.d.ts +73 -0
  26. package/dist/durable-run.js +262 -0
  27. package/dist/durable-run.js.map +1 -0
  28. package/dist/effect-policy.d.ts +22 -0
  29. package/dist/effect-policy.js +45 -0
  30. package/dist/effect-policy.js.map +1 -0
  31. package/dist/errors.d.ts +36 -0
  32. package/dist/errors.js +71 -0
  33. package/dist/errors.js.map +1 -0
  34. package/dist/in-memory-stores.d.ts +16 -0
  35. package/dist/in-memory-stores.js +522 -0
  36. package/dist/in-memory-stores.js.map +1 -0
  37. package/dist/index.d.ts +41 -0
  38. package/dist/index.js +26 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/operator-config.d.ts +141 -0
  41. package/dist/operator-config.js +289 -0
  42. package/dist/operator-config.js.map +1 -0
  43. package/dist/orchestration-activation.d.ts +116 -0
  44. package/dist/orchestration-activation.js +223 -0
  45. package/dist/orchestration-activation.js.map +1 -0
  46. package/dist/orchestration-canary-conformance.d.ts +10 -0
  47. package/dist/orchestration-canary-conformance.js +726 -0
  48. package/dist/orchestration-canary-conformance.js.map +1 -0
  49. package/dist/orchestration-commands.d.ts +47 -0
  50. package/dist/orchestration-commands.js +538 -0
  51. package/dist/orchestration-commands.js.map +1 -0
  52. package/dist/orchestration-conformance.d.ts +59 -0
  53. package/dist/orchestration-conformance.js +594 -0
  54. package/dist/orchestration-conformance.js.map +1 -0
  55. package/dist/orchestration-driver-types.d.ts +163 -0
  56. package/dist/orchestration-driver-types.js +21 -0
  57. package/dist/orchestration-driver-types.js.map +1 -0
  58. package/dist/orchestration-driver.d.ts +48 -0
  59. package/dist/orchestration-driver.js +1030 -0
  60. package/dist/orchestration-driver.js.map +1 -0
  61. package/dist/orchestration-in-memory.d.ts +18 -0
  62. package/dist/orchestration-in-memory.js +1313 -0
  63. package/dist/orchestration-in-memory.js.map +1 -0
  64. package/dist/orchestration-join-conformance.d.ts +20 -0
  65. package/dist/orchestration-join-conformance.js +542 -0
  66. package/dist/orchestration-join-conformance.js.map +1 -0
  67. package/dist/orchestration-plan.d.ts +52 -0
  68. package/dist/orchestration-plan.js +265 -0
  69. package/dist/orchestration-plan.js.map +1 -0
  70. package/dist/orchestration-race-conformance.d.ts +35 -0
  71. package/dist/orchestration-race-conformance.js +1265 -0
  72. package/dist/orchestration-race-conformance.js.map +1 -0
  73. package/dist/orchestration-remediation-conformance.d.ts +54 -0
  74. package/dist/orchestration-remediation-conformance.js +632 -0
  75. package/dist/orchestration-remediation-conformance.js.map +1 -0
  76. package/dist/orchestration-store-types.d.ts +568 -0
  77. package/dist/orchestration-store-types.js +2 -0
  78. package/dist/orchestration-store-types.js.map +1 -0
  79. package/dist/pack-conformance.d.ts +59 -0
  80. package/dist/pack-conformance.js +222 -0
  81. package/dist/pack-conformance.js.map +1 -0
  82. package/dist/pack-install.d.ts +44 -0
  83. package/dist/pack-install.js +126 -0
  84. package/dist/pack-install.js.map +1 -0
  85. package/dist/registry.d.ts +107 -0
  86. package/dist/registry.js +481 -0
  87. package/dist/registry.js.map +1 -0
  88. package/dist/runtime.d.ts +158 -0
  89. package/dist/runtime.js +1341 -0
  90. package/dist/runtime.js.map +1 -0
  91. package/dist/serialization.d.ts +26 -0
  92. package/dist/serialization.js +162 -0
  93. package/dist/serialization.js.map +1 -0
  94. package/dist/store-conformance.d.ts +45 -0
  95. package/dist/store-conformance.js +876 -0
  96. package/dist/store-conformance.js.map +1 -0
  97. package/dist/store-errors.d.ts +49 -0
  98. package/dist/store-errors.js +48 -0
  99. package/dist/store-errors.js.map +1 -0
  100. package/dist/store-types.d.ts +331 -0
  101. package/dist/store-types.js +14 -0
  102. package/dist/store-types.js.map +1 -0
  103. package/dist/store-validation.d.ts +63 -0
  104. package/dist/store-validation.js +164 -0
  105. package/dist/store-validation.js.map +1 -0
  106. package/dist/stream-hub.d.ts +151 -0
  107. package/dist/stream-hub.js +402 -0
  108. package/dist/stream-hub.js.map +1 -0
  109. package/dist/testing.d.ts +25 -0
  110. package/dist/testing.js +19 -0
  111. package/dist/testing.js.map +1 -0
  112. package/dist/types.d.ts +221 -0
  113. package/dist/types.js +2 -0
  114. package/dist/types.js.map +1 -0
  115. 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