@logicsrc/validators 0.1.0 → 0.1.1

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/dist/index.d.ts CHANGED
@@ -1,5 +1,14 @@
1
1
  import type { ErrorObject } from "ajv";
2
2
  import { schemas, type SchemaKind } from "./schemas.js";
3
+ type CompiledSchema = {
4
+ (data: unknown): boolean;
5
+ errors?: ErrorObject[] | null;
6
+ };
7
+ type AjvInstance = {
8
+ compile: (schema: unknown) => CompiledSchema;
9
+ addSchema: (schema: unknown) => unknown;
10
+ getSchema: (id: string) => CompiledSchema | undefined;
11
+ };
3
12
  export type ValidationResult = {
4
13
  ok: true;
5
14
  kind: SchemaKind;
@@ -9,12 +18,7 @@ export type ValidationResult = {
9
18
  kind: SchemaKind;
10
19
  errors: ErrorObject[];
11
20
  };
12
- export declare function createValidator(): {
13
- compile: (schema: unknown) => {
14
- (data: unknown): boolean;
15
- errors?: ErrorObject[] | null;
16
- };
17
- };
21
+ export declare function createValidator(): AjvInstance;
18
22
  export declare function parseDocument(input: string, fileName?: string): unknown;
19
23
  export declare function validate(kind: SchemaKind, data: unknown): ValidationResult;
20
24
  export declare function assertSchemaKind(value: string): SchemaKind;
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ import * as Ajv2020Module from "ajv/dist/2020.js";
2
2
  import * as addFormatsModule from "ajv-formats";
3
3
  import { parse } from "yaml";
4
4
  import { isSchemaKind, schemas } from "./schemas.js";
5
+ import { validateOpenFleetReferences } from "./openfleet.js";
5
6
  const Ajv2020 = Ajv2020Module.default;
6
7
  const addFormats = addFormatsModule.default;
7
8
  export function createValidator() {
@@ -10,10 +11,21 @@ export function createValidator() {
10
11
  return ajv;
11
12
  }
12
13
  const _ajv = createValidator();
14
+ // Every schema is registered up front, by $id, so that a schema which $refs
15
+ // another one across files resolves. The OpenCreds database schema does this:
16
+ // it refers to the item and manifest schemas rather than restating them, and
17
+ // restating them is how two copies of a definition drift apart.
18
+ for (const schema of Object.values(schemas)) {
19
+ _ajv.addSchema(schema);
20
+ }
13
21
  const _compiledValidators = new Map();
14
22
  function getCompiledValidator(kind) {
15
23
  if (!_compiledValidators.has(kind)) {
16
- _compiledValidators.set(kind, _ajv.compile(schemas[kind]));
24
+ // Already registered above, so look it up by $id — compiling it a second
25
+ // time would throw on the duplicate id.
26
+ const id = schemas[kind].$id;
27
+ const registered = id ? _ajv.getSchema(id) : undefined;
28
+ _compiledValidators.set(kind, registered ?? _ajv.compile(schemas[kind]));
17
29
  }
18
30
  return _compiledValidators.get(kind);
19
31
  }
@@ -27,6 +39,11 @@ export function validate(kind, data) {
27
39
  const validateDocument = getCompiledValidator(kind);
28
40
  const ok = validateDocument(data);
29
41
  if (ok) {
42
+ if (kind === "openfleet") {
43
+ const errors = validateOpenFleetReferences(data);
44
+ if (errors.length)
45
+ return { ok: false, kind, errors };
46
+ }
30
47
  return { ok: true, kind, data };
31
48
  }
32
49
  return {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,aAAa,MAAM,kBAAkB,CAAC;AAClD,OAAO,KAAK,gBAAgB,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,OAAO,EAAmB,MAAM,cAAc,CAAC;AAEtE,MAAM,OAAO,GAAI,aAAiL,CAAC,OAAO,CAAC;AAC3M,MAAM,UAAU,GAAI,gBAAwF,CAAC,OAAO,CAAC;AAMrH,MAAM,UAAU,eAAe;IAC7B,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3D,UAAU,CAAC,GAAG,CAAC,CAAC;IAChB,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,IAAI,GAAG,eAAe,EAAE,CAAC;AAC/B,MAAM,mBAAmB,GAAG,IAAI,GAAG,EAA+C,CAAC;AAEnF,SAAS,oBAAoB,CAAC,IAAgB;IAC5C,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,mBAAmB,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAE,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAa,EAAE,QAAQ,GAAG,UAAU;IAChE,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7C,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAY,CAAC;IACtC,CAAC;IAED,OAAO,KAAK,CAAC,KAAK,CAAY,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,IAAgB,EAAE,IAAa;IACtD,MAAM,gBAAgB,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;IACpD,MAAM,EAAE,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IAElC,IAAI,EAAE,EAAE,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAClC,CAAC;IAED,OAAO;QACL,EAAE,EAAE,KAAK;QACT,IAAI;QACJ,MAAM,EAAE,gBAAgB,CAAC,MAAM,IAAI,EAAE;KACtC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,wBAAwB,KAAK,uBAAuB,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACzG,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,OAAO,EAAE,OAAO,EAAmB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { ErrorObject } from "ajv";
2
+ export declare function validateOpenFleetReferences(data: unknown): ErrorObject[];
@@ -0,0 +1,48 @@
1
+ function memberKey(member) {
2
+ return JSON.stringify([member.kind, member.id]);
3
+ }
4
+ export function validateOpenFleetReferences(data) {
5
+ const fleet = data;
6
+ const errors = [];
7
+ function report(keyword, instancePath, message) {
8
+ errors.push({ keyword, instancePath, schemaPath: "#/openfleet-semantics", params: {}, message });
9
+ }
10
+ const members = new Set();
11
+ fleet.members.forEach((member, i) => {
12
+ const key = memberKey(member);
13
+ if (members.has(key)) {
14
+ report("uniqueMember", `/members/${i}`, "duplicates an existing member kind and id");
15
+ }
16
+ members.add(key);
17
+ });
18
+ const rentals = new Set();
19
+ fleet.rentals?.forEach((rental, i) => {
20
+ const path = `/rentals/${i}`;
21
+ if (rentals.has(rental.id)) {
22
+ report("uniqueRental", `${path}/id`, "duplicates an existing rental id");
23
+ }
24
+ rentals.add(rental.id);
25
+ if (rental.scope.kind === "members") {
26
+ const scopedMembers = new Set();
27
+ rental.scope.members.forEach((member, j) => {
28
+ const key = memberKey(member);
29
+ const memberPath = `${path}/scope/members/${j}`;
30
+ if (!members.has(key)) {
31
+ report("memberReference", memberPath, "must reference a member of this fleet by kind and id");
32
+ }
33
+ if (scopedMembers.has(key)) {
34
+ report("uniqueMember", memberPath, "duplicates a member in this rental scope");
35
+ }
36
+ scopedMembers.add(key);
37
+ });
38
+ }
39
+ if (rental.maximum_units !== undefined && rental.maximum_units < (rental.minimum_units ?? 1)) {
40
+ report("rentalUnits", `${path}/maximum_units`, "must be at least minimum_units");
41
+ }
42
+ if (rental.valid_from && rental.valid_until && Date.parse(rental.valid_from) >= Date.parse(rental.valid_until)) {
43
+ report("rentalPeriod", `${path}/valid_until`, "must be later than valid_from");
44
+ }
45
+ });
46
+ return errors;
47
+ }
48
+ //# sourceMappingURL=openfleet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openfleet.js","sourceRoot":"","sources":["../src/openfleet.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,125 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { describe, expect, it } from "vitest";
3
+ import { assertSchemaKind, createValidator, schemas, validate } from "./index.js";
4
+ function fixture() {
5
+ return JSON.parse(readFileSync(new URL("../../schemas/fixtures/openfleet/mixed.json", import.meta.url), "utf8"));
6
+ }
7
+ function errorAt(data, keyword, path) {
8
+ const result = validate("openfleet", data);
9
+ expect(result.ok).toBe(false);
10
+ if (!result.ok)
11
+ expect(result.errors).toContainEqual(expect.objectContaining({ keyword, instancePath: path }));
12
+ }
13
+ describe("OpenFleet", () => {
14
+ it("registers a publicly exported schema and validates the mixed fixture", () => {
15
+ expect(assertSchemaKind("openfleet")).toBe("openfleet");
16
+ expect(validate("openfleet", fixture()).ok).toBe(true);
17
+ const ajv = createValidator();
18
+ ajv.addSchema(schemas.agent);
19
+ expect(ajv.compile(schemas.openfleet)(fixture())).toBe(true);
20
+ });
21
+ it("validates the complete descriptor in the public specification", () => {
22
+ const doc = readFileSync(new URL("../../../docs/openfleet.md", import.meta.url), "utf8");
23
+ const example = doc.match(/```json\n([\s\S]*?)\n```/);
24
+ expect(example).not.toBeNull();
25
+ expect(validate("openfleet", JSON.parse(example[1])).ok).toBe(true);
26
+ });
27
+ it.each(["openagent", "openswarm"])("accepts a fleet containing only %s members", (kind) => {
28
+ const fleet = fixture();
29
+ fleet.members = fleet.members.filter((member) => member.kind === kind);
30
+ // A whole-fleet rate applies to either single-kind fleet without expansion.
31
+ fleet.rentals = [fleet.rentals[0]];
32
+ expect(validate("openfleet", fleet).ok).toBe(true);
33
+ });
34
+ it("accepts membership without rental offers and preserves extension metadata", () => {
35
+ const fleet = fixture();
36
+ delete fleet.rentals;
37
+ fleet.metadata = { custom: { nested: [null, true, 42, "value"] } };
38
+ const snapshot = structuredClone(fleet);
39
+ expect(validate("openfleet", fleet).ok).toBe(true);
40
+ expect(fleet).toEqual(snapshot);
41
+ });
42
+ it.each([
43
+ ["no members", (f) => { f.members = []; }],
44
+ ["unsupported kind", (f) => { f.members[0].kind = "openfleet"; }],
45
+ ["agent without identity", (f) => { delete f.members[0].id; }],
46
+ ["swarm without identity", (f) => { delete f.members[1].id; }],
47
+ ["swarm with an agent DID", (f) => { f.members[1].id = "analyst.coinpay"; }],
48
+ ["agent with a swarm key", (f) => { f.members[0].id = f.members[1].id; }],
49
+ ["short swarm key", (f) => { f.members[1].id = "ed25519:abcd"; }],
50
+ ["non-HTTPS member URL", (f) => { f.members[0].url = "http://example.com/agent"; }],
51
+ ["unsupported version", (f) => { f.version = "9.0"; }],
52
+ ["unknown contract field", (f) => { f.rental = []; }]
53
+ ])("rejects %s", (_, mutate) => {
54
+ const fleet = fixture();
55
+ mutate(fleet);
56
+ expect(validate("openfleet", fleet).ok).toBe(false);
57
+ });
58
+ it("rejects duplicate member identities even when URL and metadata differ", () => {
59
+ const fleet = fixture();
60
+ fleet.members.push({ ...fleet.members[0], url: "https://elsewhere.example/agent", metadata: { alias: true } });
61
+ errorAt(fleet, "uniqueMember", "/members/2");
62
+ });
63
+ it("rejects duplicate rental IDs", () => {
64
+ const fleet = fixture();
65
+ fleet.rentals[1].id = fleet.rentals[0].id;
66
+ errorAt(fleet, "uniqueRental", "/rentals/1/id");
67
+ });
68
+ it("rejects dangling references after a member is removed", () => {
69
+ const fleet = fixture();
70
+ fleet.members.shift();
71
+ errorAt(fleet, "memberReference", "/rentals/1/scope/members/0");
72
+ });
73
+ it("accepts a rental scoped to both kinds without counting a member twice", () => {
74
+ const fleet = fixture();
75
+ fleet.rentals[1].scope.members.push({ kind: "openswarm", id: fleet.members[1].id });
76
+ expect(validate("openfleet", fleet).ok).toBe(true);
77
+ fleet.rentals[1].scope.members.push({ ...fleet.rentals[1].scope.members[0] });
78
+ errorAt(fleet, "uniqueMember", "/rentals/1/scope/members/2");
79
+ });
80
+ it.each([25, -1, "-1.000000", "01.000000", "1e3", "NaN", "Infinity", "1.00", "0.0000001"])("rejects an inexact or invalid amount %s", (amount) => {
81
+ const fleet = fixture();
82
+ fleet.rentals[0].rate.amount = amount;
83
+ expect(validate("openfleet", fleet).ok).toBe(false);
84
+ });
85
+ it.each(["0.000000", "0.000001", "9007199254740993.123456"])("preserves exact amount %s", (amount) => {
86
+ const fleet = fixture();
87
+ fleet.rentals[0].rate.amount = amount;
88
+ expect(validate("openfleet", fleet).ok).toBe(true);
89
+ expect(fleet.rentals[0].rate.amount).toBe(amount);
90
+ });
91
+ it.each([
92
+ ["non-CoinPay provider", (r) => { r.payment.provider = "other"; }],
93
+ ["missing CoinPay metadata", (r) => { delete r.payment; }],
94
+ ["missing payee", (r) => { delete r.payment.payee_did; }],
95
+ ["missing checkout", (r) => { delete r.payment.checkout_url; }],
96
+ ["insecure checkout", (r) => { r.payment.checkout_url = "http://example.com/checkout"; }],
97
+ ["unsupported currency", (r) => { r.rate.currency = "EUR"; }],
98
+ ["missing unit", (r) => { delete r.rate.unit; }],
99
+ ["unsupported unit", (r) => { r.rate.unit = "second"; }],
100
+ ["fractional billing units", (r) => { r.minimum_units = 0.5; }],
101
+ ["unsafe integer units", (r) => { r.maximum_units = 9007199254740992; }],
102
+ ["empty member scope", (r) => { r.scope = { kind: "members", members: [] }; }],
103
+ ["ambiguous fleet scope", (r) => { r.scope.members = []; }]
104
+ ])("rejects a rental with %s", (_, mutate) => {
105
+ const fleet = fixture();
106
+ mutate(fleet.rentals[0]);
107
+ expect(validate("openfleet", fleet).ok).toBe(false);
108
+ });
109
+ it("rejects maximum units below the minimum", () => {
110
+ const fleet = fixture();
111
+ fleet.rentals[0].minimum_units = 25;
112
+ errorAt(fleet, "rentalUnits", "/rentals/0/maximum_units");
113
+ });
114
+ it("checks offer windows by instant, including timezone offsets", () => {
115
+ const fleet = fixture();
116
+ fleet.rentals[0].valid_from = "2026-09-13T12:00:00Z";
117
+ fleet.rentals[0].valid_until = "2026-09-13T14:00:00+02:00";
118
+ errorAt(fleet, "rentalPeriod", "/rentals/0/valid_until");
119
+ fleet.rentals[0].valid_until = "2026-09-13T13:00:00+02:00";
120
+ errorAt(fleet, "rentalPeriod", "/rentals/0/valid_until");
121
+ fleet.rentals[0].valid_until = "2026-09-13T15:00:00+02:00";
122
+ expect(validate("openfleet", fleet).ok).toBe(true);
123
+ });
124
+ });
125
+ //# sourceMappingURL=openfleet.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openfleet.test.js","sourceRoot":"","sources":["../src/openfleet.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,106 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { describe, expect, it } from "vitest";
3
+ import { validate } from "./index.js";
4
+ function fixture(name) {
5
+ return JSON.parse(readFileSync(new URL(`../../schemas/fixtures/openwall/${name}.json`, import.meta.url), "utf8"));
6
+ }
7
+ describe("OpenWall draft structural contracts", () => {
8
+ it.each(["broadcast", "direct", "announcement"])("accepts the %s message fixture", (name) => {
9
+ expect(validate("openwall-message", fixture(name))).toMatchObject({ ok: true });
10
+ });
11
+ it.each(["receipt-accepted", "receipt-retrying"])("accepts the %s fixture", (name) => {
12
+ expect(validate("openwall-receipt", fixture(name))).toMatchObject({ ok: true });
13
+ });
14
+ it("requires a service scope for every users selector", () => {
15
+ const message = fixture("broadcast");
16
+ delete message.audience.selectors[0].scope;
17
+ expect(validate("openwall-message", message).ok).toBe(false);
18
+ });
19
+ it("permits a union of relationship groups and sources", () => {
20
+ const message = fixture("broadcast");
21
+ message.audience.selectors.push({
22
+ source: "https://social.example/graph",
23
+ actor: "did:web:ops.example",
24
+ groups: ["connections", "followers", "following"]
25
+ });
26
+ expect(validate("openwall-message", message).ok).toBe(true);
27
+ });
28
+ it.each(["broadcast", "direct"])("rejects changing a %s to public while retaining recipients", (name) => {
29
+ const message = fixture(name);
30
+ message.visibility = "public";
31
+ expect(validate("openwall-message", message).ok).toBe(false);
32
+ });
33
+ it("rejects a private message with an unrestricted public audience", () => {
34
+ const message = fixture("announcement");
35
+ message.visibility = "private";
36
+ expect(validate("openwall-message", message).ok).toBe(false);
37
+ });
38
+ it("rejects recipient metadata in a public audience", () => {
39
+ const message = fixture("announcement");
40
+ message.audience.recipient = "did:web:ada.example";
41
+ expect(validate("openwall-message", message).ok).toBe(false);
42
+ });
43
+ it("requires explicit groups and rejects an implicit all audience", () => {
44
+ const message = fixture("broadcast");
45
+ message.audience.selectors[0].groups = [];
46
+ expect(validate("openwall-message", message).ok).toBe(false);
47
+ message.audience.selectors[0].groups = ["all"];
48
+ expect(validate("openwall-message", message).ok).toBe(false);
49
+ });
50
+ it("requires a resolved recipient identity rather than a display handle", () => {
51
+ const message = fixture("direct");
52
+ message.audience.recipient = "ada.example";
53
+ expect(validate("openwall-message", message).ok).toBe(false);
54
+ });
55
+ it("rejects absent expiry, malformed timestamps and oversized text", () => {
56
+ const message = fixture("direct");
57
+ delete message.expiresAt;
58
+ expect(validate("openwall-message", message).ok).toBe(false);
59
+ message.expiresAt = "tomorrow";
60
+ expect(validate("openwall-message", message).ok).toBe(false);
61
+ message.expiresAt = "2026-09-13T13:00:00Z";
62
+ message.content.text = "x".repeat(4001);
63
+ expect(validate("openwall-message", message).ok).toBe(false);
64
+ });
65
+ it("requires receipt delivery correlation and disallows audience disclosure", () => {
66
+ const receipt = fixture("receipt-accepted");
67
+ delete receipt.deliveryId;
68
+ expect(validate("openwall-receipt", receipt).ok).toBe(false);
69
+ receipt.deliveryId = "unscoped-message-id";
70
+ expect(validate("openwall-receipt", receipt).ok).toBe(false);
71
+ receipt.deliveryId = "urn:uuid:db925d64-4e1a-4e5a-83c1-e37b68aa5eef";
72
+ receipt.recipients = ["did:web:other.example"];
73
+ expect(validate("openwall-receipt", receipt).ok).toBe(false);
74
+ });
75
+ it("requires retry timing and a reason and disallows retry timing on acceptance", () => {
76
+ const receipt = fixture("receipt-retrying");
77
+ delete receipt.nextAttemptAt;
78
+ expect(validate("openwall-receipt", receipt).ok).toBe(false);
79
+ receipt.nextAttemptAt = "2026-09-13T12:01:00Z";
80
+ delete receipt.reason;
81
+ expect(validate("openwall-receipt", receipt).ok).toBe(false);
82
+ receipt.reason = "rate-limited";
83
+ receipt.state = "accepted";
84
+ expect(validate("openwall-receipt", receipt).ok).toBe(false);
85
+ });
86
+ it("represents ambiguous provider acceptance separately from a known failure", () => {
87
+ const receipt = fixture("receipt-accepted");
88
+ receipt.state = "unknown";
89
+ receipt.reason = "acceptance-unknown";
90
+ expect(validate("openwall-receipt", receipt).ok).toBe(true);
91
+ receipt.reason = "rate-limited";
92
+ expect(validate("openwall-receipt", receipt).ok).toBe(false);
93
+ delete receipt.reason;
94
+ receipt.state = "failed";
95
+ expect(validate("openwall-receipt", receipt).ok).toBe(false);
96
+ });
97
+ it("requires a real route and attempt for provider acceptance", () => {
98
+ const receipt = fixture("receipt-accepted");
99
+ receipt.attempt = 0;
100
+ expect(validate("openwall-receipt", receipt).ok).toBe(false);
101
+ receipt.attempt = 1;
102
+ delete receipt.route;
103
+ expect(validate("openwall-receipt", receipt).ok).toBe(false);
104
+ });
105
+ });
106
+ //# sourceMappingURL=openwall.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openwall.test.js","sourceRoot":"","sources":["../src/openwall.test.ts"],"names":[],"mappings":"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"}