@helm-protocol/ttt-mcp 0.3.4 → 0.4.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.
package/README.md CHANGED
@@ -55,12 +55,12 @@ Claude workflow → [context compressed] → agents call pot_query(eventId)
55
55
  ### Claude Code
56
56
 
57
57
  ```bash
58
- claude mcp add ttt -- npx -y @helm-protocol/ttt-mcp@0.3.3
58
+ claude mcp add ttt -- npx -y @helm-protocol/ttt-mcp@0.4.0
59
59
  ```
60
60
 
61
61
  With an API key (raises the free limit to your plan's monthly quota):
62
62
  ```bash
63
- claude mcp add ttt -e TTT_API_KEY=your-key -- npx -y @helm-protocol/ttt-mcp@0.3.3
63
+ claude mcp add ttt -e TTT_API_KEY=your-key -- npx -y @helm-protocol/ttt-mcp@0.4.0
64
64
  ```
65
65
 
66
66
  ### Claude Desktop
@@ -72,7 +72,7 @@ Add to `claude_desktop_config.json`:
72
72
  "mcpServers": {
73
73
  "ttt": {
74
74
  "command": "npx",
75
- "args": ["-y", "@helm-protocol/ttt-mcp@0.3.3"],
75
+ "args": ["-y", "@helm-protocol/ttt-mcp@0.4.0"],
76
76
  "env": { "TTT_API_KEY": "your-key" }
77
77
  }
78
78
  }
@@ -81,7 +81,7 @@ Add to `claude_desktop_config.json`:
81
81
 
82
82
  ### Cursor
83
83
 
84
- [![Add to Cursor](https://img.shields.io/badge/Add%20to%20Cursor-1a1a1a?style=flat&logo=cursor&logoColor=white)](https://cursor.com/install-mcp?name=ttt&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBoZWxtLXByb3RvY29sL3R0dC1tY3BAMC4zLjMiXX0=)
84
+ [![Add to Cursor](https://img.shields.io/badge/Add%20to%20Cursor-1a1a1a?style=flat&logo=cursor&logoColor=white)](https://cursor.com/install-mcp?name=ttt&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBoZWxtLXByb3RvY29sL3R0dC1tY3BAMC40LjAiXX0=)
85
85
 
86
86
  One-click install, or add the same `mcpServers` block above to `.cursor/mcp.json`.
87
87
 
@@ -127,12 +127,14 @@ pot_graph(eventId: "step-2", depth: 5)
127
127
 
128
128
  ---
129
129
 
130
- ## 8 Tools
130
+ ## 10 Tools
131
131
 
132
132
  | Tool | Purpose |
133
133
  |------|---------|
134
134
  | `pot_generate` | Stamp a workflow step with a cryptographic timestamp |
135
135
  | `pot_verify` | Verify a PoT signature |
136
+ | `pot_generate_v2` | Generate the draft-11 180-octet PoT Record v2 core (canonical wire profile) |
137
+ | `pot_verify_v2` | Verify a v2 record; optional TLS 1.3 exporter binding proof (fails closed without TLS) |
136
138
  | `pot_verify_v08` | Verify a draft-08 §3 Payload Digest record |
137
139
  | `pot_query` | O(1) exact lookup by eventId — core amnesia recovery |
138
140
  | `pot_graph` | Traverse causal DAG (backward + forward chain) |
@@ -168,6 +170,12 @@ Verify a draft-08 §3 record produced by `pot_generate`'s `potRecordV08` field:
168
170
  | ctxId | string | No | Must match what `pot_generate` used, or verification fails |
169
171
  | issuerPubKey | string | No | Hex-encoded 32-byte raw Ed25519 public key. Defaults to this server's own key. |
170
172
  | content | string | No | Payload to check against the record's Payload Digest field |
173
+ | clientId | string | Required when `TTTPS_REQUIRE_REPLAY_LEDGER=1` | Stable caller identity for the replay key |
174
+ | sessionId | string | Required when `TTTPS_REQUIRE_REPLAY_LEDGER=1` | Active transport/session identifier for the replay key |
175
+
176
+ When `TTTPS_REQUIRE_V08_FRESHNESS=1`, `TTTPS_V08_MAX_SKEW_NS` MUST be set to a non-negative integer. The verifier rejects records outside `maxSkewNs + errorBoundUs`. When `TTTPS_REQUIRE_REPLAY_LEDGER=1`, a successful verification atomically claims `clientId/sessionId/nonce` in Redis with a 90-day TTL; Redis failure or a second claim is rejected. These flags are fail-closed controls.
177
+
178
+ The current MCP canonical wire profile is draft-11 PoT Record v2 (180 octets). `pot_generate_v2` and `pot_verify_v2` implement the fixed core record, including SHA-256 integrity over octets 0-79 and Ed25519 issuer authentication over octets 0-115. Draft-08 remains available through `pot_verify_v08` for legacy interoperability only. TLS binding proof is a separate 64/32-octet value; `TTTPS_V2_REQUIRE_BINDING=1` fails closed unless the server is running over direct TLS 1.3; stdio and plain HTTP have no TLS exporter. Configure `MCP_TLS_CERT_FILE` and `MCP_TLS_KEY_FILE` for the HTTPS mode. Replay claims use `(ctx_id, nonce)` with `TTTPS_REPLAY_TTL_SECONDS` (default 86400).
171
179
 
172
180
  ### pot_query
173
181
 
package/dist/index.js CHANGED
@@ -4,6 +4,10 @@ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
4
4
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
5
5
  var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
6
6
  var import_http = require("http");
7
+ var import_https = require("https");
8
+ var import_fs = require("fs");
9
+ var import_tls_exporter = require("./tls_exporter");
10
+ var import_transport_context = require("./transport_context");
7
11
  var import_zod = require("zod");
8
12
  var import_tools = require("./tools");
9
13
  var import_auth = require("./auth");
@@ -101,8 +105,9 @@ function toolSuccess(result) {
101
105
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
102
106
  }
103
107
  function buildMcpServer() {
104
- const s = new import_mcp.McpServer({ name: "ttt-mcp", version: "0.3.2" });
105
- s.tool(
108
+ const s = new import_mcp.McpServer({ name: "ttt-mcp", version: "0.4.1" });
109
+ const registerTool = s.tool.bind(s);
110
+ registerTool(
106
111
  "pot_generate",
107
112
  "Generate a cryptographic Proof of Time timestamp (draft-helmprotocol-tttps, https://datatracker.ietf.org/doc/draft-helmprotocol-tttps/). For Claude Code workflows: use eventId + prevEventId to build a causal chain. For DeFi: use txHash + chainId + poolAddress. For a spec-conformant draft-08 \xA73 record binding this attestation to a specific piece of content, also supply contentDigest. One of eventId, txHash, or contentDigest is required.",
108
113
  {
@@ -126,7 +131,7 @@ function buildMcpServer() {
126
131
  }
127
132
  }
128
133
  );
129
- s.tool(
134
+ registerTool(
130
135
  "pot_verify",
131
136
  "Verify a Proof of Time using its hash and integrity shards. Returns validity, mode (turbo/full), and timestamp.",
132
137
  {
@@ -145,16 +150,58 @@ function buildMcpServer() {
145
150
  }
146
151
  }
147
152
  );
148
- s.tool(
153
+ registerTool(
154
+ "pot_generate_v2",
155
+ "Generate the draft-11 180-octet Proof-of-Time Record v2 core. TLS binding proof is computed only after a live TLS session exists.",
156
+ {
157
+ tsTaiUs: import_zod.z.string().describe("TAI timestamp in microseconds as a decimal string"),
158
+ dispersionUs: import_zod.z.number().int().nonnegative().describe("Uncertainty bound in microseconds"),
159
+ ctxId: import_zod.z.string().length(32).describe("16-octet context identifier encoded as hex"),
160
+ holderAuthData: import_zod.z.string().length(64).describe("32-octet holder public key or PSK digest encoded as hex"),
161
+ holderAuthType: import_zod.z.number().int().optional().describe("0x01 Ed25519 holder key (default) or 0x02 shared secret")
162
+ },
163
+ { title: "Generate draft-11 180-octet PoT v2", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
164
+ async (args) => {
165
+ try {
166
+ return toolSuccess(await (0, import_tools.potGenerateV2)(args));
167
+ } catch (err) {
168
+ return toolError(err);
169
+ }
170
+ }
171
+ );
172
+ registerTool(
173
+ "pot_verify_v2",
174
+ "Verify the draft-11 180-octet PoT Record v2 core. Binding-required mode fails closed in stdio because no TLS exporter session exists.",
175
+ {
176
+ potRecordV2: import_zod.z.string().length(360).describe("Hex-encoded 180-octet draft-11 v2 record"),
177
+ issuerPubKey: import_zod.z.string().length(64).optional().describe("Raw 32-octet issuer Ed25519 public key in hex"),
178
+ nowTaiUs: import_zod.z.string().optional().describe("Current TAI timestamp in microseconds"),
179
+ maxSkewUs: import_zod.z.string().optional().describe("Configured freshness allowance in microseconds"),
180
+ clientId: import_zod.z.string().optional(),
181
+ sessionId: import_zod.z.string().optional(),
182
+ bindingProof: import_zod.z.string().regex(/^(?:[0-9a-fA-F]{128}|[0-9a-fA-F]{64})$/).optional().describe("64-octet Ed25519 or 32-octet HMAC TLS binding proof")
183
+ },
184
+ { title: "Verify draft-11 180-octet PoT v2", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
185
+ async (args) => {
186
+ try {
187
+ return toolSuccess(await (0, import_tools.potVerifyV2)(args));
188
+ } catch (err) {
189
+ return toolError(err);
190
+ }
191
+ }
192
+ );
193
+ registerTool(
149
194
  "pot_verify_v08",
150
- "Verify a draft-helmprotocol-tttps-08 \xA73 Proof-of-Time record (as produced by pot_generate's potRecordV08 field): recomputes the Commitment and checks the Ed25519 signature, and \u2014 if content is supplied \u2014 recomputes SHA-256(content) and checks it against the record's Payload Digest field.",
195
+ "Verify and, when configured, admit a draft-helmprotocol-tttps-08 \xA73 Proof-of-Time record: recomputes the Commitment and Ed25519 signature, applies configured freshness, and atomically claims client/session/nonce in Redis before admission.",
151
196
  {
152
197
  potRecordV08: import_zod.z.string().describe("Hex-encoded 184 or 216-octet record from pot_generate's potRecordV08 field"),
153
198
  ctxId: import_zod.z.string().max(255).optional().describe("Context identifier the record was generated under. Must match what pot_generate used, or verification fails."),
154
199
  issuerPubKey: import_zod.z.string().optional().describe("Hex-encoded 32-byte raw Ed25519 issuer public key. Defaults to this server's own key."),
155
- content: import_zod.z.string().optional().describe("The payload (utf8) to check against the record's Payload Digest field, if available")
200
+ content: import_zod.z.string().optional().describe("The payload (utf8) to check against the record's Payload Digest field, if available"),
201
+ clientId: import_zod.z.string().optional().describe("Stable caller identity for the durable replay ledger when TTTPS_REQUIRE_REPLAY_LEDGER=1"),
202
+ sessionId: import_zod.z.string().optional().describe("Active transport/session identifier for the durable replay ledger when TTTPS_REQUIRE_REPLAY_LEDGER=1")
156
203
  },
157
- { title: "Verify draft-08 Proof-of-Time Record", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
204
+ { title: "Verify draft-08 Proof-of-Time Record", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
158
205
  async (args) => {
159
206
  try {
160
207
  const result = await (0, import_tools.potVerifyV08)(args);
@@ -164,9 +211,9 @@ function buildMcpServer() {
164
211
  }
165
212
  }
166
213
  );
167
- s.tool(
214
+ registerTool(
168
215
  "pot_query",
169
- "Query Proof of Time records. Use eventId for exact O(1) lookup of a specific workflow step (collision probability 2^-256). Use startTime/endTime for time-range queries.",
216
+ "Query Proof of Time records. Use eventId for exact lookup of a specific workflow step. Use startTime/endTime for time-range queries.",
170
217
  {
171
218
  eventId: import_zod.z.string().optional().describe("Exact eventId lookup \u2014 call this at workflow start to restore action history after context compression"),
172
219
  startTime: import_zod.z.number().optional().describe("Start time (unix ms). Default: 24h ago"),
@@ -183,7 +230,7 @@ function buildMcpServer() {
183
230
  }
184
231
  }
185
232
  );
186
- s.tool(
233
+ registerTool(
187
234
  "pot_graph",
188
235
  "Traverse the causal chain of workflow steps. Given an eventId, returns the full backward chain (ancestors via prevEventId) and forward chain (steps that follow). Use after context compression to reconstruct the complete workflow timeline.",
189
236
  {
@@ -200,7 +247,7 @@ function buildMcpServer() {
200
247
  }
201
248
  }
202
249
  );
203
- s.tool(
250
+ registerTool(
204
251
  "pot_stats",
205
252
  "Get PoT statistics: total swaps, turbo/full counts, and turbo ratio for a given period.",
206
253
  { period: import_zod.z.enum(["day", "week", "month"]).describe("Time period for statistics") },
@@ -214,7 +261,7 @@ function buildMcpServer() {
214
261
  }
215
262
  }
216
263
  );
217
- s.tool(
264
+ registerTool(
218
265
  "pot_health",
219
266
  "Check PoT system health: time source status, subgraph sync, server uptime, and current mode.",
220
267
  {},
@@ -228,7 +275,7 @@ function buildMcpServer() {
228
275
  }
229
276
  }
230
277
  );
231
- s.tool(
278
+ registerTool(
232
279
  "pot_checkpoint",
233
280
  "Create a compressed rollup checkpoint of workflow history. Call this periodically to prevent token explosion when recovering from context compression. Returns checkpointId, compressed event history, chainIntact status, and nextCheckpointHint (recommended events before next checkpoint).",
234
281
  {
@@ -254,10 +301,10 @@ async function main() {
254
301
  await restoreDAGFromRedis();
255
302
  const port = process.env.PORT ? parseInt(process.env.PORT, 10) : null;
256
303
  if (port) {
257
- const httpServer = (0, import_http.createServer)(async (req, res) => {
304
+ const requestHandler = async (req, res) => {
258
305
  if (req.method === "GET" && (req.url === "/health" || req.url === "/ping")) {
259
306
  res.writeHead(200, { "Content-Type": "application/json" });
260
- res.end(JSON.stringify({ status: "ok", server: "ttt-mcp", version: "0.3.2" }));
307
+ res.end(JSON.stringify({ status: "ok", server: "ttt-mcp", version: "0.4.1" }));
261
308
  return;
262
309
  }
263
310
  if (req.method === "POST") {
@@ -309,9 +356,18 @@ async function main() {
309
356
  res.end(JSON.stringify({ error: "Internal server error" }));
310
357
  }
311
358
  }
312
- });
359
+ };
360
+ const certFile = process.env.MCP_TLS_CERT_FILE?.trim();
361
+ const keyFile = process.env.MCP_TLS_KEY_FILE?.trim();
362
+ if (certFile && !keyFile || !certFile && keyFile) {
363
+ throw new Error("MCP_TLS_CERT_FILE and MCP_TLS_KEY_FILE must be configured together");
364
+ }
365
+ const httpServer = certFile && keyFile ? (0, import_https.createServer)({ minVersion: "TLSv1.3", cert: (0, import_fs.readFileSync)(certFile), key: (0, import_fs.readFileSync)(keyFile) }, (req, res) => {
366
+ const exporter = (0, import_tls_exporter.extractTls13Exporter)(req);
367
+ return (0, import_transport_context.withTransportBinding)({ exporter, remoteAddress: req.socket.remoteAddress ?? "", clientId: String(req.headers["x-ttt-client-id"] ?? ""), sessionId: String(req.headers["x-ttt-session-id"] ?? "") }, () => requestHandler(req, res));
368
+ }) : (0, import_http.createServer)((req, res) => (0, import_transport_context.withTransportBinding)({ remoteAddress: req.socket.remoteAddress ?? "", clientId: String(req.headers["x-ttt-client-id"] ?? ""), sessionId: String(req.headers["x-ttt-session-id"] ?? "") }, () => requestHandler(req, res)));
313
369
  httpServer.listen(port, () => {
314
- console.error(`[ttt-mcp] OpenTTT MCP Server (HTTP) on port ${port}`);
370
+ console.error(`[ttt-mcp] OpenTTT MCP Server (${certFile ? "HTTPS/TLS1.3" : "HTTP"}) on port ${port}`);
315
371
  });
316
372
  } else {
317
373
  const stdioServer = buildMcpServer();
@@ -161,7 +161,7 @@ function decodePotRecordV08(record) {
161
161
  p
162
162
  };
163
163
  }
164
- function verifyPotRecordV08(record, ctxId, issuerPublicKeyRaw, content) {
164
+ function verifyPotRecordV08(record, ctxId, issuerPublicKeyRaw, content, freshness) {
165
165
  let decoded;
166
166
  try {
167
167
  decoded = decodePotRecordV08(record);
@@ -177,6 +177,15 @@ function verifyPotRecordV08(record, ctxId, issuerPublicKeyRaw, content) {
177
177
  if (decoded.errorBoundUs === RESERVED_ERROR_BOUND) {
178
178
  return { verdict: "rejected", reason: "Error Bound carries the reserved value 0xFFFFFF" };
179
179
  }
180
+ if (freshness !== void 0) {
181
+ if (freshness.maxSkewNs < 0n) {
182
+ return { verdict: "rejected", reason: "invalid freshness policy" };
183
+ }
184
+ const delta = decoded.timestampNs >= freshness.nowNs ? decoded.timestampNs - freshness.nowNs : freshness.nowNs - decoded.timestampNs;
185
+ if (delta > freshness.maxSkewNs + BigInt(decoded.errorBoundUs) * 1000n) {
186
+ return { verdict: "rejected", reason: "freshness window exceeded" };
187
+ }
188
+ }
180
189
  const expectedCommitment = computeCommitmentSha256(decoded.fieldsPre, ctxId);
181
190
  if (!expectedCommitment.equals(decoded.commitment)) {
182
191
  return { verdict: "rejected", reason: "commitment mismatch" };
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var pot_record_v2_exports = {};
20
+ __export(pot_record_v2_exports, {
21
+ HOLDER_AUTH_ED25519: () => HOLDER_AUTH_ED25519,
22
+ HOLDER_AUTH_SHARED_SECRET: () => HOLDER_AUTH_SHARED_SECRET,
23
+ POT_V2_INTEGRITY_SHA256: () => POT_V2_INTEGRITY_SHA256,
24
+ POT_V2_SIZE: () => POT_V2_SIZE,
25
+ POT_V2_VERSION: () => POT_V2_VERSION,
26
+ computeV2BindingInput: () => computeV2BindingInput,
27
+ computeV2ExporterContext: () => computeV2ExporterContext,
28
+ decodePotRecordV2: () => decodePotRecordV2,
29
+ encodePotRecordV2: () => encodePotRecordV2,
30
+ verifyPotRecordV2: () => verifyPotRecordV2,
31
+ verifyV2BindingProof: () => verifyV2BindingProof
32
+ });
33
+ module.exports = __toCommonJS(pot_record_v2_exports);
34
+ var import_crypto = require("crypto");
35
+ const POT_V2_SIZE = 180;
36
+ const POT_V2_VERSION = 2;
37
+ const POT_V2_INTEGRITY_SHA256 = 1;
38
+ const HOLDER_AUTH_ED25519 = 1;
39
+ const HOLDER_AUTH_SHARED_SECRET = 2;
40
+ const OFF = {
41
+ VERSION: 0,
42
+ HOLDER_AUTH_TYPE: 1,
43
+ ALG_ID: 2,
44
+ TS: 4,
45
+ DISPERSION: 12,
46
+ CTX_ID: 16,
47
+ NONCE: 32,
48
+ HOLDER_AUTH_DATA: 48,
49
+ INTEGRITY_TAG: 80,
50
+ ISSUER_KEY_ID: 112,
51
+ ISSUER_SIG: 116
52
+ };
53
+ const ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
54
+ function requireLength(name, value, length) {
55
+ if (value.length !== length) throw new Error(`${name} must be ${length} bytes`);
56
+ }
57
+ function issuerPublicKeyFromRaw(raw) {
58
+ requireLength("issuer public key", raw, 32);
59
+ return (0, import_crypto.createPublicKey)({ key: Buffer.concat([ED25519_SPKI_PREFIX, raw]), format: "der", type: "spki" });
60
+ }
61
+ function integrityTag(signedPrefix) {
62
+ return (0, import_crypto.createHash)("sha256").update(signedPrefix).digest();
63
+ }
64
+ function encodePotRecordV2(fields, issuerPrivateKey) {
65
+ if (fields.holderAuthType !== HOLDER_AUTH_ED25519 && fields.holderAuthType !== HOLDER_AUTH_SHARED_SECRET) {
66
+ throw new Error("unsupported holder_auth_type");
67
+ }
68
+ if (fields.algId !== POT_V2_INTEGRITY_SHA256) throw new Error("unsupported alg_id");
69
+ if (fields.tsTaiUs < 0n || fields.tsTaiUs > 0xffffffffffffffffn) throw new Error("ts out of range");
70
+ if (fields.dispersionUs < 0 || fields.dispersionUs > 4294967295) throw new Error("dispersion out of range");
71
+ if (fields.issuerKeyId < 0 || fields.issuerKeyId > 4294967295) throw new Error("issuer_key_id out of range");
72
+ requireLength("ctx_id", fields.ctxId, 16);
73
+ requireLength("nonce", fields.nonce, 16);
74
+ requireLength("holder_auth_data", fields.holderAuthData, 32);
75
+ const prefix = Buffer.alloc(116);
76
+ prefix.writeUInt8(POT_V2_VERSION, OFF.VERSION);
77
+ prefix.writeUInt8(fields.holderAuthType, OFF.HOLDER_AUTH_TYPE);
78
+ prefix.writeUInt16BE(fields.algId, OFF.ALG_ID);
79
+ prefix.writeBigUInt64BE(fields.tsTaiUs, OFF.TS);
80
+ prefix.writeUInt32BE(fields.dispersionUs, OFF.DISPERSION);
81
+ fields.ctxId.copy(prefix, OFF.CTX_ID);
82
+ fields.nonce.copy(prefix, OFF.NONCE);
83
+ fields.holderAuthData.copy(prefix, OFF.HOLDER_AUTH_DATA);
84
+ integrityTag(prefix.subarray(0, 80)).copy(prefix, OFF.INTEGRITY_TAG);
85
+ prefix.writeUInt32BE(fields.issuerKeyId, OFF.ISSUER_KEY_ID);
86
+ const signature = (0, import_crypto.sign)(null, prefix, issuerPrivateKey);
87
+ return Buffer.concat([prefix, signature]);
88
+ }
89
+ function decodePotRecordV2(record) {
90
+ if (record.length !== POT_V2_SIZE) throw new Error(`expected 180 octets, got ${record.length}`);
91
+ const signedPrefix = record.subarray(0, 116);
92
+ return {
93
+ holderAuthType: record.readUInt8(OFF.HOLDER_AUTH_TYPE),
94
+ algId: record.readUInt16BE(OFF.ALG_ID),
95
+ tsTaiUs: record.readBigUInt64BE(OFF.TS),
96
+ dispersionUs: record.readUInt32BE(OFF.DISPERSION),
97
+ ctxId: Buffer.from(record.subarray(OFF.CTX_ID, OFF.CTX_ID + 16)),
98
+ nonce: Buffer.from(record.subarray(OFF.NONCE, OFF.NONCE + 16)),
99
+ holderAuthData: Buffer.from(record.subarray(OFF.HOLDER_AUTH_DATA, OFF.HOLDER_AUTH_DATA + 32)),
100
+ issuerKeyId: record.readUInt32BE(OFF.ISSUER_KEY_ID),
101
+ integrityTag: Buffer.from(record.subarray(OFF.INTEGRITY_TAG, OFF.INTEGRITY_TAG + 32)),
102
+ issuerSig: Buffer.from(record.subarray(OFF.ISSUER_SIG, POT_V2_SIZE)),
103
+ signedPrefix: Buffer.from(signedPrefix)
104
+ };
105
+ }
106
+ function computeV2ExporterContext(record) {
107
+ const decoded = decodePotRecordV2(record);
108
+ const bindingPrefix = Buffer.from("tttps-binding-v2\0", "ascii");
109
+ const recordHash = (0, import_crypto.createHash)("sha256").update(record).digest();
110
+ return (0, import_crypto.createHash)("sha256").update(Buffer.concat([bindingPrefix, recordHash, decoded.ctxId, decoded.nonce])).digest();
111
+ }
112
+ function computeV2BindingInput(record, exporterOutput) {
113
+ decodePotRecordV2(record);
114
+ if (exporterOutput.length !== 32) throw new Error("TLS exporter output must be 32 octets");
115
+ return Buffer.concat([Buffer.from("tttps-binding-v2\0", "ascii"), exporterOutput, record.subarray(16, 32), record.subarray(32, 48)]);
116
+ }
117
+ function verifyV2BindingProof(record, bindingProof, exporterOutput, sharedSecret) {
118
+ const decoded = decodePotRecordV2(record);
119
+ const bindingInput = computeV2BindingInput(record, exporterOutput);
120
+ if (decoded.holderAuthType === HOLDER_AUTH_ED25519) {
121
+ if (bindingProof.length !== 64) return { verdict: "rejected", reason: "Ed25519 binding proof must be 64 octets" };
122
+ const holderPublicKey = issuerPublicKeyFromRaw(decoded.holderAuthData);
123
+ return (0, import_crypto.verify)(null, bindingInput, holderPublicKey, bindingProof) ? { verdict: "intact" } : { verdict: "rejected", reason: "TLS exporter holder proof mismatch" };
124
+ }
125
+ if (decoded.holderAuthType === HOLDER_AUTH_SHARED_SECRET) {
126
+ if (!sharedSecret) return { verdict: "rejected", reason: "shared-secret holder binding is not configured" };
127
+ if (bindingProof.length !== 32) return { verdict: "rejected", reason: "HMAC binding proof must be 32 octets" };
128
+ const expectedDigest = (0, import_crypto.createHash)("sha256").update(sharedSecret).digest();
129
+ if (!(0, import_crypto.timingSafeEqual)(expectedDigest, decoded.holderAuthData)) return { verdict: "rejected", reason: "shared-secret digest mismatch" };
130
+ const expected = (0, import_crypto.createHmac)("sha256", sharedSecret).update(bindingInput).digest();
131
+ return (0, import_crypto.timingSafeEqual)(expected, bindingProof) ? { verdict: "intact" } : { verdict: "rejected", reason: "TLS exporter HMAC proof mismatch" };
132
+ }
133
+ return { verdict: "rejected", reason: "unsupported holder authentication type" };
134
+ }
135
+ function verifyPotRecordV2(record, issuerPublicKeyRaw, freshness) {
136
+ let decoded;
137
+ try {
138
+ decoded = decodePotRecordV2(record);
139
+ } catch (e) {
140
+ return { verdict: "rejected", reason: e instanceof Error ? e.message : String(e) };
141
+ }
142
+ if (record[OFF.VERSION] !== POT_V2_VERSION) return { verdict: "rejected", reason: "unknown version" };
143
+ if (decoded.holderAuthType !== HOLDER_AUTH_ED25519 && decoded.holderAuthType !== HOLDER_AUTH_SHARED_SECRET) {
144
+ return { verdict: "rejected", reason: "unsupported holder_auth_type" };
145
+ }
146
+ if (decoded.algId !== POT_V2_INTEGRITY_SHA256) return { verdict: "rejected", reason: "unsupported alg_id" };
147
+ if (!integrityTag(decoded.signedPrefix.subarray(0, 80)).equals(decoded.integrityTag)) return { verdict: "rejected", reason: "integrity mismatch" };
148
+ if (freshness) {
149
+ const delta = decoded.tsTaiUs >= freshness.nowTaiUs ? decoded.tsTaiUs - freshness.nowTaiUs : freshness.nowTaiUs - decoded.tsTaiUs;
150
+ if (delta > freshness.maxSkewUs + BigInt(decoded.dispersionUs)) return { verdict: "rejected", reason: "freshness window exceeded" };
151
+ }
152
+ try {
153
+ if (!(0, import_crypto.verify)(null, decoded.signedPrefix, issuerPublicKeyFromRaw(issuerPublicKeyRaw), decoded.issuerSig)) {
154
+ return { verdict: "rejected", reason: "issuer signature invalid" };
155
+ }
156
+ } catch (e) {
157
+ return { verdict: "rejected", reason: e instanceof Error ? e.message : String(e) };
158
+ }
159
+ return { verdict: "intact", nonce: decoded.nonce };
160
+ }
161
+ // Annotate the CommonJS export names for ESM import in node:
162
+ 0 && (module.exports = {
163
+ HOLDER_AUTH_ED25519,
164
+ HOLDER_AUTH_SHARED_SECRET,
165
+ POT_V2_INTEGRITY_SHA256,
166
+ POT_V2_SIZE,
167
+ POT_V2_VERSION,
168
+ computeV2BindingInput,
169
+ computeV2ExporterContext,
170
+ decodePotRecordV2,
171
+ encodePotRecordV2,
172
+ verifyPotRecordV2,
173
+ verifyV2BindingProof
174
+ });
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var tls_exporter_exports = {};
20
+ __export(tls_exporter_exports, {
21
+ TTTPS_V2_EXPORTER_LABEL: () => TTTPS_V2_EXPORTER_LABEL,
22
+ TTTPS_V2_EXPORTER_LENGTH: () => TTTPS_V2_EXPORTER_LENGTH,
23
+ exporterForTlsSocket: () => exporterForTlsSocket,
24
+ extractTls13Exporter: () => extractTls13Exporter
25
+ });
26
+ module.exports = __toCommonJS(tls_exporter_exports);
27
+ const TTTPS_V2_EXPORTER_LABEL = "EXPORTER-TTTPS-v2-Binding";
28
+ const TTTPS_V2_EXPORTER_LENGTH = 32;
29
+ function exporterForTlsSocket(socket) {
30
+ if (!socket.encrypted || typeof socket.exportKeyingMaterial !== "function") return void 0;
31
+ if (typeof socket.getProtocol === "function" && socket.getProtocol() !== "TLSv1.3") return void 0;
32
+ return (contextValue) => socket.exportKeyingMaterial(TTTPS_V2_EXPORTER_LENGTH, TTTPS_V2_EXPORTER_LABEL, contextValue);
33
+ }
34
+ function extractTls13Exporter(req) {
35
+ return exporterForTlsSocket(req.socket);
36
+ }
37
+ // Annotate the CommonJS export names for ESM import in node:
38
+ 0 && (module.exports = {
39
+ TTTPS_V2_EXPORTER_LABEL,
40
+ TTTPS_V2_EXPORTER_LENGTH,
41
+ exporterForTlsSocket,
42
+ extractTls13Exporter
43
+ });
package/dist/tools.js CHANGED
@@ -30,12 +30,14 @@ var tools_exports = {};
30
30
  __export(tools_exports, {
31
31
  potCheckpoint: () => potCheckpoint,
32
32
  potGenerate: () => potGenerate,
33
+ potGenerateV2: () => potGenerateV2,
33
34
  potGraph: () => potGraph,
34
35
  potHealth: () => potHealth,
35
36
  potQuery: () => potQuery,
36
37
  potStats: () => potStats,
37
38
  potVerify: () => potVerify,
38
39
  potVerifyV08: () => potVerifyV08,
40
+ potVerifyV2: () => potVerifyV2,
39
41
  redis: () => redis,
40
42
  restoreDagEntry: () => restoreDagEntry,
41
43
  tttsFreshnessSeal: () => tttsFreshnessSeal,
@@ -46,6 +48,8 @@ var import_openttt = require("openttt");
46
48
  var import_telemetry = require("./telemetry");
47
49
  var import_server = require("./server");
48
50
  var import_crypto = require("crypto");
51
+ var import_pot_record_v2 = require("./pot_record_v2");
52
+ var import_transport_context = require("./transport_context");
49
53
  var import_pot_record_v08 = require("./pot_record_v08");
50
54
  var import_ioredis = __toESM(require("ioredis"));
51
55
  const GrgPipeline = (
@@ -71,6 +75,41 @@ const redis = new import_ioredis.default(process.env.REDIS_URL ?? "redis://127.0
71
75
  });
72
76
  redis.on("error", () => {
73
77
  });
78
+ const REPLAY_TTL_SECONDS = Number.parseInt(process.env.TTTPS_REPLAY_TTL_SECONDS ?? "86400", 10);
79
+ function requiredFlag(name) {
80
+ return process.env[name]?.trim() === "1";
81
+ }
82
+ function configuredFreshnessPolicy() {
83
+ const raw = process.env.TTTPS_V08_MAX_SKEW_NS?.trim();
84
+ if (!raw) return void 0;
85
+ try {
86
+ const maxSkewNs = BigInt(raw);
87
+ if (maxSkewNs < 0n) return void 0;
88
+ return { nowNs: BigInt(Date.now()) * 1000000n, maxSkewNs };
89
+ } catch {
90
+ return void 0;
91
+ }
92
+ }
93
+ async function claimV2Replay(ctxIdHex, nonceHex) {
94
+ if (!ctxIdHex || !nonceHex) return "unavailable";
95
+ const key = `tttps:v2:replay:${ctxIdHex}:${nonceHex}`;
96
+ try {
97
+ const result = await redis.set(key, "1", "EX", REPLAY_TTL_SECONDS, "NX");
98
+ return result === "OK" ? "claimed" : "replay";
99
+ } catch {
100
+ return "unavailable";
101
+ }
102
+ }
103
+ async function claimV08Replay(clientId, sessionId, nonceHex) {
104
+ if (!clientId || !sessionId || !nonceHex) return "unavailable";
105
+ const key = `tttps:v08:replay:${clientId}:${sessionId}:${nonceHex}`;
106
+ try {
107
+ const result = await redis.set(key, "1", "EX", REPLAY_TTL_SECONDS, "NX");
108
+ return result === "OK" ? "claimed" : "replay";
109
+ } catch {
110
+ return "unavailable";
111
+ }
112
+ }
74
113
  const timeSynth = new import_openttt.TimeSynthesis();
75
114
  const adaptiveSwitch = new import_openttt.AdaptiveSwitch();
76
115
  const potSigner = new import_openttt.PotSigner();
@@ -374,17 +413,210 @@ async function potVerify(args) {
374
413
  verifiedAt: Date.now()
375
414
  });
376
415
  }
416
+ async function potGenerateV2(args) {
417
+ (0, import_telemetry.telemetryIncrement)("pot_generate_v2");
418
+ const ctxId = Buffer.from(args.ctxId, "hex");
419
+ const holderAuthData = Buffer.from(args.holderAuthData, "hex");
420
+ if (ctxId.length !== 16) throw new Error("ctxId must be exactly 16 bytes of hex");
421
+ if (holderAuthData.length !== 32) throw new Error("holderAuthData must be exactly 32 bytes of hex");
422
+ const tsTaiUs = BigInt(args.tsTaiUs);
423
+ const nonce = (0, import_crypto.randomBytes)(16);
424
+ const issuerKeyId = (0, import_crypto.createHash)("sha256").update(potSignerPublicKeyRawV08).digest().readUInt32BE(0);
425
+ const record = (0, import_pot_record_v2.encodePotRecordV2)({
426
+ holderAuthType: args.holderAuthType ?? 1,
427
+ algId: 1,
428
+ tsTaiUs,
429
+ dispersionUs: args.dispersionUs,
430
+ ctxId,
431
+ nonce,
432
+ holderAuthData,
433
+ issuerKeyId
434
+ }, potSignerPrivateKeyV08);
435
+ return serialize({
436
+ wireProfile: "draft-helmprotocol-tttps-11-v2",
437
+ potRecordV2: record.toString("hex"),
438
+ issuerPubKey: potSignerPublicKeyRawV08.toString("hex"),
439
+ issuerKeyId,
440
+ nonce: nonce.toString("hex"),
441
+ bindingProofRequired: process.env.TTTPS_V2_REQUIRE_BINDING === "1"
442
+ });
443
+ }
444
+ function trustedKeyList(envName) {
445
+ return (process.env[envName] ?? "").split(",").map((s) => s.trim()).filter((s) => /^[0-9a-fA-F]{64}$/.test(s)).map((h) => Buffer.from(h, "hex"));
446
+ }
447
+ let roughtimeAdmitCache = null;
448
+ async function roughtimeQuorumOk() {
449
+ const url = process.env.TTTPS_ADMISSION_STATUS_URL ?? "https://api.kenosian.com/pot/status";
450
+ const ttlMs = Number.parseInt(process.env.TTTPS_ADMISSION_TTL_MS ?? "5000", 10);
451
+ if (roughtimeAdmitCache && Date.now() - roughtimeAdmitCache.at < ttlMs) return roughtimeAdmitCache.ok;
452
+ try {
453
+ const timeoutMs = Number.parseInt(process.env.TTTPS_ADMISSION_TIMEOUT_MS ?? "1500", 10);
454
+ const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
455
+ const j = await res.json();
456
+ const ok = j?.roughtime_ok === true;
457
+ roughtimeAdmitCache = { ok, at: Date.now() };
458
+ return ok;
459
+ } catch {
460
+ roughtimeAdmitCache = { ok: false, at: Date.now() };
461
+ return false;
462
+ }
463
+ }
464
+ async function potVerifyV2Core(args) {
465
+ (0, import_telemetry.telemetryIncrement)("pot_verify_v2");
466
+ const bindingRequired = process.env.TTTPS_V2_REQUIRE_BINDING === "1";
467
+ if (bindingRequired && !args.bindingProof) {
468
+ return serialize({ verdict: "rejected", reason: "TLS exporter binding proof is required" });
469
+ }
470
+ const record = Buffer.from(args.potRecordV2, "hex");
471
+ if (process.env.TTTPS_REQUIRE_ROUGHTIME_QUORUM === "1") {
472
+ if (!await roughtimeQuorumOk()) {
473
+ return serialize({ verdict: "rejected", reason: "roughtime quorum unavailable" });
474
+ }
475
+ }
476
+ const freshness = process.env.TTTPS_ENFORCE_FRESHNESS === "1" ? { nowTaiUs: BigInt(Date.now()) * 1000n, maxSkewUs: BigInt(process.env.TTTPS_MAX_SKEW_US ?? "60000000") } : args.nowTaiUs !== void 0 && args.maxSkewUs !== void 0 ? { nowTaiUs: BigInt(args.nowTaiUs), maxSkewUs: BigInt(args.maxSkewUs) } : void 0;
477
+ const trustedIssuers = trustedKeyList("TTTPS_TRUSTED_ISSUERS");
478
+ const pinSelfIssuer = process.env.TTTPS_PIN_SELF_ISSUER === "1";
479
+ const candidates = trustedIssuers.length > 0 ? trustedIssuers : pinSelfIssuer ? [potSignerPublicKeyRawV08] : [args.issuerPubKey ? Buffer.from(args.issuerPubKey, "hex") : potSignerPublicKeyRawV08];
480
+ let result = (0, import_pot_record_v2.verifyPotRecordV2)(record, candidates[0], freshness);
481
+ for (let i = 1; i < candidates.length && result.verdict === "rejected" && result.reason === "issuer signature invalid"; i++) {
482
+ result = (0, import_pot_record_v2.verifyPotRecordV2)(record, candidates[i], freshness);
483
+ }
484
+ if (result.verdict === "rejected") {
485
+ if (trustedIssuers.length > 0 && result.reason === "issuer signature invalid") {
486
+ return serialize({ verdict: "rejected", reason: "issuer not trusted" });
487
+ }
488
+ return serialize(result);
489
+ }
490
+ const trustedHolders = trustedKeyList("TTTPS_TRUSTED_HOLDERS");
491
+ if (trustedHolders.length > 0) {
492
+ const holder = record.subarray(48, 80);
493
+ if (!trustedHolders.some((h) => h.equals(holder))) {
494
+ return serialize({ verdict: "rejected", reason: "holder not authorized" });
495
+ }
496
+ }
497
+ if (args.bindingProof) {
498
+ const exporter = (0, import_transport_context.currentTransportBinding)()?.exporter;
499
+ if (!exporter) return serialize({ verdict: "rejected", reason: "live TLS exporter unavailable" });
500
+ let exporterOutput;
501
+ try {
502
+ exporterOutput = exporter((0, import_pot_record_v2.computeV2ExporterContext)(record));
503
+ } catch {
504
+ return serialize({ verdict: "rejected", reason: "TLS exporter derivation failed" });
505
+ }
506
+ const binding = (0, import_pot_record_v2.verifyV2BindingProof)(record, Buffer.from(args.bindingProof, "hex"), exporterOutput);
507
+ if (binding.verdict === "rejected") return serialize(binding);
508
+ }
509
+ if (process.env.TTTPS_REQUIRE_REPLAY_LEDGER === "1") {
510
+ const ctxId = record.subarray(16, 32).toString("hex");
511
+ const replay = await claimV2Replay(ctxId, result.nonce.toString("hex"));
512
+ if (replay !== "claimed") return serialize({ verdict: "rejected", reason: replay === "replay" ? "replay detected" : "replay ledger unavailable" });
513
+ }
514
+ return serialize({ verdict: "intact", wireProfile: "draft-helmprotocol-tttps-11-v2", nonce: result.nonce.toString("hex") });
515
+ }
516
+ const V2_AUDIT_STREAM = process.env.TTTPS_AUDIT_STREAM ?? "tttps:audit:v2";
517
+ const V2_AUDIT_MAXLEN = Number.parseInt(process.env.TTTPS_AUDIT_MAXLEN ?? "100000", 10);
518
+ const TTTPS_FORMAL_RECEIPT = {
519
+ spec: "draft-helmprotocol-tttps-11",
520
+ // Honest status. There is a TLA+ model (TLC model-checked) and a Lean 4 module whose
521
+ // theorems the kernel accepts (sorry 0, axiom propext), and this runtime. They are
522
+ // RELATED artifacts, not a machine-checked end-to-end 1:1 equivalence. Treat this as
523
+ // provenance the auditor can chase, not proof of the byte parser or the cryptography.
524
+ binding_status: "provenance-only; TLA+<->Lean<->runtime 1:1 equivalence is NOT machine-checked end-to-end (HOLD)",
525
+ lean: {
526
+ module: "KLean.TTTPS.Core",
527
+ theorems: [
528
+ "invalid_ingress_no_mutation",
529
+ "parse_is_deterministic",
530
+ "parsed_record_has_fixed_size",
531
+ "valid_ingress_commit_delta",
532
+ "zero_state_preservation"
533
+ ],
534
+ axioms: ["propext"],
535
+ content_hash: "sha256:6116b82318d540a1e4e5b694e31978739851f658356330349b9d4289aeb5bae8",
536
+ abstraction: "models admission at frame-size (=180) granularity; does NOT model field offsets, Ed25519, SHA-256, or the TLS exporter"
537
+ },
538
+ tla: { spec: "TTTPS_Ingress_Gate.tla", method: "TLC bounded model check (not an unbounded theorem)" },
539
+ anchor: {
540
+ receipt_id: "33108706ed730e5296ae1b06",
541
+ time_source: "roughtime_chain",
542
+ verify_url: "https://kpp.kenosian.com/v1/verify?receipt_id=33108706ed730e5296ae1b06"
543
+ },
544
+ revision: { package_commit: "677a3b9", note: "live gate runs this revision with admission flags ON; published npm 0.4.0 bytes carry the v2 core only" },
545
+ scope: "Kernel-checks the 180-octet parse + admission state boundary (invalid ingress => zero state mutation). Does NOT prove Ed25519/SHA-256/TLS-exporter cryptographic security, nor a byte-exact parser equivalence."
546
+ };
547
+ async function potVerifyV2(args) {
548
+ const startedNs = process.hrtime.bigint();
549
+ let result;
550
+ try {
551
+ result = await potVerifyV2Core(args);
552
+ } catch (e) {
553
+ result = { verdict: "rejected", reason: `verifier exception: ${e instanceof Error ? e.message : String(e)}` };
554
+ }
555
+ const serverLatencyUs = Number((process.hrtime.bigint() - startedNs) / 1000n);
556
+ const binding = (0, import_transport_context.currentTransportBinding)();
557
+ const recordHex = typeof args.potRecordV2 === "string" ? args.potRecordV2 : "";
558
+ const record = Buffer.from(recordHex, "hex");
559
+ const fields = {
560
+ ts_ms: String(Date.now()),
561
+ verdict: String(result.verdict ?? "unknown"),
562
+ reason: String(result.reason ?? ""),
563
+ case: String(args.clientId ?? binding?.clientId ?? ""),
564
+ session: String(args.sessionId ?? binding?.sessionId ?? ""),
565
+ remote: String(binding?.remoteAddress ?? ""),
566
+ transport: binding?.exporter ? "tls13" : "none",
567
+ binding_proof: args.bindingProof ? "present" : "absent",
568
+ record_len: String(record.length),
569
+ record_sha16: record.length ? (0, import_crypto.createHash)("sha256").update(record).digest("hex").slice(0, 16) : "",
570
+ ctx_id: record.length >= 48 ? record.subarray(16, 32).toString("hex") : "",
571
+ nonce: record.length >= 48 ? record.subarray(32, 48).toString("hex") : "",
572
+ latency_us: String(serverLatencyUs)
573
+ };
574
+ const flat = [];
575
+ for (const [k, v] of Object.entries(fields)) flat.push(k, v);
576
+ redis.call("XADD", V2_AUDIT_STREAM, "MAXLEN", "~", String(V2_AUDIT_MAXLEN), "*", ...flat).catch(() => void 0);
577
+ const emitFormal = process.env.TTTPS_EMIT_FORMAL_RECEIPT !== "0";
578
+ return serialize({ ...result, serverLatencyUs, ...emitFormal ? { formal: TTTPS_FORMAL_RECEIPT } : {} });
579
+ }
377
580
  async function potVerifyV08(args) {
378
581
  (0, import_telemetry.telemetryIncrement)("pot_verify_v08");
379
582
  const record = Buffer.from(args.potRecordV08, "hex");
380
583
  const ctxId = args.ctxId ?? DEFAULT_CTX_ID_V08;
381
584
  const issuerPubKey = args.issuerPubKey !== void 0 ? Buffer.from(args.issuerPubKey, "hex") : potSignerPublicKeyRawV08;
382
585
  const content = args.content !== void 0 ? Buffer.from(args.content, "utf8") : void 0;
383
- const result = (0, import_pot_record_v08.verifyPotRecordV08)(record, ctxId, issuerPubKey, content);
586
+ const requireFreshness = requiredFlag("TTTPS_REQUIRE_V08_FRESHNESS");
587
+ const freshness = configuredFreshnessPolicy();
588
+ if (requireFreshness && freshness === void 0) {
589
+ return serialize({ verdict: "rejected", reason: "freshness policy is not configured", ctxId, verifiedAt: Date.now() });
590
+ }
591
+ const result = (0, import_pot_record_v08.verifyPotRecordV08)(record, ctxId, issuerPubKey, content, freshness);
592
+ if (result.verdict === "rejected") {
593
+ return serialize({
594
+ verdict: result.verdict,
595
+ reason: result.reason ?? null,
596
+ payloadDigestMatchesContent: result.payloadDigestMatchesContent ?? null,
597
+ ctxId,
598
+ verifiedAt: Date.now()
599
+ });
600
+ }
601
+ let replayClaim = "not_required";
602
+ if (requiredFlag("TTTPS_REQUIRE_REPLAY_LEDGER")) {
603
+ const decoded = (0, import_pot_record_v08.decodePotRecordV08)(record);
604
+ replayClaim = await claimV08Replay(args.clientId ?? "", args.sessionId ?? "", decoded.nonce.toString("hex"));
605
+ if (replayClaim !== "claimed") {
606
+ return serialize({
607
+ verdict: "rejected",
608
+ reason: replayClaim === "replay" ? "replay detected" : "replay ledger unavailable",
609
+ replayClaim,
610
+ ctxId,
611
+ verifiedAt: Date.now()
612
+ });
613
+ }
614
+ }
384
615
  return serialize({
385
616
  verdict: result.verdict,
386
617
  reason: result.reason ?? null,
387
618
  payloadDigestMatchesContent: result.payloadDigestMatchesContent ?? null,
619
+ replayClaim,
388
620
  ctxId,
389
621
  verifiedAt: Date.now()
390
622
  });
@@ -645,12 +877,14 @@ async function potCheckpoint(args) {
645
877
  0 && (module.exports = {
646
878
  potCheckpoint,
647
879
  potGenerate,
880
+ potGenerateV2,
648
881
  potGraph,
649
882
  potHealth,
650
883
  potQuery,
651
884
  potStats,
652
885
  potVerify,
653
886
  potVerifyV08,
887
+ potVerifyV2,
654
888
  redis,
655
889
  restoreDagEntry,
656
890
  tttsFreshnessSeal,
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var transport_context_exports = {};
20
+ __export(transport_context_exports, {
21
+ currentTransportBinding: () => currentTransportBinding,
22
+ withTransportBinding: () => withTransportBinding
23
+ });
24
+ module.exports = __toCommonJS(transport_context_exports);
25
+ var import_node_async_hooks = require("node:async_hooks");
26
+ const storage = new import_node_async_hooks.AsyncLocalStorage();
27
+ function withTransportBinding(context, fn) {
28
+ return storage.run(context, fn);
29
+ }
30
+ function currentTransportBinding() {
31
+ return storage.getStore();
32
+ }
33
+ // Annotate the CommonJS export names for ESM import in node:
34
+ 0 && (module.exports = {
35
+ currentTransportBinding,
36
+ withTransportBinding
37
+ });
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@helm-protocol/ttt-mcp",
3
- "version": "0.3.4",
3
+ "version": "0.4.1",
4
4
  "description": "Proof-of-Time attestation — Ed25519-signed timestamps with multi-source corroboration and explicit error bounds. IETF draft-helmprotocol-tttps",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
- "ttt-mcp": "./dist/index.js"
7
+ "ttt-mcp": "dist/index.js"
8
8
  },
9
9
  "files": [
10
10
  "dist",
11
11
  "README.md"
12
12
  ],
13
13
  "scripts": {
14
- "build": "esbuild index.ts tools.ts telemetry.ts auth.ts server.ts pot_record_v08.ts --platform=node --target=node18 --format=cjs --outdir=dist",
14
+ "build": "esbuild index.ts tools.ts telemetry.ts auth.ts server.ts pot_record_v08.ts pot_record_v2.ts tls_exporter.ts transport_context.ts --platform=node --target=node18 --format=cjs --outdir=dist",
15
15
  "start": "node dist/index.js",
16
16
  "dev": "npx ts-node index.ts",
17
17
  "test": "jest --forceExit",
@@ -50,7 +50,7 @@
50
50
  "license": "BSL-1.1",
51
51
  "repository": {
52
52
  "type": "git",
53
- "url": "https://github.com/Helm-Protocol/openttt-mcp"
53
+ "url": "git+https://github.com/Helm-Protocol/openttt-mcp.git"
54
54
  },
55
55
  "homepage": "https://github.com/Helm-Protocol/openttt-mcp#readme",
56
56
  "engines": {
@@ -70,5 +70,8 @@
70
70
  "ts-jest": "^29.4.11",
71
71
  "typescript": "^5.3.3"
72
72
  },
73
- "mcpName": "io.github.Helm-Protocol/openttt-pot"
73
+ "mcpName": "io.github.Helm-Protocol/openttt-pot",
74
+ "publishConfig": {
75
+ "access": "public"
76
+ }
74
77
  }