@danypops/vehicle-conformance 0.4.2 → 0.4.3

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.
@@ -48,6 +48,18 @@ const ConformanceKeyed = defineVehicleOperation({
48
48
  output: outputSchema,
49
49
  permissions: [],
50
50
  effect: "external-write",
51
+ requiresApproval: false,
52
+ idempotency: { mode: "keyed", retentionMs: 60_000 },
53
+ limits: LIMITS,
54
+ });
55
+ const ConformanceUnconfiguredRisk = defineVehicleOperation({
56
+ name: "conformance.unconfigured-risk",
57
+ version: 1,
58
+ description: "Proves that risky operations require an explicit registry approval-policy decision.",
59
+ input: passthroughSchema,
60
+ output: outputSchema,
61
+ permissions: [],
62
+ effect: "external-write",
51
63
  idempotency: { mode: "keyed", retentionMs: 60_000 },
52
64
  limits: LIMITS,
53
65
  });
@@ -94,6 +106,7 @@ export function registerConformanceOperations(registry) {
94
106
  throw new VehicleError("conformance-boom", "conformance.boom always fails", { category: "internal" });
95
107
  }));
96
108
  registry.register("conformance", bindVehicleOperation(ConformanceKeyed, () => async (context) => ({ echoed: context.input.value })));
109
+ registry.register("conformance", bindVehicleOperation(ConformanceUnconfiguredRisk, () => async (context) => ({ echoed: context.input.value })));
97
110
  registry.register("conformance", bindVehicleOperation(ConformanceProgress, () => async (context) => {
98
111
  context.reportProgress({ step: 1 });
99
112
  context.reportProgress({ step: 2 });
@@ -127,6 +140,7 @@ export function registerVehicleClientConformance(runner, fixture) {
127
140
  "conformance.never@1",
128
141
  "conformance.progress@1",
129
142
  "conformance.slow-progress@1",
143
+ "conformance.unconfigured-risk@1",
130
144
  ]);
131
145
  const echo = manifest.operations.find((op) => op.name === "conformance.echo");
132
146
  expect(echo?.permissions).toEqual(["conformance:echo"]);
@@ -211,6 +225,20 @@ export function registerVehicleClientConformance(runner, fixture) {
211
225
  await cleanup();
212
226
  }
213
227
  });
228
+ it("reports an unconfigured risky operation as a hard failure", async () => {
229
+ const { client, cleanup } = await fixture.create();
230
+ try {
231
+ const manifest = await client.manifest();
232
+ expect(manifest.approvalPolicy).toMatchObject({
233
+ status: "unconfigured",
234
+ unconfiguredRiskyOperations: ["conformance.unconfigured-risk@1"],
235
+ });
236
+ await expect(client.invoke("conformance.unconfigured-risk", 1, { value: "x" }, { idempotencyKey: "risk-1" })).rejects.toMatchObject({ code: "approval-policy-unconfigured", category: "authorization" });
237
+ }
238
+ finally {
239
+ await cleanup();
240
+ }
241
+ });
214
242
  it("invoke() rejects a request exceeding its declared byte bound", async () => {
215
243
  const { client, cleanup } = await fixture.create();
216
244
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/vehicle-conformance",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Runner-neutral conformance suite for VehicleClient implementations, with Bun and Vitest adapters over one shared assertion matrix.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -26,8 +26,8 @@
26
26
  "typecheck": "tsc --noEmit"
27
27
  },
28
28
  "dependencies": {
29
- "@danypops/vehicle-core": "^0.19.1",
30
- "@danypops/vehicle-server": "^0.27.1"
29
+ "@danypops/vehicle-core": "^0.19.2",
30
+ "@danypops/vehicle-server": "^0.27.3"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@danypops/vehicle-client": "^0.10.8",
@@ -100,6 +100,19 @@ const ConformanceKeyed = defineVehicleOperation({
100
100
  output: outputSchema,
101
101
  permissions: [],
102
102
  effect: "external-write",
103
+ requiresApproval: false,
104
+ idempotency: { mode: "keyed", retentionMs: 60_000 },
105
+ limits: LIMITS,
106
+ });
107
+
108
+ const ConformanceUnconfiguredRisk = defineVehicleOperation({
109
+ name: "conformance.unconfigured-risk",
110
+ version: 1,
111
+ description: "Proves that risky operations require an explicit registry approval-policy decision.",
112
+ input: passthroughSchema,
113
+ output: outputSchema,
114
+ permissions: [],
115
+ effect: "external-write",
103
116
  idempotency: { mode: "keyed", retentionMs: 60_000 },
104
117
  limits: LIMITS,
105
118
  });
@@ -160,6 +173,10 @@ export function registerConformanceOperations(registry: VehicleRegistry): void {
160
173
  "conformance",
161
174
  bindVehicleOperation(ConformanceKeyed, () => async (context) => ({ echoed: context.input.value })),
162
175
  );
176
+ registry.register(
177
+ "conformance",
178
+ bindVehicleOperation(ConformanceUnconfiguredRisk, () => async (context) => ({ echoed: context.input.value })),
179
+ );
163
180
  registry.register(
164
181
  "conformance",
165
182
  bindVehicleOperation(ConformanceProgress, () => async (context) => {
@@ -211,6 +228,7 @@ export function registerVehicleClientConformance(runner: VehicleConformanceRunne
211
228
  "conformance.never@1",
212
229
  "conformance.progress@1",
213
230
  "conformance.slow-progress@1",
231
+ "conformance.unconfigured-risk@1",
214
232
  ]);
215
233
  const echo = manifest.operations.find((op) => op.name === "conformance.echo");
216
234
  expect(echo?.permissions).toEqual(["conformance:echo"]);
@@ -303,6 +321,22 @@ export function registerVehicleClientConformance(runner: VehicleConformanceRunne
303
321
  }
304
322
  });
305
323
 
324
+ it("reports an unconfigured risky operation as a hard failure", async () => {
325
+ const { client, cleanup } = await fixture.create();
326
+ try {
327
+ const manifest = await client.manifest();
328
+ expect(manifest.approvalPolicy).toMatchObject({
329
+ status: "unconfigured",
330
+ unconfiguredRiskyOperations: ["conformance.unconfigured-risk@1"],
331
+ });
332
+ await expect(client.invoke("conformance.unconfigured-risk", 1, { value: "x" }, { idempotencyKey: "risk-1" })).rejects.toMatchObject(
333
+ { code: "approval-policy-unconfigured", category: "authorization" },
334
+ );
335
+ } finally {
336
+ await cleanup();
337
+ }
338
+ });
339
+
306
340
  it("invoke() rejects a request exceeding its declared byte bound", async () => {
307
341
  const { client, cleanup } = await fixture.create();
308
342
  try {