@intutic/proxy 1.10.0 → 1.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +16 -2
  2. package/bin/proxy.js +72 -4
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -30,7 +30,21 @@ The proxy processes every request through an ordered plugin chain:
30
30
 
31
31
  ### Model Selection
32
32
 
33
- - **Thompson Sampling bandit**: Selects the optimal model per-task based on historical cost, latency, and quality signals
33
+ - **Thompson Sampling bandit**: selects a model per task cell from observed
34
+ **availability and latency**.
35
+
36
+ Two corrections to what this said before. It claimed selection was based on
37
+ "historical cost, latency, and quality signals". `RewardSignals`
38
+ (`src/routing/reward.rs:32`) carries `upstream_ok`, `latency_ms`,
39
+ `token_anomaly`, `raw_cost_usd` and `actual_cost_usd` — and the cost pair does
40
+ not move the reward: `cheaper_routed_model_earns_no_bonus` asserts a cheaper
41
+ route earns nothing, deliberately. There is **no quality signal at all**: a
42
+ cheap model returning a confidently wrong answer, quickly, scores a perfect
43
+ 1.0.
44
+
45
+ So the router today optimises for *responding*, not for responding *well* or
46
+ *cheaply*. Do not describe it as saving money or preserving quality until
47
+ there is a measurement behind either word.
34
48
 
35
49
  ### Network Enforcement
36
50
 
@@ -45,7 +59,7 @@ The proxy processes every request through an ordered plugin chain:
45
59
  - **DLP scanner**: Pattern-based and entropy-based secret detection
46
60
  - **Code skeleton extraction**: Uses `tree-sitter` and `syn` for AST-level code analysis
47
61
  - **Token counting**: Accurate pre-flight token estimation via `tiktoken-rs`
48
- - **Telemetry**: OpenTelemetry OTLP export for traces, metrics, and logs
62
+ - **Telemetry**: OpenTelemetry OTLP export for traces and metrics — a `SpanExporter` and `SdkMeterProvider` ship in `src/main.rs`, with the exported instrument set declared in `src/metrics.rs` (snip compaction histograms, egress counters, policy refusals). Still no logs pipeline (TD-161's resolution covers the metrics half only)
49
63
 
50
64
  ## Dependencies
51
65
 
package/bin/proxy.js CHANGED
@@ -18,6 +18,7 @@ import { spawn } from 'child_process';
18
18
  import path from 'path';
19
19
  import fs from 'fs';
20
20
  import os from 'os';
21
+ import crypto from 'crypto';
21
22
  import { createRequire } from 'module';
22
23
  import { fileURLToPath } from 'url';
23
24
 
@@ -28,9 +29,15 @@ const { version } = require('../package.json');
28
29
  const isWindows = process.platform === 'win32';
29
30
  const binaryName = isWindows ? 'intutic-proxy.exe' : 'intutic-proxy';
30
31
 
31
- /** Asset names MUST match publish.yml's build-rust-proxy matrix artifact_name. */
32
- function resolveAssetName() {
33
- const { platform, arch } = process;
32
+ /**
33
+ * Asset names MUST match publish.yml's build-rust-proxy matrix
34
+ * artifact_name — every value returned here has a live release asset
35
+ * verified against it (`gh release view` on v1.6.0 through the current
36
+ * release). `platform`/`arch` are parameters, not read from `process`
37
+ * directly, so proxy.test.mjs can exercise every platform/arch combination
38
+ * without needing to stub global process state.
39
+ */
40
+ export function resolveAssetName(platform = process.platform, arch = process.arch) {
34
41
  if (platform === 'darwin') {
35
42
  if (arch === 'arm64') return 'intutic-proxy-darwin-arm64';
36
43
  if (arch === 'x64') return 'intutic-proxy-darwin-x64';
@@ -110,6 +117,57 @@ function pruneLegacyCache() {
110
117
  }
111
118
  }
112
119
 
120
+ /** `sha256:<hex>` of `buffer` — the same shape publish.yml's checksums.json values use. */
121
+ export function sha256Hex(buffer) {
122
+ return 'sha256:' + crypto.createHash('sha256').update(buffer).digest('hex');
123
+ }
124
+
125
+ /** Parses a checksums.json body into a plain name→digest map. Throws on any other shape. */
126
+ export function parseChecksums(text) {
127
+ const data = JSON.parse(text);
128
+ if (typeof data !== 'object' || data === null || Array.isArray(data)) {
129
+ throw new Error('checksums.json did not parse to an object');
130
+ }
131
+ return data;
132
+ }
133
+
134
+ /**
135
+ * Throws unless `buffer`'s digest matches `checksums[assetName]`. A missing entry
136
+ * fails the same as a mismatch — an asset absent from the manifest is exactly as
137
+ * unverifiable as one that fails it, and treating it as "nothing to check" would
138
+ * make the whole guard optional for any release that forgot to list an asset.
139
+ */
140
+ export function verifyChecksum(buffer, checksums, assetName) {
141
+ const expected = checksums[assetName];
142
+ const actual = sha256Hex(buffer);
143
+ if (!expected) {
144
+ throw new Error(
145
+ `checksums.json has no entry for ${assetName} — refusing to install an unverified binary.`
146
+ );
147
+ }
148
+ if (actual !== expected) {
149
+ throw new Error(
150
+ `Checksum mismatch for ${assetName}:\n` +
151
+ ` expected: ${expected}\n` +
152
+ ` actual: ${actual}\n` +
153
+ `This could mean a corrupted download or a tampered release asset — refusing to install it. Do not retry blindly.`
154
+ );
155
+ }
156
+ }
157
+
158
+ async function fetchChecksums(assetVersion) {
159
+ const url = `https://github.com/intutic/intutic/releases/download/v${assetVersion}/checksums.json`;
160
+ const response = await fetch(url);
161
+ if (!response.ok) {
162
+ throw new Error(
163
+ `Failed to download checksums.json (HTTP ${response.status} ${response.statusText}).\n` +
164
+ ` URL: ${url}\n` +
165
+ `Refusing to install an unverified binary. Build from source instead: cargo build --release --package intutic-proxy`
166
+ );
167
+ }
168
+ return parseChecksums(await response.text());
169
+ }
170
+
113
171
  async function downloadBinary(destPath) {
114
172
  const assetName = resolveAssetName();
115
173
  if (!assetName) {
@@ -132,6 +190,11 @@ async function downloadBinary(destPath) {
132
190
  }
133
191
 
134
192
  const buffer = Buffer.from(await response.arrayBuffer());
193
+
194
+ process.stderr.write(`intutic-proxy: verifying checksum…\n`);
195
+ const checksums = await fetchChecksums(version);
196
+ verifyChecksum(buffer, checksums, assetName);
197
+
135
198
  fs.mkdirSync(path.dirname(destPath), { recursive: true });
136
199
 
137
200
  // Write to a temp path and rename, so a partial download from an interrupted
@@ -183,4 +246,9 @@ async function main() {
183
246
  });
184
247
  }
185
248
 
186
- main();
249
+ // Guarded so a test can `import` this module for its pure functions
250
+ // (sha256Hex, parseChecksums, verifyChecksum) without triggering a real
251
+ // download-and-spawn as a side effect of the import.
252
+ if (import.meta.url === `file://${process.argv[1]}`) {
253
+ main();
254
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intutic/proxy",
3
- "version": "1.10.0",
3
+ "version": "1.10.1",
4
4
  "description": "Native high-performance AI agent governance proxy binary",
5
5
  "type": "module",
6
6
  "main": "bin/proxy.js",