sigrank 0.0.178 → 0.0.179
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/CHANGELOG.md +100 -0
- package/MCP_SERVER_DESCRIPTION.md +141 -0
- package/README.md +8 -0
- package/adapters/index.mjs +865 -0
- package/adapters/tokenpull.mjs +576 -0
- package/adapters.mjs +1 -865
- package/analytics/cascade.mjs +274 -0
- package/cascade.mjs +1 -274
- package/cli.mjs +1 -1853
- package/connect.mjs +1 -13
- package/identity/connect.mjs +13 -0
- package/identity/keystore.mjs +242 -0
- package/identity/sign.mjs +106 -0
- package/index.mjs +29 -153
- package/keystore.mjs +1 -242
- package/narrate.mjs +1 -77
- package/package.json +29 -13
- package/preflight.mjs +1 -191
- package/presentation/cli.mjs +1874 -0
- package/presentation/narrate.mjs +77 -0
- package/presentation/tui.mjs +2668 -0
- package/prompts/check-my-efficiency.md +1 -0
- package/prompts/compare-with-leader.md +1 -0
- package/prompts/simulate-improvement.md +1 -0
- package/resources/class-tiers.md +23 -0
- package/resources/data-policy.md +27 -0
- package/resources/install-guide.md +59 -0
- package/resources/privacy-model.md +30 -0
- package/resources/scoring-formula.md +28 -0
- package/review.mjs +263 -0
- package/sign.mjs +1 -106
- package/submit/index.mjs +309 -0
- package/submit/preflight.mjs +191 -0
- package/submit.mjs +1 -303
- package/tokenpull.mjs +1 -576
- package/tools/_framing.mjs +139 -0
- package/tools/_helpers.mjs +258 -0
- package/tools/_report.mjs +262 -0
- package/tools/_schemas.mjs +394 -0
- package/tools/_verifiers.mjs +148 -0
- package/tools/compare-operators.mjs +79 -0
- package/tools/compare-self.mjs +122 -0
- package/tools/describe-power-user.mjs +68 -0
- package/tools/diagnose-cascade.mjs +192 -0
- package/tools/enroll.mjs +121 -0
- package/tools/get-best-operator.mjs +58 -0
- package/tools/get-leaderboard.mjs +26 -0
- package/tools/get-operator.mjs +33 -0
- package/tools/index.mjs +257 -0
- package/tools/optimize-efficiency.mjs +160 -0
- package/tools/rank-paste.mjs +51 -0
- package/tools/rank-windows.mjs +120 -0
- package/tools/self-improve.mjs +423 -0
- package/tools/simulate-change.mjs +207 -0
- package/tools/submit-paste.mjs +118 -0
- package/tools/submit-verified.mjs +153 -0
- package/tools/suggest-improvements.mjs +168 -0
- package/tools/tokenpull-compare.mjs +123 -0
- package/tools/tokenpull-submit.mjs +234 -0
- package/tools/tokenpull.mjs +113 -0
- package/tools/tokscale-breakdown.mjs +159 -0
- package/tools/tokscale-competitive-intel.mjs +45 -0
- package/tools/tokscale-cost-analysis.mjs +31 -0
- package/tools/tokscale-developer-profile.mjs +36 -0
- package/tools/tokscale-device-profile.mjs +35 -0
- package/tools/tokscale-market-share.mjs +38 -0
- package/tools/tokscale-mcp-usage.mjs +33 -0
- package/tools/tokscale-model-trends.mjs +32 -0
- package/tools/watch-tokenpull.mjs +134 -0
- package/tools.mjs +1 -3897
- package/tui.mjs +4 -2650
- package/banner.svg +0 -7
package/keystore.mjs
CHANGED
|
@@ -1,242 +1 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
// Persists a per-device keypair at ~/.sigrank-mcp/identity.json (dir 0700, file 0600).
|
|
4
|
-
// The PRIVATE key never leaves this machine; only the raw 32-byte public key (as
|
|
5
|
-
// "ed25519:<base64>") is sent to the server at enroll time. The device_id (uuid) is
|
|
6
|
-
// generated once and is the immutable binding target server-side.
|
|
7
|
-
//
|
|
8
|
-
// Public-key export MUST match the server's publicKeyFromAgent: generateKeyPairSync
|
|
9
|
-
// exports SPKI DER with a 12-byte prefix; we strip it to the raw 32 bytes, base64 it,
|
|
10
|
-
// and prefix "ed25519:". The server re-adds the SPKI prefix to verify. Round-trip is
|
|
11
|
-
// locked by sign.test.mjs + the canon-parity fixture.
|
|
12
|
-
|
|
13
|
-
import { homedir } from "node:os";
|
|
14
|
-
import { join } from "node:path";
|
|
15
|
-
import {
|
|
16
|
-
mkdirSync,
|
|
17
|
-
readFileSync,
|
|
18
|
-
writeFileSync,
|
|
19
|
-
existsSync,
|
|
20
|
-
chmodSync,
|
|
21
|
-
unlinkSync,
|
|
22
|
-
readdirSync,
|
|
23
|
-
copyFileSync,
|
|
24
|
-
} from "node:fs";
|
|
25
|
-
import { generateKeyPairSync, randomUUID } from "node:crypto";
|
|
26
|
-
|
|
27
|
-
const DIR = join(homedir(), ".sigrank-mcp");
|
|
28
|
-
const PATH = join(DIR, "identity.json");
|
|
29
|
-
const BACKUP_PREFIX = "identity.json.bak-";
|
|
30
|
-
const MAX_BACKUPS = 5;
|
|
31
|
-
|
|
32
|
-
/** SPKI DER prefix length for an ed25519 public key (12 bytes before the raw 32). */
|
|
33
|
-
const SPKI_PREFIX_LEN = 12;
|
|
34
|
-
|
|
35
|
-
/** Resolve this package's version for the agent_version stamp (best-effort). */
|
|
36
|
-
function agentVersion() {
|
|
37
|
-
try {
|
|
38
|
-
const pkg = JSON.parse(
|
|
39
|
-
readFileSync(new URL("./package.json", import.meta.url), "utf-8"),
|
|
40
|
-
);
|
|
41
|
-
return `sigrank-mcp/${pkg.version}`;
|
|
42
|
-
} catch {
|
|
43
|
-
return "sigrank-mcp";
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function keystorePath() {
|
|
48
|
-
return PATH;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** Read the stored identity, or null if absent/corrupt. */
|
|
52
|
-
export function loadIdentity() {
|
|
53
|
-
if (!existsSync(PATH)) return null;
|
|
54
|
-
try {
|
|
55
|
-
return JSON.parse(readFileSync(PATH, "utf-8"));
|
|
56
|
-
} catch {
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** Write the identity with strict perms (dir 0700, file 0600). Backs up the existing
|
|
62
|
-
* file before overwriting so a corrupt write or accidental reset can be recovered. */
|
|
63
|
-
export function persistIdentity(identity) {
|
|
64
|
-
mkdirSync(DIR, { recursive: true });
|
|
65
|
-
try {
|
|
66
|
-
chmodSync(DIR, 0o700);
|
|
67
|
-
} catch {
|
|
68
|
-
/* best-effort on platforms without chmod */
|
|
69
|
-
}
|
|
70
|
-
// Back up the existing identity before overwriting (resilience: if the write
|
|
71
|
-
// fails or the file is later lost, the backup carries the binding + keys).
|
|
72
|
-
if (existsSync(PATH)) {
|
|
73
|
-
try {
|
|
74
|
-
const stamp = Date.now();
|
|
75
|
-
copyFileSync(PATH, join(DIR, `${BACKUP_PREFIX}${stamp}`));
|
|
76
|
-
pruneBackups();
|
|
77
|
-
} catch {
|
|
78
|
-
/* best-effort — don't block the write */
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
writeFileSync(PATH, `${JSON.stringify(identity, null, 2)}\n`, {
|
|
82
|
-
mode: 0o600,
|
|
83
|
-
});
|
|
84
|
-
try {
|
|
85
|
-
chmodSync(PATH, 0o600);
|
|
86
|
-
} catch {
|
|
87
|
-
/* best-effort */
|
|
88
|
-
}
|
|
89
|
-
return identity;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** Build a fresh keypair record (pure — no fs). Exposed for tests. */
|
|
93
|
-
export function generateIdentity({ device_id } = {}) {
|
|
94
|
-
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
95
|
-
const spki = publicKey.export({ type: "spki", format: "der" });
|
|
96
|
-
const rawPub = Buffer.from(spki).subarray(SPKI_PREFIX_LEN); // the raw 32 bytes
|
|
97
|
-
const pkcs8 = privateKey.export({ type: "pkcs8", format: "der" });
|
|
98
|
-
return {
|
|
99
|
-
device_id: device_id || randomUUID(),
|
|
100
|
-
codename: null,
|
|
101
|
-
operator_id: null,
|
|
102
|
-
public_key: `ed25519:${rawPub.toString("base64")}`,
|
|
103
|
-
private_key_pkcs8_b64: Buffer.from(pkcs8).toString("base64"),
|
|
104
|
-
agent_version: agentVersion(),
|
|
105
|
-
enrolled_at: null,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Decide the binding (codename/operator_id/enrolled_at) to carry onto a freshly
|
|
111
|
-
* generated identity. The binding is tied to device_id: if device_id is CHANGING
|
|
112
|
-
* (a new device, e.g. after a reset/re-enroll where the old file was deleted), the
|
|
113
|
-
* old binding is INVALIDATED — never preserve a stale codename/operator onto a
|
|
114
|
-
* different device. That mismatch is the "Frankenstein identity" root cause: a new
|
|
115
|
-
* device_id + an old codename → the server sees a mismatch → tags submissions
|
|
116
|
-
* `unverified` → never ranks, yet `isSignedIn` reads the local codename as present.
|
|
117
|
-
* Only reuse the binding when the SAME device_id is being kept. Pure — exposed for
|
|
118
|
-
* tests (no fs, so the owner's live identity is never at risk during a test run).
|
|
119
|
-
*/
|
|
120
|
-
export function bindingForFreshIdentity(existing, fresh) {
|
|
121
|
-
if (!existing)
|
|
122
|
-
return { codename: null, operator_id: null, enrolled_at: null };
|
|
123
|
-
if (existing.device_id && fresh.device_id === existing.device_id) {
|
|
124
|
-
return {
|
|
125
|
-
codename: existing.codename ?? null,
|
|
126
|
-
operator_id: existing.operator_id ?? null,
|
|
127
|
-
enrolled_at: existing.enrolled_at ?? null,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
// device_id changed → the old binding belongs to a different device; drop it.
|
|
131
|
-
return { codename: null, operator_id: null, enrolled_at: null };
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Load the existing identity, or generate + persist a fresh one. Idempotent: a
|
|
136
|
-
* complete existing identity is returned untouched (never rotates a live key).
|
|
137
|
-
* Self-healing: if the identity has keys + device_id but no codename (binding
|
|
138
|
-
* lost via partial write / version transition), scans backups for a matching
|
|
139
|
-
* device_id and restores the binding before returning.
|
|
140
|
-
*/
|
|
141
|
-
export function ensureIdentity() {
|
|
142
|
-
const existing = loadIdentity();
|
|
143
|
-
if (
|
|
144
|
-
existing?.private_key_pkcs8_b64 &&
|
|
145
|
-
existing?.public_key &&
|
|
146
|
-
existing?.device_id
|
|
147
|
-
) {
|
|
148
|
-
// Self-healing: keys present but binding lost → try to restore from backups.
|
|
149
|
-
if (!existing.codename || !existing.operator_id) {
|
|
150
|
-
const restored = restoreBindingFromBackups(existing.device_id);
|
|
151
|
-
if (restored) {
|
|
152
|
-
existing.codename = restored.codename;
|
|
153
|
-
existing.operator_id = restored.operator_id;
|
|
154
|
-
existing.enrolled_at = restored.enrolled_at;
|
|
155
|
-
return persistIdentity(existing);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return existing;
|
|
159
|
-
}
|
|
160
|
-
// Regeneration path: a partial/corrupt record. The binding is tied to device_id —
|
|
161
|
-
// if a new device_id is generated, the old codename/operator MUST NOT carry over
|
|
162
|
-
// (bindingForFreshIdentity drops them). Only a reused device_id keeps its binding.
|
|
163
|
-
const fresh = generateIdentity({ device_id: existing?.device_id });
|
|
164
|
-
const binding = bindingForFreshIdentity(existing, fresh);
|
|
165
|
-
fresh.codename = binding.codename;
|
|
166
|
-
fresh.operator_id = binding.operator_id;
|
|
167
|
-
fresh.enrolled_at = binding.enrolled_at;
|
|
168
|
-
return persistIdentity(fresh);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/** Record a successful enrollment (codename/operator_id + enrolled_at) into the keystore. */
|
|
172
|
-
export function recordEnrollment({ codename, operator_id }) {
|
|
173
|
-
const id = ensureIdentity();
|
|
174
|
-
// OVERWRITE from the server's enroll response — a new enroll is a new binding, full
|
|
175
|
-
// stop. Never `??`-preserve a stale codename/operator onto this device (the other
|
|
176
|
-
// half of the Frankenstein bug: a re-enroll used to keep the old codename when the
|
|
177
|
-
// new one was absent). A 201 enrolled always carries both.
|
|
178
|
-
id.codename = codename ?? null;
|
|
179
|
-
id.operator_id = operator_id ?? null;
|
|
180
|
-
id.enrolled_at = new Date().toISOString();
|
|
181
|
-
return persistIdentity(id);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/** Clear the local identity (sign out). Next enroll provisions a fresh device_id. */
|
|
185
|
-
export function clearIdentity() {
|
|
186
|
-
try {
|
|
187
|
-
if (existsSync(PATH)) unlinkSync(PATH);
|
|
188
|
-
} catch {
|
|
189
|
-
/* best-effort */
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/** Keep at most MAX_BACKUPS backup files (oldest deleted first). */
|
|
194
|
-
function pruneBackups() {
|
|
195
|
-
try {
|
|
196
|
-
const files = readdirSync(DIR)
|
|
197
|
-
.filter((f) => f.startsWith(BACKUP_PREFIX))
|
|
198
|
-
.sort(); // timestamp-suffixed → lexicographic = chronological
|
|
199
|
-
while (files.length > MAX_BACKUPS) {
|
|
200
|
-
unlinkSync(join(DIR, files.shift()));
|
|
201
|
-
}
|
|
202
|
-
} catch {
|
|
203
|
-
/* best-effort */
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/** Scan backup files for one with the same device_id that carries a codename +
|
|
208
|
-
* operator_id binding. Returns { codename, operator_id, enrolled_at } or null.
|
|
209
|
-
* Used by ensureIdentity() to self-heal when the live file lost its binding
|
|
210
|
-
* (e.g. a partial write or version transition that preserved keys but dropped
|
|
211
|
-
* the binding metadata). Only restores from a backup with the SAME device_id —
|
|
212
|
-
* never crosses device boundaries (Frankenstein-identity guard). */
|
|
213
|
-
export function restoreBindingFromBackups(device_id) {
|
|
214
|
-
if (!device_id) return null;
|
|
215
|
-
try {
|
|
216
|
-
const files = readdirSync(DIR)
|
|
217
|
-
.filter((f) => f.startsWith(BACKUP_PREFIX))
|
|
218
|
-
.sort()
|
|
219
|
-
.reverse(); // newest first — prefer the most recent binding
|
|
220
|
-
for (const f of files) {
|
|
221
|
-
try {
|
|
222
|
-
const bak = JSON.parse(readFileSync(join(DIR, f), "utf-8"));
|
|
223
|
-
if (
|
|
224
|
-
bak.device_id === device_id &&
|
|
225
|
-
bak.codename &&
|
|
226
|
-
bak.operator_id
|
|
227
|
-
) {
|
|
228
|
-
return {
|
|
229
|
-
codename: bak.codename,
|
|
230
|
-
operator_id: bak.operator_id,
|
|
231
|
-
enrolled_at: bak.enrolled_at ?? null,
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
} catch {
|
|
235
|
-
/* skip corrupt backups */
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
} catch {
|
|
239
|
-
/* best-effort */
|
|
240
|
-
}
|
|
241
|
-
return null;
|
|
242
|
-
}
|
|
1
|
+
export * from './identity/keystore.mjs';
|
package/narrate.mjs
CHANGED
|
@@ -1,77 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* narrate.mjs — deterministic prose "card" for a cascade result.
|
|
3
|
-
*
|
|
4
|
-
* Port of _template() from ~/Desktop/moses-sigrank/narrate.py. The model path
|
|
5
|
-
* (MiniCPM4-0.5B) is intentionally SKIPPED: the template is the trustworthy,
|
|
6
|
-
* instant, auditable fallback — same numbers in → same card out, and it can never
|
|
7
|
-
* emit a metric the cascade didn't produce. A model hook can layer behind this same
|
|
8
|
-
* narrate() interface later without a rewrite.
|
|
9
|
-
*
|
|
10
|
-
* Token-only. No network, no randomness.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
// Safe formatters: never emit NaN/Infinity/undefined into the card.
|
|
14
|
-
const safeNum = (n) => (Number.isFinite(Number(n)) ? Number(n) : null);
|
|
15
|
-
const comma = (n, dec) => {
|
|
16
|
-
const v = safeNum(n);
|
|
17
|
-
return v !== null
|
|
18
|
-
? v.toLocaleString("en-US", {
|
|
19
|
-
minimumFractionDigits: dec,
|
|
20
|
-
maximumFractionDigits: dec,
|
|
21
|
-
})
|
|
22
|
-
: "—";
|
|
23
|
-
};
|
|
24
|
-
const plain = (n, dec) => {
|
|
25
|
-
const v = safeNum(n);
|
|
26
|
-
return v !== null ? v.toFixed(dec) : "—";
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Given a cascade result ({ velocity, leverage, dev10x, pillars, class }) and an
|
|
31
|
-
* optional subject name, return "**CLASS.** <one or two sentences>". Deterministic.
|
|
32
|
-
*/
|
|
33
|
-
export function narrate(cascade, name = "This operator") {
|
|
34
|
-
const klass = cascade.class || cascade.klass || "UNCLASSED";
|
|
35
|
-
const v = safeNum(cascade.velocity);
|
|
36
|
-
const l = safeNum(cascade.leverage);
|
|
37
|
-
|
|
38
|
-
// "non-compounding" = a stateless pipe: no cache commits, so the cascade can't
|
|
39
|
-
// form. cascade.mjs leaves dev10x null when cacheCreate is 0 (the cw/o term
|
|
40
|
-
// collapses), which is exactly metrics.py's non_compounding flag.
|
|
41
|
-
// Also catches zero-input sessions where velocity/leverage are null.
|
|
42
|
-
const cw = cascade.pillars ? Number(cascade.pillars.cacheCreate) : NaN;
|
|
43
|
-
const nonCompounding =
|
|
44
|
-
cascade.dev10x == null || !(cw > 0) || v === null || l === null;
|
|
45
|
-
|
|
46
|
-
let body;
|
|
47
|
-
if (nonCompounding) {
|
|
48
|
-
const leverageStr =
|
|
49
|
-
l !== null
|
|
50
|
-
? `Leverage ${comma(l, 1)}x comes from reuse alone.`
|
|
51
|
-
: "Leverage is undefined (no fresh input recorded).";
|
|
52
|
-
const dev10xNote =
|
|
53
|
-
cascade.dev10x == null
|
|
54
|
-
? " 10xDEV is undefined — the compounding loop has not formed yet."
|
|
55
|
-
: "";
|
|
56
|
-
body =
|
|
57
|
-
`${name} runs a stateless pipe — no cache commits, so the cascade can't form. ` +
|
|
58
|
-
`High read volume, but nothing is being built forward. ${leverageStr}${dev10xNote}`;
|
|
59
|
-
} else if (v >= 1 && l >= 100) {
|
|
60
|
-
body =
|
|
61
|
-
`${name} holds both axes at once: ${plain(v, 1)}x generation AND ${comma(l, 0)}x memory leverage. ` +
|
|
62
|
-
`A closed kinetic loop — the rare operator the leverage/generation tradeoff says shouldn't exist.`;
|
|
63
|
-
} else if (l >= 10 && v < 1) {
|
|
64
|
-
body =
|
|
65
|
-
`${name} is an archival sponge — ${comma(l, 0)}x reuse but only ${plain(v, 2)}x generation. ` +
|
|
66
|
-
`Holds context beautifully, executes little with it. The reuse number is inflated by a weak commitment stage.`;
|
|
67
|
-
} else if (v >= 0.8 && l < 2) {
|
|
68
|
-
body =
|
|
69
|
-
`${name} is a volatile ingestor — ${plain(v, 2)}x generation but ${plain(l, 1)}x leverage. ` +
|
|
70
|
-
`Fast on single shots, resets between turns. Memory doesn't persist into a compounding loop.`;
|
|
71
|
-
} else {
|
|
72
|
-
body =
|
|
73
|
-
`${name} sits low on both axes: ${plain(v, 2)}x generation, ${plain(l, 1)}x leverage. ` +
|
|
74
|
-
`A transient profile — neither building state nor converting input to output efficiently.`;
|
|
75
|
-
}
|
|
76
|
-
return `**${klass}.** ${body}`;
|
|
77
|
-
}
|
|
1
|
+
export * from './presentation/narrate.mjs';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sigrank",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.179",
|
|
4
4
|
"author": "SunrisesIllNeverSee (https://github.com/SunrisesIllNeverSee)",
|
|
5
5
|
"description": "SigRank MCP server — the yield cascade + live leaderboard as MCP tools any agent can call",
|
|
6
6
|
"mcpName": "io.github.SunrisesIllNeverSee/sigrank-mcp",
|
|
@@ -24,26 +24,39 @@
|
|
|
24
24
|
},
|
|
25
25
|
"scripts": {
|
|
26
26
|
"start": "node index.mjs",
|
|
27
|
-
"test": "node test.mjs && node sign.test.mjs && node --test __tests__/cascade.test.mjs __tests__/badges.test.mjs",
|
|
28
|
-
"sync:platforms": "node scripts/sync-platforms.mjs"
|
|
27
|
+
"test": "node test.mjs && node __tests__/sign.test.mjs && node --test __tests__/cascade.test.mjs __tests__/badges.test.mjs __tests__/tui-input.test.mjs",
|
|
28
|
+
"sync:platforms": "node scripts/sync-platforms.mjs",
|
|
29
|
+
"sync:spine": "node scripts/sync-spine.mjs",
|
|
30
|
+
"spine:check": "node scripts/sync-spine.mjs --check",
|
|
31
|
+
"spine:diff": "node scripts/sync-spine.mjs --dry-run"
|
|
29
32
|
},
|
|
30
33
|
"files": [
|
|
31
34
|
"index.mjs",
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
+
"tools/",
|
|
36
|
+
"analytics/",
|
|
37
|
+
"identity/",
|
|
38
|
+
"submit/",
|
|
39
|
+
"adapters/",
|
|
40
|
+
"presentation/",
|
|
41
|
+
"resources/",
|
|
42
|
+
"prompts/",
|
|
43
|
+
"badges.mjs",
|
|
44
|
+
"review.mjs",
|
|
35
45
|
"cascade.mjs",
|
|
36
|
-
"narrate.mjs",
|
|
37
|
-
"tokenpull.mjs",
|
|
38
|
-
"adapters.mjs",
|
|
39
|
-
"sign.mjs",
|
|
40
46
|
"keystore.mjs",
|
|
41
|
-
"
|
|
47
|
+
"sign.mjs",
|
|
42
48
|
"connect.mjs",
|
|
43
|
-
"
|
|
49
|
+
"submit.mjs",
|
|
44
50
|
"preflight.mjs",
|
|
45
|
-
"
|
|
51
|
+
"adapters.mjs",
|
|
52
|
+
"tokenpull.mjs",
|
|
53
|
+
"tui.mjs",
|
|
54
|
+
"cli.mjs",
|
|
55
|
+
"narrate.mjs",
|
|
56
|
+
"tools.mjs",
|
|
46
57
|
"README.md",
|
|
58
|
+
"CHANGELOG.md",
|
|
59
|
+
"MCP_SERVER_DESCRIPTION.md",
|
|
47
60
|
"LICENSE"
|
|
48
61
|
],
|
|
49
62
|
"keywords": [
|
|
@@ -67,5 +80,8 @@
|
|
|
67
80
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
68
81
|
"ccusage": "^20.0.17",
|
|
69
82
|
"tokscale": "^4.5.2"
|
|
83
|
+
},
|
|
84
|
+
"overrides": {
|
|
85
|
+
"@hono/node-server": "^2.0.5"
|
|
70
86
|
}
|
|
71
87
|
}
|
package/preflight.mjs
CHANGED
|
@@ -1,191 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* preflight.mjs — local plausibility pre-checks for the MCP submit path.
|
|
3
|
-
*
|
|
4
|
-
* Mirrors ONLY the server's public plausibility gate (gates.ts:86-137) —
|
|
5
|
-
* totals consistency, turns/sessions ratios, output rate, cache ratio,
|
|
6
|
-
* cadence. These are integrity guards labeled "NOT the proprietary RS.xx"
|
|
7
|
-
* and are safe to replicate in an open agent.
|
|
8
|
-
*
|
|
9
|
-
* The proprietary battery (Benford / cadence / contamination) is SERVER-ONLY
|
|
10
|
-
* and must NOT be shipped in the agent. The server always runs its own
|
|
11
|
-
* battery after the POST — this preflight is a client-side preview of the
|
|
12
|
-
* public plausibility checks only, not a replacement for the full gate chain.
|
|
13
|
-
*
|
|
14
|
-
* If preflight flags, the operator sees WHY their submission would be
|
|
15
|
-
* rejected or flagged, and can fix the issue or submit anyway.
|
|
16
|
-
*
|
|
17
|
-
* Source of truth: lib/ingest/gates.ts (plausibilityGate). This port must
|
|
18
|
-
* stay in sync with the plausibility gate ONLY.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
// ── plausibility checks (from gates.ts:86-137) ──────────────────────────────
|
|
22
|
-
|
|
23
|
-
const GATE_LIMITS = {
|
|
24
|
-
TOTALS_TOLERANCE_FRAC: 0.01,
|
|
25
|
-
MAX_OUTPUT_TOKENS_PER_MIN: 20_000,
|
|
26
|
-
// Tightened range-plausibility bounds (deviewreview3)
|
|
27
|
-
MAX_CACHE_REUSE_RATIO: 35,
|
|
28
|
-
MIN_CACHE_WRITE_RATIO: 0.5,
|
|
29
|
-
MIN_INPUT_SHARE_FRAC: 0.0003,
|
|
30
|
-
MAX_CADENCE_PER_MIN: 15,
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Run plausibility checks against a payload's raw_telemetry.
|
|
35
|
-
* Returns array of issues: { severity, code, detail }
|
|
36
|
-
*/
|
|
37
|
-
export function plausibilityCheck(rt, window) {
|
|
38
|
-
const out = [];
|
|
39
|
-
const pillars =
|
|
40
|
-
rt.tokens_input_fresh +
|
|
41
|
-
rt.tokens_output +
|
|
42
|
-
rt.tokens_cache_read +
|
|
43
|
-
rt.tokens_cache_creation;
|
|
44
|
-
|
|
45
|
-
if (rt.tokens_total > 0) {
|
|
46
|
-
const tol = Math.max(
|
|
47
|
-
1,
|
|
48
|
-
rt.tokens_total * GATE_LIMITS.TOTALS_TOLERANCE_FRAC,
|
|
49
|
-
);
|
|
50
|
-
if (Math.abs(pillars - rt.tokens_total) > tol) {
|
|
51
|
-
out.push({
|
|
52
|
-
severity: "reject",
|
|
53
|
-
code: "totals_inconsistent",
|
|
54
|
-
detail: `Σ4 pillars (${pillars}) ≠ tokens_total (${rt.tokens_total})`,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
if (rt.sessions_count === 0) {
|
|
58
|
-
out.push({
|
|
59
|
-
severity: "reject",
|
|
60
|
-
code: "tokens_without_sessions",
|
|
61
|
-
detail: `tokens_total ${rt.tokens_total} with sessions_count 0`,
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
if (rt.turns_total < rt.sessions_count) {
|
|
66
|
-
out.push({
|
|
67
|
-
severity: "reject",
|
|
68
|
-
code: "turns_lt_sessions",
|
|
69
|
-
detail: `turns_total ${rt.turns_total} < sessions_count ${rt.sessions_count}`,
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
if (rt.tokens_output > 0 && rt.turns_total === 0) {
|
|
73
|
-
out.push({
|
|
74
|
-
severity: "reject",
|
|
75
|
-
code: "output_without_turns",
|
|
76
|
-
detail: `tokens_output ${rt.tokens_output} with turns_total 0`,
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (window) {
|
|
81
|
-
const spanMin =
|
|
82
|
-
(Date.parse(window.end) - Date.parse(window.start)) / 60_000;
|
|
83
|
-
if (Number.isFinite(spanMin) && rt.active_minutes_est > spanMin + 1) {
|
|
84
|
-
out.push({
|
|
85
|
-
severity: "flag",
|
|
86
|
-
code: "active_exceeds_window",
|
|
87
|
-
detail: `active_minutes_est ${rt.active_minutes_est} > window span ${Math.round(spanMin)}m`,
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const outPerMin = rt.tokens_output / Math.max(rt.active_minutes_est, 1);
|
|
93
|
-
if (outPerMin > GATE_LIMITS.MAX_OUTPUT_TOKENS_PER_MIN) {
|
|
94
|
-
out.push({
|
|
95
|
-
severity: "flag",
|
|
96
|
-
code: "implausible_output_rate",
|
|
97
|
-
detail: `${Math.round(outPerMin)} output tok/min > ${GATE_LIMITS.MAX_OUTPUT_TOKENS_PER_MIN}`,
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Cross-field ratio checks (defense-in-depth — also in the plausibility gate)
|
|
102
|
-
// Bounds tightened (deviewreview3): original 100:1 + 50/min were too loose.
|
|
103
|
-
if (rt.tokens_cache_read > 1_000 && rt.tokens_cache_creation === 0) {
|
|
104
|
-
out.push({
|
|
105
|
-
severity: "flag",
|
|
106
|
-
code: "cache_without_creation",
|
|
107
|
-
detail: `${rt.tokens_cache_read} cache_read with 0 cache_creation (impossible cascade)`,
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
if (
|
|
111
|
-
rt.tokens_cache_creation > 0 &&
|
|
112
|
-
rt.tokens_cache_read / rt.tokens_cache_creation >
|
|
113
|
-
GATE_LIMITS.MAX_CACHE_REUSE_RATIO
|
|
114
|
-
) {
|
|
115
|
-
out.push({
|
|
116
|
-
severity: "flag",
|
|
117
|
-
code: "extreme_cache_ratio",
|
|
118
|
-
detail: `cache_read/cache_creation = ${(rt.tokens_cache_read / rt.tokens_cache_creation).toFixed(1)}:1 (real max ~30:1)`,
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
if (
|
|
122
|
-
rt.tokens_output > 1_000 &&
|
|
123
|
-
rt.tokens_cache_creation / rt.tokens_output <
|
|
124
|
-
GATE_LIMITS.MIN_CACHE_WRITE_RATIO
|
|
125
|
-
) {
|
|
126
|
-
out.push({
|
|
127
|
-
severity: "flag",
|
|
128
|
-
code: "low_cache_write_ratio",
|
|
129
|
-
detail: `cache_creation/output = ${(rt.tokens_cache_creation / rt.tokens_output).toFixed(2)}:1 (real min ~1.5:1)`,
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
if (
|
|
133
|
-
pillars > 10_000 &&
|
|
134
|
-
rt.tokens_input_fresh / pillars < GATE_LIMITS.MIN_INPUT_SHARE_FRAC
|
|
135
|
-
) {
|
|
136
|
-
out.push({
|
|
137
|
-
severity: "flag",
|
|
138
|
-
code: "implausible_input_share",
|
|
139
|
-
detail: `input is ${((rt.tokens_input_fresh / pillars) * 100).toFixed(3)}% of total (real min ~0.3%)`,
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
if (
|
|
143
|
-
rt.active_minutes_est > 0 &&
|
|
144
|
-
rt.turns_total / rt.active_minutes_est > GATE_LIMITS.MAX_CADENCE_PER_MIN
|
|
145
|
-
) {
|
|
146
|
-
out.push({
|
|
147
|
-
severity: "flag",
|
|
148
|
-
code: "implausible_cadence",
|
|
149
|
-
detail: `${(rt.turns_total / rt.active_minutes_est).toFixed(1)} turns/min (real: 0.5-10)`,
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return out;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// ── full preflight (plausibility only — NO battery) ─────────────────────────
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Run preflight plausibility checks against a payload. Returns:
|
|
160
|
-
* { pass: true, issues: [] } — clean submission
|
|
161
|
-
* { pass: false, issues: [...], wouldDowngrade } — would be flagged/rejected
|
|
162
|
-
*
|
|
163
|
-
* NOTE: this only mirrors the PUBLIC plausibility gate. The server runs
|
|
164
|
-
* additional proprietary checks (the battery) that this preflight does NOT
|
|
165
|
-
* replicate. A "pass" here means "passes the plausibility gate," not
|
|
166
|
-
* "guaranteed to pass all server gates."
|
|
167
|
-
*/
|
|
168
|
-
export function preflight(payload) {
|
|
169
|
-
const issues = plausibilityCheck(payload.raw_telemetry, payload.window);
|
|
170
|
-
|
|
171
|
-
const hasReject = issues.some((i) => i.severity === "reject");
|
|
172
|
-
const hasFlag = issues.some((i) => i.severity === "flag");
|
|
173
|
-
|
|
174
|
-
return {
|
|
175
|
-
pass: issues.length === 0,
|
|
176
|
-
wouldReject: hasReject,
|
|
177
|
-
wouldDowngrade: hasFlag,
|
|
178
|
-
issues,
|
|
179
|
-
summary: hasReject
|
|
180
|
-
? `REJECT: ${issues
|
|
181
|
-
.filter((i) => i.severity === "reject")
|
|
182
|
-
.map((i) => i.code)
|
|
183
|
-
.join(", ")}`
|
|
184
|
-
: hasFlag
|
|
185
|
-
? `FLAG (verified → flagged, not ranked): ${issues
|
|
186
|
-
.filter((i) => i.severity === "flag")
|
|
187
|
-
.map((i) => i.code)
|
|
188
|
-
.join(", ")}`
|
|
189
|
-
: "clean — passes plausibility gate (server runs additional proprietary checks)",
|
|
190
|
-
};
|
|
191
|
-
}
|
|
1
|
+
export * from './submit/preflight.mjs';
|