@danypops/vehicle-conformance 0.2.1 → 0.3.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/README.md +14 -1
- package/package.json +5 -5
- package/src/vehicle-conformance.ts +113 -0
package/README.md
CHANGED
|
@@ -10,8 +10,21 @@ bun add -d @danypops/vehicle-conformance
|
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
```ts
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
registerConformanceOperations,
|
|
15
|
+
runVehicleClientConformance,
|
|
16
|
+
runToolShellDualChannelConformance,
|
|
17
|
+
} from "@danypops/vehicle-conformance";
|
|
14
18
|
```
|
|
15
19
|
|
|
20
|
+
`runToolShellDualChannelConformance(fixture)` is the host-neutral Tool Shell
|
|
21
|
+
matrix. A fixture adapts one provider's real projection/rendering boundary via
|
|
22
|
+
`execute`, `render`, `replay`, `renderCall`, and `invalidProjection`; the shared
|
|
23
|
+
suite checks independent model/presentation sentinels and named bounds,
|
|
24
|
+
JSON-safe secret-free details, malformed/unknown replay fallback, collapsed vs.
|
|
25
|
+
expanded immutability, schema-sensitive call rendering, 40/80/120-column
|
|
26
|
+
physical-line safety, partial output, and projector exception policy. Pi-specific
|
|
27
|
+
component construction stays in the adapter fixture rather than this package.
|
|
28
|
+
|
|
16
29
|
See the [workspace README](https://github.com/DanyPops/vehicle#readme) for
|
|
17
30
|
the full Vehicle package layout.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danypops/vehicle-conformance",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Host-neutral conformance suite for any VehicleClient implementation: one shared bun:test assertion set that LocalVehicleClient, RemoteVehicleClient, and any future transport must satisfy identically. A Bun-only devDependency for testing, not a runtime library -- ships raw TypeScript, never precompiled.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
"typecheck": "tsc --noEmit"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@danypops/vehicle-core": "^0.
|
|
18
|
-
"@danypops/vehicle-server": "^0.
|
|
17
|
+
"@danypops/vehicle-core": "^0.13.0",
|
|
18
|
+
"@danypops/vehicle-server": "^0.18.3"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@danypops/vehicle-client": "^0.
|
|
21
|
+
"@danypops/vehicle-client": "^0.7.0",
|
|
22
22
|
"@types/node": "^22.0.0",
|
|
23
|
-
"typescript": "^5.9.
|
|
23
|
+
"typescript": "^5.9.3"
|
|
24
24
|
},
|
|
25
25
|
"repository": {
|
|
26
26
|
"type": "git",
|
|
@@ -418,3 +418,116 @@ export function runVehicleClientConformance(fixture: VehicleConformanceFixture):
|
|
|
418
418
|
});
|
|
419
419
|
});
|
|
420
420
|
}
|
|
421
|
+
|
|
422
|
+
export interface ToolShellConformanceSnapshot {
|
|
423
|
+
readonly content: string;
|
|
424
|
+
readonly details: unknown;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export interface ToolShellRenderOptions {
|
|
428
|
+
readonly width: 40 | 80 | 120;
|
|
429
|
+
readonly expanded: boolean;
|
|
430
|
+
readonly partial?: boolean;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Host adapter for the Tool Shell's two independent persisted channels. The
|
|
435
|
+
* conformance package stays Pi-free: a Pi adapter supplies component output,
|
|
436
|
+
* while a CLI or another host can supply its own renderer through this same API.
|
|
437
|
+
*/
|
|
438
|
+
export interface ToolShellDualChannelSubject {
|
|
439
|
+
readonly bounds: { readonly modelContentBytes: number; readonly presentationDetailsBytes: number };
|
|
440
|
+
execute(): Promise<ToolShellConformanceSnapshot>;
|
|
441
|
+
render(snapshot: ToolShellConformanceSnapshot, options: ToolShellRenderOptions): readonly string[];
|
|
442
|
+
replay(details: unknown, fallbackContent: string, options: ToolShellRenderOptions): readonly string[];
|
|
443
|
+
renderCall(args: unknown, width: 40 | 80 | 120): readonly string[];
|
|
444
|
+
invalidProjection(): Promise<unknown>;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export interface ToolShellDualChannelFixture {
|
|
448
|
+
readonly label: string;
|
|
449
|
+
create(): Promise<{ readonly subject: ToolShellDualChannelSubject; readonly cleanup: () => Promise<void> }>;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function utf8Length(value: string): number {
|
|
453
|
+
return new TextEncoder().encode(value).byteLength;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// biome-ignore lint/complexity/useRegexLiterals: a constructor avoids control-character lint on the equivalent literal.
|
|
457
|
+
const ANSI_CSI_PATTERN = new RegExp("\\u001B\\[[0-?]*[ -/]*[@-~]", "g");
|
|
458
|
+
|
|
459
|
+
function assertPhysicalLines(lines: readonly string[], width: number): void {
|
|
460
|
+
expect(lines.length).toBeGreaterThan(0);
|
|
461
|
+
for (const line of lines) {
|
|
462
|
+
expect(line).not.toContain("\n");
|
|
463
|
+
// ANSI is forbidden in model content, but permitted in host rendering.
|
|
464
|
+
const visible = line.replace(ANSI_CSI_PATTERN, "");
|
|
465
|
+
expect([...visible].length).toBeLessThanOrEqual(width);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/** Reusable provider-facing dual-channel contract matrix. */
|
|
470
|
+
export function runToolShellDualChannelConformance(fixture: ToolShellDualChannelFixture): void {
|
|
471
|
+
describe(`Vehicle Tool Shell dual-channel conformance: ${fixture.label}`, () => {
|
|
472
|
+
it("keeps model and persisted-presentation sentinels isolated under independent named bounds", async () => {
|
|
473
|
+
const { subject, cleanup } = await fixture.create();
|
|
474
|
+
try {
|
|
475
|
+
const snapshot = await subject.execute();
|
|
476
|
+
expect(snapshot.content).toContain("MODEL_ONLY");
|
|
477
|
+
expect(snapshot.content).not.toContain("PRESENTATION_ONLY");
|
|
478
|
+
const details = JSON.stringify(snapshot.details);
|
|
479
|
+
expect(details).toContain("PRESENTATION_ONLY");
|
|
480
|
+
expect(details).not.toContain("MODEL_ONLY");
|
|
481
|
+
expect(details).not.toContain("RAW_SECRET");
|
|
482
|
+
expect(utf8Length(snapshot.content)).toBeLessThanOrEqual(subject.bounds.modelContentBytes);
|
|
483
|
+
expect(utf8Length(details)).toBeLessThanOrEqual(subject.bounds.presentationDetailsBytes);
|
|
484
|
+
} finally {
|
|
485
|
+
await cleanup();
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
it("keeps model content semantic, ANSI-free, and useful when replay details reject", async () => {
|
|
490
|
+
const { subject, cleanup } = await fixture.create();
|
|
491
|
+
try {
|
|
492
|
+
const snapshot = await subject.execute();
|
|
493
|
+
expect(snapshot.content).not.toContain("\u001b[");
|
|
494
|
+
for (const details of [{ schema: "unknown/v99" }, { malformed: true }, { output: { legacy: true } }, undefined]) {
|
|
495
|
+
const lines = subject.replay(details, snapshot.content, { width: 80, expanded: false });
|
|
496
|
+
expect(lines.join("\n")).toContain("MODEL_ONLY");
|
|
497
|
+
}
|
|
498
|
+
} finally {
|
|
499
|
+
await cleanup();
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it("changes only human rendering across collapsed/expanded and 40/80/120 layouts", async () => {
|
|
504
|
+
const { subject, cleanup } = await fixture.create();
|
|
505
|
+
try {
|
|
506
|
+
const snapshot = await subject.execute();
|
|
507
|
+
const before = JSON.stringify(snapshot);
|
|
508
|
+
for (const width of [40, 80, 120] as const) {
|
|
509
|
+
assertPhysicalLines(subject.render(snapshot, { width, expanded: false }), width);
|
|
510
|
+
assertPhysicalLines(subject.render(snapshot, { width, expanded: true }), width);
|
|
511
|
+
assertPhysicalLines(subject.render(snapshot, { width, expanded: false, partial: true }), width);
|
|
512
|
+
}
|
|
513
|
+
expect(JSON.stringify(snapshot)).toBe(before);
|
|
514
|
+
} finally {
|
|
515
|
+
await cleanup();
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
it("never echoes schema-sensitive call input and follows the documented projector exception policy", async () => {
|
|
520
|
+
const { subject, cleanup } = await fixture.create();
|
|
521
|
+
try {
|
|
522
|
+
for (const width of [40, 80, 120] as const) {
|
|
523
|
+
const call = subject.renderCall({ name: "safe-task", token: "RAW_SECRET" }, width).join("\n");
|
|
524
|
+
expect(call).toContain("safe-task");
|
|
525
|
+
expect(call).not.toContain("RAW_SECRET");
|
|
526
|
+
}
|
|
527
|
+
await expect(subject.invalidProjection()).rejects.toBeTruthy();
|
|
528
|
+
} finally {
|
|
529
|
+
await cleanup();
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
}
|