@nanobpm/agentic 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.
Files changed (228) hide show
  1. package/README.md +22 -0
  2. package/dist/blackboard/family.d.ts +40 -0
  3. package/dist/blackboard/family.js +151 -0
  4. package/dist/blackboard/index.d.ts +21 -0
  5. package/dist/blackboard/index.js +19 -0
  6. package/dist/blackboard/schema.d.ts +30 -0
  7. package/dist/blackboard/schema.js +42 -0
  8. package/dist/blackboard/store.d.ts +138 -0
  9. package/dist/blackboard/store.js +216 -0
  10. package/dist/blackboard/test-db.d.ts +5 -0
  11. package/dist/blackboard/test-db.js +42 -0
  12. package/dist/channel/auth.d.ts +41 -0
  13. package/dist/channel/auth.js +67 -0
  14. package/dist/channel/clock.d.ts +11 -0
  15. package/dist/channel/clock.js +4 -0
  16. package/dist/channel/connection.d.ts +75 -0
  17. package/dist/channel/connection.js +14 -0
  18. package/dist/channel/dispatch.d.ts +46 -0
  19. package/dist/channel/dispatch.js +86 -0
  20. package/dist/channel/hub.d.ts +78 -0
  21. package/dist/channel/hub.js +157 -0
  22. package/dist/channel/index.d.ts +27 -0
  23. package/dist/channel/index.js +20 -0
  24. package/dist/channel/registry.d.ts +68 -0
  25. package/dist/channel/registry.js +84 -0
  26. package/dist/channel/ws-transport.d.ts +23 -0
  27. package/dist/channel/ws-transport.js +178 -0
  28. package/dist/cockpit/boot.d.ts +68 -0
  29. package/dist/cockpit/boot.js +202 -0
  30. package/dist/cockpit/fake-dom.d.ts +37 -0
  31. package/dist/cockpit/fake-dom.js +73 -0
  32. package/dist/cockpit/index.d.ts +27 -0
  33. package/dist/cockpit/index.js +27 -0
  34. package/dist/cockpit/relay-client.d.ts +52 -0
  35. package/dist/cockpit/relay-client.js +192 -0
  36. package/dist/cockpit/render.d.ts +58 -0
  37. package/dist/cockpit/render.js +122 -0
  38. package/dist/cockpit/terminal-session.d.ts +95 -0
  39. package/dist/cockpit/terminal-session.js +123 -0
  40. package/dist/cockpit/view.d.ts +79 -0
  41. package/dist/cockpit/view.js +58 -0
  42. package/dist/demand/c8-rest.d.ts +77 -0
  43. package/dist/demand/c8-rest.js +123 -0
  44. package/dist/demand/index.d.ts +24 -0
  45. package/dist/demand/index.js +24 -0
  46. package/dist/demand/model.d.ts +68 -0
  47. package/dist/demand/model.js +118 -0
  48. package/dist/demand/taskdef.d.ts +40 -0
  49. package/dist/demand/taskdef.js +67 -0
  50. package/dist/index.d.ts +17 -0
  51. package/dist/index.js +17 -0
  52. package/dist/presence/family.d.ts +40 -0
  53. package/dist/presence/family.js +166 -0
  54. package/dist/presence/index.d.ts +19 -0
  55. package/dist/presence/index.js +17 -0
  56. package/dist/presence/schema.d.ts +20 -0
  57. package/dist/presence/schema.js +32 -0
  58. package/dist/presence/store.d.ts +130 -0
  59. package/dist/presence/store.js +191 -0
  60. package/dist/presence/test-db.d.ts +5 -0
  61. package/dist/presence/test-db.js +42 -0
  62. package/dist/protocol/conformance/frames.d.ts +24 -0
  63. package/dist/protocol/conformance/frames.js +116 -0
  64. package/dist/protocol/conformance/index.d.ts +13 -0
  65. package/dist/protocol/conformance/index.js +13 -0
  66. package/dist/protocol/conformance/malformed.d.ts +14 -0
  67. package/dist/protocol/conformance/malformed.js +44 -0
  68. package/dist/protocol/conformance/tokens.d.ts +19 -0
  69. package/dist/protocol/conformance/tokens.js +49 -0
  70. package/dist/protocol/conformance/vocab.d.ts +23 -0
  71. package/dist/protocol/conformance/vocab.js +97 -0
  72. package/dist/protocol/families.d.ts +32 -0
  73. package/dist/protocol/families.js +45 -0
  74. package/dist/protocol/frame.d.ts +45 -0
  75. package/dist/protocol/frame.js +114 -0
  76. package/dist/protocol/hex.d.ts +7 -0
  77. package/dist/protocol/hex.js +26 -0
  78. package/dist/protocol/index.d.ts +23 -0
  79. package/dist/protocol/index.js +23 -0
  80. package/dist/protocol/lanes.d.ts +36 -0
  81. package/dist/protocol/lanes.js +40 -0
  82. package/dist/protocol/payloads.d.ts +64 -0
  83. package/dist/protocol/payloads.js +122 -0
  84. package/dist/protocol/token.d.ts +34 -0
  85. package/dist/protocol/token.js +81 -0
  86. package/dist/protocol/vocab/schema.d.ts +51 -0
  87. package/dist/protocol/vocab/schema.js +218 -0
  88. package/dist/relay/incarnation.d.ts +17 -0
  89. package/dist/relay/incarnation.js +50 -0
  90. package/dist/relay/index.d.ts +25 -0
  91. package/dist/relay/index.js +22 -0
  92. package/dist/relay/relay-family.d.ts +68 -0
  93. package/dist/relay/relay-family.js +272 -0
  94. package/dist/relay/ring.d.ts +49 -0
  95. package/dist/relay/ring.js +105 -0
  96. package/dist/relay/scheduler.d.ts +72 -0
  97. package/dist/relay/scheduler.js +180 -0
  98. package/dist/relay/validate.d.ts +29 -0
  99. package/dist/relay/validate.js +39 -0
  100. package/dist/transcript/index.d.ts +18 -0
  101. package/dist/transcript/index.js +17 -0
  102. package/dist/transcript/schema.d.ts +32 -0
  103. package/dist/transcript/schema.js +48 -0
  104. package/dist/transcript/store.d.ts +192 -0
  105. package/dist/transcript/store.js +347 -0
  106. package/dist/transcript/test-db.d.ts +5 -0
  107. package/dist/transcript/test-db.js +41 -0
  108. package/dist/vocab/core-vocab.d.ts +26 -0
  109. package/dist/vocab/core-vocab.js +67 -0
  110. package/dist/vocab/diversity.d.ts +78 -0
  111. package/dist/vocab/diversity.js +89 -0
  112. package/dist/vocab/index.d.ts +22 -0
  113. package/dist/vocab/index.js +22 -0
  114. package/dist/vocab/merge.d.ts +9 -0
  115. package/dist/vocab/merge.js +104 -0
  116. package/dist/vocab/requires.d.ts +49 -0
  117. package/dist/vocab/requires.js +107 -0
  118. package/dist/vocab/resolver.d.ts +62 -0
  119. package/dist/vocab/resolver.js +149 -0
  120. package/dist/vocab/serve.d.ts +39 -0
  121. package/dist/vocab/serve.js +36 -0
  122. package/package.json +108 -0
  123. package/page/cockpit.css +114 -0
  124. package/page/cockpit.page.json +33 -0
  125. package/page/embed.html +40 -0
  126. package/page/mount.js +78 -0
  127. package/page/standalone.html +43 -0
  128. package/src/blackboard/family.test.ts +280 -0
  129. package/src/blackboard/family.ts +208 -0
  130. package/src/blackboard/index.ts +42 -0
  131. package/src/blackboard/schema.test.ts +60 -0
  132. package/src/blackboard/schema.ts +44 -0
  133. package/src/blackboard/store.test.ts +189 -0
  134. package/src/blackboard/store.ts +331 -0
  135. package/src/blackboard/test-db.ts +47 -0
  136. package/src/channel/auth.test.ts +64 -0
  137. package/src/channel/auth.ts +101 -0
  138. package/src/channel/clock.ts +14 -0
  139. package/src/channel/connection.ts +77 -0
  140. package/src/channel/dispatch.test.ts +83 -0
  141. package/src/channel/dispatch.ts +102 -0
  142. package/src/channel/hub.test.ts +335 -0
  143. package/src/channel/hub.ts +222 -0
  144. package/src/channel/index.ts +55 -0
  145. package/src/channel/registry.test.ts +73 -0
  146. package/src/channel/registry.ts +137 -0
  147. package/src/channel/ws-transport.test.ts +234 -0
  148. package/src/channel/ws-transport.ts +212 -0
  149. package/src/cockpit/boot.test.ts +374 -0
  150. package/src/cockpit/boot.ts +280 -0
  151. package/src/cockpit/fake-dom.ts +90 -0
  152. package/src/cockpit/index.ts +63 -0
  153. package/src/cockpit/relay-client.test.ts +359 -0
  154. package/src/cockpit/relay-client.ts +234 -0
  155. package/src/cockpit/render.test.ts +149 -0
  156. package/src/cockpit/render.ts +194 -0
  157. package/src/cockpit/terminal-session.test.ts +252 -0
  158. package/src/cockpit/terminal-session.ts +194 -0
  159. package/src/cockpit/view.test.ts +117 -0
  160. package/src/cockpit/view.ts +140 -0
  161. package/src/demand/c8-rest.test.ts +140 -0
  162. package/src/demand/c8-rest.ts +167 -0
  163. package/src/demand/index.ts +42 -0
  164. package/src/demand/model.test.ts +197 -0
  165. package/src/demand/model.ts +183 -0
  166. package/src/demand/taskdef.test.ts +85 -0
  167. package/src/demand/taskdef.ts +78 -0
  168. package/src/index.ts +17 -0
  169. package/src/presence/family.test.ts +252 -0
  170. package/src/presence/family.ts +205 -0
  171. package/src/presence/index.ts +26 -0
  172. package/src/presence/schema.test.ts +53 -0
  173. package/src/presence/schema.ts +34 -0
  174. package/src/presence/store.test.ts +190 -0
  175. package/src/presence/store.ts +287 -0
  176. package/src/presence/test-db.test.ts +57 -0
  177. package/src/presence/test-db.ts +47 -0
  178. package/src/protocol/conformance/corpus.test.ts +66 -0
  179. package/src/protocol/conformance/frames.ts +142 -0
  180. package/src/protocol/conformance/index.ts +29 -0
  181. package/src/protocol/conformance/malformed.ts +59 -0
  182. package/src/protocol/conformance/tokens.ts +70 -0
  183. package/src/protocol/conformance/vocab.ts +122 -0
  184. package/src/protocol/families.ts +54 -0
  185. package/src/protocol/frame.test.ts +116 -0
  186. package/src/protocol/frame.ts +171 -0
  187. package/src/protocol/hex.ts +28 -0
  188. package/src/protocol/index.ts +84 -0
  189. package/src/protocol/lanes.test.ts +82 -0
  190. package/src/protocol/lanes.ts +54 -0
  191. package/src/protocol/payloads.test.ts +91 -0
  192. package/src/protocol/payloads.ts +201 -0
  193. package/src/protocol/token.test.ts +57 -0
  194. package/src/protocol/token.ts +123 -0
  195. package/src/protocol/vocab/schema.test.ts +67 -0
  196. package/src/protocol/vocab/schema.ts +281 -0
  197. package/src/relay/incarnation.test.ts +53 -0
  198. package/src/relay/incarnation.ts +54 -0
  199. package/src/relay/index.ts +34 -0
  200. package/src/relay/integration.test.ts +135 -0
  201. package/src/relay/relay-family.test.ts +236 -0
  202. package/src/relay/relay-family.ts +336 -0
  203. package/src/relay/ring.test.ts +138 -0
  204. package/src/relay/ring.ts +136 -0
  205. package/src/relay/scheduler.test.ts +233 -0
  206. package/src/relay/scheduler.ts +208 -0
  207. package/src/relay/validate.test.ts +43 -0
  208. package/src/relay/validate.ts +44 -0
  209. package/src/transcript/index.ts +33 -0
  210. package/src/transcript/integration.test.ts +108 -0
  211. package/src/transcript/schema.test.ts +69 -0
  212. package/src/transcript/schema.ts +51 -0
  213. package/src/transcript/store.test.ts +285 -0
  214. package/src/transcript/store.ts +530 -0
  215. package/src/transcript/test-db.ts +46 -0
  216. package/src/vocab/core-vocab.test.ts +34 -0
  217. package/src/vocab/core-vocab.ts +88 -0
  218. package/src/vocab/diversity.test.ts +153 -0
  219. package/src/vocab/diversity.ts +169 -0
  220. package/src/vocab/index.ts +55 -0
  221. package/src/vocab/merge.test.ts +73 -0
  222. package/src/vocab/merge.ts +117 -0
  223. package/src/vocab/requires.test.ts +69 -0
  224. package/src/vocab/requires.ts +155 -0
  225. package/src/vocab/resolver.test.ts +118 -0
  226. package/src/vocab/resolver.ts +187 -0
  227. package/src/vocab/serve.test.ts +64 -0
  228. package/src/vocab/serve.ts +66 -0
@@ -0,0 +1,24 @@
1
+ import type { Frame } from "../frame.ts";
2
+ /** The channels a golden frame may travel on. Single source of truth for the
3
+ * direction contract — the corpus completeness test derives its coverage from
4
+ * this union so a new variant cannot be added without a covering vector. */
5
+ export type FrameDirection = "worker->hub" | "hub->worker" | "hub->observers";
6
+ /**
7
+ * Golden frame vectors: canonical (frame ↔ bytes) pairs the codec must satisfy
8
+ * in BOTH directions. `hex` is the exact wire encoding of `frame`. These are
9
+ * committed goldens — a codec change that alters the wire bytes fails the
10
+ * round-trip test, catching drift between this repo and the c8ctl client.
11
+ *
12
+ * Coverage spans every message family, every QoS lane, both directions, and
13
+ * boundary values (seq = 0, seq = uint32 max, null payload, empty string,
14
+ * multi-byte UTF-8).
15
+ */
16
+ export interface GoldenFrame {
17
+ readonly name: string;
18
+ /** Informational: which way the frame travels on the channel. */
19
+ readonly direction: FrameDirection;
20
+ readonly frame: Frame;
21
+ /** Exact wire encoding of `frame`, as a lowercase hex string. */
22
+ readonly hex: string;
23
+ }
24
+ export declare const GOLDEN_FRAMES: readonly GoldenFrame[];
@@ -0,0 +1,116 @@
1
+ export const GOLDEN_FRAMES = [
2
+ {
3
+ name: "register-worker-to-hub",
4
+ direction: "worker->hub",
5
+ frame: {
6
+ lane: "control",
7
+ family: "register",
8
+ seq: 1,
9
+ payload: {
10
+ instance: "w-abc123",
11
+ capability: { cognition: "opus", weight: 3, family: "anthropic", host: "mac-01" },
12
+ },
13
+ },
14
+ hex: "4e4101000100000001000000697b22696e7374616e6365223a22772d616263313233222c226361706162696c697479223a7b22636f676e6974696f6e223a226f707573222c22776569676874223a332c2266616d696c79223a22616e7468726f706963222c22686f7374223a226d61632d3031227d7d",
15
+ },
16
+ {
17
+ name: "heartbeat-seq-zero",
18
+ direction: "worker->hub",
19
+ frame: { lane: "control", family: "heartbeat", seq: 0, payload: { instance: "w-abc123" } },
20
+ hex: "4e4101000200000000000000177b22696e7374616e6365223a22772d616263313233227d",
21
+ },
22
+ {
23
+ name: "deregister-with-reason",
24
+ direction: "worker->hub",
25
+ frame: {
26
+ lane: "control",
27
+ family: "deregister",
28
+ seq: 42,
29
+ payload: { instance: "w-abc123", reason: "shutdown" },
30
+ },
31
+ hex: "4e410100030000002a0000002b7b22696e7374616e6365223a22772d616263313233222c22726561736f6e223a2273687574646f776e227d",
32
+ },
33
+ {
34
+ name: "serve-hub-to-worker",
35
+ direction: "hub->worker",
36
+ frame: {
37
+ lane: "control",
38
+ family: "serve",
39
+ seq: 2,
40
+ payload: { instance: "w-abc123", tokens: ["implementation.qa.red#1", "planning.decide"] },
41
+ },
42
+ hex: "4e41010004000000020000004e7b22696e7374616e6365223a22772d616263313233222c22746f6b656e73223a5b22696d706c656d656e746174696f6e2e71612e7265642331222c22706c616e6e696e672e646563696465225d7d",
43
+ },
44
+ {
45
+ name: "demand-hub-to-observers",
46
+ direction: "hub->observers",
47
+ frame: {
48
+ lane: "control",
49
+ family: "demand",
50
+ seq: 7,
51
+ payload: { network: "implementation", missing: ["implementation.ci"] },
52
+ },
53
+ hex: "4e41010005000000070000003c7b226e6574776f726b223a22696d706c656d656e746174696f6e222c226d697373696e67223a5b22696d706c656d656e746174696f6e2e6369225d7d",
54
+ },
55
+ {
56
+ name: "blackboard-append-interactive",
57
+ direction: "worker->hub",
58
+ frame: {
59
+ lane: "interactive",
60
+ family: "blackboard",
61
+ seq: 100,
62
+ payload: { op: "append", dedupeKey: "regen-before-build" },
63
+ },
64
+ hex: "4e4101010600000064000000307b226f70223a22617070656e64222c226465647570654b6579223a22726567656e2d6265666f72652d6275696c64227d",
65
+ },
66
+ {
67
+ name: "blackboard-read-interactive",
68
+ direction: "worker->hub",
69
+ frame: {
70
+ lane: "interactive",
71
+ family: "blackboard",
72
+ seq: 101,
73
+ payload: { op: "read", since: 12 },
74
+ },
75
+ hex: "4e4101010600000065000000187b226f70223a2272656164222c2273696e6365223a31327d",
76
+ },
77
+ {
78
+ name: "relay-chunk-bulk",
79
+ direction: "worker->hub",
80
+ frame: {
81
+ lane: "bulk",
82
+ family: "relay",
83
+ seq: 5,
84
+ payload: { stream: "job-1223", offset: 2048, chunk: "hello world\n" },
85
+ },
86
+ hex: "4e41010207000000050000003b7b2273747265616d223a226a6f622d31323233222c226f6666736574223a323034382c226368756e6b223a2268656c6c6f20776f726c645c6e227d",
87
+ },
88
+ {
89
+ name: "relay-seq-max-bulk",
90
+ direction: "hub->worker",
91
+ frame: {
92
+ lane: "bulk",
93
+ family: "relay",
94
+ seq: 4294967295,
95
+ payload: { stream: "job-1223", offset: 0, chunk: "" },
96
+ },
97
+ hex: "4e41010207ffffffff0000002b7b2273747265616d223a226a6f622d31323233222c226f6666736574223a302c226368756e6b223a22227d",
98
+ },
99
+ {
100
+ name: "null-payload-envelope",
101
+ direction: "hub->worker",
102
+ frame: { lane: "control", family: "heartbeat", seq: 3, payload: null },
103
+ hex: "4e4101000200000003000000046e756c6c",
104
+ },
105
+ {
106
+ name: "unicode-payload-relay",
107
+ direction: "worker->hub",
108
+ frame: {
109
+ lane: "bulk",
110
+ family: "relay",
111
+ seq: 9,
112
+ payload: { stream: "job-1223", offset: 4, chunk: "✓ café — 日本語" },
113
+ },
114
+ hex: "4e4101020700000009000000427b2273747265616d223a226a6f622d31323233222c226f6666736574223a342c226368756e6b223a22e29c9320636166c3a920e2809420e697a5e69cace8aa9e227d",
115
+ },
116
+ ];
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Shared conformance corpus for the Nano agentic protocol.
3
+ *
4
+ * Exported so cross-repo tests (this repo AND jwulf/c8ctl-plugin-nano) consume
5
+ * the SAME golden frames, malformed vectors, vocab documents and token vectors.
6
+ * Import it and hold your codec/client to it:
7
+ *
8
+ * import { GOLDEN_FRAMES, MALFORMED_FRAMES } from "./index.ts";
9
+ */
10
+ export { GOLDEN_FRAMES, type GoldenFrame, } from "./frames.ts";
11
+ export { MALFORMED_FRAMES, type MalformedFrame, } from "./malformed.ts";
12
+ export { VALID_VOCABS, INVALID_VOCABS, type ValidVocab, type InvalidVocab, } from "./vocab.ts";
13
+ export { VALID_TOKENS, INVALID_TOKENS, type ValidToken, type InvalidToken, } from "./tokens.ts";
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Shared conformance corpus for the Nano agentic protocol.
3
+ *
4
+ * Exported so cross-repo tests (this repo AND jwulf/c8ctl-plugin-nano) consume
5
+ * the SAME golden frames, malformed vectors, vocab documents and token vectors.
6
+ * Import it and hold your codec/client to it:
7
+ *
8
+ * import { GOLDEN_FRAMES, MALFORMED_FRAMES } from "./index.ts";
9
+ */
10
+ export { GOLDEN_FRAMES, } from "./frames.js";
11
+ export { MALFORMED_FRAMES, } from "./malformed.js";
12
+ export { VALID_VOCABS, INVALID_VOCABS, } from "./vocab.js";
13
+ export { VALID_TOKENS, INVALID_TOKENS, } from "./tokens.js";
@@ -0,0 +1,14 @@
1
+ import type { FrameDecodeErrorCode } from "../frame.ts";
2
+ /**
3
+ * Malformed frame vectors: adversarial byte sequences that MUST be rejected,
4
+ * each paired with the exact {@link FrameDecodeErrorCode} the decoder must
5
+ * raise. A shared prose spec does not stop divergence — shared adversarial
6
+ * vectors do. Both this decoder and the c8ctl client are held to these.
7
+ */
8
+ export interface MalformedFrame {
9
+ readonly name: string;
10
+ /** The raw bytes to feed the decoder, as a lowercase hex string (may be ""). */
11
+ readonly hex: string;
12
+ readonly expected: FrameDecodeErrorCode;
13
+ }
14
+ export declare const MALFORMED_FRAMES: readonly MalformedFrame[];
@@ -0,0 +1,44 @@
1
+ export const MALFORMED_FRAMES = [
2
+ { name: "empty-buffer", hex: "", expected: "empty" },
3
+ { name: "short-header", hex: "4e410100", expected: "short-header" },
4
+ {
5
+ name: "bad-magic",
6
+ hex: "ffff01000200000000000000177b22696e7374616e6365223a22772d616263313233227d",
7
+ expected: "bad-magic",
8
+ },
9
+ {
10
+ name: "unsupported-version",
11
+ hex: "4e4102000200000000000000177b22696e7374616e6365223a22772d616263313233227d",
12
+ expected: "unsupported-version",
13
+ },
14
+ {
15
+ name: "unknown-lane",
16
+ hex: "4e4101090200000000000000177b22696e7374616e6365223a22772d616263313233227d",
17
+ expected: "unknown-lane",
18
+ },
19
+ {
20
+ name: "unknown-family",
21
+ hex: "4e4101006300000000000000177b22696e7374616e6365223a22772d616263313233227d",
22
+ expected: "unknown-family",
23
+ },
24
+ {
25
+ name: "truncated-payload",
26
+ hex: "4e4101000200000000000000177b",
27
+ expected: "truncated-payload",
28
+ },
29
+ {
30
+ name: "trailing-bytes",
31
+ hex: "4e4101000200000000000000177b22696e7374616e6365223a22772d616263313233227d00",
32
+ expected: "trailing-bytes",
33
+ },
34
+ {
35
+ name: "invalid-payload-json-nonjson",
36
+ hex: "4e4101000200000000000000027a7a",
37
+ expected: "invalid-payload-json",
38
+ },
39
+ {
40
+ name: "invalid-payload-json-bad-utf8",
41
+ hex: "4e410100020000000000000001ff",
42
+ expected: "invalid-payload-json",
43
+ },
44
+ ];
@@ -0,0 +1,19 @@
1
+ import type { RoutingToken, TokenParseErrorCode } from "../token.ts";
2
+ /**
3
+ * Routing-token vectors. Valid ones assert the parse decomposes exactly as
4
+ * `network[.subnetwork…].role[#seat]`; invalid ones assert the grammar rejects
5
+ * with a specific {@link TokenParseErrorCode}. Capability is never present in a
6
+ * token — there is deliberately no "capability" field to parse.
7
+ */
8
+ export interface ValidToken {
9
+ readonly name: string;
10
+ readonly token: string;
11
+ readonly parsed: RoutingToken;
12
+ }
13
+ export interface InvalidToken {
14
+ readonly name: string;
15
+ readonly token: string;
16
+ readonly expected: TokenParseErrorCode;
17
+ }
18
+ export declare const VALID_TOKENS: readonly ValidToken[];
19
+ export declare const INVALID_TOKENS: readonly InvalidToken[];
@@ -0,0 +1,49 @@
1
+ export const VALID_TOKENS = [
2
+ {
3
+ name: "single-segment-role",
4
+ token: "decide",
5
+ parsed: { subnetworks: [], role: "decide" },
6
+ },
7
+ {
8
+ name: "network-and-role",
9
+ token: "planning.decide",
10
+ parsed: { network: "planning", subnetworks: [], role: "decide" },
11
+ },
12
+ {
13
+ name: "nested-subnetwork",
14
+ token: "implementation.ci.fix",
15
+ parsed: { network: "implementation", subnetworks: ["ci"], role: "fix" },
16
+ },
17
+ {
18
+ name: "role-with-named-seat",
19
+ token: "qa.review#red",
20
+ parsed: { network: "qa", subnetworks: [], role: "review", seat: "red" },
21
+ },
22
+ {
23
+ name: "role-with-numeric-seat",
24
+ token: "implementation.qa.red#1",
25
+ parsed: { network: "implementation", subnetworks: ["qa"], role: "red", seat: "1" },
26
+ },
27
+ {
28
+ name: "hyphenated-segments",
29
+ token: "code-review.fast-lane.senior-dev#blue",
30
+ parsed: {
31
+ network: "code-review",
32
+ subnetworks: ["fast-lane"],
33
+ role: "senior-dev",
34
+ seat: "blue",
35
+ },
36
+ },
37
+ ];
38
+ export const INVALID_TOKENS = [
39
+ { name: "empty", token: "", expected: "empty" },
40
+ { name: "whitespace", token: "planning .decide", expected: "whitespace" },
41
+ { name: "leading-dot", token: ".decide", expected: "empty-segment" },
42
+ { name: "trailing-dot", token: "planning.", expected: "empty-segment" },
43
+ { name: "double-dot", token: "planning..decide", expected: "empty-segment" },
44
+ { name: "uppercase-segment", token: "Planning.decide", expected: "bad-segment" },
45
+ { name: "segment-starts-with-digit", token: "1planning.decide", expected: "bad-segment" },
46
+ { name: "two-seat-markers", token: "qa.review#red#blue", expected: "multiple-seat-markers" },
47
+ { name: "empty-seat", token: "qa.review#", expected: "empty-seat" },
48
+ { name: "uppercase-seat", token: "qa.review#Red", expected: "bad-seat" },
49
+ ];
@@ -0,0 +1,23 @@
1
+ import type { VocabDocument } from "../vocab/schema.ts";
2
+ /**
3
+ * Valid vocab documents (core-shaped + an author extension) and invalid ones
4
+ * paired with the error code they must surface. Shared so the c8ctl client's
5
+ * resolver is held to the same schema this repo's validator enforces.
6
+ *
7
+ * NOTE: the FULL opinionated core vocabulary is S3's deliverable; the valid
8
+ * document below is a representative, schema-complete sample (it exercises
9
+ * `requires`, `weight`, numeric `seats`, named `seats`, and
10
+ * `seatsDistinctFamily`), not the canonical core artifact.
11
+ */
12
+ export interface ValidVocab {
13
+ readonly name: string;
14
+ readonly document: VocabDocument;
15
+ }
16
+ export interface InvalidVocab {
17
+ readonly name: string;
18
+ readonly document: unknown;
19
+ /** A `code` that MUST appear among the validation errors. */
20
+ readonly expectedCode: string;
21
+ }
22
+ export declare const VALID_VOCABS: readonly ValidVocab[];
23
+ export declare const INVALID_VOCABS: readonly InvalidVocab[];
@@ -0,0 +1,97 @@
1
+ export const VALID_VOCABS = [
2
+ {
3
+ name: "core-shaped-sample",
4
+ document: {
5
+ version: 1,
6
+ networks: {
7
+ planning: {
8
+ roles: {
9
+ decide: { requires: ["cognition=reasoning"], weight: 3, seats: 1 },
10
+ },
11
+ },
12
+ qa: {
13
+ roles: {
14
+ review: {
15
+ requires: ["cognition=reasoning"],
16
+ weight: 2,
17
+ seats: ["red", "blue"],
18
+ seatsDistinctFamily: true,
19
+ },
20
+ },
21
+ },
22
+ implementation: {
23
+ roles: { senior: { weight: 3, seats: 4 } },
24
+ subnetworks: {
25
+ ci: { roles: { fix: { weight: 1, seats: 2 } } },
26
+ },
27
+ },
28
+ },
29
+ },
30
+ },
31
+ {
32
+ name: "author-extension-merges-in-same-schema",
33
+ document: {
34
+ version: 2,
35
+ networks: {
36
+ support: {
37
+ roles: {
38
+ triage: { requires: ["host=on-call"], weight: 1, seats: 3 },
39
+ },
40
+ },
41
+ },
42
+ },
43
+ },
44
+ ];
45
+ export const INVALID_VOCABS = [
46
+ { name: "not-an-object", document: 42, expectedCode: "not-object" },
47
+ { name: "missing-version", document: { networks: {} }, expectedCode: "bad-version" },
48
+ { name: "zero-version", document: { version: 0, networks: {} }, expectedCode: "bad-version" },
49
+ {
50
+ name: "missing-networks",
51
+ document: { version: 1 },
52
+ expectedCode: "bad-networks",
53
+ },
54
+ {
55
+ name: "unknown-document-field",
56
+ document: { version: 1, networks: {}, extra: true },
57
+ expectedCode: "unknown-document-field",
58
+ },
59
+ {
60
+ name: "bad-network-name",
61
+ document: { version: 1, networks: { "Bad Name": { roles: {} } } },
62
+ expectedCode: "bad-network-name",
63
+ },
64
+ {
65
+ name: "unknown-role-field",
66
+ document: { version: 1, networks: { planning: { roles: { decide: { bogus: 1 } } } } },
67
+ expectedCode: "unknown-role-field",
68
+ },
69
+ {
70
+ name: "bad-weight",
71
+ document: { version: 1, networks: { planning: { roles: { decide: { weight: "high" } } } } },
72
+ expectedCode: "bad-weight",
73
+ },
74
+ {
75
+ name: "bad-seats-count",
76
+ document: { version: 1, networks: { planning: { roles: { decide: { seats: -1 } } } } },
77
+ expectedCode: "bad-seats",
78
+ },
79
+ {
80
+ name: "bad-named-seat",
81
+ document: { version: 1, networks: { qa: { roles: { review: { seats: ["Red"] } } } } },
82
+ expectedCode: "bad-seat-label",
83
+ },
84
+ {
85
+ name: "bad-requires",
86
+ document: { version: 1, networks: { planning: { roles: { decide: { requires: "x" } } } } },
87
+ expectedCode: "bad-requires",
88
+ },
89
+ {
90
+ name: "bad-seats-distinct-family",
91
+ document: {
92
+ version: 1,
93
+ networks: { qa: { roles: { review: { seatsDistinctFamily: "yes" } } } },
94
+ },
95
+ expectedCode: "bad-seats-distinct-family",
96
+ },
97
+ ];
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Canonical message-family set for the Nano agentic protocol.
3
+ *
4
+ * This is the single source of truth every family module keys off — S1's
5
+ * `registerFamilyHandler(family, handler)` seam, the frame codec, and each
6
+ * per-family module all derive their family key from {@link MESSAGE_FAMILIES}.
7
+ * Do not fork this list; extend it here.
8
+ *
9
+ * Directions (informational — the codec is direction-agnostic):
10
+ * - `register` / `heartbeat` / `deregister` — worker → hub (presence)
11
+ * - `serve` — hub → worker (assigned tokens)
12
+ * - `demand` — hub → observers (demand×supply)
13
+ * - `blackboard` — both directions (coordination)
14
+ * - `relay` — both directions (terminal bytes)
15
+ */
16
+ export declare const MESSAGE_FAMILIES: readonly ["register", "heartbeat", "deregister", "serve", "demand", "blackboard", "relay"];
17
+ export type MessageFamily = (typeof MESSAGE_FAMILIES)[number];
18
+ export declare function isMessageFamily(value: unknown): value is MessageFamily;
19
+ /**
20
+ * Stable on-wire codes for each family. Codes are part of the wire contract and
21
+ * MUST NOT be renumbered; only append new families with new, unused codes.
22
+ */
23
+ export declare const FAMILY_CODES: {
24
+ readonly register: 1;
25
+ readonly heartbeat: 2;
26
+ readonly deregister: 3;
27
+ readonly serve: 4;
28
+ readonly demand: 5;
29
+ readonly blackboard: 6;
30
+ readonly relay: 7;
31
+ };
32
+ export declare function familyForCode(code: number): MessageFamily | undefined;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Canonical message-family set for the Nano agentic protocol.
3
+ *
4
+ * This is the single source of truth every family module keys off — S1's
5
+ * `registerFamilyHandler(family, handler)` seam, the frame codec, and each
6
+ * per-family module all derive their family key from {@link MESSAGE_FAMILIES}.
7
+ * Do not fork this list; extend it here.
8
+ *
9
+ * Directions (informational — the codec is direction-agnostic):
10
+ * - `register` / `heartbeat` / `deregister` — worker → hub (presence)
11
+ * - `serve` — hub → worker (assigned tokens)
12
+ * - `demand` — hub → observers (demand×supply)
13
+ * - `blackboard` — both directions (coordination)
14
+ * - `relay` — both directions (terminal bytes)
15
+ */
16
+ export const MESSAGE_FAMILIES = [
17
+ "register",
18
+ "heartbeat",
19
+ "deregister",
20
+ "serve",
21
+ "demand",
22
+ "blackboard",
23
+ "relay",
24
+ ];
25
+ const FAMILY_SET = new Set(MESSAGE_FAMILIES);
26
+ export function isMessageFamily(value) {
27
+ return typeof value === "string" && FAMILY_SET.has(value);
28
+ }
29
+ /**
30
+ * Stable on-wire codes for each family. Codes are part of the wire contract and
31
+ * MUST NOT be renumbered; only append new families with new, unused codes.
32
+ */
33
+ export const FAMILY_CODES = {
34
+ register: 1,
35
+ heartbeat: 2,
36
+ deregister: 3,
37
+ serve: 4,
38
+ demand: 5,
39
+ blackboard: 6,
40
+ relay: 7,
41
+ };
42
+ const CODE_TO_FAMILY = new Map(MESSAGE_FAMILIES.map((family) => [FAMILY_CODES[family], family]));
43
+ export function familyForCode(code) {
44
+ return CODE_TO_FAMILY.get(code);
45
+ }
@@ -0,0 +1,45 @@
1
+ import { type MessageFamily } from "./families.ts";
2
+ import { type QosLane } from "./lanes.ts";
3
+ /**
4
+ * A single agentic-channel frame: the QoS lane it rides, the message family it
5
+ * belongs to, a monotonic sequence number (used by the relay for
6
+ * resume-from-offset), and a JSON-serialisable family payload.
7
+ *
8
+ * The codec is an ENVELOPE codec — it does not interpret `payload` beyond
9
+ * round-tripping it as JSON. Per-family payload shape is validated separately
10
+ * (see `payloads.ts`).
11
+ */
12
+ export interface Frame {
13
+ readonly lane: QosLane;
14
+ readonly family: MessageFamily;
15
+ readonly seq: number;
16
+ readonly payload: unknown;
17
+ }
18
+ /**
19
+ * Wire layout (all integers big-endian, unsigned):
20
+ *
21
+ * offset size field
22
+ * 0 2 magic 0x4E41 ("NA")
23
+ * 2 1 version = 1
24
+ * 3 1 lane code 0=control | 1=interactive | 2=bulk
25
+ * 4 1 family code 1..7 (see FAMILY_CODES)
26
+ * 5 4 seq uint32
27
+ * 9 4 payloadLen uint32 (bytes of UTF-8 JSON that follow)
28
+ * 13 N payload UTF-8 JSON
29
+ */
30
+ export declare const FRAME_MAGIC = 20033;
31
+ export declare const FRAME_VERSION = 1;
32
+ export declare const FRAME_HEADER_BYTES = 13;
33
+ export declare const MAX_SEQ = 4294967295;
34
+ export type FrameDecodeErrorCode = "empty" | "short-header" | "bad-magic" | "unsupported-version" | "unknown-lane" | "unknown-family" | "truncated-payload" | "trailing-bytes" | "invalid-payload-json";
35
+ export declare class FrameDecodeError extends Error {
36
+ readonly code: FrameDecodeErrorCode;
37
+ constructor(code: FrameDecodeErrorCode, message: string);
38
+ }
39
+ export type FrameEncodeErrorCode = "invalid-lane" | "invalid-family" | "invalid-seq" | "unserialisable-payload";
40
+ export declare class FrameEncodeError extends Error {
41
+ readonly code: FrameEncodeErrorCode;
42
+ constructor(code: FrameEncodeErrorCode, message: string);
43
+ }
44
+ export declare function encodeFrame(frame: Frame): Uint8Array;
45
+ export declare function decodeFrame(bytes: Uint8Array): Frame;
@@ -0,0 +1,114 @@
1
+ import { FAMILY_CODES, familyForCode, isMessageFamily, } from "./families.js";
2
+ import { LANE_CODES, laneForCode, isQosLane } from "./lanes.js";
3
+ /**
4
+ * Wire layout (all integers big-endian, unsigned):
5
+ *
6
+ * offset size field
7
+ * 0 2 magic 0x4E41 ("NA")
8
+ * 2 1 version = 1
9
+ * 3 1 lane code 0=control | 1=interactive | 2=bulk
10
+ * 4 1 family code 1..7 (see FAMILY_CODES)
11
+ * 5 4 seq uint32
12
+ * 9 4 payloadLen uint32 (bytes of UTF-8 JSON that follow)
13
+ * 13 N payload UTF-8 JSON
14
+ */
15
+ export const FRAME_MAGIC = 0x4e41;
16
+ export const FRAME_VERSION = 1;
17
+ export const FRAME_HEADER_BYTES = 13;
18
+ export const MAX_SEQ = 0xffffffff;
19
+ export class FrameDecodeError extends Error {
20
+ code;
21
+ constructor(code, message) {
22
+ super(message);
23
+ this.name = "FrameDecodeError";
24
+ this.code = code;
25
+ }
26
+ }
27
+ export class FrameEncodeError extends Error {
28
+ code;
29
+ constructor(code, message) {
30
+ super(message);
31
+ this.name = "FrameEncodeError";
32
+ this.code = code;
33
+ }
34
+ }
35
+ export function encodeFrame(frame) {
36
+ if (!isQosLane(frame.lane)) {
37
+ throw new FrameEncodeError("invalid-lane", `unknown QoS lane: ${String(frame.lane)}`);
38
+ }
39
+ if (!isMessageFamily(frame.family)) {
40
+ throw new FrameEncodeError("invalid-family", `unknown message family: ${String(frame.family)}`);
41
+ }
42
+ if (!Number.isInteger(frame.seq) || frame.seq < 0 || frame.seq > MAX_SEQ) {
43
+ throw new FrameEncodeError("invalid-seq", `seq must be a uint32, got: ${String(frame.seq)}`);
44
+ }
45
+ // Encode `payload` as-is: a top-level `undefined` is NOT valid JSON
46
+ // (`JSON.stringify(undefined) === undefined`), so it is rejected rather than
47
+ // silently coerced to `null` — that keeps the codec invertible and surfaces a
48
+ // caller that forgot to set a payload. A `null` payload is valid JSON and
49
+ // round-trips.
50
+ let json;
51
+ try {
52
+ json = JSON.stringify(frame.payload);
53
+ }
54
+ catch {
55
+ json = undefined;
56
+ }
57
+ if (json === undefined) {
58
+ throw new FrameEncodeError("unserialisable-payload", "payload is not JSON-serialisable");
59
+ }
60
+ const payloadBytes = new TextEncoder().encode(json);
61
+ const out = new Uint8Array(FRAME_HEADER_BYTES + payloadBytes.length);
62
+ const view = new DataView(out.buffer);
63
+ view.setUint16(0, FRAME_MAGIC);
64
+ view.setUint8(2, FRAME_VERSION);
65
+ view.setUint8(3, LANE_CODES[frame.lane]);
66
+ view.setUint8(4, FAMILY_CODES[frame.family]);
67
+ view.setUint32(5, frame.seq);
68
+ view.setUint32(9, payloadBytes.length);
69
+ out.set(payloadBytes, FRAME_HEADER_BYTES);
70
+ return out;
71
+ }
72
+ export function decodeFrame(bytes) {
73
+ if (bytes.length === 0) {
74
+ throw new FrameDecodeError("empty", "cannot decode an empty buffer");
75
+ }
76
+ if (bytes.length < FRAME_HEADER_BYTES) {
77
+ throw new FrameDecodeError("short-header", `need at least ${FRAME_HEADER_BYTES} header bytes, got ${bytes.length}`);
78
+ }
79
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
80
+ if (view.getUint16(0) !== FRAME_MAGIC) {
81
+ throw new FrameDecodeError("bad-magic", "frame magic mismatch");
82
+ }
83
+ const version = view.getUint8(2);
84
+ if (version !== FRAME_VERSION) {
85
+ throw new FrameDecodeError("unsupported-version", `unsupported frame version: ${version}`);
86
+ }
87
+ const lane = laneForCode(view.getUint8(3));
88
+ if (lane === undefined) {
89
+ throw new FrameDecodeError("unknown-lane", `unknown lane code: ${view.getUint8(3)}`);
90
+ }
91
+ const family = familyForCode(view.getUint8(4));
92
+ if (family === undefined) {
93
+ throw new FrameDecodeError("unknown-family", `unknown family code: ${view.getUint8(4)}`);
94
+ }
95
+ const seq = view.getUint32(5);
96
+ const payloadLen = view.getUint32(9);
97
+ const end = FRAME_HEADER_BYTES + payloadLen;
98
+ if (end > bytes.length) {
99
+ throw new FrameDecodeError("truncated-payload", `payload length ${payloadLen} exceeds available ${bytes.length - FRAME_HEADER_BYTES} bytes`);
100
+ }
101
+ if (end < bytes.length) {
102
+ throw new FrameDecodeError("trailing-bytes", `${bytes.length - end} trailing byte(s) after payload`);
103
+ }
104
+ const payloadBytes = bytes.subarray(FRAME_HEADER_BYTES, end);
105
+ let payload;
106
+ try {
107
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(payloadBytes);
108
+ payload = JSON.parse(text);
109
+ }
110
+ catch {
111
+ throw new FrameDecodeError("invalid-payload-json", "payload is not valid UTF-8 JSON");
112
+ }
113
+ return { lane, family, seq, payload };
114
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Language-neutral hex helpers so the conformance corpus can express golden
3
+ * frames as hex strings that any consumer (this repo, jwulf/c8ctl-plugin-nano,
4
+ * a future non-JS client) can decode identically.
5
+ */
6
+ export declare function bytesToHex(bytes: Uint8Array): string;
7
+ export declare function hexToBytes(hex: string): Uint8Array;