@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
@@ -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.13.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",
@@ -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,
@@ -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({