verify-canister-migration.mjs
189 lines 6.2 KB
Raw
sha256:e4c529f14a0bb908c1caaaeb3f95f3623a1a82e636e7e3722ca2cd3dc9821263 security: npm audit fix pre-bridge 2026-07-29 Human 39 days ago
1 #!/usr/bin/env node
2 /**
3 * Static checks that hub/icp stable-memory migration contracts are still present.
4 * Fails fast if Migration.mo or main.mo drift in ways that risk an incompatible upgrade.
5 *
6 * Run: node scripts/verify-canister-migration.mjs
7 * (Also invoked from scripts/canister-predeploy.sh)
8 */
9 import fs from 'fs';
10 import path from 'path';
11 import { fileURLToPath } from 'url';
12
13 const __dirname = path.dirname(fileURLToPath(import.meta.url));
14 const REPO_ROOT = path.join(__dirname, '..');
15 const MIGRATION = path.join(REPO_ROOT, 'hub/icp/src/hub/Migration.mo');
16 const MAIN = path.join(REPO_ROOT, 'hub/icp/src/hub/main.mo');
17 const JSON_VALIDATE = path.join(REPO_ROOT, 'hub/icp/src/hub/JsonValidate.mo');
18
19 function readUtf8(p) {
20 return fs.readFileSync(p, 'utf8');
21 }
22
23 const migrationChecks = [
24 {
25 name: 'StableStorageV0: vaultEntries is (userId, pathMap) — pre–Phase-15.1',
26 ok: (s) => s.includes('vaultEntries : [(Text, [(Text, (Text, Text))])];'),
27 },
28 {
29 name: 'StableStorage V1: vaultEntries is (userId, vaultId, pathMap)',
30 ok: (s) => s.includes('vaultEntries : [(Text, Text, [(Text, (Text, Text))])];'),
31 },
32 {
33 name: 'StableStorage V1: billingByUser reserved (hosted billing roadmap)',
34 ok: (s) => s.includes('billingByUser : [(Text, BillingRecord)];'),
35 },
36 {
37 name: 'ProposalRecordV1 includes vault_id field (pre–evaluation)',
38 ok: (s) => s.includes('external_ref : Text;\n vault_id : Text;\n created_at : Text;'),
39 },
40 {
41 name: 'ProposalRecord (V2) includes evaluation_status',
42 ok: (s) => s.includes('evaluation_status : Text;') && s.includes('evaluation_waiver_json : Text;'),
43 },
44 {
45 name: 'migrateFromV0ToV1(old : { var storage : StableStorageV0 }) — historical V0→V1',
46 ok: (s) => s.includes('migrateFromV0ToV1(old : { var storage : StableStorageV0 })'),
47 },
48 {
49 name: 'migration(old : { var storage : StableStorageV7 }) — V7→V8 adds cors_allowed_origin',
50 ok: (s) => s.includes('migration(old : { var storage : StableStorageV7 })'),
51 },
52 {
53 name: 'StableStorageV5 — pre-V6 on-chain layout',
54 ok: (s) => s.includes('public type StableStorageV5'),
55 },
56 {
57 name: 'StableStorageV6 — pre-V7 on-chain layout (has operator_export_secret)',
58 ok: (s) => s.includes('public type StableStorageV6'),
59 },
60 {
61 name: 'StableStorageV7 — pre-V8 on-chain layout (has gateway_auth_secret)',
62 ok: (s) => s.includes('public type StableStorageV7'),
63 },
64 {
65 name: 'StableStorage (V8) includes operator_export_secret, gateway_auth_secret, cors_allowed_origin',
66 ok: (s) =>
67 s.includes('operator_export_secret : Text') &&
68 s.includes('gateway_auth_secret : Text') &&
69 s.includes('cors_allowed_origin : Text'),
70 },
71 {
72 name: 'StableStorageV4 type (pre-V5 proposals)',
73 ok: (s) => s.includes('public type StableStorageV4') && s.includes('[ProposalRecordV4]'),
74 },
75 {
76 name: 'ProposalRecord includes enrich + suggested frontmatter JSON (V5)',
77 ok: (s) =>
78 s.includes('assistant_notes : Text;') &&
79 s.includes('suggested_labels_json : Text;') &&
80 s.includes('assistant_suggested_frontmatter_json : Text;'),
81 },
82 {
83 name: 'ProposalRecord includes created_by (SEC-KN-4)',
84 ok: (s) => s.includes('created_by : Text;'),
85 },
86 {
87 name: 'StableStorageV5/V6/V7 pin proposal rows to ProposalRecordV7',
88 ok: (s) =>
89 s.includes('proposalEntries : [(Text, [ProposalRecordV7])];') &&
90 s.includes('public type StableStorageV5') &&
91 s.includes('public type StableStorageV6') &&
92 s.includes('public type StableStorageV7'),
93 },
94 {
95 name: 'Historical row maps return ProposalRecordV7',
96 ok: (s) =>
97 s.includes('func _proposalBeforeEnrichToCurrent(p : ProposalRecordBeforeEnrich) : ProposalRecordV7') &&
98 s.includes('func _proposalV4ToV5(p : ProposalRecordV4) : ProposalRecordV7'),
99 },
100 {
101 name: 'SEC-KN-4 migration hook maps V7 rows via _proposalV7ToCurrent',
102 ok: (s) =>
103 s.includes('func _proposalV7ToCurrent(p : ProposalRecordV7) : ProposalRecord') &&
104 s.includes('TODO(SEC-KN-4c)'),
105 },
106 {
107 name: 'V0 → V1 maps notes into vault "default"',
108 ok: (s) => s.includes('(entry.0, "default", entry.1)'),
109 },
110 {
111 name: 'V0 proposals gain vault_id "default"',
112 ok: (s) => s.includes('vault_id = "default"') && s.includes('v0ToProposalV1'),
113 },
114 ];
115
116 const mainChecks = [
117 {
118 name: 'Actor uses Migration.migration hook',
119 ok: (s) => s.includes('(with migration = Migration.migration)'),
120 },
121 {
122 name: 'persistent actor Hub',
123 ok: (s) => s.includes('persistent actor Hub'),
124 },
125 {
126 name: 'Imports Migration module',
127 ok: (s) => s.includes('import Migration "Migration"'),
128 },
129 {
130 name: 'Imports JsonValidate + normalizes enrich fragments on GET proposal',
131 ok: (s) =>
132 s.includes('import JsonValidate "JsonValidate"') &&
133 s.includes('JsonValidate.normalizeJsonArrayFragment') &&
134 s.includes('JsonValidate.prepareEnrichJsonArray'),
135 },
136 {
137 name: 'Stable storage type matches Migration.StableStorage',
138 ok: (s) => s.includes('type StableStorage = Migration.StableStorage'),
139 },
140 ];
141
142 let failed = 0;
143
144 for (const { name, ok } of migrationChecks) {
145 const text = readUtf8(MIGRATION);
146 if (!ok(text)) {
147 console.error(`FAIL: ${name}\n file: ${MIGRATION}`);
148 failed++;
149 }
150 }
151
152 for (const { name, ok } of mainChecks) {
153 const text = readUtf8(MAIN);
154 if (!ok(text)) {
155 console.error(`FAIL: ${name}\n file: ${MAIN}`);
156 failed++;
157 }
158 }
159
160 const jsonValidateChecks = [
161 {
162 name: 'JsonValidate.mo: enrich prepare + GET normalize helpers',
163 ok: (s) =>
164 s.includes('prepareEnrichJsonArray') &&
165 s.includes('prepareEnrichJsonObject') &&
166 s.includes('normalizeJsonArrayFragment') &&
167 s.includes('normalizeJsonObjectFragment'),
168 },
169 ];
170
171 if (!fs.existsSync(JSON_VALIDATE)) {
172 console.error(`FAIL: JsonValidate.mo missing\n file: ${JSON_VALIDATE}`);
173 failed++;
174 } else {
175 const jv = readUtf8(JSON_VALIDATE);
176 for (const { name, ok } of jsonValidateChecks) {
177 if (!ok(jv)) {
178 console.error(`FAIL: ${name}\n file: ${JSON_VALIDATE}`);
179 failed++;
180 }
181 }
182 }
183
184 if (failed > 0) {
185 console.error(`\nverify-canister-migration: ${failed} check(s) failed.`);
186 process.exit(1);
187 }
188
189 console.log('verify-canister-migration: OK (Migration.mo + main.mo contracts).');
File History 6 commits
sha256:e4c529f14a0bb908c1caaaeb3f95f3623a1a82e636e7e3722ca2cd3dc9821263 security: npm audit fix pre-bridge 2026-07-29 Human 39 days ago
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor 55 days ago
sha256:873e30b7fafe601346295f8f4289f388f21d8f715f28584d5481899ba2b714fc Merge pull request #249 from aaronrene/muse-mirror Agent 72 days ago
sha256:d8c648b20a4d53b2673c5c082ee7edfa7b2fc9b11080832da1f38807b6bf940b fix(7C-L1b): route hosted delegation proposals through cani… Human minor 75 days ago
sha256:2827ba9e7632a4b141c50caf1e8f7d77abbc3515be20e7465f2bccb0ac4edf91 fix: repair endpoint now sets has_active_subscription when … Human minor 95 days ago