Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run check
- run: npm test

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
93 changes: 93 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@
"description": "ontology-based server communications bootstrap kit",
"type": "module",
"scripts": {
"check": "node scripts/check.mjs"
"check": "node scripts/check.mjs",
"test": "node --test test/*.test.mjs"
},
"engines": {
"node": ">=18"
},
"devDependencies": {
"ajv": "^8.20.0",
"yaml": "^2.9.0"
}
}
59 changes: 59 additions & 0 deletions test/check.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";

const exec = promisify(execFile);
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const checkScript = resolve(root, "scripts/check.mjs");

const REQUIRED_FILES = [
"README.md",
"spec/graphite.protocol.md",
"spec/adoption_gate.md",
"spec/non_adoption_boundary.md",
"ontology/graphite.yaml",
"protocol/envelope.schema.json",
"docs/github_repo_plan.md",
];

describe("scripts/check.mjs", () => {
it("should list all required files in the script source", async () => {
const src = await readFile(checkScript, "utf8");
for (const file of REQUIRED_FILES) {
assert.ok(src.includes(file), `script should reference ${file}`);
}
});

it("should exit 0 when all required files are present", async () => {
const { stdout } = await exec("node", [checkScript], { cwd: root });
assert.ok(stdout.includes("graphite incubator check passed"));
for (const file of REQUIRED_FILES) {
assert.ok(stdout.includes(`ok ${file}`), `should report ok for ${file}`);
}
});

it("should not report any missing files in a valid repo", async () => {
const { stdout } = await exec("node", [checkScript], { cwd: root });
assert.ok(!stdout.includes("missing"), "no files should be missing");
});

it("should verify each required file is readable", async () => {
for (const file of REQUIRED_FILES) {
const content = await readFile(resolve(root, file), "utf8");
assert.ok(content.length > 0, `${file} should not be empty`);
}
});

it("should use ES module syntax", async () => {
const src = await readFile(checkScript, "utf8");
assert.ok(src.includes("import"), "should use ES module imports");
assert.ok(
src.includes("import.meta.url"),
"should reference import.meta.url for path resolution"
);
});
});
187 changes: 187 additions & 0 deletions test/envelope.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
import Ajv2020 from "ajv/dist/2020.js";

const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const schemaPath = resolve(root, "protocol/envelope.schema.json");

async function loadSchema() {
const raw = await readFile(schemaPath, "utf8");
return JSON.parse(raw);
}

describe("protocol/envelope.schema.json", () => {
it("should be valid JSON", async () => {
const raw = await readFile(schemaPath, "utf8");
assert.doesNotThrow(() => JSON.parse(raw));
});

it("should reference JSON Schema 2020-12 draft", async () => {
const schema = await loadSchema();
assert.equal(
schema.$schema,
"https://json-schema.org/draft/2020-12/schema"
);
});

it("should have the correct title", async () => {
const schema = await loadSchema();
assert.equal(schema.title, "Graphite Envelope Draft");
});

it("should require protocol, kind, created_at, and payload", async () => {
const schema = await loadSchema();
assert.deepEqual(schema.required.sort(), [
"created_at",
"kind",
"payload",
"protocol",
]);
});

it("should define protocol as a string with pattern", async () => {
const schema = await loadSchema();
const prop = schema.properties.protocol;
assert.equal(prop.type, "string");
assert.ok(prop.pattern, "protocol should have a regex pattern");
});

it("should allow additional properties", async () => {
const schema = await loadSchema();
assert.equal(schema.additionalProperties, true);
});

describe("envelope validation", () => {
let validate;

it("should compile into a valid AJV validator", async () => {
const schema = await loadSchema();
const ajv = new Ajv2020();
validate = ajv.compile(schema);
assert.equal(typeof validate, "function");
});

it("should accept a valid envelope", async () => {
const schema = await loadSchema();
const ajv = new Ajv2020();
validate = ajv.compile(schema);

const valid = validate({
protocol: "graphite/0.0",
kind: "message.text",
created_at: "2026-06-26T00:00:00.000Z",
payload: { text: "hello" },
});
assert.ok(valid, `validation errors: ${JSON.stringify(validate.errors)}`);
});

it("should accept an envelope with an optional gate", async () => {
const schema = await loadSchema();
const ajv = new Ajv2020();
validate = ajv.compile(schema);

const valid = validate({
protocol: "graphite/1.0",
kind: "governance.decision",
created_at: "2026-06-26T12:00:00.000Z",
gate: { signer: "foundation", decision: "approve" },
payload: { ref: "adopt-graphite-1-0" },
});
assert.ok(valid, `validation errors: ${JSON.stringify(validate.errors)}`);
});

it("should accept an envelope with additional properties", async () => {
const schema = await loadSchema();
const ajv = new Ajv2020();
validate = ajv.compile(schema);

const valid = validate({
protocol: "graphite/0.0",
kind: "claim.source",
created_at: "2026-06-26T00:00:00.000Z",
payload: {},
origin: "lab-alpha",
tags: ["experiment"],
});
assert.ok(valid, `validation errors: ${JSON.stringify(validate.errors)}`);
});

it("should reject an envelope missing protocol", async () => {
const schema = await loadSchema();
const ajv = new Ajv2020();
validate = ajv.compile(schema);

const valid = validate({
kind: "message.text",
created_at: "2026-06-26T00:00:00.000Z",
payload: {},
});
assert.ok(!valid, "should be invalid without protocol");
});

it("should reject an envelope missing payload", async () => {
const schema = await loadSchema();
const ajv = new Ajv2020();
validate = ajv.compile(schema);

const valid = validate({
protocol: "graphite/0.0",
kind: "message.text",
created_at: "2026-06-26T00:00:00.000Z",
});
assert.ok(!valid, "should be invalid without payload");
});

it("should reject an envelope missing kind", async () => {
const schema = await loadSchema();
const ajv = new Ajv2020();
validate = ajv.compile(schema);

const valid = validate({
protocol: "graphite/0.0",
created_at: "2026-06-26T00:00:00.000Z",
payload: {},
});
assert.ok(!valid, "should be invalid without kind");
});

it("should reject an envelope missing created_at", async () => {
const schema = await loadSchema();
const ajv = new Ajv2020();
validate = ajv.compile(schema);

const valid = validate({
protocol: "graphite/0.0",
kind: "message.text",
payload: {},
});
assert.ok(!valid, "should be invalid without created_at");
});

it("should reject a protocol value not matching the pattern", async () => {
const schema = await loadSchema();
const ajv = new Ajv2020();
validate = ajv.compile(schema);

const valid = validate({
protocol: "not-graphite",
kind: "message.text",
created_at: "2026-06-26T00:00:00.000Z",
payload: {},
});
assert.ok(!valid, "should reject malformed protocol string");
});

it("should reject a completely empty object", async () => {
const schema = await loadSchema();
const ajv = new Ajv2020();
validate = ajv.compile(schema);

const valid = validate({});
assert.ok(!valid, "empty object should be invalid");
});
});
});
Loading
Loading