sigrank 1.0.11 → 1.0.14
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 +11 -10
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/presentation/tui.mjs +39 -0
- package/proxy.mjs +10 -1
- package/tools/rank-windows.mjs +2 -2
- package/tools/submit-paste.mjs +14 -63
- package/tools/tokenpull-submit.mjs +21 -106
package/README.md
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
#
|
|
1
|
+
# SigRank SignalAF — `sigrank` CLI/MCP
|
|
2
2
|
|
|
3
|
-
> **
|
|
4
|
-
>
|
|
5
|
-
>
|
|
3
|
+
> **SignalAF** is the public brand. **SigRank** is the leaderboard and proof surface.
|
|
4
|
+
> The `sigrank` package reads local AI session logs, derives your token cascade,
|
|
5
|
+
> and publishes to the board at [signalaf.com](https://signalaf.com).
|
|
6
|
+
> _Four token counts only. Never prompts or code._
|
|
6
7
|
|
|
7
8
|
<div align="center">
|
|
8
9
|
|
|
9
10
|
<p><img src="./.github/assets/og-card.png" alt="SigRank SignalAF — AI operator evaluation and token cascade efficiency leaderboard" width="800"></p>
|
|
10
11
|
|
|
11
|
-
**
|
|
12
|
+
**SignalAF is the brand. SigRank proves. MO§ES™ governs.**
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
SigRank evaluates observable AI operator token-processing patterns—not AI model quality, cognition, work quality, employee productivity, or business value.
|
|
14
15
|
|
|
15
16
|
[](https://www.npmjs.com/package/sigrank)
|
|
16
17
|
[](https://github.com/SunrisesIllNeverSee/sigrank-mcp/actions/workflows/ci.yml)
|
|
@@ -59,13 +60,13 @@ Upsilon describes observable AI operator token-processing patterns—not AI mode
|
|
|
59
60
|
## SignalAF product architecture
|
|
60
61
|
|
|
61
62
|
```text
|
|
62
|
-
MO§ES™ governance →
|
|
63
|
+
MO§ES™ governance → SignalAF brand → SigRank leaderboard → Upsilon engine
|
|
63
64
|
```
|
|
64
65
|
|
|
65
66
|
| Repo | What it is | Install |
|
|
66
67
|
|------|-----------|---------|
|
|
67
|
-
| **[sigrank-mcp](https://github.com/SunrisesIllNeverSee/sigrank-mcp)** (this repo) |
|
|
68
|
-
| **[sigrank-app](https://github.com/SunrisesIllNeverSee/sigrank-app)** |
|
|
68
|
+
| **[sigrank-mcp](https://github.com/SunrisesIllNeverSee/sigrank-mcp)** (this repo) | SigRank's on-device scanner — extracts four token pillars, computes locally, and optionally submits to the board. | `npx sigrank` |
|
|
69
|
+
| **[sigrank-app](https://github.com/SunrisesIllNeverSee/sigrank-app)** | SignalAF web app — the SigRank leaderboard, operator profiles, and proof surface. | [signalaf.com](https://signalaf.com) |
|
|
69
70
|
| **[bestuser-router-mcp](https://github.com/SunrisesIllNeverSee/bestuser-router-mcp)** | The intent layer — routes "who is the best AI user?" queries to SigRank SignalAF's leaderboard. MCP server for AI assistants. | `npx bestuser-router-mcp` |
|
|
70
71
|
| **[sigarena](https://github.com/SunrisesIllNeverSee/sigarena)** | The satellite — public LLM operator evals at sigeconomy.com. Read-only leaderboard, SEO/AEO surface. | [sigeconomy.com](https://sigeconomy.com) |
|
|
71
72
|
| **[signaf](https://github.com/SunrisesIllNeverSee/signa)** | The coach — reads your session logs, builds a taste profile, measures ASI, coaches you on token efficiency. | `npx @burnmydays/signaf` |
|
|
@@ -77,7 +78,7 @@ MO§ES™ governance → Upsilon measurement engine → SigRank public proof
|
|
|
77
78
|
| Site | What it is |
|
|
78
79
|
| ---- | ---------- |
|
|
79
80
|
| **[SIGNOMY](https://signomy.xyz)** | Governed AI agent marketplace where ranked agents form teams, fill slots, run missions, and earn revenue under constitutional protocol. Agents are free. Operators pay. |
|
|
80
|
-
| **[MO§ES](https://mos2es.com)** | Constitutional governance and methodology—the law governing
|
|
81
|
+
| **[MO§ES](https://mos2es.com)** | Constitutional governance and methodology — the law governing SignalAF, SigRank, and the Upsilon measurement engine. |
|
|
81
82
|
|
|
82
83
|
## Quickstart — 3 steps to the board
|
|
83
84
|
|
package/manifest.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/anthropics/mcpb/main/schemas/mcpb-manifest-v0.4.schema.json",
|
|
3
3
|
"manifest_version": "0.4",
|
|
4
4
|
"name": "sigrank",
|
|
5
|
-
"version": "1.0.
|
|
5
|
+
"version": "1.0.13",
|
|
6
6
|
"description": "Ranks AI coding operators by token cascade efficiency (Yield = cache_read \u00d7 output / input\u00b2). 25 tools including SigRank Standard record, yield calculation, local token usage pulls, signed submission, and tokscale-powered analytics (market share, developer profiles, model trends, cost analysis, device profiles, MCP usage, competitive intelligence). Privacy-first: runs locally, only four token counts leave the machine.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "SunrisesIllNeverSee",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sigrank",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.14",
|
|
4
4
|
"author": "SunrisesIllNeverSee (https://github.com/SunrisesIllNeverSee)",
|
|
5
5
|
"description": "Upsilon measurement engine distributed through the sigrank CLI/MCP compatibility package, with SigRank public leaderboard tools.",
|
|
6
6
|
"mcpName": "io.github.SunrisesIllNeverSee/sigrank-mcp",
|
package/presentation/tui.mjs
CHANGED
|
@@ -2335,6 +2335,45 @@ export async function runTui({
|
|
|
2335
2335
|
return;
|
|
2336
2336
|
}
|
|
2337
2337
|
|
|
2338
|
+
// ── Terminal size guard ────────────────────────────────────────────────────
|
|
2339
|
+
// The TUI renders a tab bar + content + footer in a locked alternate-screen
|
|
2340
|
+
// frame. If the terminal is too small, the footer smears off the bottom and
|
|
2341
|
+
// content rows get clamped — confusing, not useful. Refuse to render until
|
|
2342
|
+
// there's enough room, matching the PostHog wizard's pattern: show a clear
|
|
2343
|
+
// message with current vs required dimensions, and re-check on resize.
|
|
2344
|
+
const MIN_COLS = 80;
|
|
2345
|
+
const MIN_ROWS = 24;
|
|
2346
|
+
const tooSmall = () =>
|
|
2347
|
+
(process.stdout.columns || 80) < MIN_COLS ||
|
|
2348
|
+
(process.stdout.rows || 24) < MIN_ROWS;
|
|
2349
|
+
if (tooSmall() && process.stdout.isTTY) {
|
|
2350
|
+
const renderSizeMsg = () => {
|
|
2351
|
+
const cols = process.stdout.columns || 80;
|
|
2352
|
+
const rows = process.stdout.rows || 24;
|
|
2353
|
+
process.stdout.write(`${ESC}H${ESC}2J`);
|
|
2354
|
+
process.stdout.write(
|
|
2355
|
+
`\n SigRank TUI needs more room to display required content.\n` +
|
|
2356
|
+
` Please make this terminal bigger.\n\n` +
|
|
2357
|
+
` Currently ${cols}×${rows}, needs at least ${MIN_COLS}×${MIN_ROWS}.\n\n` +
|
|
2358
|
+
` Waiting for resize… (Ctrl+C to cancel)\n`,
|
|
2359
|
+
);
|
|
2360
|
+
};
|
|
2361
|
+
renderSizeMsg();
|
|
2362
|
+
await new Promise((resolve) => {
|
|
2363
|
+
const onResize = () => {
|
|
2364
|
+
if (!tooSmall()) {
|
|
2365
|
+
process.stdout.off("resize", onResize);
|
|
2366
|
+
resolve();
|
|
2367
|
+
} else {
|
|
2368
|
+
renderSizeMsg();
|
|
2369
|
+
}
|
|
2370
|
+
};
|
|
2371
|
+
process.stdout.on("resize", onResize);
|
|
2372
|
+
});
|
|
2373
|
+
// Clear the size-warning message before entering the alt screen
|
|
2374
|
+
process.stdout.write(`${ESC}H${ESC}2J`);
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2338
2377
|
write(ENTER_ALT); // switch to alternate screen — original terminal state preserved on exit
|
|
2339
2378
|
write(CLEAR_SB); // FIXED-WINDOW (2026-06-27): clear scrollback so nothing is above the TUI
|
|
2340
2379
|
write(HIDE);
|
package/proxy.mjs
CHANGED
|
@@ -360,7 +360,16 @@ export async function startProxy({
|
|
|
360
360
|
return;
|
|
361
361
|
}
|
|
362
362
|
|
|
363
|
-
const upstreamUrl = new URL(
|
|
363
|
+
const upstreamUrl = new URL(parsedUrl.pathname + parsedUrl.search, upstreamBase);
|
|
364
|
+
// Defense-in-depth: verify the constructed URL origin matches the intended
|
|
365
|
+
// upstream. This prevents an absolute-form request target (e.g.
|
|
366
|
+
// "http://attacker.example/v1/messages") from overriding upstreamBase via
|
|
367
|
+
// new URL(req.url, base) — which would forward credentials to an attacker.
|
|
368
|
+
if (upstreamUrl.origin !== new URL(upstreamBase).origin) {
|
|
369
|
+
res.writeHead(400, { "content-type": "text/plain; charset=utf-8" });
|
|
370
|
+
res.end("Refused to proxy to unexpected origin\n");
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
364
373
|
const transport = upstreamUrl.protocol === "http:" ? http : https;
|
|
365
374
|
const tracker = createUsageTracker({
|
|
366
375
|
backend: route.backend,
|
package/tools/rank-windows.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import { MAX_INPUT, withParseWarnings } from "./_helpers.mjs";
|
|
|
10
10
|
export const TOOL_DEF = {
|
|
11
11
|
name: "rank_windows",
|
|
12
12
|
description:
|
|
13
|
-
"Rank all four time windows (7d/30d/90d/all-time) in one call from a dashboard paste — paste the full table from ccusage, tokscale, or the Claude Max usage dashboard and get the cascade (Υ, SNR, Leverage, Velocity, 10xDEV, class, card) for each window. Each window is parsed and scored independently. Named keys required (input/output/cacheCreate/cacheRead); positional order is NOT safe here (dashboards list cache_read before cache_create — see WINDOWED_PROFILES gotcha). Omit windows you don't have — partial input is allowed (1–4 windows). Does NOT submit to the board; use tokenpull_submit for zero-paste
|
|
13
|
+
"Rank all four time windows (7d/30d/90d/all-time) in one call from a dashboard paste — paste the full table from ccusage, tokscale, or the Claude Max usage dashboard and get the cascade (Υ, SNR, Leverage, Velocity, 10xDEV, class, card) for each window. Each window is parsed and scored independently. Named keys required (input/output/cacheCreate/cacheRead); positional order is NOT safe here (dashboards list cache_read before cache_create — see WINDOWED_PROFILES gotcha). Omit windows you don't have — partial input is allowed (1–4 windows). Does NOT submit to the board; use tokenpull_submit for a local zero-paste preview, or submit_verified to publish via the enrolled-device path.",
|
|
14
14
|
annotations: {
|
|
15
15
|
title: "Rank all time windows",
|
|
16
16
|
...ANNOTATIONS.readOnlyHint,
|
|
@@ -115,6 +115,6 @@ export async function handleRankWindows(args) {
|
|
|
115
115
|
return {
|
|
116
116
|
windows,
|
|
117
117
|
source_tool: sourceTool,
|
|
118
|
-
note: "Local preview only — use
|
|
118
|
+
note: "Local preview only — use submit_verified to publish to the board.",
|
|
119
119
|
};
|
|
120
120
|
}
|
package/tools/submit-paste.mjs
CHANGED
|
@@ -7,17 +7,15 @@ import { narrate } from "../presentation/narrate.mjs";
|
|
|
7
7
|
import { SUBMIT_OUTPUT, ANNOTATIONS } from "./_schemas.mjs";
|
|
8
8
|
import {
|
|
9
9
|
MAX_INPUT,
|
|
10
|
-
uploadStamp,
|
|
11
10
|
withParseWarnings,
|
|
12
11
|
} from "./_helpers.mjs";
|
|
13
|
-
import { TERMS_VERSION, PRIVACY_VERSION, isRankedAck } from "../lib/constants.mjs";
|
|
14
12
|
|
|
15
13
|
export const TOOL_DEF = {
|
|
16
14
|
name: "submit_paste",
|
|
17
15
|
description:
|
|
18
|
-
"Ranks a paste of token counts
|
|
16
|
+
"Ranks a paste of token counts locally and shows the cascade result (yield, leverage, velocity, class, card). This is a PREVIEW-ONLY tool — it does not publish to the board. The board's /api/v1/ingest-paste endpoint now requires an authenticated Supabase session, which MCP tools do not carry. To publish to the leaderboard, use submit_verified (which signs and posts to /api/v1/snapshots via the enrolled-device path) or submit directly through the signalaf.com web UI. Use this when you have token counts from ccusage or a dashboard and want to see your score instantly. Do NOT use this if you want to pull your local usage automatically — use tokenpull_submit for the zero-paste flow. Do NOT use this for multi-window dashboard pastes — use rank_windows to rank them first.",
|
|
19
17
|
annotations: {
|
|
20
|
-
title: "
|
|
18
|
+
title: "Preview paste ranking",
|
|
21
19
|
...ANNOTATIONS.destructiveHint,
|
|
22
20
|
...ANNOTATIONS.idempotentHint,
|
|
23
21
|
...ANNOTATIONS.openWorldHint,
|
|
@@ -28,17 +26,17 @@ export const TOOL_DEF = {
|
|
|
28
26
|
text: {
|
|
29
27
|
type: "string",
|
|
30
28
|
description:
|
|
31
|
-
'Token counts to rank
|
|
29
|
+
'Token counts to rank. Two formats: (1) JSON {"input":N,"output":N,"cacheCreate":N,"cacheRead":N} from ccusage (preferred), or (2) four whitespace-separated numbers: input output cacheCreate cacheRead. Example: {"input":1000000,"output":500000,"cacheCreate":50000,"cacheRead":800000}',
|
|
32
30
|
},
|
|
33
31
|
codename: {
|
|
34
32
|
type: "string",
|
|
35
33
|
description:
|
|
36
|
-
'Operator codename
|
|
34
|
+
'Operator codename for the ranking card display (e.g. "Ghost Falcon"). Optional — used only for the local preview card, not for board submission.',
|
|
37
35
|
},
|
|
38
36
|
},
|
|
39
37
|
required: ["text"],
|
|
40
38
|
description:
|
|
41
|
-
"Requires token counts (text).
|
|
39
|
+
"Requires token counts (text). Returns a local preview only — does not publish to the board.",
|
|
42
40
|
},
|
|
43
41
|
outputSchema: SUBMIT_OUTPUT,
|
|
44
42
|
};
|
|
@@ -53,69 +51,22 @@ export async function handleSubmitPaste(args, ctx) {
|
|
|
53
51
|
detail: `text exceeds ${MAX_INPUT} chars (${args.text.length}). Paste only the token-count table, not full output.`,
|
|
54
52
|
};
|
|
55
53
|
}
|
|
56
|
-
// Local preview
|
|
54
|
+
// Local preview — this tool is preview-only. The board's /api/v1/ingest-paste
|
|
55
|
+
// endpoint now requires an authenticated Supabase session, which MCP tools
|
|
56
|
+
// do not carry. Use submit_verified for authenticated board submission.
|
|
57
57
|
const pillars = parsePillars(args.text);
|
|
58
58
|
const c = withParseWarnings(pillars, cascade(pillars));
|
|
59
59
|
const codename = String(args?.codename || "").trim();
|
|
60
60
|
const card = narrate(c, codename || "This operator");
|
|
61
61
|
|
|
62
|
-
// No codename → cannot publish (the board endpoint requires it). Fail fast at the
|
|
63
|
-
// tool boundary with a clear message instead of an opaque server 400.
|
|
64
|
-
if (!codename) {
|
|
65
|
-
return {
|
|
66
|
-
...c,
|
|
67
|
-
card,
|
|
68
|
-
submission: {
|
|
69
|
-
status: "not_submitted",
|
|
70
|
-
reason: "codename_required",
|
|
71
|
-
detail:
|
|
72
|
-
"Pass a codename to publish to the board. Showing local preview only.",
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Privacy: send ONLY the 4 parsed token numbers (canonical "input output
|
|
78
|
-
// cacheCreate cacheRead" form), never the raw user text. parsePillars above
|
|
79
|
-
// already validated the input — we reconstruct from the parsed pillars so
|
|
80
|
-
// no conversation content or prose can leak to the server. The server's
|
|
81
|
-
// ingestMeta() re-parses these 4 numbers authoritatively (Strategy 3:
|
|
82
|
-
// four bare numbers). This makes every submit path — signed and paste —
|
|
83
|
-
// send only token counts.
|
|
84
|
-
const canonicalPaste = `${pillars.input} ${pillars.output} ${pillars.cacheCreate} ${pillars.cacheRead}`;
|
|
85
|
-
const stamp = uploadStamp({
|
|
86
|
-
codename,
|
|
87
|
-
pillars: c.pillars,
|
|
88
|
-
source: "web_paste",
|
|
89
|
-
});
|
|
90
|
-
const res = await ctx.doFetch(`${ctx.apiBase}/api/v1/ingest-paste`, {
|
|
91
|
-
method: "POST",
|
|
92
|
-
headers: {
|
|
93
|
-
"content-type": "application/json",
|
|
94
|
-
accept: "application/json",
|
|
95
|
-
},
|
|
96
|
-
body: JSON.stringify({
|
|
97
|
-
codename,
|
|
98
|
-
raw_paste: canonicalPaste,
|
|
99
|
-
consent_acknowledged: true,
|
|
100
|
-
terms_version: TERMS_VERSION,
|
|
101
|
-
privacy_version: PRIVACY_VERSION,
|
|
102
|
-
...stamp,
|
|
103
|
-
}),
|
|
104
|
-
});
|
|
105
|
-
let ack;
|
|
106
|
-
try {
|
|
107
|
-
ack = await res.json();
|
|
108
|
-
} catch {
|
|
109
|
-
ack = {
|
|
110
|
-
status: "error",
|
|
111
|
-
detail: `HTTP ${res.status} (non-JSON response)`,
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
const ranked = isRankedAck(res, ack);
|
|
115
62
|
return {
|
|
116
63
|
...c,
|
|
117
64
|
card,
|
|
118
|
-
|
|
119
|
-
|
|
65
|
+
submission: {
|
|
66
|
+
status: "not_submitted",
|
|
67
|
+
reason: "preview_only",
|
|
68
|
+
detail:
|
|
69
|
+
"submit_paste is preview-only. The board requires an authenticated session. Use submit_verified (enrolled device) or the signalaf.com web UI to publish.",
|
|
70
|
+
},
|
|
120
71
|
};
|
|
121
72
|
}
|
|
@@ -7,19 +7,15 @@ import { narrate } from "../presentation/narrate.mjs";
|
|
|
7
7
|
import { ALL_PLATFORMS } from "../adapters/index.mjs";
|
|
8
8
|
import { SUBMIT_OUTPUT, ANNOTATIONS } from "./_schemas.mjs";
|
|
9
9
|
import {
|
|
10
|
-
MAX_INPUT,
|
|
11
10
|
pullByPlatform,
|
|
12
|
-
uploadStamp,
|
|
13
|
-
WINDOW_TYPE,
|
|
14
11
|
} from "./_helpers.mjs";
|
|
15
|
-
import { TERMS_VERSION, PRIVACY_VERSION, isRankedAck } from "../lib/constants.mjs";
|
|
16
12
|
|
|
17
13
|
export const TOOL_DEF = {
|
|
18
14
|
name: "tokenpull_submit",
|
|
19
15
|
description:
|
|
20
|
-
"Pull your LOCAL token usage from session logs
|
|
16
|
+
"Pull your LOCAL token usage from session logs and compute the cascade per window — the zero-paste preview flow. Reads the four canonical pillars (input, output, cacheCreate, cacheRead) per window from your local logs and computes yield, leverage, velocity, class, and card. This is a PREVIEW-ONLY tool — it does not publish to the board. The board's /api/v1/ingest-paste endpoint now requires an authenticated Supabase session, which MCP tools do not carry. To publish to the leaderboard, use submit_verified (which signs and posts to /api/v1/snapshots via the enrolled-device path) or submit directly through the signalaf.com web UI. Token-only — no prompt content is read or transmitted.",
|
|
21
17
|
annotations: {
|
|
22
|
-
title: "Pull and
|
|
18
|
+
title: "Pull and preview tokens",
|
|
23
19
|
...ANNOTATIONS.destructiveHint,
|
|
24
20
|
...ANNOTATIONS.idempotentHint,
|
|
25
21
|
...ANNOTATIONS.openWorldHint,
|
|
@@ -30,13 +26,13 @@ export const TOOL_DEF = {
|
|
|
30
26
|
codename: {
|
|
31
27
|
type: "string",
|
|
32
28
|
description:
|
|
33
|
-
'Operator codename
|
|
29
|
+
'Operator codename for the ranking card display (e.g. "Iron Lotus"). Optional — used only for the local preview card, not for board submission.',
|
|
34
30
|
},
|
|
35
31
|
window: {
|
|
36
32
|
type: "string",
|
|
37
33
|
enum: ["7d", "30d", "90d", "all"],
|
|
38
34
|
description:
|
|
39
|
-
'
|
|
35
|
+
'Preview only this time window (default: all 4 windows). Use "7d" for recent activity or "all" for all-time ranking.',
|
|
40
36
|
},
|
|
41
37
|
platform: {
|
|
42
38
|
type: "string",
|
|
@@ -49,15 +45,16 @@ export const TOOL_DEF = {
|
|
|
49
45
|
};
|
|
50
46
|
|
|
51
47
|
export async function handleTokenpullSubmit(args, ctx) {
|
|
52
|
-
// Pull local usage
|
|
53
|
-
//
|
|
54
|
-
//
|
|
48
|
+
// Pull local usage and compute the cascade per window. This tool is
|
|
49
|
+
// preview-only — the board's /api/v1/ingest-paste endpoint now requires
|
|
50
|
+
// an authenticated Supabase session, which MCP tools do not carry. Use
|
|
51
|
+
// submit_verified for authenticated board submission.
|
|
55
52
|
const codename = String(args?.codename || "").trim();
|
|
56
53
|
const platform = args?.platform || "claude";
|
|
57
54
|
|
|
58
55
|
// MULTI: same combined cross-platform cascade as submit_verified. Includes Devin
|
|
59
56
|
// (cloud, via tokscale). Aggregate every locally-detected platform's pillars per
|
|
60
|
-
// window
|
|
57
|
+
// window.
|
|
61
58
|
if (platform === "multi") {
|
|
62
59
|
const detected = [];
|
|
63
60
|
for (const p of ALL_PLATFORMS) {
|
|
@@ -102,58 +99,17 @@ export async function handleTokenpullSubmit(args, ctx) {
|
|
|
102
99
|
continue;
|
|
103
100
|
const c = cascade(sum);
|
|
104
101
|
const card = narrate(c, `${wk} multi`);
|
|
105
|
-
if (!codename) {
|
|
106
|
-
out.push({
|
|
107
|
-
window: wk,
|
|
108
|
-
pillars: sum,
|
|
109
|
-
cascade: c,
|
|
110
|
-
card,
|
|
111
|
-
submission: { status: "not_submitted", reason: "codename_required" },
|
|
112
|
-
});
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
const rawPaste = `${sum.input} ${sum.output} ${sum.cacheCreate} ${sum.cacheRead}`;
|
|
116
|
-
const windowType = WINDOW_TYPE[wk] || wk;
|
|
117
|
-
const stamp = uploadStamp({
|
|
118
|
-
codename,
|
|
119
|
-
window: windowType,
|
|
120
|
-
pillars: sum,
|
|
121
|
-
platform: "multi",
|
|
122
|
-
});
|
|
123
|
-
const res = await ctx.doFetch(`${ctx.apiBase}/api/v1/ingest-paste`, {
|
|
124
|
-
method: "POST",
|
|
125
|
-
headers: {
|
|
126
|
-
"content-type": "application/json",
|
|
127
|
-
accept: "application/json",
|
|
128
|
-
},
|
|
129
|
-
body: JSON.stringify({
|
|
130
|
-
codename,
|
|
131
|
-
raw_paste: rawPaste,
|
|
132
|
-
window_type: windowType,
|
|
133
|
-
telemetry: { platform: { primary: "multi" } },
|
|
134
|
-
consent_acknowledged: true,
|
|
135
|
-
terms_version: TERMS_VERSION,
|
|
136
|
-
privacy_version: PRIVACY_VERSION,
|
|
137
|
-
...stamp,
|
|
138
|
-
}),
|
|
139
|
-
});
|
|
140
|
-
let ack;
|
|
141
|
-
try {
|
|
142
|
-
ack = await res.json();
|
|
143
|
-
} catch {
|
|
144
|
-
ack = { status: "parse_error", httpStatus: res.status };
|
|
145
|
-
}
|
|
146
|
-
// Fix 4: was `ack?.ranked ?? ack?.accepted ?? false` — a looser predicate
|
|
147
|
-
// than the single-platform branch below, which would mark a non-persisted
|
|
148
|
-
// 202 as ranked. Now both branches use the shared isRankedAck() so every
|
|
149
|
-
// submit path agrees on what "ranked" means.
|
|
150
|
-
const ranked = isRankedAck(res, ack);
|
|
151
102
|
out.push({
|
|
152
103
|
window: wk,
|
|
153
104
|
pillars: sum,
|
|
154
105
|
cascade: c,
|
|
155
106
|
card,
|
|
156
|
-
submission: {
|
|
107
|
+
submission: {
|
|
108
|
+
status: "not_submitted",
|
|
109
|
+
reason: "preview_only",
|
|
110
|
+
detail:
|
|
111
|
+
"tokenpull_submit is preview-only. Use submit_verified (enrolled device) or the signalaf.com web UI to publish.",
|
|
112
|
+
},
|
|
157
113
|
});
|
|
158
114
|
}
|
|
159
115
|
return {
|
|
@@ -172,58 +128,17 @@ export async function handleTokenpullSubmit(args, ctx) {
|
|
|
172
128
|
for (const w of targets) {
|
|
173
129
|
const c = cascade(w.pillars);
|
|
174
130
|
const card = narrate(c, `${w.window} window`);
|
|
175
|
-
if (!codename) {
|
|
176
|
-
out.push({
|
|
177
|
-
window: w.window,
|
|
178
|
-
pillars: w.pillars,
|
|
179
|
-
cascade: c,
|
|
180
|
-
card,
|
|
181
|
-
submission: { status: "not_submitted", reason: "codename_required" },
|
|
182
|
-
});
|
|
183
|
-
continue;
|
|
184
|
-
}
|
|
185
|
-
// canonical pillars → "input output cacheCreate cacheRead" (the parser's 4-bare-number form)
|
|
186
|
-
const rawPaste = `${w.pillars.input} ${w.pillars.output} ${w.pillars.cacheCreate} ${w.pillars.cacheRead}`;
|
|
187
|
-
const windowType = WINDOW_TYPE[w.window] || w.window;
|
|
188
|
-
const stamp = uploadStamp({
|
|
189
|
-
codename,
|
|
190
|
-
window: windowType,
|
|
191
|
-
pillars: w.pillars,
|
|
192
|
-
platform: pulled.platform,
|
|
193
|
-
});
|
|
194
|
-
const res = await ctx.doFetch(`${ctx.apiBase}/api/v1/ingest-paste`, {
|
|
195
|
-
method: "POST",
|
|
196
|
-
headers: {
|
|
197
|
-
"content-type": "application/json",
|
|
198
|
-
accept: "application/json",
|
|
199
|
-
},
|
|
200
|
-
body: JSON.stringify({
|
|
201
|
-
codename,
|
|
202
|
-
raw_paste: rawPaste,
|
|
203
|
-
window_type: windowType,
|
|
204
|
-
telemetry: { platform: { primary: pulled.platform } },
|
|
205
|
-
consent_acknowledged: true,
|
|
206
|
-
terms_version: TERMS_VERSION,
|
|
207
|
-
privacy_version: PRIVACY_VERSION,
|
|
208
|
-
...stamp,
|
|
209
|
-
}),
|
|
210
|
-
});
|
|
211
|
-
let ack;
|
|
212
|
-
try {
|
|
213
|
-
ack = await res.json();
|
|
214
|
-
} catch {
|
|
215
|
-
ack = { status: "error", detail: `HTTP ${res.status} (non-JSON)` };
|
|
216
|
-
}
|
|
217
|
-
// ranked = actually on the board (verified + persisted), not just received — parity
|
|
218
|
-
// with submit_verified (submit/index.mjs). An unenrolled/revoked device gets 202 but is NEVER ranked.
|
|
219
|
-
const ranked = isRankedAck(res, ack);
|
|
220
131
|
out.push({
|
|
221
132
|
window: w.window,
|
|
222
133
|
pillars: w.pillars,
|
|
223
134
|
cascade: c,
|
|
224
135
|
card,
|
|
225
|
-
|
|
226
|
-
|
|
136
|
+
submission: {
|
|
137
|
+
status: "not_submitted",
|
|
138
|
+
reason: "preview_only",
|
|
139
|
+
detail:
|
|
140
|
+
"tokenpull_submit is preview-only. Use submit_verified (enrolled device) or the signalaf.com web UI to publish.",
|
|
141
|
+
},
|
|
227
142
|
});
|
|
228
143
|
}
|
|
229
144
|
return {
|