From 4c189408150f4525761bf62dac5a4c7b50dae589 Mon Sep 17 00:00:00 2001 From: claude-bot-go Date: Fri, 24 Jul 2026 01:46:03 +0800 Subject: [PATCH] Fix #36: [milestone Milestone 2] Create @wasmagent/protocol package with consumer registry and supported version band --- consumers.json | 40 +++++++++++++++++++++++++++++ index.d.ts | 22 ++++++++++++++++ index.js | 3 +++ package.json | 6 +++-- tests/loader.test.js | 16 +++++++++++- tests/test_conformance.py | 54 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 consumers.json create mode 100644 tests/test_conformance.py diff --git a/consumers.json b/consumers.json new file mode 100644 index 0000000..756aa1c --- /dev/null +++ b/consumers.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "Registry of repositories that consume @wasmagent/protocol as an npm dependency, and the supported version band.", + "consumers": [ + { + "repo": "WasmAgent/wasmagent-js", + "packageJsonPath": "package.json", + "version": "^0.1.5", + "notes": "Primary consumer; placeholder version until the repo actually adds the dependency." + }, + { + "repo": "WasmAgent/wasmagent-proxy", + "packageJsonPath": "package.json", + "version": "^0.1.0", + "notes": "Placeholder version until the repo actually adds the dependency." + }, + { + "repo": "WasmAgent/trace-pipeline", + "packageJsonPath": "package.json", + "version": "^0.1.0", + "notes": "Placeholder version until the repo actually adds the dependency." + }, + { + "repo": "WasmAgent/wasmagent-train-replay", + "packageJsonPath": "package.json", + "version": "^0.1.0", + "notes": "Placeholder version until the repo actually adds the dependency." + }, + { + "repo": "WasmAgent/open-agent-audit", + "packageJsonPath": "package.json", + "version": "^0.1.0", + "notes": "Placeholder version until the repo actually adds the dependency." + } + ], + "supportedBand": { + "min": "0.1.0", + "max": "0.1.99" + } +} diff --git a/index.d.ts b/index.d.ts index 34c4dde..c2ef430 100644 --- a/index.d.ts +++ b/index.d.ts @@ -30,3 +30,25 @@ export const schemas: Record; * (e.g. "aep-record", "constraint-ir"). Throws on unknown id. */ export function getSchema(id: string): unknown; + +export interface ConsumerEntry { + repo: string; + packageJsonPath: string; + version?: string; + notes?: string; +} + +export interface SupportedBand { + min: string; + max: string; +} + +export interface ConsumerRegistry { + $schema: string; + description: string; + consumers: ConsumerEntry[]; + supportedBand: SupportedBand; +} + +/** Machine-readable registry of repositories that consume @wasmagent/protocol. */ +export const consumerRegistry: ConsumerRegistry; diff --git a/index.js b/index.js index dc7a00a..d3a9d8d 100644 --- a/index.js +++ b/index.js @@ -30,3 +30,6 @@ export function getSchema(id) { /** All schemas as a plain object keyed by id. */ export const schemas = Object.fromEntries(index.schemas.map((s) => [s.id, getSchema(s.id)])); + +/** Machine-readable registry of repositories that consume @wasmagent/protocol. */ +export const consumerRegistry = JSON.parse(readFileSync(join(here, 'consumers.json'), 'utf8')); diff --git a/package.json b/package.json index 8bd4f37..7db72bf 100644 --- a/package.json +++ b/package.json @@ -20,12 +20,14 @@ "types": "./index.d.ts", "import": "./index.js" }, - "./schemas/*": "./schemas/*" + "./schemas/*": "./schemas/*", + "./consumers.json": "./consumers.json" }, "files": [ "index.js", "index.d.ts", - "schemas/" + "schemas/", + "consumers.json" ], "scripts": { "test": "node --test" diff --git a/tests/loader.test.js b/tests/loader.test.js index 5d82b18..7864501 100644 --- a/tests/loader.test.js +++ b/tests/loader.test.js @@ -1,6 +1,6 @@ import assert from 'node:assert/strict'; import { test } from 'node:test'; -import { getSchema, index, schemas } from '../index.js'; +import { consumerRegistry, getSchema, index, schemas } from '../index.js'; test('registry lists at least one schema', () => { assert.ok(index.schemas.length >= 1); @@ -23,3 +23,17 @@ test('schemas map is keyed by id', () => { test('getSchema throws on unknown id', () => { assert.throws(() => getSchema('does-not-exist'), /unknown schema id/); }); + +test('consumerRegistry has required structure', () => { + assert.ok(Array.isArray(consumerRegistry.consumers)); + assert.ok(consumerRegistry.consumers.length >= 1, 'at least one consumer required'); + assert.equal(typeof consumerRegistry.supportedBand.min, 'string'); + assert.equal(typeof consumerRegistry.supportedBand.max, 'string'); +}); + +test('consumerRegistry entries have repo and packageJsonPath', () => { + for (const entry of consumerRegistry.consumers) { + assert.ok(entry.repo, `consumer missing repo: ${JSON.stringify(entry)}`); + assert.ok(entry.packageJsonPath, `consumer missing packageJsonPath: ${JSON.stringify(entry)}`); + } +}); diff --git a/tests/test_conformance.py b/tests/test_conformance.py new file mode 100644 index 0000000..9863156 --- /dev/null +++ b/tests/test_conformance.py @@ -0,0 +1,54 @@ +# Pytest wrapper around the conformance harness. +# The conformance script is a standalone CLI; this module lets +# `pytest tests/` discover and run it as a test. + +from __future__ import annotations + +import json +import subprocess +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +CONFORMANCE = ROOT / "tests" / "conformance.py" +CONSUMERS_JSON = ROOT / "consumers.json" + + +def test_conformance_script_passes(): + """Run the conformance harness; it must exit 0.""" + result = subprocess.run( + [sys.executable, str(CONFORMANCE)], + capture_output=True, + text=True, + ) + assert result.returncode == 0, ( + f"conformance.py failed (exit {result.returncode}):\n" + f"stdout: {result.stdout}\n" + f"stderr: {result.stderr}" + ) + + +def test_consumers_json_exists_and_is_valid(): + """consumers.json must exist and be valid JSON with required fields.""" + assert CONSUMERS_JSON.is_file(), "consumers.json does not exist" + doc = json.loads(CONSUMERS_JSON.read_text(encoding="utf-8")) + + # Top-level shape + assert "consumers" in doc, "missing 'consumers' key" + assert "supportedBand" in doc, "missing 'supportedBand' key" + + # supportedBand must have min and max + band = doc["supportedBand"] + assert "min" in band, "supportedBand missing 'min'" + assert "max" in band, "supportedBand missing 'max'" + + # At least one consumer entry + assert isinstance(doc["consumers"], list), "'consumers' must be a list" + assert len(doc["consumers"]) >= 1, "consumers list must have >= 1 entry" + + # Each consumer must have 'repo' and 'packageJsonPath' + for entry in doc["consumers"]: + assert "repo" in entry, f"consumer entry missing 'repo': {entry}" + assert "packageJsonPath" in entry, ( + f"consumer entry missing 'packageJsonPath': {entry}" + )