@labacacia/nps-sdk 1.0.0-alpha.3 → 1.0.0-alpha.5

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 (241) hide show
  1. package/CHANGELOG.cn.md +73 -0
  2. package/CHANGELOG.md +82 -0
  3. package/README.cn.md +17 -4
  4. package/README.md +17 -4
  5. package/dist/core/anchor-cache.js +104 -0
  6. package/dist/core/anchor-cache.js.map +1 -0
  7. package/dist/core/cache.js +80 -0
  8. package/dist/core/cache.js.map +1 -0
  9. package/dist/core/canonical-json.js +44 -0
  10. package/dist/core/canonical-json.js.map +1 -0
  11. package/dist/core/codec.js +119 -0
  12. package/dist/core/codec.js.map +1 -0
  13. package/dist/core/codecs/index.js +6 -0
  14. package/dist/core/codecs/index.js.map +1 -0
  15. package/dist/core/codecs/ncp-codec.js +93 -0
  16. package/dist/core/codecs/ncp-codec.js.map +1 -0
  17. package/dist/core/codecs/tier1-json-codec.js +28 -0
  18. package/dist/core/codecs/tier1-json-codec.js.map +1 -0
  19. package/dist/core/codecs/tier2-msgpack-codec.js +26 -0
  20. package/dist/core/codecs/tier2-msgpack-codec.js.map +1 -0
  21. package/dist/core/crypto-provider.js +10 -0
  22. package/dist/core/crypto-provider.js.map +1 -0
  23. package/dist/core/exceptions.js +52 -0
  24. package/dist/core/exceptions.js.map +1 -0
  25. package/dist/core/frame-header.js +185 -0
  26. package/dist/core/frame-header.js.map +1 -0
  27. package/dist/core/frame-registry.js +63 -0
  28. package/dist/core/frame-registry.js.map +1 -0
  29. package/dist/core/frames.js +154 -0
  30. package/dist/core/frames.js.map +1 -0
  31. package/dist/core/index.js +21 -405
  32. package/dist/core/index.js.map +1 -1
  33. package/dist/core/registry.js +17 -0
  34. package/dist/core/registry.js.map +1 -0
  35. package/dist/core/status-codes.d.ts +1 -0
  36. package/dist/core/status-codes.d.ts.map +1 -1
  37. package/dist/core/status-codes.js +39 -0
  38. package/dist/core/status-codes.js.map +1 -0
  39. package/dist/index.d.ts +1 -1
  40. package/dist/index.js +9 -5
  41. package/dist/index.js.map +1 -1
  42. package/dist/ncp/frames/anchor-frame.js +54 -0
  43. package/dist/ncp/frames/anchor-frame.js.map +1 -0
  44. package/dist/ncp/frames/caps-frame.js +29 -0
  45. package/dist/ncp/frames/caps-frame.js.map +1 -0
  46. package/dist/ncp/frames/diff-frame.js +37 -0
  47. package/dist/ncp/frames/diff-frame.js.map +1 -0
  48. package/dist/ncp/frames/error-frame.js +13 -0
  49. package/dist/ncp/frames/error-frame.js.map +1 -0
  50. package/dist/ncp/frames/hello-frame.js +25 -0
  51. package/dist/ncp/frames/hello-frame.js.map +1 -0
  52. package/dist/ncp/frames/stream-frame.js +18 -0
  53. package/dist/ncp/frames/stream-frame.js.map +1 -0
  54. package/dist/ncp/frames.js +192 -0
  55. package/dist/ncp/frames.js.map +1 -0
  56. package/dist/ncp/handshake.js +80 -0
  57. package/dist/ncp/handshake.js.map +1 -0
  58. package/dist/ncp/index.d.ts +1 -0
  59. package/dist/ncp/index.d.ts.map +1 -1
  60. package/dist/ncp/index.js +13 -368
  61. package/dist/ncp/index.js.map +1 -1
  62. package/dist/ncp/ncp-error-codes.d.ts +1 -0
  63. package/dist/ncp/ncp-error-codes.d.ts.map +1 -1
  64. package/dist/ncp/ncp-error-codes.js +34 -0
  65. package/dist/ncp/ncp-error-codes.js.map +1 -0
  66. package/dist/ncp/ncp-patch-format.js +13 -0
  67. package/dist/ncp/ncp-patch-format.js.map +1 -0
  68. package/dist/ncp/preamble.d.ts +47 -0
  69. package/dist/ncp/preamble.d.ts.map +1 -0
  70. package/dist/ncp/preamble.js +74 -0
  71. package/dist/ncp/preamble.js.map +1 -0
  72. package/dist/ncp/registry.js +13 -0
  73. package/dist/ncp/registry.js.map +1 -0
  74. package/dist/ncp/stream-manager.js +163 -0
  75. package/dist/ncp/stream-manager.js.map +1 -0
  76. package/dist/ndp/dns-txt.d.ts +35 -0
  77. package/dist/ndp/dns-txt.d.ts.map +1 -0
  78. package/dist/ndp/dns-txt.js +67 -0
  79. package/dist/ndp/dns-txt.js.map +1 -0
  80. package/dist/ndp/frames.js +87 -0
  81. package/dist/ndp/frames.js.map +1 -0
  82. package/dist/ndp/index.d.ts +1 -0
  83. package/dist/ndp/index.d.ts.map +1 -1
  84. package/dist/ndp/index.js +7 -223
  85. package/dist/ndp/index.js.map +1 -1
  86. package/dist/ndp/ndp-registry.d.ts +2 -0
  87. package/dist/ndp/ndp-registry.d.ts.map +1 -1
  88. package/dist/ndp/ndp-registry.js +104 -0
  89. package/dist/ndp/ndp-registry.js.map +1 -0
  90. package/dist/ndp/registry.js +10 -0
  91. package/dist/ndp/registry.js.map +1 -0
  92. package/dist/ndp/validator.js +48 -0
  93. package/dist/ndp/validator.js.map +1 -0
  94. package/dist/nip/acme/client.d.ts +31 -0
  95. package/dist/nip/acme/client.d.ts.map +1 -0
  96. package/dist/nip/acme/client.js +136 -0
  97. package/dist/nip/acme/client.js.map +1 -0
  98. package/dist/nip/acme/index.d.ts +6 -0
  99. package/dist/nip/acme/index.d.ts.map +1 -0
  100. package/dist/nip/acme/index.js +8 -0
  101. package/dist/nip/acme/index.js.map +1 -0
  102. package/dist/nip/acme/jws.d.ts +31 -0
  103. package/dist/nip/acme/jws.d.ts.map +1 -0
  104. package/dist/nip/acme/jws.js +76 -0
  105. package/dist/nip/acme/jws.js.map +1 -0
  106. package/dist/nip/acme/messages.d.ts +71 -0
  107. package/dist/nip/acme/messages.d.ts.map +1 -0
  108. package/dist/nip/acme/messages.js +4 -0
  109. package/dist/nip/acme/messages.js.map +1 -0
  110. package/dist/nip/acme/server.d.ts +41 -0
  111. package/dist/nip/acme/server.d.ts.map +1 -0
  112. package/dist/nip/acme/server.js +458 -0
  113. package/dist/nip/acme/server.js.map +1 -0
  114. package/dist/nip/acme/wire.d.ts +19 -0
  115. package/dist/nip/acme/wire.d.ts.map +1 -0
  116. package/dist/nip/acme/wire.js +21 -0
  117. package/dist/nip/acme/wire.js.map +1 -0
  118. package/dist/nip/assurance-level.d.ts +19 -0
  119. package/dist/nip/assurance-level.d.ts.map +1 -0
  120. package/dist/nip/assurance-level.js +38 -0
  121. package/dist/nip/assurance-level.js.map +1 -0
  122. package/dist/nip/cert-format.d.ts +5 -0
  123. package/dist/nip/cert-format.d.ts.map +1 -0
  124. package/dist/nip/cert-format.js +6 -0
  125. package/dist/nip/cert-format.js.map +1 -0
  126. package/dist/nip/error-codes.d.ts +25 -0
  127. package/dist/nip/error-codes.d.ts.map +1 -0
  128. package/dist/nip/error-codes.js +32 -0
  129. package/dist/nip/error-codes.js.map +1 -0
  130. package/dist/nip/frames.d.ts +10 -1
  131. package/dist/nip/frames.d.ts.map +1 -1
  132. package/dist/nip/frames.js +106 -0
  133. package/dist/nip/frames.js.map +1 -0
  134. package/dist/nip/identity.js +94 -0
  135. package/dist/nip/identity.js.map +1 -0
  136. package/dist/nip/index.d.ts +6 -0
  137. package/dist/nip/index.d.ts.map +1 -1
  138. package/dist/nip/index.js +12 -187
  139. package/dist/nip/index.js.map +1 -1
  140. package/dist/nip/registry.js +10 -0
  141. package/dist/nip/registry.js.map +1 -0
  142. package/dist/nip/verifier.d.ts +23 -0
  143. package/dist/nip/verifier.d.ts.map +1 -0
  144. package/dist/nip/verifier.js +90 -0
  145. package/dist/nip/verifier.js.map +1 -0
  146. package/dist/nip/x509/builder.d.ts +35 -0
  147. package/dist/nip/x509/builder.d.ts.map +1 -0
  148. package/dist/nip/x509/builder.js +59 -0
  149. package/dist/nip/x509/builder.js.map +1 -0
  150. package/dist/nip/x509/index.d.ts +4 -0
  151. package/dist/nip/x509/index.d.ts.map +1 -0
  152. package/dist/nip/x509/index.js +6 -0
  153. package/dist/nip/x509/index.js.map +1 -0
  154. package/dist/nip/x509/oids.d.ts +17 -0
  155. package/dist/nip/x509/oids.d.ts.map +1 -0
  156. package/dist/nip/x509/oids.js +23 -0
  157. package/dist/nip/x509/oids.js.map +1 -0
  158. package/dist/nip/x509/verifier.d.ts +26 -0
  159. package/dist/nip/x509/verifier.d.ts.map +1 -0
  160. package/dist/nip/x509/verifier.js +171 -0
  161. package/dist/nip/x509/verifier.js.map +1 -0
  162. package/dist/nop/client.js +90 -0
  163. package/dist/nop/client.js.map +1 -0
  164. package/dist/nop/frames.js +148 -0
  165. package/dist/nop/frames.js.map +1 -0
  166. package/dist/nop/index.js +6 -789
  167. package/dist/nop/index.js.map +1 -1
  168. package/dist/nop/models.js +50 -0
  169. package/dist/nop/models.js.map +1 -0
  170. package/dist/nop/nop-types.js +44 -0
  171. package/dist/nop/nop-types.js.map +1 -0
  172. package/dist/nop/registry.js +11 -0
  173. package/dist/nop/registry.js.map +1 -0
  174. package/dist/nwp/client.js +101 -0
  175. package/dist/nwp/client.js.map +1 -0
  176. package/dist/nwp/error-codes.d.ts +42 -0
  177. package/dist/nwp/error-codes.d.ts.map +1 -0
  178. package/dist/nwp/error-codes.js +53 -0
  179. package/dist/nwp/error-codes.js.map +1 -0
  180. package/dist/nwp/frames.js +81 -0
  181. package/dist/nwp/frames.js.map +1 -0
  182. package/dist/nwp/index.d.ts +1 -0
  183. package/dist/nwp/index.d.ts.map +1 -1
  184. package/dist/nwp/index.js +6 -693
  185. package/dist/nwp/index.js.map +1 -1
  186. package/dist/nwp/registry.js +9 -0
  187. package/dist/nwp/registry.js.map +1 -0
  188. package/dist/setup.js +29 -0
  189. package/dist/setup.js.map +1 -0
  190. package/doc/nps-sdk.nip.cn.md +30 -0
  191. package/doc/nps-sdk.nip.md +30 -0
  192. package/doc/nps-sdk.nwp.cn.md +71 -0
  193. package/doc/nps-sdk.nwp.md +71 -0
  194. package/package.json +2 -1
  195. package/src/core/status-codes.ts +1 -0
  196. package/src/index.ts +1 -1
  197. package/src/ncp/index.ts +1 -0
  198. package/src/ncp/ncp-error-codes.ts +2 -0
  199. package/src/ncp/preamble.ts +79 -0
  200. package/src/ndp/dns-txt.ts +86 -0
  201. package/src/ndp/index.ts +1 -0
  202. package/src/ndp/ndp-registry.ts +34 -0
  203. package/src/nip/acme/client.ts +185 -0
  204. package/src/nip/acme/index.ts +8 -0
  205. package/src/nip/acme/jws.ts +109 -0
  206. package/src/nip/acme/messages.ts +85 -0
  207. package/src/nip/acme/server.ts +480 -0
  208. package/src/nip/acme/wire.ts +24 -0
  209. package/src/nip/assurance-level.ts +40 -0
  210. package/src/nip/cert-format.ts +9 -0
  211. package/src/nip/error-codes.ts +38 -0
  212. package/src/nip/frames.ts +35 -3
  213. package/src/nip/index.ts +8 -0
  214. package/src/nip/verifier.ts +122 -0
  215. package/src/nip/x509/builder.ts +91 -0
  216. package/src/nip/x509/index.ts +6 -0
  217. package/src/nip/x509/oids.ts +28 -0
  218. package/src/nip/x509/verifier.ts +214 -0
  219. package/src/nop/client.ts +1 -1
  220. package/src/nwp/client.ts +4 -4
  221. package/src/nwp/error-codes.ts +62 -0
  222. package/src/nwp/index.ts +1 -0
  223. package/tests/_rfc0002-keys.ts +57 -0
  224. package/tests/ncp/preamble.test.ts +93 -0
  225. package/tests/ndp.test.ts +106 -0
  226. package/tests/nip-acme-agent01.test.ts +192 -0
  227. package/tests/nip-x509.test.ts +280 -0
  228. package/dist/core/index.cjs +0 -452
  229. package/dist/core/index.cjs.map +0 -1
  230. package/dist/index.cjs +0 -8
  231. package/dist/index.cjs.map +0 -1
  232. package/dist/ncp/index.cjs +0 -388
  233. package/dist/ncp/index.cjs.map +0 -1
  234. package/dist/ndp/index.cjs +0 -252
  235. package/dist/ndp/index.cjs.map +0 -1
  236. package/dist/nip/index.cjs +0 -214
  237. package/dist/nip/index.cjs.map +0 -1
  238. package/dist/nop/index.cjs +0 -823
  239. package/dist/nop/index.cjs.map +0 -1
  240. package/dist/nwp/index.cjs +0 -720
  241. package/dist/nwp/index.cjs.map +0 -1
@@ -1,388 +0,0 @@
1
- 'use strict';
2
-
3
- var crypto = require('crypto');
4
- var canonicalizeDefault = require('canonicalize');
5
-
6
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
-
8
- var canonicalizeDefault__default = /*#__PURE__*/_interopDefault(canonicalizeDefault);
9
-
10
- // src/ncp/frames/anchor-frame.ts
11
-
12
- // src/core/frame-header.ts
13
- var NcpError = class extends Error {
14
- // `code` accepts NcpErrorCode constants (preferred) as well as NPS status
15
- // strings that are not NCP-prefixed (e.g. "NPS-CLIENT-CONFLICT") for cases
16
- // where the spec delegates to NPS-level codes without assigning an NCP code.
17
- constructor(code, message) {
18
- super(message);
19
- this.code = code;
20
- this.name = "NcpError";
21
- }
22
- code;
23
- };
24
-
25
- // src/ncp/frames/anchor-frame.ts
26
- var canonicalize = canonicalizeDefault__default.default;
27
- var VALID_FIELD_TYPES = [
28
- "string",
29
- "uint64",
30
- "int64",
31
- "decimal",
32
- "bool",
33
- "timestamp",
34
- "bytes",
35
- "object",
36
- "array"
37
- ];
38
- function computeAnchorId(schema) {
39
- const canonical = canonicalize(schema);
40
- if (!canonical) {
41
- throw new NcpError("NCP-ANCHOR-SCHEMA-INVALID", "Schema cannot be canonicalized");
42
- }
43
- const hash = crypto.createHash("sha256").update(canonical).digest("hex");
44
- return `sha256:${hash}`;
45
- }
46
- function validateAnchorFrame(frame) {
47
- for (const field of frame.schema.fields) {
48
- if (!VALID_FIELD_TYPES.includes(field.type)) {
49
- throw new NcpError(
50
- "NCP-ANCHOR-SCHEMA-INVALID",
51
- `Unsupported field type "${field.type}" for field "${field.name}". Valid types: ${VALID_FIELD_TYPES.join(", ")}`
52
- );
53
- }
54
- }
55
- const expected = computeAnchorId(frame.schema);
56
- if (frame.anchor_id !== expected) {
57
- throw new NcpError(
58
- "NCP-ANCHOR-SCHEMA-INVALID",
59
- `anchor_id mismatch: expected ${expected}, got ${frame.anchor_id}`
60
- );
61
- }
62
- }
63
-
64
- // src/ncp/frames/caps-frame.ts
65
- function validateCapsFrame(frame) {
66
- if (frame.count !== frame.data.length) {
67
- throw new NcpError(
68
- "NPS-CLIENT-BAD-FRAME",
69
- `CapsFrame count mismatch: count=${frame.count}, data.length=${frame.data.length}`
70
- );
71
- }
72
- if (frame.inline_anchor !== void 0) {
73
- const computed = computeAnchorId(frame.inline_anchor.schema);
74
- if (frame.inline_anchor.anchor_id !== computed) {
75
- throw new NcpError(
76
- "NCP-ANCHOR-SCHEMA-INVALID",
77
- `inline_anchor anchor_id mismatch: expected ${computed}, got ${frame.inline_anchor.anchor_id}`
78
- );
79
- }
80
- }
81
- }
82
-
83
- // src/ncp/ncp-patch-format.ts
84
- var PATCH_FORMAT = {
85
- JSON_PATCH: "json_patch",
86
- BINARY_BITSET: "binary_bitset"
87
- };
88
- function isValidPatchFormat(v) {
89
- return v === PATCH_FORMAT.JSON_PATCH || v === PATCH_FORMAT.BINARY_BITSET;
90
- }
91
-
92
- // src/ncp/frames/diff-frame.ts
93
- function validateDiffSeq(frame, currentSeq) {
94
- if (frame.base_seq !== currentSeq) {
95
- throw new NcpError(
96
- "NCP-STREAM-SEQ-GAP",
97
- `DiffFrame base_seq=${frame.base_seq} does not match current seq=${currentSeq}`
98
- );
99
- }
100
- }
101
- function validateDiffFrame(frame, encodingTier) {
102
- const fmt = frame.patch_format;
103
- if (fmt !== void 0 && !isValidPatchFormat(fmt)) {
104
- throw new NcpError(
105
- "NCP-DIFF-FORMAT-UNSUPPORTED",
106
- `Unknown patch_format "${String(fmt)}"`
107
- );
108
- }
109
- if (fmt === "binary_bitset" && encodingTier !== 1 /* MsgPack */) {
110
- throw new NcpError(
111
- "NCP-DIFF-FORMAT-UNSUPPORTED",
112
- "patch_format=binary_bitset requires Tier-2 MsgPack encoding"
113
- );
114
- }
115
- }
116
-
117
- // src/ncp/frames/error-frame.ts
118
- function isErrorFrame(obj) {
119
- if (typeof obj !== "object" || obj === null) return false;
120
- const o = obj;
121
- return o.frame === "0xFE" && typeof o.status === "string" && typeof o.error === "string";
122
- }
123
-
124
- // src/ncp/frames/hello-frame.ts
125
- function validateHelloFrame(frame) {
126
- if (!frame.nps_version) {
127
- throw new NcpError(
128
- "NPS-CLIENT-BAD-FRAME",
129
- "HelloFrame missing required field: nps_version"
130
- );
131
- }
132
- if (!frame.supported_encodings || frame.supported_encodings.length === 0) {
133
- throw new NcpError(
134
- "NPS-CLIENT-BAD-FRAME",
135
- "HelloFrame missing required field: supported_encodings (must be non-empty)"
136
- );
137
- }
138
- if (!frame.supported_protocols || frame.supported_protocols.length === 0) {
139
- throw new NcpError(
140
- "NPS-CLIENT-BAD-FRAME",
141
- "HelloFrame missing required field: supported_protocols (must be non-empty)"
142
- );
143
- }
144
- }
145
-
146
- // src/ncp/frames/stream-frame.ts
147
- var UUID_V4_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
148
- function validateStreamFrame(frame) {
149
- if (!UUID_V4_RE.test(frame.stream_id)) {
150
- throw new NcpError(
151
- "NPS-CLIENT-BAD-FRAME",
152
- `stream_id "${frame.stream_id}" is not a valid UUID v4`
153
- );
154
- }
155
- }
156
-
157
- // src/ncp/ncp-error-codes.ts
158
- var NCP_ERROR_CODES = {
159
- // Implementation-only codes (not in spec §6 — see test_results.md spec question 2)
160
- NCP_FRAME_PARSE_ERROR: "NCP-FRAME-PARSE-ERROR",
161
- NCP_FRAME_INCOMPLETE: "NCP-FRAME-INCOMPLETE",
162
- // Spec-defined codes
163
- NCP_FRAME_UNKNOWN_TYPE: "NCP-FRAME-UNKNOWN-TYPE",
164
- NCP_FRAME_PAYLOAD_TOO_LARGE: "NCP-FRAME-PAYLOAD-TOO-LARGE",
165
- NCP_FRAME_FLAGS_INVALID: "NCP-FRAME-FLAGS-INVALID",
166
- NCP_ANCHOR_NOT_FOUND: "NCP-ANCHOR-NOT-FOUND",
167
- NCP_ANCHOR_SCHEMA_INVALID: "NCP-ANCHOR-SCHEMA-INVALID",
168
- NCP_ANCHOR_ID_MISMATCH: "NCP-ANCHOR-ID-MISMATCH",
169
- NCP_ANCHOR_STALE: "NCP-ANCHOR-STALE",
170
- NCP_STREAM_SEQ_GAP: "NCP-STREAM-SEQ-GAP",
171
- NCP_STREAM_NOT_FOUND: "NCP-STREAM-NOT-FOUND",
172
- NCP_STREAM_LIMIT_EXCEEDED: "NCP-STREAM-LIMIT-EXCEEDED",
173
- NCP_STREAM_WINDOW_OVERFLOW: "NCP-STREAM-WINDOW-OVERFLOW",
174
- NCP_ENCODING_UNSUPPORTED: "NCP-ENCODING-UNSUPPORTED",
175
- NCP_DIFF_FORMAT_UNSUPPORTED: "NCP-DIFF-FORMAT-UNSUPPORTED",
176
- NCP_VERSION_INCOMPATIBLE: "NCP-VERSION-INCOMPATIBLE",
177
- NCP_ENC_NOT_NEGOTIATED: "NCP-ENC-NOT-NEGOTIATED",
178
- NCP_ENC_AUTH_FAILED: "NCP-ENC-AUTH-FAILED"
179
- };
180
-
181
- // src/ncp/handshake.ts
182
- function parseVersion(v) {
183
- return v.split(".").map((p) => Number.parseInt(p, 10));
184
- }
185
- function compareVersions(a, b) {
186
- const partsA = parseVersion(a);
187
- const partsB = parseVersion(b);
188
- const len = Math.max(partsA.length, partsB.length);
189
- for (let i = 0; i < len; i += 1) {
190
- const x = partsA[i] ?? 0;
191
- const y = partsB[i] ?? 0;
192
- if (x !== y) return x - y;
193
- }
194
- return 0;
195
- }
196
- function negotiateVersion(client, server) {
197
- const clientMin = client.min_version ?? client.nps_version;
198
- const serverVersion = server.nps_version;
199
- if (compareVersions(clientMin, serverVersion) > 0) {
200
- return {
201
- session_version: serverVersion,
202
- compatible: false,
203
- error_code: "NCP-VERSION-INCOMPATIBLE"
204
- };
205
- }
206
- const sessionVersion = compareVersions(client.nps_version, serverVersion) <= 0 ? client.nps_version : serverVersion;
207
- return { session_version: sessionVersion, compatible: true };
208
- }
209
- function negotiateEncoding(client, server) {
210
- const serverSet = new Set(server);
211
- if (client.includes("msgpack") && serverSet.has("msgpack")) {
212
- return { encoding: "msgpack" };
213
- }
214
- if (client.includes("json") && serverSet.has("json")) {
215
- return { encoding: "json" };
216
- }
217
- for (const enc of client) {
218
- if (serverSet.has(enc)) {
219
- return { encoding: enc };
220
- }
221
- }
222
- return { encoding: null };
223
- }
224
-
225
- // src/ncp/stream-manager.ts
226
- var StreamManager = class {
227
- streams = /* @__PURE__ */ new Map();
228
- outgoing = /* @__PURE__ */ new Map();
229
- maxConcurrent;
230
- constructor(options) {
231
- this.maxConcurrent = options?.maxConcurrent ?? 32;
232
- }
233
- /**
234
- * Receive a StreamFrame chunk.
235
- *
236
- * @returns true if stream is complete (is_last=true or error_code set).
237
- * @throws {NcpError} NCP-STREAM-LIMIT-EXCEEDED if too many concurrent streams.
238
- * @throws {NcpError} NCP-STREAM-NOT-FOUND if frame.seq > 0 for a stream that was never opened.
239
- * @throws {NcpError} NPS-CLIENT-CONFLICT if the stream_id was already completed (stream-id reuse; see test_cases NCP-S-04).
240
- * @throws {NcpError} NCP-STREAM-SEQ-GAP if sequence number is not expected.
241
- */
242
- receive(frame) {
243
- let stream = this.streams.get(frame.stream_id);
244
- if (!stream) {
245
- if (frame.seq !== 0) {
246
- throw new NcpError(
247
- NCP_ERROR_CODES.NCP_STREAM_NOT_FOUND,
248
- `Unknown stream_id ${frame.stream_id} \u2014 first frame must have seq=0`
249
- );
250
- }
251
- if (this.streams.size >= this.maxConcurrent) {
252
- throw new NcpError(
253
- NCP_ERROR_CODES.NCP_STREAM_LIMIT_EXCEEDED,
254
- `Max concurrent streams (${this.maxConcurrent}) exceeded`
255
- );
256
- }
257
- stream = {
258
- streamId: frame.stream_id,
259
- expectedSeq: 0,
260
- chunks: [],
261
- completed: false
262
- };
263
- this.streams.set(frame.stream_id, stream);
264
- }
265
- if (stream.completed) {
266
- throw new NcpError(
267
- "NPS-CLIENT-CONFLICT",
268
- `Stream ${frame.stream_id} is already completed \u2014 cannot reuse stream_id`
269
- );
270
- }
271
- if (frame.seq !== stream.expectedSeq) {
272
- if (frame.seq < stream.expectedSeq) {
273
- return false;
274
- }
275
- throw new NcpError(
276
- NCP_ERROR_CODES.NCP_STREAM_SEQ_GAP,
277
- `Expected seq ${stream.expectedSeq}, got ${frame.seq} on stream ${frame.stream_id}`
278
- );
279
- }
280
- stream.chunks.push(frame.data);
281
- stream.expectedSeq = frame.seq + 1;
282
- if (frame.error_code) {
283
- stream.completed = true;
284
- stream.errorCode = frame.error_code;
285
- return true;
286
- }
287
- if (frame.is_last) {
288
- stream.completed = true;
289
- return true;
290
- }
291
- return false;
292
- }
293
- /**
294
- * Send a StreamFrame on an outgoing stream, enforcing window-based flow control.
295
- *
296
- * - seq=0 with window_size initialises remainingWindow (no decrement for opening frame).
297
- * - Subsequent sends decrement remainingWindow when flow control is active.
298
- * - Throws NCP-STREAM-WINDOW-OVERFLOW when remainingWindow === 0.
299
- *
300
- * @throws {NcpError} NCP-STREAM-WINDOW-OVERFLOW if window is exhausted.
301
- */
302
- send(frame) {
303
- let out = this.outgoing.get(frame.stream_id);
304
- if (!out) {
305
- out = {
306
- streamId: frame.stream_id,
307
- remainingWindow: void 0,
308
- paused: false
309
- };
310
- this.outgoing.set(frame.stream_id, out);
311
- }
312
- if (frame.seq === 0 && frame.window_size !== void 0) {
313
- out.remainingWindow = frame.window_size;
314
- return;
315
- }
316
- if (out.remainingWindow !== void 0) {
317
- if (out.remainingWindow === 0) {
318
- throw new NcpError(
319
- NCP_ERROR_CODES.NCP_STREAM_WINDOW_OVERFLOW,
320
- `Window exhausted on stream ${frame.stream_id}`
321
- );
322
- }
323
- out.remainingWindow -= 1;
324
- }
325
- }
326
- /**
327
- * Update the send window for a stream.
328
- *
329
- * Called when a reverse-direction StreamFrame arrives with data=[] and window_size set.
330
- * Replaces remainingWindow with new_size. Sets paused=true when new_size === 0.
331
- */
332
- updateWindow(streamId, newSize) {
333
- let out = this.outgoing.get(streamId);
334
- if (!out) {
335
- out = {
336
- streamId,
337
- remainingWindow: newSize,
338
- paused: newSize === 0
339
- };
340
- this.outgoing.set(streamId, out);
341
- return;
342
- }
343
- out.remainingWindow = newSize;
344
- out.paused = newSize === 0;
345
- }
346
- /**
347
- * Returns true when the outgoing stream is paused (window=0 was received).
348
- * Resumes (returns false) once a non-zero window update arrives.
349
- */
350
- isPaused(streamId) {
351
- return this.outgoing.get(streamId)?.paused ?? false;
352
- }
353
- /** Get reassembled data for a completed stream. */
354
- getData(streamId) {
355
- const stream = this.streams.get(streamId);
356
- if (!stream || !stream.completed) return null;
357
- return stream.chunks.flat();
358
- }
359
- /** Get error code if stream terminated with error. */
360
- getError(streamId) {
361
- return this.streams.get(streamId)?.errorCode;
362
- }
363
- /** Number of active (non-completed) streams. */
364
- get activeCount() {
365
- let count = 0;
366
- for (const s of this.streams.values()) {
367
- if (!s.completed) count++;
368
- }
369
- return count;
370
- }
371
- };
372
-
373
- exports.NCP_ERROR_CODES = NCP_ERROR_CODES;
374
- exports.PATCH_FORMAT = PATCH_FORMAT;
375
- exports.StreamManager = StreamManager;
376
- exports.computeAnchorId = computeAnchorId;
377
- exports.isErrorFrame = isErrorFrame;
378
- exports.isValidPatchFormat = isValidPatchFormat;
379
- exports.negotiateEncoding = negotiateEncoding;
380
- exports.negotiateVersion = negotiateVersion;
381
- exports.validateAnchorFrame = validateAnchorFrame;
382
- exports.validateCapsFrame = validateCapsFrame;
383
- exports.validateDiffFrame = validateDiffFrame;
384
- exports.validateDiffSeq = validateDiffSeq;
385
- exports.validateHelloFrame = validateHelloFrame;
386
- exports.validateStreamFrame = validateStreamFrame;
387
- //# sourceMappingURL=index.cjs.map
388
- //# sourceMappingURL=index.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/core/frame-header.ts","../../src/ncp/frames/anchor-frame.ts","../../src/ncp/frames/caps-frame.ts","../../src/ncp/ncp-patch-format.ts","../../src/ncp/frames/diff-frame.ts","../../src/ncp/frames/error-frame.ts","../../src/ncp/frames/hello-frame.ts","../../src/ncp/frames/stream-frame.ts","../../src/ncp/ncp-error-codes.ts","../../src/ncp/handshake.ts","../../src/ncp/stream-manager.ts"],"names":["canonicalizeDefault","createHash"],"mappings":"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","file":"index.cjs","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n// Copyright (c) 2026 LabAcacia / INNO LOTUS PTY LTD\n//\n// NCP Frame Header — Binary parse/write for NPS wire format\n// NPS-1 Neural Communication Protocol §3.1, §3.2\n\n// ---------------------------------------------------------------------------\n// Enums\n// ---------------------------------------------------------------------------\n\n/** Wire encoding tier (NPS-1 §3.2, flags bits 0-1). */\nexport enum EncodingTier {\n /** Tier-1: UTF-8 JSON — development & compatibility. */\n Json = 0x00,\n /** Tier-2: MessagePack binary — production (~60% compression). */\n MsgPack = 0x01,\n // 0x02 = Reserved\n // 0x03 = Reserved\n}\n\n/** Unified frame type namespace for the full NPS suite (NPS-0 §9). */\nexport enum FrameType {\n // NCP 0x01–0x0F\n Anchor = 0x01,\n Diff = 0x02,\n Stream = 0x03,\n Caps = 0x04,\n Align = 0x05, // deprecated — use AlignStream (0x43)\n Hello = 0x06,\n // NWP 0x10–0x1F\n Query = 0x10,\n Action = 0x11,\n Subscribe = 0x12,\n // NIP 0x20–0x2F\n Ident = 0x20,\n Trust = 0x21,\n Revoke = 0x22,\n // NDP 0x30–0x3F\n Announce = 0x30,\n Resolve = 0x31,\n Graph = 0x32,\n // NOP 0x40–0x4F\n Task = 0x40,\n Delegate = 0x41,\n Sync = 0x42,\n AlignStream = 0x43,\n // System 0xF0–0xFF\n Error = 0xfe,\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Default (compact) header size in bytes. */\nexport const DEFAULT_HEADER_SIZE = 4;\n\n/** Extended header size in bytes (EXT=1). */\nexport const EXTENDED_HEADER_SIZE = 8;\n\n/** Maximum payload in default mode (64 KiB − 1). */\nexport const DEFAULT_MAX_PAYLOAD = 0xffff;\n\n/** Maximum payload in extended mode (4 GiB − 1). */\nexport const EXTENDED_MAX_PAYLOAD = 0xffffffff;\n\n// Flag bit positions (NPS-1 §3.2)\nconst TIER_MASK = 0x03; // bits 0-1\nconst FINAL_BIT = 0x04; // bit 2\nconst ENC_BIT = 0x08; // bit 3\nconst RESERVED_MASK = 0x70; // bits 4-6\nconst EXT_BIT = 0x80; // bit 7\n\n// ---------------------------------------------------------------------------\n// Error\n// ---------------------------------------------------------------------------\n\n/** NCP protocol error with machine-readable error code. */\nexport class NcpError extends Error {\n // `code` accepts NcpErrorCode constants (preferred) as well as NPS status\n // strings that are not NCP-prefixed (e.g. \"NPS-CLIENT-CONFLICT\") for cases\n // where the spec delegates to NPS-level codes without assigning an NCP code.\n constructor(\n public readonly code: string,\n message: string,\n ) {\n super(message);\n this.name = \"NcpError\";\n }\n}\n\n// ---------------------------------------------------------------------------\n// Parsed header\n// ---------------------------------------------------------------------------\n\n/** Parsed frame header. */\nexport interface FrameHeader {\n /** Raw frame type byte. */\n frameType: number;\n /** Raw flags byte. */\n flags: number;\n /** Payload length in bytes. */\n payloadLength: number;\n /** Encoding tier extracted from flags bits 0-1. */\n tier: EncodingTier;\n /** True when FINAL flag (bit 2) is set. */\n isFinal: boolean;\n /** True when ENC flag (bit 3) is set. */\n isEncrypted: boolean;\n /** True when EXT flag (bit 7) is set — 8-byte header. */\n isExtended: boolean;\n /** Header size in bytes (4 or 8). */\n headerSize: number;\n}\n\n// ---------------------------------------------------------------------------\n// Parse\n// ---------------------------------------------------------------------------\n\n/**\n * Parse a frame header from the start of the buffer.\n * Reads 2 bytes first to determine EXT, then reads remaining bytes.\n *\n * @throws {NcpError} NCP-FRAME-FLAGS-INVALID if reserved bits 4-6 are non-zero.\n * @throws {NcpError} NCP-FRAME-PARSE-ERROR if buffer is too small.\n * @throws {NcpError} NCP-FRAME-PAYLOAD-TOO-LARGE if opts.max_frame_payload is set and exceeded.\n */\nexport function parseFrameHeader(\n buffer: Uint8Array,\n opts?: { max_frame_payload?: number },\n): FrameHeader {\n if (buffer.length < 2) {\n throw new NcpError(\n \"NCP-FRAME-PARSE-ERROR\",\n `Buffer too small to read frame type and flags: need >= 2 bytes, got ${buffer.length}`,\n );\n }\n\n const frameType = buffer[0]!;\n const flags = buffer[1]!;\n\n // Validate reserved bits (NPS-1 §3.2: bits 4-6 MUST be 0)\n if ((flags & RESERVED_MASK) !== 0) {\n throw new NcpError(\n \"NCP-FRAME-FLAGS-INVALID\",\n \"Reserved flag bits 4-6 must be zero\",\n );\n }\n\n const isExtended = (flags & EXT_BIT) !== 0;\n\n if (isExtended) {\n if (buffer.length < EXTENDED_HEADER_SIZE) {\n throw new NcpError(\n \"NCP-FRAME-PARSE-ERROR\",\n `Buffer too small for extended header: need ${EXTENDED_HEADER_SIZE} bytes, got ${buffer.length}`,\n );\n }\n\n const view = new DataView(\n buffer.buffer,\n buffer.byteOffset,\n buffer.byteLength,\n );\n const payloadLength = view.getUint32(2, false); // big-endian\n\n if (\n opts?.max_frame_payload !== undefined &&\n payloadLength > opts.max_frame_payload\n ) {\n throw new NcpError(\n \"NCP-FRAME-PAYLOAD-TOO-LARGE\",\n `Payload length ${payloadLength} exceeds max_frame_payload ${opts.max_frame_payload}`,\n );\n }\n\n return {\n frameType,\n flags,\n payloadLength,\n tier: (flags & TIER_MASK) as EncodingTier,\n isFinal: (flags & FINAL_BIT) !== 0,\n isEncrypted: (flags & ENC_BIT) !== 0,\n isExtended: true,\n headerSize: EXTENDED_HEADER_SIZE,\n };\n }\n\n // Default 4-byte header\n if (buffer.length < DEFAULT_HEADER_SIZE) {\n throw new NcpError(\n \"NCP-FRAME-PARSE-ERROR\",\n `Buffer too small for header: need ${DEFAULT_HEADER_SIZE} bytes, got ${buffer.length}`,\n );\n }\n\n const view = new DataView(\n buffer.buffer,\n buffer.byteOffset,\n buffer.byteLength,\n );\n const payloadLength = view.getUint16(2, false); // big-endian\n\n if (\n opts?.max_frame_payload !== undefined &&\n payloadLength > opts.max_frame_payload\n ) {\n throw new NcpError(\n \"NCP-FRAME-PAYLOAD-TOO-LARGE\",\n `Payload length ${payloadLength} exceeds max_frame_payload ${opts.max_frame_payload}`,\n );\n }\n\n return {\n frameType,\n flags,\n payloadLength,\n tier: (flags & TIER_MASK) as EncodingTier,\n isFinal: (flags & FINAL_BIT) !== 0,\n isEncrypted: (flags & ENC_BIT) !== 0,\n isExtended: false,\n headerSize: DEFAULT_HEADER_SIZE,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Write\n// ---------------------------------------------------------------------------\n\n/**\n * Write a frame header into the buffer.\n * @returns Number of bytes written (4 or 8).\n * @throws {Error} if buffer is too small.\n */\nexport function writeFrameHeader(\n header: FrameHeader,\n buffer: Uint8Array,\n): number {\n const size = header.isExtended ? EXTENDED_HEADER_SIZE : DEFAULT_HEADER_SIZE;\n if (buffer.length < size) {\n throw new Error(\n `Destination buffer must be at least ${size} bytes, got ${buffer.length}`,\n );\n }\n\n buffer[0] = header.frameType;\n buffer[1] = header.flags;\n\n const view = new DataView(\n buffer.buffer,\n buffer.byteOffset,\n buffer.byteLength,\n );\n\n if (header.isExtended) {\n view.setUint32(2, header.payloadLength, false); // big-endian\n buffer[6] = 0; // reserved\n buffer[7] = 0; // reserved\n } else {\n view.setUint16(2, header.payloadLength, false); // big-endian\n }\n\n return size;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Build a flags byte from individual options. */\nexport function buildFlags(options: {\n tier?: EncodingTier;\n final?: boolean;\n encrypted?: boolean;\n extended?: boolean;\n}): number {\n let flags = (options.tier ?? EncodingTier.Json) & TIER_MASK;\n if (options.final) flags |= FINAL_BIT;\n if (options.encrypted) flags |= ENC_BIT;\n if (options.extended) flags |= EXT_BIT;\n return flags;\n}\n","// SPDX-License-Identifier: Apache-2.0\n// Copyright (c) 2026 LabAcacia / INNO LOTUS PTY LTD\n//\n// AnchorFrame (0x01) — Schema anchor for global reference\n// NPS-1 §4.1\n\nimport { createHash } from \"node:crypto\";\n// canonicalize ships CJS with TS `export default` — NodeNext resolves as namespace\nimport canonicalizeDefault from \"canonicalize\";\nconst canonicalize = canonicalizeDefault as unknown as (input: unknown) => string | undefined;\nimport { NcpError } from \"../../core/frame-header.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nconst VALID_FIELD_TYPES = [\n \"string\", \"uint64\", \"int64\", \"decimal\", \"bool\",\n \"timestamp\", \"bytes\", \"object\", \"array\",\n] as const;\n\nexport type SchemaFieldType = (typeof VALID_FIELD_TYPES)[number];\n\nexport interface SchemaField {\n name: string;\n type: string;\n semantic?: string;\n nullable?: boolean;\n}\n\nexport interface FrameSchema {\n fields: SchemaField[];\n}\n\nexport interface AnchorFrame {\n frame: string;\n anchor_id: string;\n schema: FrameSchema;\n ttl?: number;\n}\n\n// ---------------------------------------------------------------------------\n// anchor_id computation (RFC 8785 JCS + SHA-256)\n// ---------------------------------------------------------------------------\n\n/**\n * Compute anchor_id from schema using RFC 8785 JCS canonicalization + SHA-256.\n * Format: \"sha256:{64 lowercase hex chars}\"\n */\nexport function computeAnchorId(schema: FrameSchema): string {\n const canonical = canonicalize(schema);\n if (!canonical) {\n throw new NcpError(\"NCP-ANCHOR-SCHEMA-INVALID\", \"Schema cannot be canonicalized\");\n }\n const hash = createHash(\"sha256\").update(canonical).digest(\"hex\");\n return `sha256:${hash}`;\n}\n\n// ---------------------------------------------------------------------------\n// Validation\n// ---------------------------------------------------------------------------\n\n/**\n * Validate an AnchorFrame.\n * @throws {NcpError} NCP-ANCHOR-SCHEMA-INVALID if anchor_id doesn't match or schema is invalid.\n */\nexport function validateAnchorFrame(frame: AnchorFrame): void {\n // Validate schema field types\n for (const field of frame.schema.fields) {\n if (!VALID_FIELD_TYPES.includes(field.type as SchemaFieldType)) {\n throw new NcpError(\n \"NCP-ANCHOR-SCHEMA-INVALID\",\n `Unsupported field type \"${field.type}\" for field \"${field.name}\". ` +\n `Valid types: ${VALID_FIELD_TYPES.join(\", \")}`,\n );\n }\n }\n\n // Validate anchor_id matches computed hash\n const expected = computeAnchorId(frame.schema);\n if (frame.anchor_id !== expected) {\n throw new NcpError(\n \"NCP-ANCHOR-SCHEMA-INVALID\",\n `anchor_id mismatch: expected ${expected}, got ${frame.anchor_id}`,\n );\n }\n}\n","// SPDX-License-Identifier: Apache-2.0\n// Copyright (c) 2026 LabAcacia / INNO LOTUS PTY LTD\n//\n// CapsFrame (0x04) — Capsule response envelope\n// NPS-1 §4.4\n\nimport { NcpError } from \"../../core/frame-header.js\";\nimport {\n computeAnchorId,\n type AnchorFrame,\n type FrameSchema,\n} from \"./anchor-frame.js\";\n\nexport interface CapsFrameInlineAnchor {\n anchor_id: string;\n schema: FrameSchema;\n ttl?: number;\n}\n\nexport interface CapsFrame {\n frame: string;\n anchor_ref: string;\n count: number;\n data: unknown[];\n next_cursor?: string | null;\n token_est?: number;\n tokenizer_used?: string;\n cached?: boolean;\n inline_anchor?: CapsFrameInlineAnchor;\n}\n\n/**\n * Validate a CapsFrame.\n *\n * Checks:\n * - count matches data.length (NPS-CLIENT-BAD-FRAME)\n * - if inline_anchor present, recomputes anchor_id and validates match (NCP-ANCHOR-SCHEMA-INVALID)\n *\n * @throws {NcpError} NPS-CLIENT-BAD-FRAME if count doesn't match data length.\n * @throws {NcpError} NCP-ANCHOR-SCHEMA-INVALID if inline_anchor.anchor_id doesn't match schema.\n */\nexport function validateCapsFrame(frame: CapsFrame): void {\n if (frame.count !== frame.data.length) {\n throw new NcpError(\n \"NPS-CLIENT-BAD-FRAME\",\n `CapsFrame count mismatch: count=${frame.count}, data.length=${frame.data.length}`,\n );\n }\n\n if (frame.inline_anchor !== undefined) {\n const computed = computeAnchorId(frame.inline_anchor.schema);\n if (frame.inline_anchor.anchor_id !== computed) {\n throw new NcpError(\n \"NCP-ANCHOR-SCHEMA-INVALID\",\n `inline_anchor anchor_id mismatch: expected ${computed}, got ${frame.inline_anchor.anchor_id}`,\n );\n }\n }\n}\n","// SPDX-License-Identifier: Apache-2.0\n// Copyright (c) 2026 LabAcacia / INNO LOTUS PTY LTD\n//\n// NCP Patch Format — DiffFrame patch encoding types\n// NPS-1 §4.2\n\nexport const PATCH_FORMAT = {\n JSON_PATCH: \"json_patch\",\n BINARY_BITSET: \"binary_bitset\",\n} as const;\n\nexport type PatchFormat = typeof PATCH_FORMAT[keyof typeof PATCH_FORMAT];\n\nexport function isValidPatchFormat(v: unknown): v is PatchFormat {\n return v === PATCH_FORMAT.JSON_PATCH || v === PATCH_FORMAT.BINARY_BITSET;\n}\n","// SPDX-License-Identifier: Apache-2.0\n// Copyright (c) 2026 LabAcacia / INNO LOTUS PTY LTD\n//\n// DiffFrame (0x02) — Incremental data patch\n// NPS-1 §4.2\n\nimport { NcpError, EncodingTier } from \"../../core/frame-header.js\";\nimport { isValidPatchFormat, type PatchFormat } from \"../ncp-patch-format.js\";\n\nexport interface JsonPatchOperation {\n op: \"add\" | \"remove\" | \"replace\" | \"move\" | \"copy\" | \"test\";\n path: string;\n value?: unknown;\n from?: string;\n}\n\nexport interface DiffFrame {\n frame: string;\n anchor_ref: string;\n base_seq: number;\n patch_format?: PatchFormat;\n patch: JsonPatchOperation[] | Uint8Array;\n entity_id?: string;\n}\n\n/**\n * Validate DiffFrame base_seq against current sequence.\n * @throws {NcpError} NCP-STREAM-SEQ-GAP if sequences don't match.\n */\nexport function validateDiffSeq(frame: DiffFrame, currentSeq: number): void {\n if (frame.base_seq !== currentSeq) {\n throw new NcpError(\n \"NCP-STREAM-SEQ-GAP\",\n `DiffFrame base_seq=${frame.base_seq} does not match current seq=${currentSeq}`,\n );\n }\n}\n\n/**\n * Validate DiffFrame patch_format against the encoding tier.\n *\n * binary_bitset is only supported on Tier-2 (MsgPack) frames.\n * Unknown patch_format values are also rejected.\n *\n * @throws {NcpError} NCP-DIFF-FORMAT-UNSUPPORTED if binary_bitset on non-Tier-2,\n * or if patch_format is an unknown value.\n */\nexport function validateDiffFrame(\n frame: DiffFrame,\n encodingTier: EncodingTier | number,\n): void {\n const fmt = frame.patch_format;\n\n // Unknown patch_format\n if (fmt !== undefined && !isValidPatchFormat(fmt)) {\n throw new NcpError(\n \"NCP-DIFF-FORMAT-UNSUPPORTED\",\n `Unknown patch_format \"${String(fmt)}\"`,\n );\n }\n\n // binary_bitset requires Tier-2 MsgPack\n if (fmt === \"binary_bitset\" && encodingTier !== EncodingTier.MsgPack) {\n throw new NcpError(\n \"NCP-DIFF-FORMAT-UNSUPPORTED\",\n \"patch_format=binary_bitset requires Tier-2 MsgPack encoding\",\n );\n }\n}\n","// SPDX-License-Identifier: Apache-2.0\n// Copyright (c) 2026 LabAcacia / INNO LOTUS PTY LTD\n//\n// ErrorFrame (0xFE) — Unified error frame for all NPS protocol layers\n// NPS-1 §4.7\n\n/** Unified error frame shared across all NPS protocol layers. */\nexport interface ErrorFrame {\n /** Fixed value \"0xFE\". */\n frame: string;\n /** NPS status code, e.g. \"NPS-CLIENT-NOT-FOUND\". */\n status: string;\n /** Protocol-level error code, e.g. \"NCP-ANCHOR-NOT-FOUND\". */\n error: string;\n /** Human-readable error description. */\n message?: string;\n /** Structured error details (e.g. anchor_ref, stream_id). */\n details?: Record<string, unknown>;\n}\n\n/** Type guard for ErrorFrame. */\nexport function isErrorFrame(obj: unknown): obj is ErrorFrame {\n if (typeof obj !== \"object\" || obj === null) return false;\n const o = obj as Record<string, unknown>;\n return o.frame === \"0xFE\" && typeof o.status === \"string\" && typeof o.error === \"string\";\n}\n","// SPDX-License-Identifier: Apache-2.0\n// Copyright (c) 2026 LabAcacia / INNO LOTUS PTY LTD\n//\n// HelloFrame (0x06) — Native-mode client handshake\n// NPS-1 §4.6\n\nimport { NcpError } from \"../../core/frame-header.js\";\n\nexport interface HelloFrame {\n frame: string;\n nps_version: string;\n min_version?: string;\n supported_encodings: string[];\n supported_protocols: string[];\n agent_id?: string;\n max_frame_payload?: number;\n ext_support?: boolean;\n max_concurrent_streams?: number;\n e2e_enc_algorithms?: string[];\n}\n\n/**\n * Validate a HelloFrame.\n *\n * Required fields: nps_version, supported_encodings (non-empty), supported_protocols (non-empty).\n *\n * @throws {NcpError} NPS-CLIENT-BAD-FRAME if any required field is missing or empty.\n */\nexport function validateHelloFrame(frame: HelloFrame): void {\n if (!frame.nps_version) {\n throw new NcpError(\n \"NPS-CLIENT-BAD-FRAME\",\n \"HelloFrame missing required field: nps_version\",\n );\n }\n\n if (!frame.supported_encodings || frame.supported_encodings.length === 0) {\n throw new NcpError(\n \"NPS-CLIENT-BAD-FRAME\",\n \"HelloFrame missing required field: supported_encodings (must be non-empty)\",\n );\n }\n\n if (!frame.supported_protocols || frame.supported_protocols.length === 0) {\n throw new NcpError(\n \"NPS-CLIENT-BAD-FRAME\",\n \"HelloFrame missing required field: supported_protocols (must be non-empty)\",\n );\n }\n}\n","// SPDX-License-Identifier: Apache-2.0\n// Copyright (c) 2026 LabAcacia / INNO LOTUS PTY LTD\n//\n// StreamFrame (0x03) — Streaming data chunks with flow control\n// NPS-1 §4.3\n\nimport { NcpError } from \"../../core/frame-header.js\";\n\nexport interface StreamFrame {\n frame: string;\n stream_id: string;\n seq: number;\n is_last: boolean;\n anchor_ref?: string;\n data: unknown[];\n window_size?: number;\n error_code?: string;\n}\n\n// UUID v4 format: xxxxxxxx-xxxx-4xxx-[89ab]xxx-xxxxxxxxxxxx\nconst UUID_V4_RE =\n /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\n/**\n * Validate stream_id is a valid UUID v4.\n * @throws {NcpError} NPS-CLIENT-BAD-FRAME if stream_id is not a valid UUID v4.\n */\nexport function validateStreamFrame(frame: StreamFrame): void {\n if (!UUID_V4_RE.test(frame.stream_id)) {\n throw new NcpError(\n \"NPS-CLIENT-BAD-FRAME\",\n `stream_id \"${frame.stream_id}\" is not a valid UUID v4`,\n );\n }\n}\n","// SPDX-License-Identifier: Apache-2.0\n// Copyright (c) 2026 LabAcacia / INNO LOTUS PTY LTD\n//\n// NCP Error Codes — All v0.4 protocol error codes\n// NPS-1 §6 + §7.4\n//\n// Implementation-only codes (NCP_FRAME_PARSE_ERROR, NCP_FRAME_INCOMPLETE) cover\n// wire-layer parse failures not in spec §6. See test/ncp_test_results.md spec\n// question 2 for the proposal to register them upstream.\n\nexport const NCP_ERROR_CODES = {\n // Implementation-only codes (not in spec §6 — see test_results.md spec question 2)\n NCP_FRAME_PARSE_ERROR: \"NCP-FRAME-PARSE-ERROR\",\n NCP_FRAME_INCOMPLETE: \"NCP-FRAME-INCOMPLETE\",\n // Spec-defined codes\n NCP_FRAME_UNKNOWN_TYPE: \"NCP-FRAME-UNKNOWN-TYPE\",\n NCP_FRAME_PAYLOAD_TOO_LARGE: \"NCP-FRAME-PAYLOAD-TOO-LARGE\",\n NCP_FRAME_FLAGS_INVALID: \"NCP-FRAME-FLAGS-INVALID\",\n NCP_ANCHOR_NOT_FOUND: \"NCP-ANCHOR-NOT-FOUND\",\n NCP_ANCHOR_SCHEMA_INVALID: \"NCP-ANCHOR-SCHEMA-INVALID\",\n NCP_ANCHOR_ID_MISMATCH: \"NCP-ANCHOR-ID-MISMATCH\",\n NCP_ANCHOR_STALE: \"NCP-ANCHOR-STALE\",\n NCP_STREAM_SEQ_GAP: \"NCP-STREAM-SEQ-GAP\",\n NCP_STREAM_NOT_FOUND: \"NCP-STREAM-NOT-FOUND\",\n NCP_STREAM_LIMIT_EXCEEDED: \"NCP-STREAM-LIMIT-EXCEEDED\",\n NCP_STREAM_WINDOW_OVERFLOW: \"NCP-STREAM-WINDOW-OVERFLOW\",\n NCP_ENCODING_UNSUPPORTED: \"NCP-ENCODING-UNSUPPORTED\",\n NCP_DIFF_FORMAT_UNSUPPORTED: \"NCP-DIFF-FORMAT-UNSUPPORTED\",\n NCP_VERSION_INCOMPATIBLE: \"NCP-VERSION-INCOMPATIBLE\",\n NCP_ENC_NOT_NEGOTIATED: \"NCP-ENC-NOT-NEGOTIATED\",\n NCP_ENC_AUTH_FAILED: \"NCP-ENC-AUTH-FAILED\",\n} as const;\n\nexport type NcpErrorCode = typeof NCP_ERROR_CODES[keyof typeof NCP_ERROR_CODES];\n","// SPDX-License-Identifier: Apache-2.0\n// Copyright (c) 2026 LabAcacia / INNO LOTUS PTY LTD\n//\n// Handshake — Version negotiation and encoding negotiation\n// NPS-1 §2.6\n\n/**\n * Parse a \"major.minor\" (optionally \"major.minor.patch\") version string into a\n * tuple of numeric components. Invalid parts become NaN which makes subsequent\n * comparisons return false in both directions (safe failure).\n */\nfunction parseVersion(v: string): number[] {\n return v.split(\".\").map((p) => Number.parseInt(p, 10));\n}\n\n/**\n * Numeric component-wise comparison of two version strings.\n * Returns negative if a < b, zero if equal, positive if a > b.\n * Avoids the lexicographic pitfall where \"0.9\" > \"0.10\".\n */\nfunction compareVersions(a: string, b: string): number {\n const partsA = parseVersion(a);\n const partsB = parseVersion(b);\n const len = Math.max(partsA.length, partsB.length);\n for (let i = 0; i < len; i += 1) {\n const x = partsA[i] ?? 0;\n const y = partsB[i] ?? 0;\n if (x !== y) return x - y;\n }\n return 0;\n}\n\n/**\n * Negotiate the session NPS version between client and server.\n *\n * Session version = numeric min of client.nps_version and server.nps_version\n * (component-wise — \"0.9\" < \"0.10\" < \"1.0\").\n * If the effective client minimum (min_version ?? nps_version) > server.nps_version,\n * the versions are incompatible.\n *\n * Spec: NPS-1 §2.6\n */\nexport function negotiateVersion(\n client: { nps_version: string; min_version?: string },\n server: { nps_version: string },\n): { session_version: string; compatible: boolean; error_code?: string } {\n const clientMin = client.min_version ?? client.nps_version;\n const serverVersion = server.nps_version;\n\n if (compareVersions(clientMin, serverVersion) > 0) {\n return {\n session_version: serverVersion,\n compatible: false,\n error_code: \"NCP-VERSION-INCOMPATIBLE\",\n };\n }\n\n // Session version = component-wise min of client.nps_version and server.nps_version\n const sessionVersion =\n compareVersions(client.nps_version, serverVersion) <= 0\n ? client.nps_version\n : serverVersion;\n\n return { session_version: sessionVersion, compatible: true };\n}\n\n/**\n * Negotiate the encoding between client and server preferred lists.\n *\n * Returns the first mutually supported encoding, preferring \"msgpack\" over \"json\".\n * Returns null if there is no intersection.\n */\nexport function negotiateEncoding(\n client: string[],\n server: string[],\n): { encoding: string | null } {\n const serverSet = new Set(server);\n\n // Prefer msgpack over json (and over any other encoding)\n if (client.includes(\"msgpack\") && serverSet.has(\"msgpack\")) {\n return { encoding: \"msgpack\" };\n }\n if (client.includes(\"json\") && serverSet.has(\"json\")) {\n return { encoding: \"json\" };\n }\n\n // Fall back to first intersection in client-preference order\n for (const enc of client) {\n if (serverSet.has(enc)) {\n return { encoding: enc };\n }\n }\n\n return { encoding: null };\n}\n","// SPDX-License-Identifier: Apache-2.0\n// Copyright (c) 2026 LabAcacia / INNO LOTUS PTY LTD\n//\n// StreamManager — Concurrent stream tracking, sequence validation, flow control\n// NPS-1 §4.3, §7.3\n\nimport { NcpError } from \"../core/frame-header.js\";\nimport { NCP_ERROR_CODES } from \"./ncp-error-codes.js\";\nimport type { StreamFrame } from \"./frames/stream-frame.js\";\n\ninterface ActiveStream {\n streamId: string;\n expectedSeq: number;\n chunks: unknown[][];\n completed: boolean;\n errorCode?: string;\n}\n\ninterface OutgoingStream {\n streamId: string;\n remainingWindow: number | undefined; // undefined = no flow control\n paused: boolean;\n}\n\n/**\n * Manages concurrent StreamFrame streams.\n *\n * - Tracks active streams by stream_id\n * - Validates sequential seq numbers\n * - Enforces max concurrent stream limit (NPS-1 §7.3, default 32)\n * - Detects early termination via error_code\n * - Enforces window-based flow control on outgoing streams (NCP-S-07–11)\n */\nexport class StreamManager {\n private readonly streams = new Map<string, ActiveStream>();\n private readonly outgoing = new Map<string, OutgoingStream>();\n private readonly maxConcurrent: number;\n\n constructor(options?: { maxConcurrent?: number }) {\n this.maxConcurrent = options?.maxConcurrent ?? 32;\n }\n\n /**\n * Receive a StreamFrame chunk.\n *\n * @returns true if stream is complete (is_last=true or error_code set).\n * @throws {NcpError} NCP-STREAM-LIMIT-EXCEEDED if too many concurrent streams.\n * @throws {NcpError} NCP-STREAM-NOT-FOUND if frame.seq > 0 for a stream that was never opened.\n * @throws {NcpError} NPS-CLIENT-CONFLICT if the stream_id was already completed (stream-id reuse; see test_cases NCP-S-04).\n * @throws {NcpError} NCP-STREAM-SEQ-GAP if sequence number is not expected.\n */\n receive(frame: StreamFrame): boolean {\n let stream = this.streams.get(frame.stream_id);\n\n if (!stream) {\n // A stream is opened only by seq=0. Any other seq on an unknown stream_id\n // means the opener was never seen (NCP-S-13: unknown stream_id).\n if (frame.seq !== 0) {\n throw new NcpError(\n NCP_ERROR_CODES.NCP_STREAM_NOT_FOUND,\n `Unknown stream_id ${frame.stream_id} — first frame must have seq=0`,\n );\n }\n\n // New stream — check concurrent limit\n if (this.streams.size >= this.maxConcurrent) {\n throw new NcpError(\n NCP_ERROR_CODES.NCP_STREAM_LIMIT_EXCEEDED,\n `Max concurrent streams (${this.maxConcurrent}) exceeded`,\n );\n }\n\n stream = {\n streamId: frame.stream_id,\n expectedSeq: 0,\n chunks: [],\n completed: false,\n };\n this.streams.set(frame.stream_id, stream);\n }\n\n // Reject writes to completed streams. Per test_cases NCP-S-04, the spec does\n // not assign a dedicated NCP code for stream-id reuse; interim mapping uses\n // the NPS-level NPS-CLIENT-CONFLICT until a spec-side code is added.\n if (stream.completed) {\n throw new NcpError(\n \"NPS-CLIENT-CONFLICT\",\n `Stream ${frame.stream_id} is already completed — cannot reuse stream_id`,\n );\n }\n\n // Sequence validation\n if (frame.seq !== stream.expectedSeq) {\n // Duplicate detection — same seq as last accepted\n if (frame.seq < stream.expectedSeq) {\n // Ignore duplicate (idempotent)\n return false;\n }\n throw new NcpError(\n NCP_ERROR_CODES.NCP_STREAM_SEQ_GAP,\n `Expected seq ${stream.expectedSeq}, got ${frame.seq} on stream ${frame.stream_id}`,\n );\n }\n\n stream.chunks.push(frame.data);\n stream.expectedSeq = frame.seq + 1;\n\n // Early termination via error_code\n if (frame.error_code) {\n stream.completed = true;\n stream.errorCode = frame.error_code;\n return true;\n }\n\n // Normal completion\n if (frame.is_last) {\n stream.completed = true;\n return true;\n }\n\n return false;\n }\n\n /**\n * Send a StreamFrame on an outgoing stream, enforcing window-based flow control.\n *\n * - seq=0 with window_size initialises remainingWindow (no decrement for opening frame).\n * - Subsequent sends decrement remainingWindow when flow control is active.\n * - Throws NCP-STREAM-WINDOW-OVERFLOW when remainingWindow === 0.\n *\n * @throws {NcpError} NCP-STREAM-WINDOW-OVERFLOW if window is exhausted.\n */\n send(frame: StreamFrame): void {\n let out = this.outgoing.get(frame.stream_id);\n\n if (!out) {\n out = {\n streamId: frame.stream_id,\n remainingWindow: undefined,\n paused: false,\n };\n this.outgoing.set(frame.stream_id, out);\n }\n\n // Opening frame: initialise window from window_size if provided.\n if (frame.seq === 0 && frame.window_size !== undefined) {\n out.remainingWindow = frame.window_size;\n return; // opening frame does not consume a window slot\n }\n\n // Flow control check for subsequent frames.\n if (out.remainingWindow !== undefined) {\n if (out.remainingWindow === 0) {\n throw new NcpError(\n NCP_ERROR_CODES.NCP_STREAM_WINDOW_OVERFLOW,\n `Window exhausted on stream ${frame.stream_id}`,\n );\n }\n out.remainingWindow -= 1;\n }\n }\n\n /**\n * Update the send window for a stream.\n *\n * Called when a reverse-direction StreamFrame arrives with data=[] and window_size set.\n * Replaces remainingWindow with new_size. Sets paused=true when new_size === 0.\n */\n updateWindow(streamId: string, newSize: number): void {\n let out = this.outgoing.get(streamId);\n if (!out) {\n out = {\n streamId,\n remainingWindow: newSize,\n paused: newSize === 0,\n };\n this.outgoing.set(streamId, out);\n return;\n }\n out.remainingWindow = newSize;\n out.paused = newSize === 0;\n }\n\n /**\n * Returns true when the outgoing stream is paused (window=0 was received).\n * Resumes (returns false) once a non-zero window update arrives.\n */\n isPaused(streamId: string): boolean {\n return this.outgoing.get(streamId)?.paused ?? false;\n }\n\n /** Get reassembled data for a completed stream. */\n getData(streamId: string): unknown[] | null {\n const stream = this.streams.get(streamId);\n if (!stream || !stream.completed) return null;\n return stream.chunks.flat();\n }\n\n /** Get error code if stream terminated with error. */\n getError(streamId: string): string | undefined {\n return this.streams.get(streamId)?.errorCode;\n }\n\n /** Number of active (non-completed) streams. */\n get activeCount(): number {\n let count = 0;\n for (const s of this.streams.values()) {\n if (!s.completed) count++;\n }\n return count;\n }\n}\n"]}