@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,1017 @@
|
|
|
1
|
+
import { type AgentStreamEvent, type AgentStreamEventKind } from '@victframework/contracts';
|
|
2
|
+
import type { EffectClass } from '@victframework/kernel';
|
|
3
|
+
/**
|
|
4
|
+
* Stage 06B — neutral control-plane and governed-remote-execution ports.
|
|
5
|
+
*
|
|
6
|
+
* This module defines the durable records, closed state machines, and store
|
|
7
|
+
* ports for:
|
|
8
|
+
*
|
|
9
|
+
* - authenticated actors, roles, and scopes (SEC-001/SEC-002; default deny);
|
|
10
|
+
* - ChangeSets with immutable content identity, evidence, approvals,
|
|
11
|
+
* expiry, and forward-only status (CTRL-001..CTRL-007);
|
|
12
|
+
* - Application Release publish/select/rollback over immutable versions;
|
|
13
|
+
* - audit events for every attributable transition;
|
|
14
|
+
* - durable agent turns, tool invocations (durable-before-invocation), and
|
|
15
|
+
* VICT-authoritative approval records (AI-006/AI-007/MSTR-005);
|
|
16
|
+
* - the durable agent-stream ledger (per-stream monotonic sequences,
|
|
17
|
+
* durable milestones, transient deltas — AI-009, `vict.agent-stream@1`).
|
|
18
|
+
*
|
|
19
|
+
* In-memory reference implementations live in `control-in-memory.ts`; the
|
|
20
|
+
* SQLite adapter implements the same ports in `@victframework/store-sqlite` and both
|
|
21
|
+
* pass the shared conformance suite (`control-conformance.ts`).
|
|
22
|
+
*
|
|
23
|
+
* These are NEUTRAL ports: no Mastra, HTTP, transport, or provider type
|
|
24
|
+
* appears here.
|
|
25
|
+
*/
|
|
26
|
+
/** Bounded namespace identifier for control-plane and agent records. */
|
|
27
|
+
export declare const CONTROL_ID_PATTERN: RegExp;
|
|
28
|
+
/** Validate one bounded namespace identifier (throws a stable error). */
|
|
29
|
+
export declare function assertControlId(value: unknown, field: string): asserts value is string;
|
|
30
|
+
/** Stable bounded safe-integer validation. */
|
|
31
|
+
export declare function assertControlTimestamp(value: unknown, field: string): asserts value is number;
|
|
32
|
+
/**
|
|
33
|
+
* Stable content identity over strict canonical data. The canonicalizer is
|
|
34
|
+
* the Stage 01 verified canonical JSON; identity MUST NOT be derived from
|
|
35
|
+
* function text, timestamps of capture, or mutable objects.
|
|
36
|
+
*/
|
|
37
|
+
export declare function controlContentHash(payload: unknown): string;
|
|
38
|
+
/** Bounded safe string field (rationales, summaries, reason codes). */
|
|
39
|
+
export declare function assertBoundedString(value: unknown, field: string, max: number, allowEmpty?: boolean): asserts value is string;
|
|
40
|
+
/** The closed reference role vocabulary (system reference §14.2). */
|
|
41
|
+
export declare const ACTOR_ROLES: readonly ["viewer", "developer", "operator", "approver", "administrator"];
|
|
42
|
+
export type ActorRole = (typeof ACTOR_ROLES)[number];
|
|
43
|
+
/** The closed scope vocabulary enforced below the HTTP/CLI/UI layers. */
|
|
44
|
+
export declare const ACTOR_SCOPES: readonly ["changeset.propose", "changeset.revise", "changeset.approve", "changeset.commit", "changeset.read", "release.publish", "release.select", "release.read", "activation.select", "activation.read", "run.read", "run.cancel", "operator.resolve", "agent.turn.start", "agent.turn.cancel", "agent.tool.approve", "agent.tool.decline", "agent.stream.read", "app.data.read", "app.data.write", "conversation.read", "conversation.delete", "audit.read"];
|
|
45
|
+
export type ActorScope = (typeof ACTOR_SCOPES)[number];
|
|
46
|
+
/** Deterministic role → scope policy (default policy is denial). */
|
|
47
|
+
export declare const ROLE_SCOPES: Readonly<Record<ActorRole, readonly ActorScope[]>>;
|
|
48
|
+
export type ActorStatus = 'active' | 'disabled';
|
|
49
|
+
/** One authoritative actor record (server-side truth, never client-supplied). */
|
|
50
|
+
export interface ActorRecord {
|
|
51
|
+
readonly actorId: string;
|
|
52
|
+
readonly status: ActorStatus;
|
|
53
|
+
readonly roles: readonly ActorRole[];
|
|
54
|
+
/** Epoch-ms creation time from the injected clock. */
|
|
55
|
+
readonly createdAt: number;
|
|
56
|
+
}
|
|
57
|
+
/** Port resolving actor records from the authoritative directory. */
|
|
58
|
+
export interface ActorDirectory {
|
|
59
|
+
get(actorId: string): Promise<ActorRecord | undefined>;
|
|
60
|
+
list(): Promise<readonly ActorRecord[]>;
|
|
61
|
+
upsert(record: ActorRecord): Promise<void>;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Derive the permitted scopes of one actor record. Unknown, disabled, and
|
|
65
|
+
* malformed actors fail closed: no scopes are derived. Roles outside the
|
|
66
|
+
* closed vocabulary are ignored (fail closed, never widened).
|
|
67
|
+
*/
|
|
68
|
+
export declare function authoritativeScopes(actor: ActorRecord | undefined): readonly ActorScope[];
|
|
69
|
+
/** Authoritative server-side actor context (the ONLY identity authority). */
|
|
70
|
+
export interface AuthenticatedActorContext {
|
|
71
|
+
readonly actorId: string;
|
|
72
|
+
readonly roles: readonly ActorRole[];
|
|
73
|
+
readonly scopes: readonly ActorScope[];
|
|
74
|
+
/**
|
|
75
|
+
* The permitted Mastra memory identity, derived ONLY from the
|
|
76
|
+
* authenticated VICT actor (MSTR-007): `vict-actor-<actorId>`.
|
|
77
|
+
*/
|
|
78
|
+
readonly mastraResourceId: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Derive the authoritative server context from a resolved actor record.
|
|
82
|
+
* Unknown, disabled, malformed, or mismatched actors fail closed.
|
|
83
|
+
*/
|
|
84
|
+
export declare function authenticatedActorContext(actor: ActorRecord | undefined, requestedActorId: string): AuthenticatedActorContext;
|
|
85
|
+
/** Stable authorization denial (never echoes the attempted operation's payload). */
|
|
86
|
+
export declare class ActorScopeDeniedError extends Error {
|
|
87
|
+
readonly code = "VICT_ACTOR_SCOPE_DENIED";
|
|
88
|
+
readonly scope: string;
|
|
89
|
+
constructor(scope: string);
|
|
90
|
+
}
|
|
91
|
+
/** Enforce one scope (fail closed; stable non-echoing denial). */
|
|
92
|
+
export declare function assertActorScope(context: AuthenticatedActorContext, scope: ActorScope): void;
|
|
93
|
+
/** In-memory ActorDirectory (tests and local compositions). */
|
|
94
|
+
export declare class InMemoryActorDirectory implements ActorDirectory {
|
|
95
|
+
#private;
|
|
96
|
+
get(actorId: string): Promise<ActorRecord | undefined>;
|
|
97
|
+
list(): Promise<readonly ActorRecord[]>;
|
|
98
|
+
upsert(record: ActorRecord): Promise<void>;
|
|
99
|
+
}
|
|
100
|
+
/** The versioned ChangeSet schema marker. */
|
|
101
|
+
export declare const CHANGESET_SCHEMA = "vict.changeset@1";
|
|
102
|
+
/** Risk/effect classification of a proposed change. */
|
|
103
|
+
export type ChangeSetRiskClass = 'low' | 'medium' | 'high';
|
|
104
|
+
/** Closed typed operation: a reference to immutable published identity. */
|
|
105
|
+
export type ChangeSetOperation = {
|
|
106
|
+
readonly kind: 'select-activation';
|
|
107
|
+
readonly graphId: string;
|
|
108
|
+
readonly activationVersion: string;
|
|
109
|
+
} | {
|
|
110
|
+
kind: 'rollback-activation';
|
|
111
|
+
graphId: string;
|
|
112
|
+
targetActivationVersion: string;
|
|
113
|
+
} | {
|
|
114
|
+
kind: 'publish-and-select-release';
|
|
115
|
+
release: ApplicationReleaseContent;
|
|
116
|
+
} | {
|
|
117
|
+
kind: 'select-release';
|
|
118
|
+
applicationId: string;
|
|
119
|
+
releaseVersion: string;
|
|
120
|
+
} | {
|
|
121
|
+
kind: 'rollback-release';
|
|
122
|
+
applicationId: string;
|
|
123
|
+
targetReleaseVersion: string;
|
|
124
|
+
};
|
|
125
|
+
/** The closed immutable release content a ChangeSet may publish. */
|
|
126
|
+
export type ApplicationReleaseContent = Pick<ApplicationReleaseRecord, 'releaseVersion' | 'applicationId' | 'applicationVersion' | 'rendererIdentity' | 'componentRegistryIdentity' | 'dataAdapterIdentity' | 'activationBinding'>;
|
|
127
|
+
/** Validation evidence attached to a ChangeSet (bound to an EXECUTED run). */
|
|
128
|
+
export interface ChangeSetValidationEvidence {
|
|
129
|
+
/** Identity of the authoritative VICT-executed validation run. */
|
|
130
|
+
readonly runId: string;
|
|
131
|
+
readonly outcome: 'passed' | 'failed';
|
|
132
|
+
readonly recordedAt: number;
|
|
133
|
+
/** The EXACT ChangeSet content identity the run was executed against. */
|
|
134
|
+
readonly contentHash: string;
|
|
135
|
+
/** The EXACT expected base identity the run was executed against. */
|
|
136
|
+
readonly base: ChangeSetBase;
|
|
137
|
+
/** The runner/profile version identity that executed the run. */
|
|
138
|
+
readonly runnerProfile: string;
|
|
139
|
+
/** The authenticated actor for whom the run was executed. */
|
|
140
|
+
readonly actorId: string;
|
|
141
|
+
}
|
|
142
|
+
/** Simulation evidence attached to a ChangeSet (bound to an EXECUTED run). */
|
|
143
|
+
export interface ChangeSetSimulationEvidence {
|
|
144
|
+
/** Identity of the authoritative VICT-executed simulation run. */
|
|
145
|
+
readonly runId: string;
|
|
146
|
+
readonly outcome: 'passed' | 'failed' | 'blocked';
|
|
147
|
+
readonly recordedAt: number;
|
|
148
|
+
/** The EXACT ChangeSet content identity the run was executed against. */
|
|
149
|
+
readonly contentHash: string;
|
|
150
|
+
/** The EXACT expected base identity the run was executed against. */
|
|
151
|
+
readonly base: ChangeSetBase;
|
|
152
|
+
/** The runner/profile version identity that executed the run. */
|
|
153
|
+
readonly runnerProfile: string;
|
|
154
|
+
/** The authenticated actor for whom the run was executed. */
|
|
155
|
+
readonly actorId: string;
|
|
156
|
+
}
|
|
157
|
+
/** Forward-only ChangeSet status. `applying` is a DURABLE, non-final
|
|
158
|
+
* state: a commit whose operations are being applied (saga). A failure
|
|
159
|
+
* inside `applying` leaves the record truthfully `applying` — never a
|
|
160
|
+
* falsely final state — and deterministic recovery completes it. */
|
|
161
|
+
export type ChangeSetStatus = 'draft' | 'approved' | 'applying' | 'committed' | 'declined' | 'expired';
|
|
162
|
+
/** One proposed, version-guarded control-plane change. */
|
|
163
|
+
export interface ChangeSetRecord {
|
|
164
|
+
readonly changesetId: string;
|
|
165
|
+
readonly schema: typeof CHANGESET_SCHEMA;
|
|
166
|
+
readonly authorActorId: string;
|
|
167
|
+
readonly createdAt: number;
|
|
168
|
+
/** The exact expected base identity/version (concurrency guard). */
|
|
169
|
+
readonly base: ChangeSetBase;
|
|
170
|
+
/** Closed, typed operations (never executable functions or JSON patches). */
|
|
171
|
+
readonly operations: readonly ChangeSetOperation[];
|
|
172
|
+
/** Rationale or safe reference (bounded, no payloads). */
|
|
173
|
+
readonly rationale: string;
|
|
174
|
+
readonly riskClass: ChangeSetRiskClass;
|
|
175
|
+
/** Required approval policy (count of distinct approvers). */
|
|
176
|
+
readonly requiredApproverCount: number;
|
|
177
|
+
/** Epoch-ms expiry of the proposal. */
|
|
178
|
+
readonly expiresAt: number;
|
|
179
|
+
readonly validation: ChangeSetValidationEvidence | undefined;
|
|
180
|
+
readonly simulation: ChangeSetSimulationEvidence | undefined;
|
|
181
|
+
/** Immutable content identity — approvals bind to EXACTLY this hash. */
|
|
182
|
+
readonly contentHash: string;
|
|
183
|
+
readonly status: ChangeSetStatus;
|
|
184
|
+
}
|
|
185
|
+
/** The sentinel base version meaning 'no version is currently selected'. */
|
|
186
|
+
export declare const CHANGESET_BASE_NONE = "none";
|
|
187
|
+
/** The expected base identity/version of a ChangeSet. */
|
|
188
|
+
export interface ChangeSetBase {
|
|
189
|
+
readonly kind: 'activation' | 'release';
|
|
190
|
+
/** The graphId (for activations) or applicationId (for releases). */
|
|
191
|
+
readonly subjectId: string;
|
|
192
|
+
/** The expected currently-selected version at proposal and commit time. */
|
|
193
|
+
readonly expectedVersion: string;
|
|
194
|
+
}
|
|
195
|
+
/** One control-plane approval decision (bound to the exact content hash). */
|
|
196
|
+
export interface ChangeSetApprovalDecision {
|
|
197
|
+
readonly approvalId: string;
|
|
198
|
+
readonly changesetId: string;
|
|
199
|
+
/** The exact content hash the decision binds to. */
|
|
200
|
+
readonly contentHash: string;
|
|
201
|
+
readonly approverActorId: string;
|
|
202
|
+
readonly decision: 'approved' | 'declined';
|
|
203
|
+
readonly decidedAt: number;
|
|
204
|
+
}
|
|
205
|
+
/** The closed governance-run vocabulary. */
|
|
206
|
+
export type ControlRunKind = 'validation' | 'simulation';
|
|
207
|
+
/**
|
|
208
|
+
* One authoritative validation/simulation run EXECUTED by the trusted VICT
|
|
209
|
+
* boundary. Run records are the ONLY source from which ChangeSet evidence
|
|
210
|
+
* may be derived; caller-claimed evidence is verified against these
|
|
211
|
+
* durable records before it can authorize a commit.
|
|
212
|
+
*/
|
|
213
|
+
export interface ControlRunRecord {
|
|
214
|
+
readonly runId: string;
|
|
215
|
+
readonly kind: ControlRunKind;
|
|
216
|
+
/** The ChangeSet subject the run was executed against. */
|
|
217
|
+
readonly changesetId: string;
|
|
218
|
+
/** The EXACT content identity the run was executed against. */
|
|
219
|
+
readonly contentHash: string;
|
|
220
|
+
/** The EXACT expected base identity at run time. */
|
|
221
|
+
readonly base: ChangeSetBase;
|
|
222
|
+
/** The EXACT operation set the run covered (canonical content). */
|
|
223
|
+
readonly operations: readonly ChangeSetOperation[];
|
|
224
|
+
/** The runner/profile version identity that executed the run. */
|
|
225
|
+
readonly runnerProfile: string;
|
|
226
|
+
/** The authenticated actor the run was executed for. */
|
|
227
|
+
readonly actorId: string;
|
|
228
|
+
readonly outcome: 'passed' | 'failed' | 'blocked';
|
|
229
|
+
/** Epoch-ms execution time from the injected clock. */
|
|
230
|
+
readonly createdAt: number;
|
|
231
|
+
/** The EXACT observed subject base at run time (what the run verified):
|
|
232
|
+
* the currently selected version for the subject, with the
|
|
233
|
+
* `CHANGESET_BASE_NONE` sentinel for explicit absence. This proves what
|
|
234
|
+
* base the run actually checked (never a caller claim). */
|
|
235
|
+
readonly observedBase?: ChangeSetBase;
|
|
236
|
+
/** Simulation-run detail: the exact simulation inputs, per-operation
|
|
237
|
+
* identities and outcomes of the sandboxed execution (IDs and outcomes
|
|
238
|
+
* only — never payloads). Validation runs carry `undefined`. */
|
|
239
|
+
readonly detail?: ControlRunDetail;
|
|
240
|
+
}
|
|
241
|
+
/** Per-operation simulation outcome (operation identity + outcome only). */
|
|
242
|
+
export interface ControlRunOperationOutcome {
|
|
243
|
+
readonly operationIndex: number;
|
|
244
|
+
readonly operationDigest: string;
|
|
245
|
+
readonly outcome: 'applied' | 'failed' | 'blocked';
|
|
246
|
+
/** Stable sanitized code when not applied. */
|
|
247
|
+
readonly code?: string;
|
|
248
|
+
}
|
|
249
|
+
/** The safe detail record of an executed simulation run. */
|
|
250
|
+
export interface ControlRunDetail {
|
|
251
|
+
readonly simulator: string;
|
|
252
|
+
/** The exact sandbox inputs: the subject base and activation/release
|
|
253
|
+
* inputs derived from durable state at simulation time (IDs only). */
|
|
254
|
+
readonly inputs: {
|
|
255
|
+
readonly base: ChangeSetBase;
|
|
256
|
+
readonly operationIdentities: readonly string[];
|
|
257
|
+
};
|
|
258
|
+
readonly operations: readonly ControlRunOperationOutcome[];
|
|
259
|
+
}
|
|
260
|
+
/** One durable receipt of a single applied ChangeSet operation (saga).
|
|
261
|
+
*
|
|
262
|
+
* The receipt is written in TWO states that make the external effect
|
|
263
|
+
* exactly-once even across a crash between effect and receipt:
|
|
264
|
+
*
|
|
265
|
+
* - `prepared`: the durable OPERATION INTENT, recorded BEFORE the effect
|
|
266
|
+
* runs. It carries the stable operation identity and the subject guard
|
|
267
|
+
* (fencing data) captured at prevalidation.
|
|
268
|
+
* - `applied`: recorded after the effect (atomically with it where the
|
|
269
|
+
* effect shares the store's transaction boundary).
|
|
270
|
+
*
|
|
271
|
+
* Recovery verifies the TARGET state for prepared intents (a selection
|
|
272
|
+
* with the same operation identity already applied) and never repeats the
|
|
273
|
+
* effect: it either confirms the target state and marks the receipt
|
|
274
|
+
* applied, or re-applies through the same idempotent/fenced effect path.
|
|
275
|
+
*/
|
|
276
|
+
export interface ChangeSetOperationReceipt {
|
|
277
|
+
readonly changesetId: string;
|
|
278
|
+
/** Zero-based index of the operation inside the ChangeSet. */
|
|
279
|
+
readonly operationIndex: number;
|
|
280
|
+
readonly operationKind: ChangeSetOperation['kind'];
|
|
281
|
+
/** Stable operation identity: derived from ChangeSet ID, content hash,
|
|
282
|
+
* operation index, and the exact operation content. */
|
|
283
|
+
readonly operationDigest: string;
|
|
284
|
+
/** Stable identity of the applied effect (idempotency anchor). */
|
|
285
|
+
readonly effectRef: string;
|
|
286
|
+
readonly actorId: string;
|
|
287
|
+
readonly appliedAt: number;
|
|
288
|
+
/** `prepared` (intent, pre-effect) or `applied` (post-effect). */
|
|
289
|
+
readonly state: 'prepared' | 'applied';
|
|
290
|
+
/** Serialized subject guard (expected base/selection revision) captured
|
|
291
|
+
* at intent time; re-used verbatim by idempotent re-application. */
|
|
292
|
+
readonly guardJson: string | undefined;
|
|
293
|
+
}
|
|
294
|
+
/** Derive the stable operation identity for one ChangeSet operation. */
|
|
295
|
+
export declare function changeSetOperationIdentity(input: {
|
|
296
|
+
changesetId: string;
|
|
297
|
+
contentHash: string;
|
|
298
|
+
operationIndex: number;
|
|
299
|
+
operation: ChangeSetOperation;
|
|
300
|
+
}): string;
|
|
301
|
+
/**
|
|
302
|
+
* Capture a CLOSED plain-data record without ever invoking caller code or
|
|
303
|
+
* echoing captured values:
|
|
304
|
+
*
|
|
305
|
+
* - the value must be a non-null, non-array object whose prototype is
|
|
306
|
+
* exactly `Object.prototype` (or null) — class instances, exotic
|
|
307
|
+
* prototypes, and structs smuggled through exotic prototypes fail;
|
|
308
|
+
* - property enumeration, `Reflect.ownKeys`, and descriptor reads are
|
|
309
|
+
* guarded — a hostile proxy that throws is rejected without a raw
|
|
310
|
+
* exception crossing the boundary;
|
|
311
|
+
* - OWN string-keyed ENUMERABLE data properties only: accessors
|
|
312
|
+
* (getters/setters), inherited members, non-enumerable fields, and
|
|
313
|
+
* symbol keys are rejected, and getters are NEVER invoked (values are
|
|
314
|
+
* read exclusively through the captured property descriptor).
|
|
315
|
+
* - the field set must be EXACTLY the declared closed set.
|
|
316
|
+
*
|
|
317
|
+
* The returned capture is a freshly allocated plain object owned by VICT
|
|
318
|
+
* (the caller's object is never retained, frozen, or mutated).
|
|
319
|
+
*/
|
|
320
|
+
export declare function captureClosedControlRecord(raw: unknown, field: string, allowed: readonly string[] | undefined): Record<string, unknown>;
|
|
321
|
+
/**
|
|
322
|
+
* Capture a CLOSED dense array without ever invoking caller code:
|
|
323
|
+
*
|
|
324
|
+
* - the value must be a REAL array (`Array.isArray` under a guard — a
|
|
325
|
+
* revoked Proxy throws there — whose prototype is exactly
|
|
326
|
+
* `Array.prototype`);
|
|
327
|
+
* - the `length` is read through its OWN DESCRIPTOR under a guard (never a
|
|
328
|
+
* `get`, which would be a caller-controlled trap): it must be an own
|
|
329
|
+
* DATA property carrying a safe integer within [1, maxLength];
|
|
330
|
+
* - `Reflect.ownKeys` (guarded) must declare EXACTLY the length and the
|
|
331
|
+
* dense index keys: extra string or symbol properties, sparse holes,
|
|
332
|
+
* non-enumerable indices, accessors, and hostile enumeration/descriptor
|
|
333
|
+
* traps are ALL rejected;
|
|
334
|
+
* - elements are captured exclusively through their guarded own property
|
|
335
|
+
* DESCRIPTORS (enumerable data properties only) — caller `.map()`,
|
|
336
|
+
* iterators, getters, and index `get` traps are never consulted.
|
|
337
|
+
*
|
|
338
|
+
* The returned array is freshly allocated and owned by VICT (the caller's
|
|
339
|
+
* array is never retained, frozen, or aliased).
|
|
340
|
+
*/
|
|
341
|
+
export declare function captureClosedControlArray(raw: unknown, field: string, maxLength: number): readonly unknown[];
|
|
342
|
+
/**
|
|
343
|
+
* Validate the structural shape of one ChangeSet operation (fail closed).
|
|
344
|
+
*
|
|
345
|
+
* EVERY required member of the operation kind is enforced with its exact
|
|
346
|
+
* runtime type, non-empty bounded identifier form, and the closed own-field
|
|
347
|
+
* set. Accessors, inherited/non-enumerable members, symbols, exotic
|
|
348
|
+
* prototypes, and hostile proxies are rejected WITHOUT invoking getters or
|
|
349
|
+
* echoing captured values. Only the validated VICT-owned canonical capture
|
|
350
|
+
* is returned/hashed — never the original caller object.
|
|
351
|
+
*/
|
|
352
|
+
export declare function validateChangeSetOperation(operation: unknown): ChangeSetOperation;
|
|
353
|
+
/**
|
|
354
|
+
* Validate one immutable Application Release content record (fail closed).
|
|
355
|
+
* The content is captured as a CLOSED plain-data structure (exact member
|
|
356
|
+
* set, exact string types, no accessors/symbols/hostile containers) and
|
|
357
|
+
* the returned record is a fresh VICT-owned capture.
|
|
358
|
+
*/
|
|
359
|
+
export declare function validateApplicationReleaseContent(content: unknown): ApplicationReleaseContent;
|
|
360
|
+
/** Validate a full ChangeSet authoring input and derive its content hash.
|
|
361
|
+
*
|
|
362
|
+
* The COMPLETE untrusted runtime input is CAPTURED at this boundary before
|
|
363
|
+
* any member is read: the outer envelope must be a plain object with the
|
|
364
|
+
* EXACT closed field set, and `base`, the operation list, and every
|
|
365
|
+
* operation are captured through guarded descriptors (no caller getter,
|
|
366
|
+
* iterator, `.map()`, or other caller-controlled behavior is ever
|
|
367
|
+
* consulted; hostile/revoked proxies, sparse arrays, accessors, symbols,
|
|
368
|
+
* non-enumerable members, and exotic prototypes are all rejected with ONE
|
|
369
|
+
* stable, non-echoing error). Only VICT-owned validated captures are
|
|
370
|
+
* hashed and returned; caller objects are never retained, frozen, or aliased.
|
|
371
|
+
*/
|
|
372
|
+
export declare function validateChangeSetContent(untrustedInput: unknown): {
|
|
373
|
+
changesetId: string;
|
|
374
|
+
base: ChangeSetBase;
|
|
375
|
+
operations: readonly ChangeSetOperation[];
|
|
376
|
+
rationale: string;
|
|
377
|
+
riskClass: ChangeSetRiskClass;
|
|
378
|
+
requiredApproverCount: number;
|
|
379
|
+
expiresAt: number;
|
|
380
|
+
contentHash: string;
|
|
381
|
+
};
|
|
382
|
+
/** The durable ChangeSet store port. */
|
|
383
|
+
export interface ControlPlaneStore {
|
|
384
|
+
saveChangeSet(record: ChangeSetRecord): Promise<void>;
|
|
385
|
+
getChangeSet(changesetId: string): Promise<ChangeSetRecord | undefined>;
|
|
386
|
+
listChangeSets(): Promise<readonly ChangeSetRecord[]>;
|
|
387
|
+
/** Update one ChangeSet with optimistic compare-and-set on its status. */
|
|
388
|
+
updateChangeSet(changesetId: string, update: (record: ChangeSetRecord) => ChangeSetRecord): Promise<ChangeSetRecord>;
|
|
389
|
+
/**
|
|
390
|
+
* Revise a ChangeSet's CONTENT (draft/approved only): the update may
|
|
391
|
+
* derive a NEW immutable content identity; the implementation must reset
|
|
392
|
+
* validation/simulation evidence atomically with the new content.
|
|
393
|
+
*/
|
|
394
|
+
reviseChangeSetContent(changesetId: string, revise: (record: ChangeSetRecord) => ChangeSetRecord): Promise<ChangeSetRecord>;
|
|
395
|
+
recordChangeSetApproval(decision: ChangeSetApprovalDecision): Promise<void>;
|
|
396
|
+
listChangeSetApprovals(changesetId: string): Promise<readonly ChangeSetApprovalDecision[]>;
|
|
397
|
+
/** Durable record of one authoritative governance run (idempotent by runId). */
|
|
398
|
+
recordControlRun(record: ControlRunRecord): Promise<void>;
|
|
399
|
+
getControlRun(runId: string): Promise<ControlRunRecord | undefined>;
|
|
400
|
+
/**
|
|
401
|
+
* Durable saga receipts for applied ChangeSet operations. The protocol
|
|
402
|
+
* is: record the PREPARED intent (idempotent by (changesetId, index);
|
|
403
|
+
* conflicting content under the same identity fails closed) BEFORE the
|
|
404
|
+
* effect, then record the APPLIED state after the effect (atomically
|
|
405
|
+
* with it where effect and receipt share a transaction). Recovery skips
|
|
406
|
+
* operations whose receipt is `applied` and VERIFIES target state for
|
|
407
|
+
* `prepared` intents — it never manufactures a receipt merely because
|
|
408
|
+
* the operation was supposed to run.
|
|
409
|
+
*/
|
|
410
|
+
recordOperationIntent(receipt: ChangeSetOperationReceipt): Promise<'recorded' | 'exists'>;
|
|
411
|
+
markOperationApplied(input: {
|
|
412
|
+
changesetId: string;
|
|
413
|
+
operationIndex: number;
|
|
414
|
+
at: number;
|
|
415
|
+
}): Promise<ChangeSetOperationReceipt>;
|
|
416
|
+
recordOperationReceipt(receipt: ChangeSetOperationReceipt): Promise<void>;
|
|
417
|
+
listOperationReceipts(changesetId: string): Promise<readonly ChangeSetOperationReceipt[]>;
|
|
418
|
+
/**
|
|
419
|
+
* Compare-and-set the ChangeSet status (one winner). The update is
|
|
420
|
+
* applied only when the CURRENT durable status equals `expectedStatus`;
|
|
421
|
+
* a mismatch throws `VICT_CONTROL_CHANGESET_STATUS_CONFLICT`.
|
|
422
|
+
*/
|
|
423
|
+
compareAndSetChangeSetStatus(input: {
|
|
424
|
+
changesetId: string;
|
|
425
|
+
expectedStatus: ChangeSetStatus;
|
|
426
|
+
nextStatus: ChangeSetStatus;
|
|
427
|
+
}): Promise<ChangeSetRecord>;
|
|
428
|
+
publishRelease(record: ApplicationReleaseRecord): Promise<void>;
|
|
429
|
+
getRelease(releaseVersion: string): Promise<ApplicationReleaseRecord | undefined>;
|
|
430
|
+
listReleases(applicationId: string): Promise<readonly ApplicationReleaseRecord[]>;
|
|
431
|
+
/**
|
|
432
|
+
* Select one release with the operation protocol: `operationId` is the
|
|
433
|
+
* idempotency/fencing key (re-application returns the ORIGINAL selection
|
|
434
|
+
* revision without adding another one; conflicting content under the
|
|
435
|
+
* same identity fails closed) and `expectedBaseVersion` is the
|
|
436
|
+
* SUBJECT-LEVEL base guard (the currently selected release version for
|
|
437
|
+
* the application; `'none'`-equivalent when no version is selected).
|
|
438
|
+
* The guard is evaluated ATOMICALLY inside the selection mutation — two
|
|
439
|
+
* ChangeSets racing on one base produce exactly one winner, and the
|
|
440
|
+
* loser receives `VICT_CONTROL_BASE_STALE` with NO effects.
|
|
441
|
+
*/
|
|
442
|
+
selectRelease(command: {
|
|
443
|
+
applicationId: string;
|
|
444
|
+
releaseVersion: string;
|
|
445
|
+
actorId: string;
|
|
446
|
+
at: number;
|
|
447
|
+
reason: 'select' | 'rollback';
|
|
448
|
+
readonly operationId?: string;
|
|
449
|
+
readonly expectedBaseVersion?: string;
|
|
450
|
+
}): Promise<{
|
|
451
|
+
selectionRevision: number;
|
|
452
|
+
}>;
|
|
453
|
+
getSelectedRelease(applicationId: string): Promise<ApplicationReleaseRecord | undefined>;
|
|
454
|
+
listReleaseSelections(applicationId: string): Promise<readonly ReleaseSelectionRecord[]>;
|
|
455
|
+
/**
|
|
456
|
+
* OPTIONAL atomic composition: publish the release, apply the guarded
|
|
457
|
+
* selection, and record the APPLIED operation receipt in ONE durable
|
|
458
|
+
* transaction (closes the effect/receipt dual-write gap where the
|
|
459
|
+
* effect shares the store). Implementations without a shared
|
|
460
|
+
* transaction boundary may omit it; the service then uses the
|
|
461
|
+
* intent → effect → applied protocol.
|
|
462
|
+
*/
|
|
463
|
+
applyReleaseOperation?(input: {
|
|
464
|
+
release: ApplicationReleaseRecord;
|
|
465
|
+
selection: {
|
|
466
|
+
applicationId: string;
|
|
467
|
+
releaseVersion: string;
|
|
468
|
+
actorId: string;
|
|
469
|
+
at: number;
|
|
470
|
+
reason: 'select' | 'rollback';
|
|
471
|
+
operationId: string;
|
|
472
|
+
expectedBaseVersion?: string;
|
|
473
|
+
};
|
|
474
|
+
receipt: ChangeSetOperationReceipt;
|
|
475
|
+
}): Promise<{
|
|
476
|
+
selectionRevision: number;
|
|
477
|
+
}>;
|
|
478
|
+
appendAuditEvent(event: ControlAuditEvent): Promise<void>;
|
|
479
|
+
listAuditEvents(subject: {
|
|
480
|
+
subjectType?: string;
|
|
481
|
+
subjectId?: string;
|
|
482
|
+
}): Promise<readonly ControlAuditEvent[]>;
|
|
483
|
+
}
|
|
484
|
+
/** One published immutable Application Release. */
|
|
485
|
+
export interface ApplicationReleaseRecord {
|
|
486
|
+
readonly releaseVersion: string;
|
|
487
|
+
readonly applicationId: string;
|
|
488
|
+
readonly applicationVersion: string;
|
|
489
|
+
readonly rendererIdentity: string;
|
|
490
|
+
readonly componentRegistryIdentity: string;
|
|
491
|
+
readonly dataAdapterIdentity: string;
|
|
492
|
+
readonly activationBinding: string;
|
|
493
|
+
readonly publishedByActorId: string;
|
|
494
|
+
readonly publishedAt: number;
|
|
495
|
+
/** Immutable content identity of the release record. */
|
|
496
|
+
readonly contentHash: string;
|
|
497
|
+
}
|
|
498
|
+
/** One immutable release selection (selection is monotonic; rollback = select). */
|
|
499
|
+
export interface ReleaseSelectionRecord {
|
|
500
|
+
readonly applicationId: string;
|
|
501
|
+
readonly releaseVersion: string;
|
|
502
|
+
readonly selectionRevision: number;
|
|
503
|
+
readonly actorId: string;
|
|
504
|
+
readonly at: number;
|
|
505
|
+
/** `select` or `rollback` — distinct, attributable operations. */
|
|
506
|
+
readonly reason: 'select' | 'rollback';
|
|
507
|
+
/** The ChangeSet operation identity that produced this selection, when
|
|
508
|
+
* selection ran under the operation protocol (idempotency anchor). */
|
|
509
|
+
readonly operationId?: string;
|
|
510
|
+
}
|
|
511
|
+
/** One attributable audit event (safe summaries only). */
|
|
512
|
+
export interface ControlAuditEvent {
|
|
513
|
+
readonly auditId: string;
|
|
514
|
+
readonly at: number;
|
|
515
|
+
readonly actorId: string;
|
|
516
|
+
readonly action: ControlAuditAction;
|
|
517
|
+
readonly subjectType: string;
|
|
518
|
+
readonly subjectId: string;
|
|
519
|
+
/** Safe bounded summary — never payloads, never credentials. */
|
|
520
|
+
readonly summary: string;
|
|
521
|
+
}
|
|
522
|
+
/** The closed audit action vocabulary. */
|
|
523
|
+
export declare const CONTROL_AUDIT_ACTIONS: readonly ["changeset.proposed", "changeset.revised", "changeset.evidence-attached", "changeset.approved", "changeset.declined", "changeset.committed", "changeset.expired", "release.published", "release.selected", "release.rolled-back", "activation.selected", "activation.rolled-back", "actor.recorded", "run.cancelled", "turn.cancelled", "approval.decided", "approval.expired", "operator.intervened"];
|
|
524
|
+
export type ControlAuditAction = (typeof CONTROL_AUDIT_ACTIONS)[number];
|
|
525
|
+
export declare const AGENT_TURN_SCHEMA = "vict.agent-turn@1";
|
|
526
|
+
export type AgentTurnStatus = 'intent' | 'running' | 'awaiting-approval' | 'completed' | 'failed' | 'cancelled' | 'blocked';
|
|
527
|
+
/** One durable agent-turn record (VICT-authoritative). */
|
|
528
|
+
export interface AgentTurnRecord {
|
|
529
|
+
readonly turnId: string;
|
|
530
|
+
readonly streamId: string;
|
|
531
|
+
readonly threadId: string;
|
|
532
|
+
readonly actorId: string;
|
|
533
|
+
readonly agentProfileVersion: string;
|
|
534
|
+
readonly activationVersion: string | undefined;
|
|
535
|
+
/** Application Release identity pinned for the turn, when applicable. */
|
|
536
|
+
readonly applicationReleaseVersion: string | undefined;
|
|
537
|
+
/** Safe bounded input summary — NEVER the full prompt. */
|
|
538
|
+
readonly inputSummary: string;
|
|
539
|
+
readonly status: AgentTurnStatus;
|
|
540
|
+
readonly createdAt: number;
|
|
541
|
+
readonly updatedAt: number;
|
|
542
|
+
readonly terminalAt: number | undefined;
|
|
543
|
+
readonly errorCode: string | undefined;
|
|
544
|
+
/** Correlation identities (IDs, never payloads). */
|
|
545
|
+
readonly traceId: string | undefined;
|
|
546
|
+
readonly victRunId: string | undefined;
|
|
547
|
+
readonly mastraRunId: string | undefined;
|
|
548
|
+
}
|
|
549
|
+
/** Correlation update for a turn (IDs only). */
|
|
550
|
+
export interface AgentTurnCorrelation {
|
|
551
|
+
readonly traceId?: string;
|
|
552
|
+
readonly victRunId?: string;
|
|
553
|
+
readonly mastraRunId?: string;
|
|
554
|
+
}
|
|
555
|
+
/** The durable agent-turn store port. */
|
|
556
|
+
export interface AgentTurnStore {
|
|
557
|
+
createTurnIntent(record: AgentTurnRecord): Promise<void>;
|
|
558
|
+
getTurn(turnId: string): Promise<AgentTurnRecord | undefined>;
|
|
559
|
+
listTurns(): Promise<readonly AgentTurnRecord[]>;
|
|
560
|
+
listOpenTurns(): Promise<readonly AgentTurnRecord[]>;
|
|
561
|
+
startTurn(turnId: string, at: number): Promise<AgentTurnRecord>;
|
|
562
|
+
awaitApproval(turnId: string, at: number, approvalId: string): Promise<AgentTurnRecord>;
|
|
563
|
+
resumeTurn(turnId: string, at: number): Promise<AgentTurnRecord>;
|
|
564
|
+
recordTurnCorrelation(turnId: string, correlation: AgentTurnCorrelation): Promise<void>;
|
|
565
|
+
completeTurn(command: {
|
|
566
|
+
turnId: string;
|
|
567
|
+
status: 'completed' | 'failed' | 'cancelled' | 'blocked';
|
|
568
|
+
at: number;
|
|
569
|
+
errorCode?: string;
|
|
570
|
+
}): Promise<AgentTurnRecord>;
|
|
571
|
+
recordCancelIntent(command: {
|
|
572
|
+
turnId: string;
|
|
573
|
+
cancelId: string;
|
|
574
|
+
actorId: string;
|
|
575
|
+
reasonCode: string;
|
|
576
|
+
at: number;
|
|
577
|
+
}): Promise<{
|
|
578
|
+
accepted: boolean;
|
|
579
|
+
duplicate: boolean;
|
|
580
|
+
}>;
|
|
581
|
+
hasCancelIntent(turnId: string): Promise<boolean>;
|
|
582
|
+
/** Restart reconciliation: force one honest terminal state for an open turn. */
|
|
583
|
+
reconcileTurn(command: {
|
|
584
|
+
turnId: string;
|
|
585
|
+
status: 'failed' | 'cancelled' | 'blocked';
|
|
586
|
+
reasonCode: string;
|
|
587
|
+
at: number;
|
|
588
|
+
}): Promise<AgentTurnRecord | undefined>;
|
|
589
|
+
listCancelIntents(turnId: string): Promise<readonly {
|
|
590
|
+
cancelId: string;
|
|
591
|
+
actorId: string;
|
|
592
|
+
reasonCode: string;
|
|
593
|
+
at: number;
|
|
594
|
+
}[]>;
|
|
595
|
+
}
|
|
596
|
+
export type AgentToolInvocationStatus = 'intent' | 'approved' | 'running' | 'completed' | 'failed' | 'declined' | 'cancelled' | 'outcome_unknown';
|
|
597
|
+
/** One durable turn-execution tool slot (stable tool-call identity). */
|
|
598
|
+
export interface TurnToolSlotAllocation {
|
|
599
|
+
/** The 1-based monotonic slot index within the turn. */
|
|
600
|
+
readonly slot: number;
|
|
601
|
+
/** The stable tool-call identity derived from the persisted slot. */
|
|
602
|
+
readonly toolCallId: string;
|
|
603
|
+
readonly turnId: string;
|
|
604
|
+
readonly toolName: string;
|
|
605
|
+
readonly argDigest: string;
|
|
606
|
+
}
|
|
607
|
+
/** One durable protected tool-invocation record.
|
|
608
|
+
*
|
|
609
|
+
* LIVE-OWNER FENCING (Stage 06B final boundary correction): the optional
|
|
610
|
+
* `runFence*` members are stamped by `claimInvocationRun` when ONE owner
|
|
611
|
+
* claims the invocation's execution attempt, carried through the durable
|
|
612
|
+
* `running` state, and required as EXACT BINDING on every fenced terminal
|
|
613
|
+
* settlement. `runGeneration` increments on every claim and on every
|
|
614
|
+
* reconciliation, so a stale owner can never settle a later generation.
|
|
615
|
+
*/
|
|
616
|
+
export interface AgentToolInvocationRecord {
|
|
617
|
+
readonly invocationId: string;
|
|
618
|
+
readonly turnId: string;
|
|
619
|
+
readonly toolCallId: string;
|
|
620
|
+
readonly toolName: string;
|
|
621
|
+
readonly capabilityId: string;
|
|
622
|
+
readonly capabilityRevision: string;
|
|
623
|
+
readonly effect: EffectClass;
|
|
624
|
+
/** Deterministic idempotency key for the logical invocation. */
|
|
625
|
+
readonly idempotencyKey: string;
|
|
626
|
+
readonly actorId: string;
|
|
627
|
+
/** Canonical argument digest — never the arguments themselves. */
|
|
628
|
+
readonly argDigest: string;
|
|
629
|
+
/** Safe bounded argument summary (never full payloads). */
|
|
630
|
+
readonly argumentSummary: string;
|
|
631
|
+
readonly status: AgentToolInvocationStatus;
|
|
632
|
+
readonly createdAt: number;
|
|
633
|
+
readonly updatedAt: number;
|
|
634
|
+
readonly completedAt: number | undefined;
|
|
635
|
+
/** Safe bounded result summary — never full payloads. */
|
|
636
|
+
readonly resultSummary: string | undefined;
|
|
637
|
+
readonly errorCode: string | undefined;
|
|
638
|
+
/** The fence token of the CURRENT execution attempt (claim-bound). */
|
|
639
|
+
readonly runFenceToken?: string | undefined;
|
|
640
|
+
/** When the current attempt was claimed (epoch ms). */
|
|
641
|
+
readonly runFenceAt?: number | undefined;
|
|
642
|
+
/** The process identity that claimed the current attempt. */
|
|
643
|
+
readonly runOwnerIdentity?: string | undefined;
|
|
644
|
+
/** Monotonic attempt generation (incremented per claim/reconciliation). */
|
|
645
|
+
readonly runGeneration?: number;
|
|
646
|
+
}
|
|
647
|
+
/** The durable tool-invocation store port. */
|
|
648
|
+
export interface AgentToolInvocationStore {
|
|
649
|
+
recordInvocationIntent(record: AgentToolInvocationRecord): Promise<AgentToolInvocationRecord>;
|
|
650
|
+
/**
|
|
651
|
+
* Allocate (or re-read) the DURABLE turn-execution tool slot for one
|
|
652
|
+
* logical tool request. The slot is a separately persisted allocation
|
|
653
|
+
* keyed by (turnId, toolName, argDigest): the FIRST allocation assigns
|
|
654
|
+
* the next monotonic slot for the turn and persists it BEFORE any
|
|
655
|
+
* invocation; every later call with the same key — including after a
|
|
656
|
+
* restart — returns the SAME slot and the SAME stable toolCallId.
|
|
657
|
+
* Logical tool-call identity therefore never derives from time, process
|
|
658
|
+
* counters, or the number of rows currently present.
|
|
659
|
+
*/
|
|
660
|
+
allocateTurnToolSlot(input: {
|
|
661
|
+
turnId: string;
|
|
662
|
+
toolName: string;
|
|
663
|
+
argDigest: string;
|
|
664
|
+
}): Promise<TurnToolSlotAllocation>;
|
|
665
|
+
getInvocation(invocationId: string): Promise<AgentToolInvocationRecord | undefined>;
|
|
666
|
+
getInvocationByIdempotencyKey(idempotencyKey: string): Promise<AgentToolInvocationRecord | undefined>;
|
|
667
|
+
updateInvocationStatus(command: {
|
|
668
|
+
invocationId: string;
|
|
669
|
+
status: 'approved' | 'running' | 'completed' | 'failed' | 'declined' | 'cancelled' | 'outcome_unknown';
|
|
670
|
+
at: number;
|
|
671
|
+
resultSummary?: string;
|
|
672
|
+
errorCode?: string;
|
|
673
|
+
}): Promise<AgentToolInvocationRecord>;
|
|
674
|
+
listInvocationsForTurn(turnId: string): Promise<readonly AgentToolInvocationRecord[]>;
|
|
675
|
+
/**
|
|
676
|
+
* CLAIM the invocation's execution attempt for ONE live owner: the durable
|
|
677
|
+
* status moves `intent`|`approved` → `running` and the record is stamped
|
|
678
|
+
* with the attempt fence (token, owner identity, claim time) and the next
|
|
679
|
+
* attempt generation. Exactly one claim per generation wins: a claim on an
|
|
680
|
+
* already-running record throws `VICT_CONTROL_INVOCATION_OWNER_ACTIVE`
|
|
681
|
+
* (the caller is the duplicate of a live owner) and a claim on a terminal
|
|
682
|
+
* record throws the stable terminal error. Duplicates NEVER mutate a
|
|
683
|
+
* live-owned record through any other command.
|
|
684
|
+
*/
|
|
685
|
+
claimInvocationRun(command: {
|
|
686
|
+
invocationId: string;
|
|
687
|
+
fenceToken: string;
|
|
688
|
+
ownerIdentity: string;
|
|
689
|
+
at: number;
|
|
690
|
+
}): Promise<AgentToolInvocationRecord>;
|
|
691
|
+
/**
|
|
692
|
+
* FENCED terminal settlement (`completed` | `failed` | `outcome_unknown`):
|
|
693
|
+
* accepted ONLY from the durable `running` state while the observed fence
|
|
694
|
+
* token equals the command's fence token — the caller must be the CURRENT
|
|
695
|
+
* owner of the attempt. An exact rematch of the requested terminal state
|
|
696
|
+
* AND its binding (status + errorCode + resultSummary) under the SAME
|
|
697
|
+
* fence is idempotent; every other conflict fails with a structured,
|
|
698
|
+
* non-echoing error (`VICT_CONTROL_INVOCATION_FENCE_MISMATCH` for a stale
|
|
699
|
+
* owner, `VICT_CONTROL_INVOCATION_TERMINAL`/`REGRESSION` otherwise). A
|
|
700
|
+
* stale owner can never settle a later claim generation.
|
|
701
|
+
*/
|
|
702
|
+
settleInvocationRun(command: {
|
|
703
|
+
invocationId: string;
|
|
704
|
+
fenceToken: string;
|
|
705
|
+
status: 'completed' | 'failed' | 'outcome_unknown';
|
|
706
|
+
at: number;
|
|
707
|
+
resultSummary?: string;
|
|
708
|
+
errorCode?: string;
|
|
709
|
+
}): Promise<AgentToolInvocationRecord>;
|
|
710
|
+
/**
|
|
711
|
+
* PRE-RUNNING terminal settlement (`failed` | `declined` | `cancelled`):
|
|
712
|
+
* applies only while the durable state is pre-running (`intent` or
|
|
713
|
+
* `approved`). An exact rematch (same status AND same errorCode) is
|
|
714
|
+
* idempotent; a conflict with a different terminal state or binding fails;
|
|
715
|
+
* a record already claimed (`running`) is never touched
|
|
716
|
+
* (`VICT_CONTROL_INVOCATION_OWNER_ACTIVE`).
|
|
717
|
+
*/
|
|
718
|
+
settleInvocationPending(command: {
|
|
719
|
+
invocationId: string;
|
|
720
|
+
status: 'failed' | 'declined' | 'cancelled';
|
|
721
|
+
at: number;
|
|
722
|
+
errorCode?: string;
|
|
723
|
+
}): Promise<AgentToolInvocationRecord>;
|
|
724
|
+
/**
|
|
725
|
+
* Conservative reconciliation of an ABANDONED `running` attempt (its
|
|
726
|
+
* owner is provably lost — no live owner exists for the recorded claim):
|
|
727
|
+
* the record is fenced to the terminal, NON-REPLAYABLE `outcome_unknown`
|
|
728
|
+
* without executing anything. Accepted ONLY when the observed durable
|
|
729
|
+
* state is exactly `running` AND the observed fence token equals
|
|
730
|
+
* `observedFenceToken` (idempotent only on that exact binding); the fence
|
|
731
|
+
* advances to `reconciledFenceToken` and the generation increments, so a
|
|
732
|
+
* stale owner's later settlement fails. Every other observed state fails
|
|
733
|
+
* with a structured, non-echoing error.
|
|
734
|
+
*/
|
|
735
|
+
reconcileAbandonedRun(command: {
|
|
736
|
+
invocationId: string;
|
|
737
|
+
observedFenceToken: string;
|
|
738
|
+
reconciledFenceToken: string;
|
|
739
|
+
at: number;
|
|
740
|
+
}): Promise<AgentToolInvocationRecord>;
|
|
741
|
+
}
|
|
742
|
+
export type AgentApprovalStatus = 'pending' | 'approved' | 'declined' | 'expired';
|
|
743
|
+
/**
|
|
744
|
+
* One VICT-authoritative approval record (MSTR-005). The record binds the
|
|
745
|
+
* decision to the EXACT requesting actor, activation/agent profile,
|
|
746
|
+
* capability revision, turn/tool-call/invocation identity, canonical
|
|
747
|
+
* argument digest, effect class, and environment — nothing else can
|
|
748
|
+
* consume it.
|
|
749
|
+
*/
|
|
750
|
+
export interface AgentApprovalRecord {
|
|
751
|
+
readonly approvalId: string;
|
|
752
|
+
readonly kind: 'tool-invocation';
|
|
753
|
+
readonly turnId: string;
|
|
754
|
+
readonly invocationId: string;
|
|
755
|
+
readonly toolCallId: string;
|
|
756
|
+
readonly toolName: string;
|
|
757
|
+
readonly capabilityId: string;
|
|
758
|
+
readonly capabilityRevision: string;
|
|
759
|
+
readonly effect: EffectClass;
|
|
760
|
+
readonly actorId: string;
|
|
761
|
+
readonly agentProfileVersion: string;
|
|
762
|
+
readonly argDigest: string;
|
|
763
|
+
readonly environment: string;
|
|
764
|
+
readonly requiredApproverRole: ActorRole;
|
|
765
|
+
readonly status: AgentApprovalStatus;
|
|
766
|
+
readonly createdAt: number;
|
|
767
|
+
readonly expiresAt: number;
|
|
768
|
+
readonly decidedAt: number | undefined;
|
|
769
|
+
readonly approverActorId: string | undefined;
|
|
770
|
+
/** Safe bounded reason/metadata — never payloads. */
|
|
771
|
+
readonly decisionReason: string | undefined;
|
|
772
|
+
}
|
|
773
|
+
/** The durable approval store port (VICT-authoritative decisions). */
|
|
774
|
+
export interface AgentApprovalStore {
|
|
775
|
+
createPendingApproval(record: AgentApprovalRecord): Promise<AgentApprovalRecord>;
|
|
776
|
+
getApproval(approvalId: string): Promise<AgentApprovalRecord | undefined>;
|
|
777
|
+
listOpenApprovals(): Promise<readonly AgentApprovalRecord[]>;
|
|
778
|
+
listApprovalsForInvocation(invocationId: string): Promise<readonly AgentApprovalRecord[]>;
|
|
779
|
+
decideApproval(command: {
|
|
780
|
+
approvalId: string;
|
|
781
|
+
approverActorId: string;
|
|
782
|
+
decision: 'approved' | 'declined';
|
|
783
|
+
decidedAt: number;
|
|
784
|
+
decisionReason?: string;
|
|
785
|
+
}): Promise<AgentApprovalRecord>;
|
|
786
|
+
expireApproval(command: {
|
|
787
|
+
approvalId: string;
|
|
788
|
+
at: number;
|
|
789
|
+
}): Promise<AgentApprovalRecord>;
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* One durably-ordered agent-stream event row. Only DURABLE kinds are
|
|
793
|
+
* persisted rows; `text.delta` consumes a sequence number but is retained
|
|
794
|
+
* only in the bounded transient buffer (never in operational history by
|
|
795
|
+
* default).
|
|
796
|
+
*/
|
|
797
|
+
export interface AgentStreamLedgerEvent {
|
|
798
|
+
readonly streamId: string;
|
|
799
|
+
readonly seq: number;
|
|
800
|
+
readonly kind: AgentStreamEventKind;
|
|
801
|
+
/** Canonical JSON of the kind-specific safe payload fields. */
|
|
802
|
+
readonly payload: string;
|
|
803
|
+
readonly createdAt: number;
|
|
804
|
+
}
|
|
805
|
+
/** The durable stream-ledger store port. */
|
|
806
|
+
export interface AgentStreamLedgerStore {
|
|
807
|
+
/**
|
|
808
|
+
* Append one event to a stream. Assigns the next strictly monotonic
|
|
809
|
+
* sequence; persists a row only for durable kinds (transient kinds
|
|
810
|
+
* advance the sequence without a row). Returns the assigned sequence.
|
|
811
|
+
*/
|
|
812
|
+
appendEvent(command: {
|
|
813
|
+
streamId: string;
|
|
814
|
+
kind: AgentStreamEventKind;
|
|
815
|
+
payload: string;
|
|
816
|
+
at: number;
|
|
817
|
+
}): Promise<{
|
|
818
|
+
seq: number;
|
|
819
|
+
persisted: boolean;
|
|
820
|
+
}>;
|
|
821
|
+
latestSeq(streamId: string): Promise<number>;
|
|
822
|
+
/**
|
|
823
|
+
* Durable events after the given sequence, in sequence order. The
|
|
824
|
+
* optional `limit` bounds one read (paged replay); implementations MUST
|
|
825
|
+
* accept calls without it.
|
|
826
|
+
*/
|
|
827
|
+
listEventsFrom(streamId: string, afterSeq: number, limit?: number): Promise<readonly AgentStreamLedgerEvent[]>;
|
|
828
|
+
listStreamIds(): Promise<readonly string[]>;
|
|
829
|
+
}
|
|
830
|
+
/** Durable kinds that a ledger row persists (transient kinds never persist). */
|
|
831
|
+
export declare function isDurableStreamKind(kind: AgentStreamEventKind): boolean;
|
|
832
|
+
/** The closed, bounded idempotency-key format (HTTP + CLI surface). */
|
|
833
|
+
export declare const COMMAND_IDEMPOTENCY_KEY_PATTERN: RegExp;
|
|
834
|
+
/**
|
|
835
|
+
* One durable command-idempotency receipt. Receipts are NAMESPACED by
|
|
836
|
+
* (authenticated actor, command, idempotency key): two actors using the
|
|
837
|
+
* same client-generated key never interfere, and the canonical request
|
|
838
|
+
* digest is bound INSIDE that namespace.
|
|
839
|
+
*
|
|
840
|
+
* A `pending` receipt carries a durable LEASE (owner + expiry): the
|
|
841
|
+
* claiming process renews/holds the lease while executing. A crash leaves
|
|
842
|
+
* the lease to expire, after which a retrying caller may take the claim
|
|
843
|
+
* over (attempt counter incremented) instead of the key being stuck as
|
|
844
|
+
* in-progress forever. A `failed` receipt is a DETERMINISTIC command
|
|
845
|
+
* failure (stable code, replayed); retryable infrastructure failures are
|
|
846
|
+
* RELEASED instead so a retry re-executes truthfully.
|
|
847
|
+
*/
|
|
848
|
+
export interface CommandIdempotencyReceipt {
|
|
849
|
+
readonly idempotencyKey: string;
|
|
850
|
+
readonly actorId: string;
|
|
851
|
+
readonly command: string;
|
|
852
|
+
/** Canonical digest over the exact request payload. */
|
|
853
|
+
readonly requestDigest: string;
|
|
854
|
+
readonly status: 'pending' | 'completed' | 'failed';
|
|
855
|
+
/** Stable failure code when status is `failed`. */
|
|
856
|
+
readonly responseCode: string | undefined;
|
|
857
|
+
/** SAFE per-command replay projection (identifiers/codes only — never
|
|
858
|
+
* full command responses). */
|
|
859
|
+
readonly resultJson: string | undefined;
|
|
860
|
+
readonly createdAt: number;
|
|
861
|
+
readonly settledAt: number | undefined;
|
|
862
|
+
/** The lease owner (process/instance token) while `pending`. */
|
|
863
|
+
readonly owner: string | undefined;
|
|
864
|
+
/** Epoch-ms lease expiry while `pending` (crash-recovery bound). */
|
|
865
|
+
readonly leaseUntil: number | undefined;
|
|
866
|
+
/** How many times the claim has been (re-)taken (lease takeovers). */
|
|
867
|
+
readonly attempts: number;
|
|
868
|
+
/**
|
|
869
|
+
* The immutable settlement FENCE token for the current claim generation.
|
|
870
|
+
* Completion, deterministic failure, and release must present exactly
|
|
871
|
+
* this token; a stale owner presenting a different token receives a
|
|
872
|
+
* stable non-echoing conflict and the receipt is left byte-identical.
|
|
873
|
+
* The token changes on every lease takeover (it is derived from the
|
|
874
|
+
* namespace, owner, and attempt generation).
|
|
875
|
+
*/
|
|
876
|
+
readonly fenceToken: string | undefined;
|
|
877
|
+
}
|
|
878
|
+
/** Stable structured conflict code thrown when a settlement fence mismatches. */
|
|
879
|
+
export declare const VICT_IDEMPOTENCY_FENCE_CONFLICT = "VICT_IDEMPOTENCY_FENCE_CONFLICT";
|
|
880
|
+
/**
|
|
881
|
+
* Derive the deterministic settlement fence token for one claim
|
|
882
|
+
* generation. The token binds the namespace, the lease owner, and the
|
|
883
|
+
* attempt generation: a lease takeover always produces a NEW token, so a
|
|
884
|
+
* stale owner can never settle a claim it no longer owns.
|
|
885
|
+
*/
|
|
886
|
+
export declare function commandIdempotencyFenceToken(input: {
|
|
887
|
+
actorId: string;
|
|
888
|
+
command: string;
|
|
889
|
+
idempotencyKey: string;
|
|
890
|
+
owner: string;
|
|
891
|
+
attempts: number;
|
|
892
|
+
}): string;
|
|
893
|
+
/** The result of a lease takeover attempt (the NEW fence token on success). */
|
|
894
|
+
export type CommandIdempotencyLeaseTakeover = {
|
|
895
|
+
outcome: 'taken';
|
|
896
|
+
fenceToken: string;
|
|
897
|
+
} | {
|
|
898
|
+
outcome: 'not-expired' | 'missing';
|
|
899
|
+
};
|
|
900
|
+
/** The namespaced lookup key of one receipt. */
|
|
901
|
+
export interface CommandIdempotencyName {
|
|
902
|
+
readonly actorId: string;
|
|
903
|
+
readonly command: string;
|
|
904
|
+
readonly idempotencyKey: string;
|
|
905
|
+
}
|
|
906
|
+
/** The durable command-idempotency store port (one winner per namespace). */
|
|
907
|
+
export interface CommandIdempotencyStore {
|
|
908
|
+
/**
|
|
909
|
+
* Insert-if-absent within the (actor, command, key) namespace. Returns
|
|
910
|
+
* `claimed` for exactly one concurrent caller (the winner); every other
|
|
911
|
+
* caller receives `exists` with the durable receipt.
|
|
912
|
+
*/
|
|
913
|
+
claimReceipt(record: CommandIdempotencyReceipt): Promise<'claimed' | 'exists'>;
|
|
914
|
+
getReceipt(name: CommandIdempotencyName): Promise<CommandIdempotencyReceipt | undefined>;
|
|
915
|
+
/**
|
|
916
|
+
* Cross-command key reuse check: the receipt bound to (actor, key) under
|
|
917
|
+
* ANY command kind, if one exists. The command-service policy layer uses
|
|
918
|
+
* this to turn a client reusing one Idempotency-Key across DIFFERENT
|
|
919
|
+
* commands into a stable conflict without relying on per-command
|
|
920
|
+
* namespaces alone.
|
|
921
|
+
*/
|
|
922
|
+
findReceiptByActorKey(input: {
|
|
923
|
+
actorId: string;
|
|
924
|
+
idempotencyKey: string;
|
|
925
|
+
}): Promise<CommandIdempotencyReceipt | undefined>;
|
|
926
|
+
completeReceipt(input: {
|
|
927
|
+
actorId: string;
|
|
928
|
+
command: string;
|
|
929
|
+
idempotencyKey: string;
|
|
930
|
+
resultJson: string;
|
|
931
|
+
at: number;
|
|
932
|
+
/** The claim's settlement fence token (exact generation match). */
|
|
933
|
+
fenceToken: string;
|
|
934
|
+
}): Promise<void>;
|
|
935
|
+
failReceipt(input: {
|
|
936
|
+
actorId: string;
|
|
937
|
+
command: string;
|
|
938
|
+
idempotencyKey: string;
|
|
939
|
+
responseCode: string;
|
|
940
|
+
at: number;
|
|
941
|
+
/** The claim's settlement fence token (exact generation match). */
|
|
942
|
+
fenceToken: string;
|
|
943
|
+
}): Promise<void>;
|
|
944
|
+
/**
|
|
945
|
+
* Release a `pending` claim WITHOUT a terminal disposition — used for
|
|
946
|
+
* RETRYABLE infrastructure failures so they are never permanently
|
|
947
|
+
* confused with deterministic command failures. The key becomes
|
|
948
|
+
* claimable again. The release is FENCED: only the current claim owner
|
|
949
|
+
* generation may release; a stale owner receives a stable conflict and
|
|
950
|
+
* the live claim is left byte-identical.
|
|
951
|
+
*/
|
|
952
|
+
releaseReceipt(input: {
|
|
953
|
+
actorId: string;
|
|
954
|
+
command: string;
|
|
955
|
+
idempotencyKey: string;
|
|
956
|
+
at: number;
|
|
957
|
+
/** The claim's settlement fence token (exact generation match). */
|
|
958
|
+
fenceToken: string;
|
|
959
|
+
}): Promise<void>;
|
|
960
|
+
/**
|
|
961
|
+
* Crash recovery: take over an EXPIRED pending lease. Returns `taken`
|
|
962
|
+
* with the NEW settlement fence token (the caller is the new owner;
|
|
963
|
+
* attempts incremented), `not-expired` (the previous owner may still be
|
|
964
|
+
* executing), or `missing`.
|
|
965
|
+
*/
|
|
966
|
+
takeOverExpiredLease(input: {
|
|
967
|
+
actorId: string;
|
|
968
|
+
command: string;
|
|
969
|
+
idempotencyKey: string;
|
|
970
|
+
owner: string;
|
|
971
|
+
leaseUntil: number;
|
|
972
|
+
at: number;
|
|
973
|
+
}): Promise<CommandIdempotencyLeaseTakeover>;
|
|
974
|
+
}
|
|
975
|
+
/** The composed control-plane store set. */
|
|
976
|
+
export interface AgentControlStores {
|
|
977
|
+
readonly actors: ActorDirectory;
|
|
978
|
+
readonly control: ControlPlaneStore;
|
|
979
|
+
readonly turns: AgentTurnStore;
|
|
980
|
+
readonly invocations: AgentToolInvocationStore;
|
|
981
|
+
readonly approvals: AgentApprovalStore;
|
|
982
|
+
readonly streamLedger: AgentStreamLedgerStore;
|
|
983
|
+
/** Durable command idempotency (state-changing HTTP/CLI commands). */
|
|
984
|
+
readonly commandIdempotency: CommandIdempotencyStore;
|
|
985
|
+
}
|
|
986
|
+
/** Validation error for any control-plane structural violation. */
|
|
987
|
+
export declare class VictControlError extends Error {
|
|
988
|
+
readonly code: string;
|
|
989
|
+
constructor(code: string, message: string);
|
|
990
|
+
}
|
|
991
|
+
/** Canonical JSON of one stream event's safe payload fields (context minus stream identity). */
|
|
992
|
+
export declare function streamEventPayloadOf(event: object): string;
|
|
993
|
+
/**
|
|
994
|
+
* Validate ONE append against the final `vict.agent-stream@1` schema at
|
|
995
|
+
* the STORE boundary — before any sequence state is incremented or any
|
|
996
|
+
* storage mutated. The RAW ledger ports are the last line of defense:
|
|
997
|
+
* plain-JavaScript callers cannot bypass the schema through them.
|
|
998
|
+
*
|
|
999
|
+
* Enforced here:
|
|
1000
|
+
* - the payload is a JSON object in CANONICAL form (non-canonical JSON is
|
|
1001
|
+
* rejected, not silently re-serialized);
|
|
1002
|
+
* - the declared kind is inside the closed vocabulary and matches the
|
|
1003
|
+
* payload's `kind` member;
|
|
1004
|
+
* - the reconstructed event (payload + stream identity + the sequence that
|
|
1005
|
+
* WOULD be assigned) passes the complete field-level schema (unknown
|
|
1006
|
+
* kinds/fields, malformed correlation IDs, unsafe codes, raw text in
|
|
1007
|
+
* `content.completed`, invalid usage — all fail closed).
|
|
1008
|
+
*
|
|
1009
|
+
* Errors are stable and non-echoing: `VICT_STREAM_EVENT_INVALID` carries
|
|
1010
|
+
* schema issue CODES only, never the rejected values.
|
|
1011
|
+
*/
|
|
1012
|
+
export declare function validateStreamLedgerAppend(command: {
|
|
1013
|
+
streamId: string;
|
|
1014
|
+
kind: AgentStreamEventKind;
|
|
1015
|
+
payload: string;
|
|
1016
|
+
assignedSeq: number;
|
|
1017
|
+
}): AgentStreamEvent;
|