@danypops/vehicle-core 0.13.0 → 0.14.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.
|
@@ -41,6 +41,8 @@ export interface VehicleApprovalOutcome {
|
|
|
41
41
|
readonly decision: VehicleApprovalDecision;
|
|
42
42
|
readonly decidedAt: number;
|
|
43
43
|
readonly decidedBy?: string;
|
|
44
|
+
/** Optional human rationale captured by a rich HITL presenter. */
|
|
45
|
+
readonly comment?: string;
|
|
44
46
|
}
|
|
45
47
|
/**
|
|
46
48
|
* The real authority behind an approvalCapability -- replaces today's
|
|
@@ -53,6 +53,7 @@ const resolvedPayloadSchema = defineVehicleSchema({
|
|
|
53
53
|
decision: { type: "string", enum: ["granted", "denied"] },
|
|
54
54
|
decidedAt: { type: "number" },
|
|
55
55
|
decidedBy: { type: "string" },
|
|
56
|
+
comment: { type: "string" },
|
|
56
57
|
},
|
|
57
58
|
required: ["requestId", "decision", "decidedAt"],
|
|
58
59
|
additionalProperties: true,
|
|
@@ -26,6 +26,14 @@ export type VehicleSchemaResult<T> = {
|
|
|
26
26
|
readonly success: false;
|
|
27
27
|
readonly issues?: readonly VehicleSchemaIssue[];
|
|
28
28
|
};
|
|
29
|
+
/**
|
|
30
|
+
* A serializable, descriptive `jsonSchema` (surfaced to a client or Pi tool
|
|
31
|
+
* projection) paired with a real `safeParse` that actually enforces it at
|
|
32
|
+
* runtime -- a Vehicle registry's own `invoke()` only ever calls
|
|
33
|
+
* `safeParse`; `jsonSchema` alone is never itself enforced, so a codec that
|
|
34
|
+
* only sets `jsonSchema` without a matching `safeParse` is a documentation
|
|
35
|
+
* gesture, not an honest contract.
|
|
36
|
+
*/
|
|
29
37
|
export interface VehicleSchemaCodec<T> {
|
|
30
38
|
readonly jsonSchema: JsonSchema;
|
|
31
39
|
safeParse(value: unknown): VehicleSchemaResult<T>;
|
|
@@ -93,6 +101,7 @@ export interface VehicleLimits {
|
|
|
93
101
|
readonly maxRequestBytes: number;
|
|
94
102
|
readonly maxResponseBytes: number;
|
|
95
103
|
}
|
|
104
|
+
/** One structured, documented failure mode a {@link VehicleOperationDescriptor} declares up front -- part of the operation's own serializable contract, not an ad hoc thrown Error a caller has to reverse-engineer from a message string. */
|
|
96
105
|
export interface VehicleFailureDescriptor {
|
|
97
106
|
readonly code: string;
|
|
98
107
|
readonly description: string;
|
|
@@ -103,6 +112,16 @@ export interface VehicleBackgroundCapability {
|
|
|
103
112
|
readonly defaultWakeBudget: VehicleJobWakeBudget;
|
|
104
113
|
readonly maxWakeBudget: VehicleJobWakeBudget;
|
|
105
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* The serializable half of a Vehicle operation -- name, version, schemas,
|
|
117
|
+
* ownership-implying permissions, effect classification, idempotency,
|
|
118
|
+
* streaming/long-running capability, request/response limits, and declared
|
|
119
|
+
* {@link VehicleFailureDescriptor} failure modes. Kept separate from the
|
|
120
|
+
* executable {@link VehicleOperationHandler} on purpose: a manifest, a Pi
|
|
121
|
+
* tool projection, or a client's own capability check can all inspect this
|
|
122
|
+
* shape without ever touching (or needing to trust) the implementation
|
|
123
|
+
* behind it.
|
|
124
|
+
*/
|
|
106
125
|
export interface VehicleOperationDescriptor {
|
|
107
126
|
readonly name: string;
|
|
108
127
|
readonly version: number;
|
|
@@ -117,6 +136,21 @@ export interface VehicleOperationDescriptor {
|
|
|
117
136
|
readonly limits: VehicleLimits;
|
|
118
137
|
readonly errors: readonly VehicleFailureDescriptor[];
|
|
119
138
|
readonly background?: VehicleBackgroundCapability;
|
|
139
|
+
/**
|
|
140
|
+
* Owner-declared override for whether this specific operation is ever a candidate for
|
|
141
|
+
* approval gating, independent of its `effect`. Undefined (the default) means "derive it
|
|
142
|
+
* from `effect` against the registry's own requireApprovalForEffects set instead" --
|
|
143
|
+
* VehicleRegistry.manifest()'s own resolution rule, unchanged for every existing
|
|
144
|
+
* operation that never sets this.
|
|
145
|
+
*
|
|
146
|
+
* Exists because VehicleEffect's five values are coarse enough that two operations a
|
|
147
|
+
* real owner classifies very differently (e.g. "restart an already-installed,
|
|
148
|
+
* already-vetted service" vs. "sync a read-only catalog mirror") can land in the same
|
|
149
|
+
* effect bucket (both external-write) -- no single requireApprovalForEffects set can
|
|
150
|
+
* gate one without also gating the other. The owner who registers the operation knows
|
|
151
|
+
* its real risk far better than a 5-value enum can; this lets them say so directly.
|
|
152
|
+
*/
|
|
153
|
+
readonly requiresApproval?: boolean;
|
|
120
154
|
}
|
|
121
155
|
export interface VehicleOperation<Input, Output> {
|
|
122
156
|
readonly descriptor: VehicleOperationDescriptor;
|
|
@@ -137,6 +171,8 @@ export interface DefineVehicleOperationOptions<Input, Output> {
|
|
|
137
171
|
readonly limits: VehicleLimits;
|
|
138
172
|
readonly errors?: readonly VehicleFailureDescriptor[];
|
|
139
173
|
readonly background?: VehicleBackgroundCapability;
|
|
174
|
+
/** See {@link VehicleOperationDescriptor.requiresApproval}. */
|
|
175
|
+
readonly requiresApproval?: boolean;
|
|
140
176
|
}
|
|
141
177
|
export interface VehiclePrincipal {
|
|
142
178
|
readonly id: string;
|
|
@@ -192,6 +228,24 @@ export interface VehicleManifestIdentity {
|
|
|
192
228
|
export interface VehicleManifestOperation extends VehicleOperationDescriptor {
|
|
193
229
|
readonly available: boolean;
|
|
194
230
|
readonly unavailableReason?: string;
|
|
231
|
+
/**
|
|
232
|
+
* The registry's own, live, fully-resolved answer to "does invoking this operation right
|
|
233
|
+
* now require approval" -- accounts for the registry's current approval policy being
|
|
234
|
+
* enabled/disabled, this operation's own `requiresApproval` override when set, and the
|
|
235
|
+
* effect-derived default otherwise. A real VehicleRegistry.manifest() always sets this
|
|
236
|
+
* (false when the registry never called configureApprovals() at all) -- unlike
|
|
237
|
+
* `requiresApproval` (the static, author-declared override on the descriptor itself),
|
|
238
|
+
* this always reflects the current instant, so a client re-fetching the manifest after a
|
|
239
|
+
* live policy change (VehicleRegistry.updateApprovalPolicy) sees the new answer with no
|
|
240
|
+
* separate sync mechanism needed.
|
|
241
|
+
*
|
|
242
|
+
* Optional purely for backward compatibility with every hand-authored VehicleManifest
|
|
243
|
+
* test fixture across the ecosystem that predates this field (the same reason
|
|
244
|
+
* VehicleManifest.events is optional) -- a consumer reading it should treat undefined the
|
|
245
|
+
* same as a caller of classifyVehicleOperationSafety does: fall back to the effect-level
|
|
246
|
+
* default, never assume false.
|
|
247
|
+
*/
|
|
248
|
+
readonly approvalRequired?: boolean;
|
|
195
249
|
}
|
|
196
250
|
/**
|
|
197
251
|
* A named, schema'd event type a provider declares as part of its
|
package/dist/vehicle-contract.js
CHANGED
|
@@ -140,6 +140,7 @@ export function defineVehicleOperation(options) {
|
|
|
140
140
|
longRunning: options.longRunning ?? false,
|
|
141
141
|
limits: Object.freeze({ ...options.limits }),
|
|
142
142
|
errors: Object.freeze((options.errors ?? []).map((failure) => Object.freeze({ ...failure }))),
|
|
143
|
+
...(options.requiresApproval !== undefined ? { requiresApproval: options.requiresApproval } : {}),
|
|
143
144
|
...(options.background
|
|
144
145
|
? {
|
|
145
146
|
background: Object.freeze({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danypops/vehicle-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Vehicle's runtime-neutral wire contract: operation descriptors, schema codecs, failure shapes. Zero runtime dependencies, zero Bun-specific code -- the one thing every Vehicle client and server package depends on.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/src/vehicle-approvals.ts
CHANGED
|
@@ -48,6 +48,8 @@ export interface VehicleApprovalOutcome {
|
|
|
48
48
|
readonly decision: VehicleApprovalDecision;
|
|
49
49
|
readonly decidedAt: number;
|
|
50
50
|
readonly decidedBy?: string;
|
|
51
|
+
/** Optional human rationale captured by a rich HITL presenter. */
|
|
52
|
+
readonly comment?: string;
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
/**
|
|
@@ -106,6 +108,7 @@ const resolvedPayloadSchema = defineVehicleSchema<VehicleApprovalOutcome>({
|
|
|
106
108
|
decision: { type: "string", enum: ["granted", "denied"] },
|
|
107
109
|
decidedAt: { type: "number" },
|
|
108
110
|
decidedBy: { type: "string" },
|
|
111
|
+
comment: { type: "string" },
|
|
109
112
|
},
|
|
110
113
|
required: ["requestId", "decision", "decidedAt"],
|
|
111
114
|
additionalProperties: true,
|
package/src/vehicle-contract.ts
CHANGED
|
@@ -42,6 +42,14 @@ export type VehicleSchemaResult<T> =
|
|
|
42
42
|
| { readonly success: true; readonly value: T }
|
|
43
43
|
| { readonly success: false; readonly issues?: readonly VehicleSchemaIssue[] };
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* A serializable, descriptive `jsonSchema` (surfaced to a client or Pi tool
|
|
47
|
+
* projection) paired with a real `safeParse` that actually enforces it at
|
|
48
|
+
* runtime -- a Vehicle registry's own `invoke()` only ever calls
|
|
49
|
+
* `safeParse`; `jsonSchema` alone is never itself enforced, so a codec that
|
|
50
|
+
* only sets `jsonSchema` without a matching `safeParse` is a documentation
|
|
51
|
+
* gesture, not an honest contract.
|
|
52
|
+
*/
|
|
45
53
|
export interface VehicleSchemaCodec<T> {
|
|
46
54
|
readonly jsonSchema: JsonSchema;
|
|
47
55
|
safeParse(value: unknown): VehicleSchemaResult<T>;
|
|
@@ -163,6 +171,7 @@ export interface VehicleLimits {
|
|
|
163
171
|
readonly maxResponseBytes: number;
|
|
164
172
|
}
|
|
165
173
|
|
|
174
|
+
/** One structured, documented failure mode a {@link VehicleOperationDescriptor} declares up front -- part of the operation's own serializable contract, not an ad hoc thrown Error a caller has to reverse-engineer from a message string. */
|
|
166
175
|
export interface VehicleFailureDescriptor {
|
|
167
176
|
readonly code: string;
|
|
168
177
|
readonly description: string;
|
|
@@ -175,6 +184,16 @@ export interface VehicleBackgroundCapability {
|
|
|
175
184
|
readonly maxWakeBudget: VehicleJobWakeBudget;
|
|
176
185
|
}
|
|
177
186
|
|
|
187
|
+
/**
|
|
188
|
+
* The serializable half of a Vehicle operation -- name, version, schemas,
|
|
189
|
+
* ownership-implying permissions, effect classification, idempotency,
|
|
190
|
+
* streaming/long-running capability, request/response limits, and declared
|
|
191
|
+
* {@link VehicleFailureDescriptor} failure modes. Kept separate from the
|
|
192
|
+
* executable {@link VehicleOperationHandler} on purpose: a manifest, a Pi
|
|
193
|
+
* tool projection, or a client's own capability check can all inspect this
|
|
194
|
+
* shape without ever touching (or needing to trust) the implementation
|
|
195
|
+
* behind it.
|
|
196
|
+
*/
|
|
178
197
|
export interface VehicleOperationDescriptor {
|
|
179
198
|
readonly name: string;
|
|
180
199
|
readonly version: number;
|
|
@@ -189,6 +208,21 @@ export interface VehicleOperationDescriptor {
|
|
|
189
208
|
readonly limits: VehicleLimits;
|
|
190
209
|
readonly errors: readonly VehicleFailureDescriptor[];
|
|
191
210
|
readonly background?: VehicleBackgroundCapability;
|
|
211
|
+
/**
|
|
212
|
+
* Owner-declared override for whether this specific operation is ever a candidate for
|
|
213
|
+
* approval gating, independent of its `effect`. Undefined (the default) means "derive it
|
|
214
|
+
* from `effect` against the registry's own requireApprovalForEffects set instead" --
|
|
215
|
+
* VehicleRegistry.manifest()'s own resolution rule, unchanged for every existing
|
|
216
|
+
* operation that never sets this.
|
|
217
|
+
*
|
|
218
|
+
* Exists because VehicleEffect's five values are coarse enough that two operations a
|
|
219
|
+
* real owner classifies very differently (e.g. "restart an already-installed,
|
|
220
|
+
* already-vetted service" vs. "sync a read-only catalog mirror") can land in the same
|
|
221
|
+
* effect bucket (both external-write) -- no single requireApprovalForEffects set can
|
|
222
|
+
* gate one without also gating the other. The owner who registers the operation knows
|
|
223
|
+
* its real risk far better than a 5-value enum can; this lets them say so directly.
|
|
224
|
+
*/
|
|
225
|
+
readonly requiresApproval?: boolean;
|
|
192
226
|
}
|
|
193
227
|
|
|
194
228
|
export interface VehicleOperation<Input, Output> {
|
|
@@ -211,6 +245,8 @@ export interface DefineVehicleOperationOptions<Input, Output> {
|
|
|
211
245
|
readonly limits: VehicleLimits;
|
|
212
246
|
readonly errors?: readonly VehicleFailureDescriptor[];
|
|
213
247
|
readonly background?: VehicleBackgroundCapability;
|
|
248
|
+
/** See {@link VehicleOperationDescriptor.requiresApproval}. */
|
|
249
|
+
readonly requiresApproval?: boolean;
|
|
214
250
|
}
|
|
215
251
|
|
|
216
252
|
export interface VehiclePrincipal {
|
|
@@ -273,6 +309,24 @@ export interface VehicleManifestIdentity {
|
|
|
273
309
|
export interface VehicleManifestOperation extends VehicleOperationDescriptor {
|
|
274
310
|
readonly available: boolean;
|
|
275
311
|
readonly unavailableReason?: string;
|
|
312
|
+
/**
|
|
313
|
+
* The registry's own, live, fully-resolved answer to "does invoking this operation right
|
|
314
|
+
* now require approval" -- accounts for the registry's current approval policy being
|
|
315
|
+
* enabled/disabled, this operation's own `requiresApproval` override when set, and the
|
|
316
|
+
* effect-derived default otherwise. A real VehicleRegistry.manifest() always sets this
|
|
317
|
+
* (false when the registry never called configureApprovals() at all) -- unlike
|
|
318
|
+
* `requiresApproval` (the static, author-declared override on the descriptor itself),
|
|
319
|
+
* this always reflects the current instant, so a client re-fetching the manifest after a
|
|
320
|
+
* live policy change (VehicleRegistry.updateApprovalPolicy) sees the new answer with no
|
|
321
|
+
* separate sync mechanism needed.
|
|
322
|
+
*
|
|
323
|
+
* Optional purely for backward compatibility with every hand-authored VehicleManifest
|
|
324
|
+
* test fixture across the ecosystem that predates this field (the same reason
|
|
325
|
+
* VehicleManifest.events is optional) -- a consumer reading it should treat undefined the
|
|
326
|
+
* same as a caller of classifyVehicleOperationSafety does: fall back to the effect-level
|
|
327
|
+
* default, never assume false.
|
|
328
|
+
*/
|
|
329
|
+
readonly approvalRequired?: boolean;
|
|
276
330
|
}
|
|
277
331
|
|
|
278
332
|
/**
|
|
@@ -384,6 +438,7 @@ export function defineVehicleOperation<Input, Output>(
|
|
|
384
438
|
longRunning: options.longRunning ?? false,
|
|
385
439
|
limits: Object.freeze({ ...options.limits }),
|
|
386
440
|
errors: Object.freeze((options.errors ?? []).map((failure) => Object.freeze({ ...failure }))),
|
|
441
|
+
...(options.requiresApproval !== undefined ? { requiresApproval: options.requiresApproval } : {}),
|
|
387
442
|
...(options.background
|
|
388
443
|
? {
|
|
389
444
|
background: Object.freeze({
|