@verax-ai/inventory 0.1.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.
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The declared roster document: what it says, and how to read it.
3
+ *
4
+ * It lives in its own package because two very different readers need it:
5
+ * the body, which serves roster health from a file on disk, and the galaxy,
6
+ * which draws it. The galaxy carries three.js and React; the body must not.
7
+ */
8
+ export type InventoryState = "live" | "stale" | "failed" | "unmonitored" | "retired" | "unknown";
9
+ export type InventoryAgent = {
10
+ id: string;
11
+ label: string;
12
+ groupId: string | null;
13
+ kind: string;
14
+ /** null = the source does not know when this agent last ran */
15
+ lastRunMs: number | null;
16
+ state: InventoryState;
17
+ note?: string;
18
+ };
19
+ export type InventoryGroup = {
20
+ id: string;
21
+ label: string;
22
+ };
23
+ /** A pulse the roster does not name: the record and the fact have come apart. */
24
+ export type InventoryOrphan = {
25
+ id: string;
26
+ lastRunMs: number | null;
27
+ why: string;
28
+ };
29
+ export type Inventory = {
30
+ takenAtMs: number;
31
+ /** Name of whoever produced the inventory. Measured.source carries this. */
32
+ source: string;
33
+ groups: InventoryGroup[];
34
+ agents: InventoryAgent[];
35
+ orphans: InventoryOrphan[];
36
+ };
37
+ export type InventoryParse = {
38
+ ok: true;
39
+ value: Inventory;
40
+ } | {
41
+ ok: false;
42
+ reason: string;
43
+ };
44
+ export type Coverage = {
45
+ total: number;
46
+ accountable: number;
47
+ };
48
+ /**
49
+ * Strict parse. A single broken field fails the whole document; nothing
50
+ * half-filled is returned.
51
+ */
52
+ export declare function parseInventory(raw: unknown): InventoryParse;
package/dist/index.js ADDED
@@ -0,0 +1,147 @@
1
+ const STATES = new Set([
2
+ "live",
3
+ "stale",
4
+ "failed",
5
+ "unmonitored",
6
+ "retired",
7
+ "unknown",
8
+ ]);
9
+ const NO_FRESHNESS = new Set(["unmonitored", "unknown"]);
10
+ function isRecord(value) {
11
+ return value !== null && typeof value === "object" && !Array.isArray(value);
12
+ }
13
+ function finiteMs(value, path) {
14
+ if (typeof value !== "number" || !Number.isFinite(value)) {
15
+ return { ok: false, reason: `invalid:${path}` };
16
+ }
17
+ return { ok: true, value };
18
+ }
19
+ function optionalMs(value, path) {
20
+ if (value === null)
21
+ return { ok: true, value: null };
22
+ return finiteMs(value, path);
23
+ }
24
+ function nonEmptyString(value, path) {
25
+ if (typeof value !== "string" || value === "") {
26
+ return { ok: false, reason: `invalid:${path}` };
27
+ }
28
+ return { ok: true, value };
29
+ }
30
+ function parseGroup(value, path) {
31
+ if (!isRecord(value))
32
+ return { ok: false, reason: `invalid:${path}` };
33
+ const id = nonEmptyString(value.id, `${path}.id`);
34
+ if (!id.ok)
35
+ return id;
36
+ const label = nonEmptyString(value.label, `${path}.label`);
37
+ if (!label.ok)
38
+ return label;
39
+ return { ok: true, value: { id: id.value, label: label.value } };
40
+ }
41
+ function parseAgent(value, path) {
42
+ if (!isRecord(value))
43
+ return { ok: false, reason: `invalid:${path}` };
44
+ const id = nonEmptyString(value.id, `${path}.id`);
45
+ if (!id.ok)
46
+ return id;
47
+ const label = nonEmptyString(value.label, `${path}.label`);
48
+ if (!label.ok)
49
+ return label;
50
+ if (value.groupId !== null && (typeof value.groupId !== "string" || value.groupId === "")) {
51
+ return { ok: false, reason: `invalid:${path}.groupId` };
52
+ }
53
+ const kind = nonEmptyString(value.kind, `${path}.kind`);
54
+ if (!kind.ok)
55
+ return kind;
56
+ const lastRunMs = optionalMs(value.lastRunMs, `${path}.lastRunMs`);
57
+ if (!lastRunMs.ok)
58
+ return lastRunMs;
59
+ if (typeof value.state !== "string" || !STATES.has(value.state)) {
60
+ return { ok: false, reason: `invalid:${path}.state` };
61
+ }
62
+ const agent = {
63
+ id: id.value,
64
+ label: label.value,
65
+ groupId: value.groupId,
66
+ kind: kind.value,
67
+ lastRunMs: lastRunMs.value,
68
+ state: value.state,
69
+ };
70
+ if (value.note !== undefined) {
71
+ if (typeof value.note !== "string")
72
+ return { ok: false, reason: `invalid:${path}.note` };
73
+ agent.note = value.note;
74
+ }
75
+ return { ok: true, value: agent };
76
+ }
77
+ function parseOrphan(value, path) {
78
+ if (!isRecord(value))
79
+ return { ok: false, reason: `invalid:${path}` };
80
+ const id = nonEmptyString(value.id, `${path}.id`);
81
+ if (!id.ok)
82
+ return id;
83
+ const lastRunMs = optionalMs(value.lastRunMs, `${path}.lastRunMs`);
84
+ if (!lastRunMs.ok)
85
+ return lastRunMs;
86
+ const why = nonEmptyString(value.why, `${path}.why`);
87
+ if (!why.ok)
88
+ return why;
89
+ return { ok: true, value: { id: id.value, lastRunMs: lastRunMs.value, why: why.value } };
90
+ }
91
+ /**
92
+ * Strict parse. A single broken field fails the whole document; nothing
93
+ * half-filled is returned.
94
+ */
95
+ export function parseInventory(raw) {
96
+ if (!isRecord(raw))
97
+ return { ok: false, reason: "invalid:root" };
98
+ const takenAtMs = finiteMs(raw.takenAtMs, "takenAtMs");
99
+ if (!takenAtMs.ok)
100
+ return takenAtMs;
101
+ const source = nonEmptyString(raw.source, "source");
102
+ if (!source.ok)
103
+ return source;
104
+ if (!Array.isArray(raw.groups))
105
+ return { ok: false, reason: "invalid:groups" };
106
+ if (!Array.isArray(raw.agents))
107
+ return { ok: false, reason: "invalid:agents" };
108
+ if (!Array.isArray(raw.orphans))
109
+ return { ok: false, reason: "invalid:orphans" };
110
+ const groups = [];
111
+ const groupIds = new Set();
112
+ for (let i = 0; i < raw.groups.length; i += 1) {
113
+ const row = parseGroup(raw.groups[i], `groups[${i}]`);
114
+ if (!row.ok)
115
+ return row;
116
+ if (groupIds.has(row.value.id))
117
+ return { ok: false, reason: `invalid:groups[${i}].id` };
118
+ groupIds.add(row.value.id);
119
+ groups.push(row.value);
120
+ }
121
+ const agents = [];
122
+ const agentIds = new Set();
123
+ for (let i = 0; i < raw.agents.length; i += 1) {
124
+ const row = parseAgent(raw.agents[i], `agents[${i}]`);
125
+ if (!row.ok)
126
+ return row;
127
+ if (agentIds.has(row.value.id))
128
+ return { ok: false, reason: `invalid:agents[${i}].id` };
129
+ agentIds.add(row.value.id);
130
+ agents.push(row.value);
131
+ }
132
+ const orphans = [];
133
+ const orphanIds = new Set();
134
+ for (let i = 0; i < raw.orphans.length; i += 1) {
135
+ const row = parseOrphan(raw.orphans[i], `orphans[${i}]`);
136
+ if (!row.ok)
137
+ return row;
138
+ if (orphanIds.has(row.value.id))
139
+ return { ok: false, reason: `invalid:orphans[${i}].id` };
140
+ orphanIds.add(row.value.id);
141
+ orphans.push(row.value);
142
+ }
143
+ return {
144
+ ok: true,
145
+ value: { takenAtMs: takenAtMs.value, source: source.value, groups, agents, orphans },
146
+ };
147
+ }
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@verax-ai/inventory",
3
+ "version": "0.1.0",
4
+ "license": "Apache-2.0",
5
+ "description": "Read the declared roster document a VERAX body serves and a VERAX galaxy draws.",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=22.6"
9
+ },
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc -p tsconfig.build.json",
23
+ "prepack": "npm run build"
24
+ },
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/verax-ai/verax.git",
28
+ "directory": "packages/inventory"
29
+ }
30
+ }