sigrank 0.15.4 → 0.16.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 +54 -7
- package/adapters.mjs +24 -11
- package/badges.mjs +163 -0
- package/cascade.mjs +69 -0
- package/package.json +4 -2
- package/preflight.mjs +119 -0
- package/submit.mjs +38 -0
- package/tokenpull.mjs +13 -1
- package/tools.mjs +1070 -2
- package/tui.mjs +2 -2
package/submit.mjs
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// the display signa_rate reflects true session shape (the ranked Υ is already exact).
|
|
11
11
|
|
|
12
12
|
import { snapshotHash, signPayload } from './sign.mjs'
|
|
13
|
+
import { preflight } from './preflight.mjs'
|
|
13
14
|
|
|
14
15
|
const WINDOW_TYPE = { '7d': '7d', '30d': '30d', '90d': '90d', all: 'all_time' }
|
|
15
16
|
const WINDOW_SPAN_DAYS = { '7d': 7, '30d': 30, '90d': 90, all_time: 3650 }
|
|
@@ -99,6 +100,14 @@ export function buildPayload(windowKey, pillars, messages, identity, platform, o
|
|
|
99
100
|
public_key: identity.public_key,
|
|
100
101
|
},
|
|
101
102
|
}
|
|
103
|
+
|
|
104
|
+
// Cascade Report block (Phase 1 — mode detection + badges + health).
|
|
105
|
+
// Computed locally (pure math), submitted alongside the 4 token pillars.
|
|
106
|
+
// The server stores it as-is — does NOT recompute modes.
|
|
107
|
+
// Weekly granularity is the privacy boundary — daily modes never leave the machine.
|
|
108
|
+
if (opts.report && typeof opts.report === 'object') {
|
|
109
|
+
payload.report = opts.report
|
|
110
|
+
}
|
|
102
111
|
payload.agent.snapshot_hash = snapshotHash(payload)
|
|
103
112
|
return payload
|
|
104
113
|
}
|
|
@@ -128,6 +137,33 @@ export async function submitSignedWindow(windowKey, pillars, messages, identity,
|
|
|
128
137
|
return fetch(url, { ...init, signal: init.signal || ctrl.signal }).finally(() => clearTimeout(timer))
|
|
129
138
|
})
|
|
130
139
|
const payload = buildPayload(windowKey, pillars, messages, identity, opts.platform || 'claude', opts)
|
|
140
|
+
|
|
141
|
+
// Preflight: run the same anti-gaming checks the server will run, locally.
|
|
142
|
+
// If the payload would be rejected or downgraded, warn the operator BEFORE
|
|
143
|
+
// submitting. The server always runs its own checks — this is a preview.
|
|
144
|
+
const pre = preflight(payload)
|
|
145
|
+
if (!pre.pass && !opts.skipPreflight) {
|
|
146
|
+
if (pre.wouldReject) {
|
|
147
|
+
return {
|
|
148
|
+
status: 'preflight_rejected',
|
|
149
|
+
window: WINDOW_TYPE[windowKey] || windowKey,
|
|
150
|
+
preflight: pre,
|
|
151
|
+
detail: `Submission would be REJECTED by the server: ${pre.summary}. Fix the issue or re-run with skipPreflight.`,
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Flags downgrade verified → flagged (not ranked). Warn but don't block
|
|
155
|
+
// unless the caller explicitly asked for strict mode.
|
|
156
|
+
if (opts.strictPreflight) {
|
|
157
|
+
return {
|
|
158
|
+
status: 'preflight_flagged',
|
|
159
|
+
window: WINDOW_TYPE[windowKey] || windowKey,
|
|
160
|
+
preflight: pre,
|
|
161
|
+
detail: `Submission would be DOWNGRADED (not ranked): ${pre.summary}. Fix the issue or re-run with skipPreflight.`,
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Non-strict: attach the preflight warning to the result but proceed.
|
|
165
|
+
}
|
|
166
|
+
|
|
131
167
|
const signature = signPayload(payload, identity.private_key_pkcs8_b64)
|
|
132
168
|
|
|
133
169
|
// Dry run: build + sign exactly as a real publish, then STOP before the POST.
|
|
@@ -140,6 +176,7 @@ export async function submitSignedWindow(windowKey, pillars, messages, identity,
|
|
|
140
176
|
would_post: `${apiBase}/api/v1/snapshots`,
|
|
141
177
|
payload,
|
|
142
178
|
signature,
|
|
179
|
+
preflight: pre,
|
|
143
180
|
detail: 'Nothing sent. Re-run without dry_run to publish.',
|
|
144
181
|
}
|
|
145
182
|
}
|
|
@@ -177,5 +214,6 @@ export async function submitSignedWindow(windowKey, pillars, messages, identity,
|
|
|
177
214
|
snapshot_hash: payload.agent.snapshot_hash,
|
|
178
215
|
reason: res.ok ? null : ack.reason || ack.status || `http_${res.status}`,
|
|
179
216
|
detail: ack.detail ?? null,
|
|
217
|
+
preflight: pre,
|
|
180
218
|
}
|
|
181
219
|
}
|
package/tokenpull.mjs
CHANGED
|
@@ -164,6 +164,14 @@ export async function tokenpull({ adapter = claudeAdapter, root, now } = {}) {
|
|
|
164
164
|
|
|
165
165
|
const windows = WINDOWS.map((w) => {
|
|
166
166
|
const cutoff = w.days === Infinity ? -Infinity : nowMs - w.days * DAY_MS
|
|
167
|
+
// NULL-TIMESTAMP WINDOW ASYMMETRY: the `all` window returns true unconditionally,
|
|
168
|
+
// so records whose adapter cannot supply a timestamp (ts: null or unparseable)
|
|
169
|
+
// appear in all-time but disappear from 7d/30d/90d. This is intentional — we do
|
|
170
|
+
// not fabricate timestamps. Session-granularity adapters (goose/droid/hermes)
|
|
171
|
+
// typically have created_at/updated_at, but if those columns are absent the row
|
|
172
|
+
// is all-time-only. A file-mtime fallback was considered but rejected: mtime
|
|
173
|
+
// reflects the log file, not the session, and would misattribute old sessions
|
|
174
|
+
// to recent file-touch times.
|
|
167
175
|
const inWin = msgs.filter((m) => {
|
|
168
176
|
if (w.days === Infinity) return true
|
|
169
177
|
const t = m.ts ? Date.parse(m.ts) : NaN
|
|
@@ -250,6 +258,8 @@ export async function tokenpullCodex({ adapter = codexAdapter, root, now, ioRati
|
|
|
250
258
|
|
|
251
259
|
const windows = WINDOWS.map((w) => {
|
|
252
260
|
const cutoff = w.days === Infinity ? -Infinity : nowMs - w.days * DAY_MS
|
|
261
|
+
// NULL-TIMESTAMP WINDOW ASYMMETRY: see tokenpull() above — same behavior.
|
|
262
|
+
// Records without a parseable ts appear in `all` only, not in 7d/30d/90d.
|
|
253
263
|
const inWin = recs.filter((m) => {
|
|
254
264
|
if (w.days === Infinity) return true
|
|
255
265
|
const t = m.ts ? Date.parse(m.ts) : NaN
|
|
@@ -284,7 +294,9 @@ export async function tokenpullAny(platform, opts = {}) {
|
|
|
284
294
|
let ioRatio = opts.ioRatio || 2.0
|
|
285
295
|
if (!opts.ioRatio) {
|
|
286
296
|
try {
|
|
287
|
-
|
|
297
|
+
// Forward root/now from opts so the Beta-ratio derivation is testable
|
|
298
|
+
// (without this, it always reads the real ~/.claude and can't be injected).
|
|
299
|
+
const c = await tokenpull({ adapter: claudeAdapter, root: opts.root, now: opts.now })
|
|
288
300
|
const all = c.windows.find((w) => w.window === 'all')
|
|
289
301
|
if (all && all.pillars.output > 0) ioRatio = all.pillars.input / all.pillars.output
|
|
290
302
|
} catch { /* no Claude data → Alpha 2.0 */ }
|