sigrank 0.16.0 → 0.17.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.
- package/README.md +122 -82
- package/adapters.mjs +493 -238
- package/badges.mjs +157 -56
- package/cascade.mjs +132 -70
- package/cli.mjs +1311 -893
- package/connect.mjs +2 -2
- package/index.mjs +277 -60
- package/keystore.mjs +64 -44
- package/narrate.mjs +34 -22
- package/package.json +2 -1
- package/preflight.mjs +101 -29
- package/sign.mjs +46 -27
- package/submit.mjs +137 -71
- package/tokenpull.mjs +325 -188
- package/tools.mjs +1717 -955
- package/tui.mjs +1860 -1081
package/submit.mjs
CHANGED
|
@@ -9,55 +9,78 @@
|
|
|
9
9
|
// TODO(tokenpull): emit real sessions_count / turns_total / active_minutes per window so
|
|
10
10
|
// the display signa_rate reflects true session shape (the ranked Υ is already exact).
|
|
11
11
|
|
|
12
|
-
import { snapshotHash, signPayload } from
|
|
13
|
-
import { preflight } from
|
|
12
|
+
import { snapshotHash, signPayload } from "./sign.mjs";
|
|
13
|
+
import { preflight } from "./preflight.mjs";
|
|
14
14
|
|
|
15
|
-
const WINDOW_TYPE = {
|
|
16
|
-
const WINDOW_SPAN_DAYS = {
|
|
17
|
-
const PLATFORM_ENUM = new Set([
|
|
15
|
+
const WINDOW_TYPE = { "7d": "7d", "30d": "30d", "90d": "90d", all: "all_time" };
|
|
16
|
+
const WINDOW_SPAN_DAYS = { "7d": 7, "30d": 30, "90d": 90, all_time: 3650 };
|
|
17
|
+
const PLATFORM_ENUM = new Set([
|
|
18
|
+
"claude",
|
|
19
|
+
"chatgpt",
|
|
20
|
+
"gemini",
|
|
21
|
+
"pi",
|
|
22
|
+
"codex",
|
|
23
|
+
"multi",
|
|
24
|
+
"other",
|
|
25
|
+
]);
|
|
18
26
|
// Provenance tag only — the board ranks by Υ (no RS.xx weights, §0.1).
|
|
19
|
-
const RULESET_VERSION =
|
|
20
|
-
const DAY_MS = 86_400_000
|
|
27
|
+
const RULESET_VERSION = "sigrank-token-1";
|
|
28
|
+
const DAY_MS = 86_400_000;
|
|
21
29
|
|
|
22
|
-
const clamp = (x, lo, hi) => Math.max(lo, Math.min(hi, x))
|
|
30
|
+
const clamp = (x, lo, hi) => Math.max(lo, Math.min(hi, x));
|
|
23
31
|
const round = (n, dp) => {
|
|
24
|
-
const f = 10 ** dp
|
|
25
|
-
return Math.round(n * f) / f
|
|
26
|
-
}
|
|
32
|
+
const f = 10 ** dp;
|
|
33
|
+
return Math.round(n * f) / f;
|
|
34
|
+
};
|
|
27
35
|
|
|
28
36
|
/** Map any tokenpull platform to the Schema 1.0 platform.primary enum (others → 'other'). */
|
|
29
37
|
export function toPlatformPrimary(platform) {
|
|
30
|
-
return PLATFORM_ENUM.has(platform) ? platform :
|
|
38
|
+
return PLATFORM_ENUM.has(platform) ? platform : "other";
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
/**
|
|
34
42
|
* buildPayload — Schema 1.0 payload from a tokenpull window + enrolled identity.
|
|
35
43
|
* Pure (given opts.now); agent.snapshot_hash is computed last (canonical strips it).
|
|
36
44
|
*/
|
|
37
|
-
export function buildPayload(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
export function buildPayload(
|
|
46
|
+
windowKey,
|
|
47
|
+
pillars,
|
|
48
|
+
messages,
|
|
49
|
+
identity,
|
|
50
|
+
platform,
|
|
51
|
+
opts = {},
|
|
52
|
+
) {
|
|
53
|
+
const windowType = WINDOW_TYPE[windowKey] || windowKey;
|
|
54
|
+
const spanDays = WINDOW_SPAN_DAYS[windowType] || 30;
|
|
55
|
+
const nowMs = opts.now ?? Date.now();
|
|
41
56
|
|
|
42
|
-
const input = Math.max(0, Math.round(pillars?.input || 0))
|
|
43
|
-
const output = Math.max(0, Math.round(pillars?.output || 0))
|
|
44
|
-
const cacheCreate = Math.max(0, Math.round(pillars?.cacheCreate || 0))
|
|
45
|
-
const cacheRead = Math.max(0, Math.round(pillars?.cacheRead || 0))
|
|
46
|
-
const tokensTotal = input + output + cacheCreate + cacheRead
|
|
57
|
+
const input = Math.max(0, Math.round(pillars?.input || 0));
|
|
58
|
+
const output = Math.max(0, Math.round(pillars?.output || 0));
|
|
59
|
+
const cacheCreate = Math.max(0, Math.round(pillars?.cacheCreate || 0));
|
|
60
|
+
const cacheRead = Math.max(0, Math.round(pillars?.cacheRead || 0));
|
|
61
|
+
const tokensTotal = input + output + cacheCreate + cacheRead;
|
|
47
62
|
|
|
48
63
|
// Proxies — plausibility-clean (see file header).
|
|
49
|
-
const msgs = Math.max(0, Math.round(messages || 0))
|
|
50
|
-
const turnsTotal = Math.max(output > 0 ? 1 : 0, msgs)
|
|
51
|
-
const sessionsCount =
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
64
|
+
const msgs = Math.max(0, Math.round(messages || 0));
|
|
65
|
+
const turnsTotal = Math.max(output > 0 ? 1 : 0, msgs);
|
|
66
|
+
const sessionsCount =
|
|
67
|
+
tokensTotal > 0
|
|
68
|
+
? clamp(Math.round(msgs / 8) || 1, 1, Math.max(1, turnsTotal))
|
|
69
|
+
: 0;
|
|
70
|
+
const spanMinutes = spanDays * 1440;
|
|
71
|
+
const minActive = Math.ceil(output / 19000) + 1; // keep output/active under the 20k/min flag
|
|
72
|
+
const activeMinutes = clamp(
|
|
73
|
+
Math.max(msgs, minActive),
|
|
74
|
+
1,
|
|
75
|
+
Math.max(1, spanMinutes - 1),
|
|
76
|
+
);
|
|
55
77
|
|
|
56
|
-
const compressionRatio =
|
|
57
|
-
|
|
78
|
+
const compressionRatio =
|
|
79
|
+
input + output > 0 ? clamp(output / (input + output), 0, 1) : 0;
|
|
80
|
+
const sessionDepth = sessionsCount > 0 ? turnsTotal / sessionsCount : 0;
|
|
58
81
|
|
|
59
82
|
const payload = {
|
|
60
|
-
schema_version:
|
|
83
|
+
schema_version: "1.0",
|
|
61
84
|
codename: identity.codename,
|
|
62
85
|
device_id: identity.device_id,
|
|
63
86
|
submitted_at: new Date(nowMs).toISOString(),
|
|
@@ -70,7 +93,11 @@ export function buildPayload(windowKey, pillars, messages, identity, platform, o
|
|
|
70
93
|
core_metrics: {
|
|
71
94
|
compression_ratio: round(compressionRatio, 4),
|
|
72
95
|
prompt_complexity: round(clamp(sessionDepth, 0, 100), 2),
|
|
73
|
-
cross_thread_score: clamp(
|
|
96
|
+
cross_thread_score: clamp(
|
|
97
|
+
Math.round(cacheRead / Math.max(cacheCreate, 1)),
|
|
98
|
+
0,
|
|
99
|
+
100000,
|
|
100
|
+
),
|
|
74
101
|
session_depth_avg: round(Math.max(0, sessionDepth), 2),
|
|
75
102
|
// M.05 token_throughput muted 2026-06-26 — word-era metric the web already
|
|
76
103
|
// mutes (RS01 tt→0) + strips. Send null (RPC p_token_throughput is nullable
|
|
@@ -80,7 +107,10 @@ export function buildPayload(windowKey, pillars, messages, identity, platform, o
|
|
|
80
107
|
background_metrics: {
|
|
81
108
|
message_volume: msgs,
|
|
82
109
|
account_age_days: Math.max(1, Math.round(opts.accountAgeDays ?? 365)),
|
|
83
|
-
total_messages_lifetime: Math.max(
|
|
110
|
+
total_messages_lifetime: Math.max(
|
|
111
|
+
msgs,
|
|
112
|
+
Math.round(opts.totalMessages ?? msgs),
|
|
113
|
+
),
|
|
84
114
|
},
|
|
85
115
|
raw_telemetry: {
|
|
86
116
|
sessions_count: sessionsCount,
|
|
@@ -92,24 +122,24 @@ export function buildPayload(windowKey, pillars, messages, identity, platform, o
|
|
|
92
122
|
tokens_cache_creation: cacheCreate,
|
|
93
123
|
active_minutes_est: activeMinutes,
|
|
94
124
|
},
|
|
95
|
-
tier:
|
|
125
|
+
tier: "free",
|
|
96
126
|
agent: {
|
|
97
127
|
version: identity.agent_version,
|
|
98
128
|
ruleset_version: RULESET_VERSION,
|
|
99
|
-
snapshot_hash:
|
|
129
|
+
snapshot_hash: "",
|
|
100
130
|
public_key: identity.public_key,
|
|
101
131
|
},
|
|
102
|
-
}
|
|
132
|
+
};
|
|
103
133
|
|
|
104
134
|
// Cascade Report block (Phase 1 — mode detection + badges + health).
|
|
105
135
|
// Computed locally (pure math), submitted alongside the 4 token pillars.
|
|
106
136
|
// The server stores it as-is — does NOT recompute modes.
|
|
107
137
|
// Weekly granularity is the privacy boundary — daily modes never leave the machine.
|
|
108
|
-
if (opts.report && typeof opts.report ===
|
|
109
|
-
payload.report = opts.report
|
|
138
|
+
if (opts.report && typeof opts.report === "object") {
|
|
139
|
+
payload.report = opts.report;
|
|
110
140
|
}
|
|
111
|
-
payload.agent.snapshot_hash = snapshotHash(payload)
|
|
112
|
-
return payload
|
|
141
|
+
payload.agent.snapshot_hash = snapshotHash(payload);
|
|
142
|
+
return payload;
|
|
113
143
|
}
|
|
114
144
|
|
|
115
145
|
/**
|
|
@@ -117,103 +147,139 @@ export function buildPayload(windowKey, pillars, messages, identity, platform, o
|
|
|
117
147
|
* Returns a structured result; never throws on a network error. opts: { apiBase,
|
|
118
148
|
* fetchImpl, platform, now } (fetchImpl lets the caller inject a timeout-wrapped fetch).
|
|
119
149
|
*/
|
|
120
|
-
export async function submitSignedWindow(
|
|
150
|
+
export async function submitSignedWindow(
|
|
151
|
+
windowKey,
|
|
152
|
+
pillars,
|
|
153
|
+
messages,
|
|
154
|
+
identity,
|
|
155
|
+
opts = {},
|
|
156
|
+
) {
|
|
121
157
|
if (!identity?.codename || !identity?.operator_id) {
|
|
122
|
-
return {
|
|
158
|
+
return {
|
|
159
|
+
status: "not_enrolled",
|
|
160
|
+
reason: "not_enrolled",
|
|
161
|
+
detail: "Run `npx sigrank-mcp enroll` to bind this device first.",
|
|
162
|
+
};
|
|
123
163
|
}
|
|
124
164
|
if (!identity?.private_key_pkcs8_b64) {
|
|
125
|
-
return {
|
|
165
|
+
return {
|
|
166
|
+
status: "error",
|
|
167
|
+
reason: "no_key",
|
|
168
|
+
detail: "No local signing key — re-run `npx sigrank-mcp enroll`.",
|
|
169
|
+
};
|
|
126
170
|
}
|
|
127
171
|
|
|
128
|
-
const apiBase =
|
|
172
|
+
const apiBase =
|
|
173
|
+
opts.apiBase || process.env.SIGRANK_API_BASE || "https://signalaf.com";
|
|
129
174
|
// FIX M: default a 15s AbortController timeout when the caller doesn't inject a
|
|
130
175
|
// fetchImpl. The TUI path already wraps fetch (callTool → doFetch, 10s), but the
|
|
131
176
|
// legacy CLI `watch --submit` + the CLI default-view [S] call submitSignedWindow
|
|
132
177
|
// with no fetchImpl → bare fetch → a hung POST blocks forever. This default fixes
|
|
133
178
|
// it everywhere at once. (15s is generous for a signed POST to /api/v1/snapshots.)
|
|
134
|
-
const fetchImpl =
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
179
|
+
const fetchImpl =
|
|
180
|
+
opts.fetchImpl ||
|
|
181
|
+
((url, init = {}) => {
|
|
182
|
+
const ctrl = new AbortController();
|
|
183
|
+
const timer = setTimeout(() => ctrl.abort(), 15_000);
|
|
184
|
+
return fetch(url, {
|
|
185
|
+
...init,
|
|
186
|
+
signal: init.signal || ctrl.signal,
|
|
187
|
+
}).finally(() => clearTimeout(timer));
|
|
188
|
+
});
|
|
189
|
+
const payload = buildPayload(
|
|
190
|
+
windowKey,
|
|
191
|
+
pillars,
|
|
192
|
+
messages,
|
|
193
|
+
identity,
|
|
194
|
+
opts.platform || "claude",
|
|
195
|
+
opts,
|
|
196
|
+
);
|
|
140
197
|
|
|
141
198
|
// Preflight: run the same anti-gaming checks the server will run, locally.
|
|
142
199
|
// If the payload would be rejected or downgraded, warn the operator BEFORE
|
|
143
200
|
// submitting. The server always runs its own checks — this is a preview.
|
|
144
|
-
const pre = preflight(payload)
|
|
201
|
+
const pre = preflight(payload);
|
|
145
202
|
if (!pre.pass && !opts.skipPreflight) {
|
|
146
203
|
if (pre.wouldReject) {
|
|
147
204
|
return {
|
|
148
|
-
status:
|
|
205
|
+
status: "preflight_rejected",
|
|
149
206
|
window: WINDOW_TYPE[windowKey] || windowKey,
|
|
150
207
|
preflight: pre,
|
|
151
208
|
detail: `Submission would be REJECTED by the server: ${pre.summary}. Fix the issue or re-run with skipPreflight.`,
|
|
152
|
-
}
|
|
209
|
+
};
|
|
153
210
|
}
|
|
154
211
|
// Flags downgrade verified → flagged (not ranked). Warn but don't block
|
|
155
212
|
// unless the caller explicitly asked for strict mode.
|
|
156
213
|
if (opts.strictPreflight) {
|
|
157
214
|
return {
|
|
158
|
-
status:
|
|
215
|
+
status: "preflight_flagged",
|
|
159
216
|
window: WINDOW_TYPE[windowKey] || windowKey,
|
|
160
217
|
preflight: pre,
|
|
161
218
|
detail: `Submission would be DOWNGRADED (not ranked): ${pre.summary}. Fix the issue or re-run with skipPreflight.`,
|
|
162
|
-
}
|
|
219
|
+
};
|
|
163
220
|
}
|
|
164
221
|
// Non-strict: attach the preflight warning to the result but proceed.
|
|
165
222
|
}
|
|
166
223
|
|
|
167
|
-
const signature = signPayload(payload, identity.private_key_pkcs8_b64)
|
|
224
|
+
const signature = signPayload(payload, identity.private_key_pkcs8_b64);
|
|
168
225
|
|
|
169
226
|
// Dry run: build + sign exactly as a real publish, then STOP before the POST.
|
|
170
227
|
// Returns the exact payload that would be sent — the privacy proof ("token
|
|
171
228
|
// counts only") is inspectable, and nothing touches the network.
|
|
172
229
|
if (opts.dryRun) {
|
|
173
230
|
return {
|
|
174
|
-
status:
|
|
231
|
+
status: "dry_run",
|
|
175
232
|
window: WINDOW_TYPE[windowKey] || windowKey,
|
|
176
233
|
would_post: `${apiBase}/api/v1/snapshots`,
|
|
177
234
|
payload,
|
|
178
235
|
signature,
|
|
179
236
|
preflight: pre,
|
|
180
|
-
detail:
|
|
181
|
-
}
|
|
237
|
+
detail: "Nothing sent. Re-run without dry_run to publish.",
|
|
238
|
+
};
|
|
182
239
|
}
|
|
183
240
|
|
|
184
|
-
let res
|
|
185
|
-
let ack
|
|
241
|
+
let res;
|
|
242
|
+
let ack;
|
|
186
243
|
try {
|
|
187
244
|
res = await fetchImpl(`${apiBase}/api/v1/snapshots`, {
|
|
188
|
-
method:
|
|
245
|
+
method: "POST",
|
|
189
246
|
headers: {
|
|
190
|
-
|
|
191
|
-
accept:
|
|
192
|
-
|
|
247
|
+
"content-type": "application/json",
|
|
248
|
+
accept: "application/json",
|
|
249
|
+
"x-agent-signature": signature,
|
|
193
250
|
},
|
|
194
251
|
body: JSON.stringify(payload),
|
|
195
|
-
})
|
|
252
|
+
});
|
|
196
253
|
try {
|
|
197
|
-
ack = await res.json()
|
|
254
|
+
ack = await res.json();
|
|
198
255
|
} catch {
|
|
199
|
-
ack = {}
|
|
256
|
+
ack = {};
|
|
200
257
|
}
|
|
201
258
|
} catch (e) {
|
|
202
|
-
return {
|
|
259
|
+
return {
|
|
260
|
+
status: "error",
|
|
261
|
+
reason: "network",
|
|
262
|
+
detail: e.message,
|
|
263
|
+
window: WINDOW_TYPE[windowKey] || windowKey,
|
|
264
|
+
};
|
|
203
265
|
}
|
|
204
266
|
|
|
205
267
|
return {
|
|
206
|
-
status: res.ok ? ack.status ||
|
|
268
|
+
status: res.ok ? ack.status || "received" : "error",
|
|
207
269
|
httpStatus: res.status,
|
|
208
270
|
window: WINDOW_TYPE[windowKey] || windowKey,
|
|
209
271
|
verification_tier: ack.verification_tier ?? null,
|
|
210
272
|
persisted: ack.persisted ?? null,
|
|
211
273
|
// ranked = actually on the board (verified + written), not just "received".
|
|
212
274
|
// An unenrolled/revoked device gets HTTP 202 received but is NEVER ranked.
|
|
213
|
-
ranked: !!(
|
|
275
|
+
ranked: !!(
|
|
276
|
+
res.ok &&
|
|
277
|
+
ack.verification_tier === "verified" &&
|
|
278
|
+
ack.persisted === true
|
|
279
|
+
),
|
|
214
280
|
snapshot_hash: payload.agent.snapshot_hash,
|
|
215
281
|
reason: res.ok ? null : ack.reason || ack.status || `http_${res.status}`,
|
|
216
282
|
detail: ack.detail ?? null,
|
|
217
283
|
preflight: pre,
|
|
218
|
-
}
|
|
284
|
+
};
|
|
219
285
|
}
|