magenta-canon 0.1.4 → 0.1.6

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
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="https://raw.githubusercontent.com/royal-ohio/magenta-canon/main/docs/magenta-canon-banner.png" alt="Magenta Canon — Verifiable MCP Gateway" width="820">
2
+ <img src="https://themagentacanon.com/assets/magenta-canon-banner_1780327038059-CLzCWO02.png" alt="Magenta Canon — Verifiable MCP Gateway" width="820">
3
3
  </p>
4
4
 
5
5
  # Magenta Canon
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "magenta-canon",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "description": "A verifiable MCP accountability gateway for AI-agent tool calls: allows authorized calls, blocks unauthorized calls, records both, and produces cryptographic evidence anyone can verify.",
@@ -45,6 +45,7 @@
45
45
  "public/canon/spine/constitutional-spine.v1.json",
46
46
  "README.md",
47
47
  "LICENSE",
48
+ "!scripts/heartbeat-loop.cjs",
48
49
  "!**/*.test.ts",
49
50
  "!**/*.test.mjs",
50
51
  "!**/*.test.js"
package/server/index.ts CHANGED
@@ -101,10 +101,13 @@ app.use((req: Request & { cspNonce?: string }, res, next) => {
101
101
  );
102
102
  res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
103
103
  } else {
104
- // Development: allow unsafe-inline since Vite needs it for HMR
104
+ // Development: allow unsafe-inline since Vite injects nonce-less inline
105
+ // scripts (React Fast Refresh preamble + HMR client). A nonce in script-src
106
+ // would make browsers ignore 'unsafe-inline', blocking those scripts and
107
+ // breaking the dev client ("can't detect preamble"), so omit the nonce here.
105
108
  res.setHeader(
106
109
  "Content-Security-Policy",
107
- `default-src 'self'; script-src 'self' 'unsafe-inline' 'nonce-${nonce}'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self'; frame-ancestors 'none'`
110
+ `default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' ws: wss:; frame-ancestors 'none'`
108
111
  );
109
112
  }
110
113
 
@@ -1,86 +0,0 @@
1
- const crypto = require('crypto');
2
- const fs = require('fs');
3
- const path = require('path');
4
-
5
- const BRAIN_HUB = process.env.BRAIN_HUB_URL || 'https://migration-hub.replit.app';
6
- const ACCESS_TOKEN = process.env.ACCESS_TOKEN;
7
- const REPO_NAME = process.env.REPO_NAME || 'magenta-canon';
8
- const IDENTITY_PATH = path.join(process.cwd(), '.reality-identity.json');
9
- const INTERVAL_MS = 45000;
10
-
11
- function stableStringify(obj) {
12
- if (obj === null || obj === undefined) return 'null';
13
- if (typeof obj === 'string') return JSON.stringify(obj);
14
- if (typeof obj === 'number' || typeof obj === 'boolean') return String(obj);
15
- if (Array.isArray(obj)) return '[' + obj.map(stableStringify).join(',') + ']';
16
- return '{' + Object.keys(obj).sort().map(k => JSON.stringify(k) + ':' + stableStringify(obj[k])).join(',') + '}';
17
- }
18
-
19
- let identity = null;
20
-
21
- function loadIdentity() {
22
- try {
23
- identity = JSON.parse(fs.readFileSync(IDENTITY_PATH, 'utf8'));
24
- return true;
25
- } catch (e) {
26
- console.error('[heartbeat] Failed to load identity:', e.message);
27
- return false;
28
- }
29
- }
30
-
31
- async function sendHeartbeat() {
32
- if (!identity) {
33
- if (!loadIdentity()) return;
34
- }
35
-
36
- const timestamp = new Date().toISOString();
37
- const payload = { adapterId: REPO_NAME, version: '2.0.0', timestamp };
38
- const canonical = stableStringify(payload);
39
-
40
- const privateKey = crypto.createPrivateKey(identity.privateKeyPem);
41
- const signature = crypto.sign(null, Buffer.from(canonical), privateKey).toString('hex');
42
-
43
- const body = {
44
- adapterId: REPO_NAME,
45
- version: '2.0.0',
46
- timestamp,
47
- signature,
48
- publicKey: identity.publicKeyHex,
49
- algorithm: 'ed25519'
50
- };
51
-
52
- try {
53
- const resp = await fetch(`${BRAIN_HUB}/api/adapters/${REPO_NAME}/heartbeat`, {
54
- method: 'POST',
55
- headers: { 'Content-Type': 'application/json', 'x-access-token': ACCESS_TOKEN },
56
- body: JSON.stringify(body)
57
- });
58
- const result = await resp.json();
59
- if (result.ok) {
60
- console.log(`[heartbeat] Heartbeat accepted at ${timestamp}`);
61
- } else {
62
- console.error(`[heartbeat] Heartbeat rejected:`, JSON.stringify(result));
63
- if (result.error && result.error.includes('mismatch')) {
64
- console.log('[heartbeat] Reloading identity...');
65
- loadIdentity();
66
- }
67
- }
68
- } catch (e) {
69
- console.error(`[heartbeat] Network error: ${e.message}`);
70
- }
71
- }
72
-
73
- async function main() {
74
- if (!loadIdentity()) {
75
- console.error('[heartbeat] Cannot start without identity file at', IDENTITY_PATH);
76
- process.exit(1);
77
- }
78
- console.log(`[heartbeat] Starting heartbeat loop for ${REPO_NAME} every ${INTERVAL_MS / 1000}s`);
79
- console.log(`[heartbeat] Identity: ${identity.adapterId}`);
80
- console.log(`[heartbeat] Brain Hub: ${BRAIN_HUB}`);
81
-
82
- await sendHeartbeat();
83
- setInterval(sendHeartbeat, INTERVAL_MS);
84
- }
85
-
86
- main();