feat: add phone frequency controls and modularize codebase

This commit is contained in:
hectorzhao
2026-07-31 22:25:23 +08:00
parent 0af671b4ed
commit ca4f591a13
216 changed files with 41579 additions and 23694 deletions
+84
View File
@@ -0,0 +1,84 @@
import { createHash } from 'node:crypto';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import ts from 'typescript';
const root = process.cwd();
const contract = JSON.parse(readFileSync(resolve(root, 'docs/contracts/admin-api-r1-methods.json'), 'utf8'));
function objectProperties(relativePath, variableNames) {
const path = resolve(root, relativePath);
const source = readFileSync(path, 'utf8');
const sourceFile = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
const result = [];
for (const statement of sourceFile.statements) {
if (!ts.isVariableStatement(statement)) continue;
for (const declaration of statement.declarationList.declarations) {
if (!variableNames.includes(declaration.name.getText(sourceFile)) || !ts.isObjectLiteralExpression(declaration.initializer)) continue;
for (const property of declaration.initializer.properties) {
result.push({
name: property.name?.getText(sourceFile),
implementationSha256: createHash('sha256').update(property.getText(sourceFile)).digest('hex'),
});
}
}
}
return result;
}
function functionBodies(relativePath) {
const path = resolve(root, relativePath);
const source = readFileSync(path, 'utf8');
const sourceFile = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
const result = new Map();
for (const statement of sourceFile.statements) {
if (ts.isFunctionDeclaration(statement) && statement.name && statement.body) {
result.set(
statement.name.text,
createHash('sha256').update(statement.body.getText(sourceFile)).digest('hex'),
);
}
}
return result;
}
function assertExactContract(label, expected, actual) {
const expectedByName = new Map(expected.map((item) => [item.name, item.implementationSha256]));
const actualByName = new Map(actual.map((item) => [item.name, item.implementationSha256]));
const missing = [...expectedByName.keys()].filter((name) => !actualByName.has(name));
const unexpected = [...actualByName.keys()].filter((name) => !expectedByName.has(name));
const changed = [...expectedByName.entries()]
.filter(([name, hash]) => actualByName.get(name) !== undefined && actualByName.get(name) !== hash)
.map(([name]) => name);
if (missing.length || unexpected.length || changed.length) {
throw new Error(`${label} contract changed: missing=[${missing}], unexpected=[${unexpected}], implementationChanged=[${changed}]`);
}
}
const adminMethods = [
...objectProperties('src/api/admin/identity.api.ts', ['adminIdentityApi']),
...objectProperties('src/api/admin/channels-reports.api.ts', ['adminChannelsReportsApi']),
...objectProperties('src/api/admin/operations.api.ts', ['adminOperationsApi']),
...objectProperties('src/api/admin/governance.api.ts', ['adminGovernanceApi']),
...objectProperties('src/api/admin/files.api.ts', ['adminFilesApi']),
];
const clientMethods = objectProperties('src/api/client/client.api.ts', ['clientApi']);
const sessionMethods = objectProperties('src/api/admin/session.api.ts', ['portalSessionApi']);
assertExactContract('adminApi', contract.objects.adminApi, adminMethods);
assertExactContract('clientApi', contract.objects.clientApi, clientMethods);
assertExactContract('portalSessionApi', contract.objects.portalSessionApi, sessionMethods);
const coreFunctions = functionBodies('src/api/core/httpClient.ts');
for (const [name, expectedHash] of Object.entries(contract.coreFunctions)) {
if (coreFunctions.get(name) !== expectedHash) {
throw new Error(`Shared HTTP behavior changed: ${name}`);
}
}
const facade = readFileSync(resolve(root, 'src/api/adminApi.ts'), 'utf8');
for (const stableExport of ['adminApi', 'clientApi', 'portalSessionApi', 'fileDownloadUrl']) {
if (!facade.includes(stableExport)) throw new Error(`Stable facade export is missing: ${stableExport}`);
}
console.log(`R1 API facade verified: ${adminMethods.length} admin methods, ${clientMethods.length} client methods, ${sessionMethods.length} session methods and ${coreFunctions.size} HTTP helpers; all implementations unchanged.`);
@@ -0,0 +1,79 @@
import fs from 'node:fs';
import path from 'node:path';
const root = process.cwd();
const contractPath = path.join(root, 'docs/contracts/admin-channels-r11.json');
const contract = JSON.parse(fs.readFileSync(contractPath, 'utf8'));
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
const failures = [];
function requirePattern(source, pattern, message) {
if (!pattern.test(source)) failures.push(message);
}
const entry = read(contract.stableEntry);
const entryLines = entry.split(/\r?\n/).length;
requirePattern(
entry,
new RegExp(`export\\s+function\\s+${contract.stableExport}\\s*\\(`),
`stable export ${contract.stableExport} is missing`,
);
if (entryLines > contract.maxStableEntryLines) {
failures.push(`stable entry has ${entryLines} lines; maximum is ${contract.maxStableEntryLines}`);
}
if (!entry.includes(`import '${contract.styleImport}';`)) {
failures.push(`stable entry does not import ${contract.styleImport}`);
}
for (const [file, exports] of Object.entries(contract.modules)) {
const source = read(file);
for (const exportedName of exports) {
requirePattern(
source,
new RegExp(`export\\s+(?:function|const|class)\\s+${exportedName}\\b`),
`${file} is missing export ${exportedName}`,
);
}
}
for (const [file, methods] of Object.entries(contract.apiCalls)) {
const source = read(file);
for (const method of methods) {
requirePattern(source, new RegExp(`adminApi\\.${method}\\b`), `${file} is missing adminApi.${method}`);
}
}
const style = read(contract.styleFile);
const globalStyle = read('src/styles/global.css');
const adminStyle = read('src/styles/admin.css');
for (const selector of contract.pageStyleSelectors) {
if (!style.includes(selector)) failures.push(`${contract.styleFile} is missing ${selector}`);
if (globalStyle.includes(selector)) failures.push(`src/styles/global.css still contains page selector ${selector}`);
}
for (const selector of contract.sharedSelectorsKeptGlobal) {
if (!globalStyle.includes(selector)) failures.push(`shared selector ${selector} was removed from global.css`);
}
for (const selector of contract.sharedSelectorsKeptInAdmin) {
if (!adminStyle.includes(selector)) failures.push(`shared selector ${selector} was removed from admin.css`);
if (globalStyle.includes(selector)) failures.push(`shared selector ${selector} still exists in global.css`);
}
for (const label of contract.interactionLabels) {
const found = [entry, ...Object.keys(contract.modules).map(read)].some((source) => source.includes(label));
if (!found) failures.push(`interaction label ${label} is missing`);
}
requirePattern(
style,
/@media\s*\(max-width:\s*780px\)[\s\S]*?\.channel-connection-summary article/,
'mobile connection-summary rule is missing',
);
if (failures.length > 0) {
console.error(`R11 admin channels verification failed:\n- ${failures.join('\n- ')}`);
process.exit(1);
}
console.log(
`R11 admin channels verified: ${entryLines} stable-entry lines, `
+ `${Object.keys(contract.modules).length} focused modules, real API calls and page-scoped CSS preserved.`,
);
@@ -0,0 +1,110 @@
import fs from 'node:fs';
import path from 'node:path';
const root = process.cwd();
const contractPath = path.join(root, 'docs/contracts/admin-enterprise-applications-r11.json');
const contract = JSON.parse(fs.readFileSync(contractPath, 'utf8'));
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
const failures = [];
function requirePattern(source, pattern, message) {
if (!pattern.test(source)) failures.push(message);
}
const entry = read(contract.stableEntry);
const entryLines = entry.split(/\r?\n/).length;
requirePattern(
entry,
new RegExp(`export\\s+function\\s+${contract.stableExport}\\s*\\(`),
`stable export ${contract.stableExport} is missing`,
);
if (entryLines > contract.maxStableEntryLines) {
failures.push(`stable entry has ${entryLines} lines; maximum is ${contract.maxStableEntryLines}`);
}
if (!entry.includes(`import '${contract.styleImport}';`)) {
failures.push(`stable entry does not import ${contract.styleImport}`);
}
const moduleSources = [];
for (const [file, exports] of Object.entries(contract.modules)) {
const source = read(file);
moduleSources.push(source);
for (const exportedName of exports) {
requirePattern(
source,
new RegExp(`export\\s+(?:function|const|class)\\s+${exportedName}\\b`),
`${file} is missing export ${exportedName}`,
);
}
if (source.includes('adminApi.')) {
failures.push(`${file} must not move real API coordination out of the stable entry`);
}
}
for (const [file, methods] of Object.entries(contract.apiCalls)) {
const source = read(file);
for (const method of methods) {
requirePattern(source, new RegExp(`adminApi\\.${method}\\b`), `${file} is missing adminApi.${method}`);
}
}
const style = read(contract.styleFile);
const globalStyle = read('src/styles/global.css');
const adminStyle = read('src/styles/admin.css');
const componentStyle = read('src/styles/components.css');
const shellStyle = read('src/styles/shell.css');
for (const selector of contract.pageStyleSelectors) {
if (!style.includes(selector)) failures.push(`${contract.styleFile} is missing ${selector}`);
if (globalStyle.includes(selector)) failures.push(`src/styles/global.css still contains page selector ${selector}`);
}
for (const selector of contract.sharedSelectorsKeptGlobal) {
if (!globalStyle.includes(selector)) failures.push(`shared selector ${selector} was removed from global.css`);
}
for (const selector of contract.sharedSelectorsKeptInAdmin) {
if (!adminStyle.includes(selector)) failures.push(`shared selector ${selector} was removed from admin.css`);
if (globalStyle.includes(selector)) failures.push(`shared selector ${selector} still exists in global.css`);
}
for (const selector of contract.sharedSelectorsKeptInComponents) {
if (!componentStyle.includes(selector)) failures.push(`shared selector ${selector} was removed from components.css`);
if (globalStyle.includes(selector)) failures.push(`shared selector ${selector} still exists in global.css`);
}
for (const selector of contract.sharedSelectorsKeptInShell) {
if (!shellStyle.includes(selector)) failures.push(`shared selector ${selector} was removed from shell.css`);
if (globalStyle.includes(selector)) failures.push(`shared selector ${selector} still exists in global.css`);
}
for (const label of contract.interactionLabels) {
if (![entry, ...moduleSources].some((source) => source.includes(label))) {
failures.push(`interaction label ${label} is missing`);
}
}
requirePattern(
style,
/@media\s*\(max-width:\s*780px\)[\s\S]*?\.admin-application-filter[\s\S]*?grid-template-columns:\s*1fr/,
'780px enterprise application filter responsive rule is missing',
);
requirePattern(
style,
/@media\s*\(max-width:\s*900px\)[\s\S]*?\.cmpp-connection-summary[\s\S]*?\.cmpp-connection-card__grid/,
'900px CMPP connection responsive rule is missing',
);
requirePattern(
style,
/@media\s*\(max-width:\s*520px\)[\s\S]*?\.cmpp-connection-summary[\s\S]*?\.cmpp-connection-card__grid/,
'520px CMPP connection responsive rule is missing',
);
requirePattern(
entry,
/const \[appliedEnterpriseKeyword,[\s\S]*?const \[appliedApplicationKeyword,[\s\S]*?const \[appliedStatus,/,
'draft and applied filter state separation is missing',
);
if (failures.length > 0) {
console.error(`R11 admin enterprise applications verification failed:\n- ${failures.join('\n- ')}`);
process.exit(1);
}
console.log(
`R11 admin enterprise applications verified: ${entryLines} stable-entry lines, `
+ `${Object.keys(contract.modules).length} focused modules, real API coordination and page-scoped CSS preserved.`,
);
@@ -0,0 +1,126 @@
import fs from 'node:fs';
import path from 'node:path';
import postcss from 'postcss';
const root = process.cwd();
const contract = JSON.parse(fs.readFileSync(path.join(root, 'docs/contracts/admin-shared-styles-r11.json'), 'utf8'));
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
const failures = [];
const style = read(contract.styleFile);
const globalStyle = read(contract.globalStyleFile);
const entry = read(contract.entryFile);
const styleTree = postcss.parse(style);
const globalTree = postcss.parse(globalStyle);
const listFiles = (directory, extensions) => {
const result = [];
const visit = (current) => {
for (const item of fs.readdirSync(current, { withFileTypes: true })) {
const absolute = path.join(current, item.name);
if (item.isDirectory()) visit(absolute);
else if (extensions.some((extension) => item.name.endsWith(extension))) result.push(absolute);
}
};
visit(path.join(root, directory));
return result;
};
const ownedClass = (className) => (
contract.ownedExactClasses.includes(className)
|| contract.ownedClassPrefixes.some((prefix) => className.startsWith(prefix))
);
const allowedContextClass = (className) => (
ownedClass(className)
|| contract.allowedContextClasses.includes(className)
|| className.startsWith('ui-')
);
const selectorClasses = (selector) => [...selector.matchAll(/\.([_a-zA-Z]+[\w-]*)/g)].map((match) => match[1]);
const selectorBranches = (rule) => rule.selectors ?? rule.selector.split(',').map((selector) => selector.trim());
if (!style.includes(contract.marker)) failures.push(`${contract.styleFile} is missing its ownership marker`);
const importOrder = [
"@/styles/tokens.css",
"@/styles/reset.css",
"@/styles/shell.css",
"@/styles/global.css",
"@/styles/admin.css",
"@/styles/components.css",
];
let previousIndex = -1;
for (const importPath of importOrder) {
const index = entry.indexOf(`import '${importPath}';`);
if (index < 0) failures.push(`${contract.entryFile} is missing ${importPath}`);
if (index <= previousIndex) failures.push(`${contract.entryFile} has an invalid shared style import order at ${importPath}`);
previousIndex = index;
}
if (entry.indexOf("import { AppRoutes }") <= previousIndex) {
failures.push('AppRoutes must load after the complete shared style dependency chain');
}
let ruleCount = 0;
let selectorCount = 0;
styleTree.walkRules((rule) => {
if (rule.parent?.type === 'atrule' && rule.parent.name.endsWith('keyframes')) return;
ruleCount += 1;
for (const selector of selectorBranches(rule)) {
selectorCount += 1;
const classes = selectorClasses(selector);
if (!classes.some(ownedClass)) failures.push(`${contract.styleFile} contains a selector without admin ownership: ${selector}`);
if (!classes.every(allowedContextClass)) failures.push(`${contract.styleFile} contains page/client context: ${selector}`);
}
});
if (ruleCount < contract.minimumRuleCount) failures.push(`${contract.styleFile} has ${ruleCount} rules; minimum is ${contract.minimumRuleCount}`);
if (selectorCount < contract.minimumSelectorCount) failures.push(`${contract.styleFile} has ${selectorCount} selectors; minimum is ${contract.minimumSelectorCount}`);
for (const selector of contract.requiredSelectors) {
if (!style.includes(selector)) failures.push(`${contract.styleFile} is missing ${selector}`);
}
globalTree.walkRules((rule) => {
if (rule.parent?.type === 'atrule' && rule.parent.name.endsWith('keyframes')) return;
for (const selector of selectorBranches(rule)) {
const classes = selectorClasses(selector);
if (!classes.some(ownedClass)) continue;
if (classes.every(allowedContextClass)) {
failures.push(`${contract.globalStyleFile} still owns a pure admin shared selector: ${selector}`);
}
}
});
const adminFiles = listFiles('src/apps/admin', ['.ts', '.tsx']);
const clientFiles = listFiles('src/apps/client', ['.ts', '.tsx']);
for (const [className, minimumFiles] of Object.entries(contract.usageRequirements)) {
const count = adminFiles.filter((file) => fs.readFileSync(file, 'utf8').includes(className)).length;
if (count < minimumFiles) failures.push(`${className} is used by ${count} admin files; minimum is ${minimumFiles}`);
}
for (const file of clientFiles) {
const source = fs.readFileSync(file, 'utf8');
for (const className of contract.ownedExactClasses) {
if (source.includes(className)) failures.push(`${path.relative(root, file)} depends on admin-only class ${className}`);
}
for (const prefix of contract.ownedClassPrefixes) {
if (source.includes(prefix)) failures.push(`${path.relative(root, file)} depends on admin-only class family ${prefix}`);
}
}
for (const requirement of contract.responsiveRequirements) {
let found = false;
styleTree.walkAtRules('media', (atRule) => {
if (atRule.params !== requirement.media) return;
atRule.walkRules((rule) => {
if (selectorBranches(rule).some((selector) => selector.includes(requirement.selector))) found = true;
});
});
if (!found) failures.push(`${contract.styleFile} is missing ${requirement.selector} under @media ${requirement.media}`);
}
if (failures.length > 0) {
console.error(`R11 admin shared styles verification failed:\n- ${failures.join('\n- ')}`);
process.exit(1);
}
console.log(
`R11 admin shared styles verified: ${ruleCount} rules, ${selectorCount} selectors, `
+ `${contract.usageRequirements ? Object.keys(contract.usageRequirements).length : 0} cross-page usages and admin-only ownership are isolated.`,
);
@@ -0,0 +1,84 @@
import fs from 'node:fs';
import path from 'node:path';
const root = process.cwd();
const contractPath = path.join(root, 'docs/contracts/admin-sms-records-r11.json');
const contract = JSON.parse(fs.readFileSync(contractPath, 'utf8'));
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
const failures = [];
function requirePattern(source, pattern, message) {
if (!pattern.test(source)) failures.push(message);
}
const entry = read(contract.stableEntry);
const entryLines = entry.split(/\r?\n/).length;
requirePattern(
entry,
new RegExp(`export\\s+function\\s+${contract.stableExport}\\s*\\(`),
`stable export ${contract.stableExport} is missing`,
);
if (entryLines > contract.maxStableEntryLines) {
failures.push(`stable entry has ${entryLines} lines; maximum is ${contract.maxStableEntryLines}`);
}
if (!entry.includes(`import '${contract.styleImport}';`)) {
failures.push(`stable entry does not import ${contract.styleImport}`);
}
for (const [file, exports] of Object.entries(contract.modules)) {
const source = read(file);
for (const exportedName of exports) {
requirePattern(
source,
new RegExp(`export\\s+(?:function|const|class)\\s+${exportedName}\\b`),
`${file} is missing export ${exportedName}`,
);
}
}
for (const [file, methods] of Object.entries(contract.apiCalls)) {
const source = read(file);
for (const method of methods) {
requirePattern(source, new RegExp(`adminApi\\.${method}\\b`), `${file} is missing adminApi.${method}`);
}
}
const style = read(contract.styleFile);
const globalStyle = read('src/styles/global.css');
const componentStyle = read('src/styles/components.css');
for (const selector of contract.pageStyleSelectors) {
if (!style.includes(selector)) failures.push(`${contract.styleFile} is missing ${selector}`);
if (globalStyle.includes(selector)) failures.push(`src/styles/global.css still contains page selector ${selector}`);
}
for (const selector of contract.sharedSelectorsKeptGlobal) {
if (!globalStyle.includes(selector)) failures.push(`shared selector ${selector} was removed from global.css`);
}
for (const selector of contract.sharedSelectorsKeptInComponents) {
if (!componentStyle.includes(selector)) failures.push(`shared selector ${selector} was removed from components.css`);
if (globalStyle.includes(selector)) failures.push(`shared selector ${selector} still exists in global.css`);
}
for (const label of contract.interactionLabels) {
const found = [entry, ...Object.keys(contract.modules).map(read)].some((source) => source.includes(label));
if (!found) failures.push(`interaction label ${label} is missing`);
}
requirePattern(
style,
/@media\s*\(max-width:\s*900px\)[\s\S]*?\.admin-sms-record-card__meta[\s\S]*?\.admin-sms-segment-card dl/,
'900px record/detail responsive rule is missing',
);
requirePattern(
style,
/@media\s*\(max-width:\s*780px\)[\s\S]*?\.admin-sms-record-filter[\s\S]*?\.admin-sms-route-list dl/,
'780px record/detail responsive rule is missing',
);
if (failures.length > 0) {
console.error(`R11 admin SMS records verification failed:\n- ${failures.join('\n- ')}`);
process.exit(1);
}
console.log(
`R11 admin SMS records verified: ${entryLines} stable-entry lines, `
+ `${Object.keys(contract.modules).length} focused modules, real API boundaries and page-scoped CSS preserved.`,
);
@@ -0,0 +1,86 @@
import fs from 'node:fs';
import path from 'node:path';
const root = process.cwd();
const contractPath = path.join(root, 'docs/contracts/admin-sms-task-progress-r11.json');
const contract = JSON.parse(fs.readFileSync(contractPath, 'utf8'));
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
const failures = [];
function requirePattern(source, pattern, message) {
if (!pattern.test(source)) failures.push(message);
}
function startsSelectorBranch(source, selector) {
const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
return new RegExp(`^\\s*${escaped}(?=[\\s,:>{])`, 'm').test(source);
}
const entry = read(contract.stableEntry);
const entryLines = entry.split(/\r?\n/).length;
requirePattern(
entry,
new RegExp(`export\\s+function\\s+${contract.stableExport}\\s*\\(`),
`stable export ${contract.stableExport} is missing`,
);
if (entryLines > contract.maxStableEntryLines) {
failures.push(`stable entry has ${entryLines} lines; maximum is ${contract.maxStableEntryLines}`);
}
if (!entry.includes(`import '${contract.styleImport}';`)) {
failures.push(`stable entry does not import ${contract.styleImport}`);
}
for (const [file, exports] of Object.entries(contract.modules)) {
const source = read(file);
for (const exportedName of exports) {
requirePattern(
source,
new RegExp(`export\\s+(?:function|const|class)\\s+${exportedName}\\b`),
`${file} is missing export ${exportedName}`,
);
}
}
for (const [file, methods] of Object.entries(contract.apiCalls)) {
const source = read(file);
for (const method of methods) {
requirePattern(source, new RegExp(`adminApi\\.${method}\\b`), `${file} is missing adminApi.${method}`);
}
}
const style = read(contract.styleFile);
const globalStyle = read('src/styles/global.css');
const adminStyle = read('src/styles/admin.css');
for (const selector of contract.pageStyleSelectors) {
if (!style.includes(selector)) failures.push(`${contract.styleFile} is missing ${selector}`);
if (globalStyle.includes(selector)) failures.push(`src/styles/global.css still contains page selector ${selector}`);
}
for (const selector of contract.sharedSelectorsKeptGlobal) {
if (!globalStyle.includes(selector)) failures.push(`shared selector ${selector} was removed from global.css`);
}
for (const selector of contract.sharedSelectorsKeptInAdmin) {
if (!adminStyle.includes(selector)) failures.push(`shared selector ${selector} was removed from admin.css`);
if (startsSelectorBranch(globalStyle, selector)) failures.push(`shared selector ${selector} still owns a branch in global.css`);
}
for (const label of contract.interactionLabels) {
const found = [entry, ...Object.keys(contract.modules).map(read)].some((source) => source.includes(label));
if (!found) failures.push(`interaction label ${label} is missing`);
}
requirePattern(
style,
/@media\s*\(max-width:\s*780px\)[\s\S]*?\.admin-task-detail-grid[\s\S]*?\.admin-carrier-grid/,
'mobile detail-grid rule is missing',
);
const model = read('src/apps/admin/sms-task-progress/taskModel.ts');
if (model.includes('adminApi.')) failures.push('pure task model contains an adminApi call');
if (failures.length > 0) {
console.error(`R11 admin SMS task progress verification failed:\n- ${failures.join('\n- ')}`);
process.exit(1);
}
console.log(
`R11 admin SMS task progress verified: ${entryLines} stable-entry lines, `
+ `${Object.keys(contract.modules).length} focused modules, real API boundaries and page-scoped CSS preserved.`,
);
@@ -0,0 +1,123 @@
import fs from 'node:fs';
import path from 'node:path';
import postcss from 'postcss';
const root = process.cwd();
const contract = JSON.parse(
fs.readFileSync(path.join(root, 'docs/contracts/app-shell-styles-r11.json'), 'utf8'),
);
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
const failures = [];
function normalizeDeclarations(rule) {
return rule.nodes
.filter((node) => node.type === 'decl')
.map((node) => `${node.prop}:${node.value}`)
.join(';');
}
function mediaOf(rule) {
let parent = rule.parent;
while (parent) {
if (parent.type === 'atrule' && parent.name === 'media') return parent.params;
parent = parent.parent;
}
return undefined;
}
const entry = read(contract.entry);
const shellImport = `import '@/styles/shell.css';`;
const afterImport = `import '${contract.importAfter}';`;
const beforeImport = `import '${contract.importBefore}';`;
const shellImportIndex = entry.indexOf(shellImport);
if (shellImportIndex < 0) {
failures.push(`${contract.entry} is missing ${shellImport}`);
} else if (
entry.indexOf(afterImport) < 0
|| entry.indexOf(beforeImport) < 0
|| entry.indexOf(afterImport) >= shellImportIndex
|| shellImportIndex >= entry.indexOf(beforeImport)
) {
failures.push(`${contract.shellFile} must load after reset and before legacy global styles`);
}
const component = read(contract.component);
for (const className of contract.shellClasses) {
if (!component.includes(className)) {
failures.push(`${contract.component} is missing shell state/class ${className}`);
}
}
const shellRoot = postcss.parse(read(contract.shellFile), { from: contract.shellFile });
const shellRules = [];
shellRoot.walkRules((rule) => shellRules.push(rule));
if (shellRules.length < contract.minimumRuleCount) {
failures.push(`${contract.shellFile} has ${shellRules.length} rules; expected at least ${contract.minimumRuleCount}`);
}
const mediaQueries = new Set();
shellRoot.walkAtRules('media', (atRule) => mediaQueries.add(atRule.params));
for (const query of contract.requiredMediaQueries) {
if (!mediaQueries.has(query)) failures.push(`${contract.shellFile} is missing @media ${query}`);
}
for (const requirement of contract.requiredRuleFragments) {
const matchingRules = shellRules.filter(
(rule) => rule.selectors.includes(requirement.selector) && mediaOf(rule) === requirement.media,
);
if (matchingRules.length !== 1) {
failures.push(
`${contract.shellFile} expected one ${requirement.selector} rule`
+ `${requirement.media ? ` in @media ${requirement.media}` : ' outside media queries'}`,
);
continue;
}
const declarations = normalizeDeclarations(matchingRules[0]);
for (const fragment of requirement.includes) {
if (!declarations.includes(fragment)) {
failures.push(`${requirement.selector} is missing declaration ${fragment}`);
}
}
}
const legacyRoots = contract.legacyFiles.map((file) => ({
file,
root: postcss.parse(read(file), { from: file }),
}));
for (const className of [...contract.shellClasses, ...contract.legacyShellClasses]) {
if (className === 'app-shell--mobile-nav-open') continue;
const classPattern = new RegExp(`(^|[^a-zA-Z0-9_-])\\.${className}(?![a-zA-Z0-9_-])`);
const owned = shellRules.some((rule) => rule.selectors.some((selector) => classPattern.test(selector)));
if (!owned) failures.push(`${contract.shellFile} does not own .${className}`);
for (const legacy of legacyRoots) {
legacy.root.walkRules((rule) => {
if (rule.selectors.some((selector) => classPattern.test(selector))) {
failures.push(`${legacy.file} still contains shell selector ${rule.selector}`);
}
});
}
}
for (const selector of contract.sharedLayoutSelectors) {
const owned = shellRules.some((rule) => rule.selectors.includes(selector) && mediaOf(rule) === undefined);
if (!owned) failures.push(`${contract.shellFile} is missing shared layout selector ${selector}`);
for (const legacy of legacyRoots) {
legacy.root.walkRules((rule) => {
if (rule.selectors.includes(selector)) {
failures.push(`${legacy.file} still owns shared layout selector ${selector}`);
}
});
}
}
if (failures.length > 0) {
console.error(`R11 AppShell styles verification failed:\n- ${[...new Set(failures)].join('\n- ')}`);
process.exit(1);
}
console.log(
`R11 AppShell styles verified: ${shellRules.length} rules, `
+ `${contract.shellClasses.length + contract.legacyShellClasses.length} shell classes, `
+ `${contract.sharedLayoutSelectors.length} shared layout selectors and responsive states are isolated.`,
);
+180
View File
@@ -0,0 +1,180 @@
import { createHash } from 'node:crypto';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import ts from '../../api/node_modules/typescript/lib/typescript.js';
const root = process.cwd();
const manifest = JSON.parse(readFileSync(resolve(root, 'docs/contracts/channels-r5-methods.json'), 'utf8'));
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
const sha256 = (value) => createHash('sha256').update(value).digest('hex');
function load(relativePath) {
const filePath = resolve(root, relativePath);
const source = readFileSync(filePath, 'utf8');
return {
source,
file: ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS),
};
}
function loadClass(relativePath, className) {
const loaded = load(relativePath);
const declaration = loaded.file.statements.find(
(statement) => ts.isClassDeclaration(statement) && statement.name?.text === className,
);
if (!declaration || !ts.isClassDeclaration(declaration)) {
throw new Error(`${className} not found in ${relativePath}`);
}
return {
...loaded,
declaration,
methods: new Map(
declaration.members.filter(ts.isMethodDeclaration).map((method) => [
method.name.getText(loaded.file),
method,
]),
),
};
}
const reverseReplacements = {
configuration: {
'this.connection.requestChannelConnection': 'this.requestChannelConnection',
'this.connection.requestChannelDisconnection': 'this.requestChannelDisconnection',
},
testing: {
'this.connection.getGatewaySubmitQueue': 'this.getGatewaySubmitQueue',
'this.connection.publishGatewaySubmitCommand': 'this.publishGatewaySubmitCommand',
},
deletion: {
'this.configuration.changeChannelStatus': 'this.changeChannelStatus',
},
};
function normalizedBody(domain, method, file) {
let printed = printer.printNode(ts.EmitHint.Unspecified, method.body, file);
for (const [current, original] of Object.entries(reverseReplacements[domain] ?? {})) {
printed = printed.split(current).join(original);
}
return printed;
}
const domainMethods = new Map();
const loadedDomains = new Map();
for (const [domain, definition] of Object.entries(manifest.domains)) {
const loaded = loadClass(definition.file, definition.className);
loadedDomains.set(domain, loaded);
for (const name of definition.methods) {
const method = loaded.methods.get(name);
if (!method?.body) throw new Error(`Missing R5 channel method: ${domain}.${name}`);
if (domainMethods.has(name)) throw new Error(`Duplicate R5 channel method: ${name}`);
domainMethods.set(name, { domain, method, file: loaded.file });
}
if (loaded.methods.size !== definition.methods.length) {
throw new Error(`Unexpected method found in R5 channel domain: ${domain}`);
}
}
const expectedMethods = [...manifest.publicMethods, ...manifest.internalMethods];
for (const expected of expectedMethods) {
const actual = domainMethods.get(expected.name);
if (!actual) throw new Error(`R5 channel implementation missing: ${expected.name}`);
if (actual.domain !== expected.domain) throw new Error(`${expected.name} moved to unexpected R5 domain`);
if (sha256(normalizedBody(actual.domain, actual.method, actual.file)) !== expected.canonicalBodySha256) {
throw new Error(`${expected.name} implementation changed during R5 split`);
}
}
if (domainMethods.size !== expectedMethods.length) {
throw new Error('Unexpected R5 channel implementation method found');
}
const facade = loadClass('api/src/channels/channels.service.ts', 'ChannelsService');
const facadePublicMethods = new Map(
[...facade.methods].filter(([, method]) =>
!method.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.PrivateKeyword),
),
);
for (const expected of manifest.publicMethods) {
const method = facadePublicMethods.get(expected.name);
if (!method?.body) throw new Error(`ChannelsService facade method missing: ${expected.name}`);
const signature = facade.source.slice(method.getStart(facade.file), method.body.getStart(facade.file)).trim();
if (signature !== expected.signature) throw new Error(`${expected.name} facade signature changed`);
}
if (facadePublicMethods.size !== manifest.publicMethods.length) {
throw new Error('Unexpected ChannelsService facade method found');
}
function declarationMap(relativePath) {
const loaded = load(relativePath);
const result = new Map();
for (const statement of loaded.file.statements) {
if (ts.isFunctionDeclaration(statement) || ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement)) {
if (statement.name) {
result.set(statement.name.text, sha256(statement.getText(loaded.file).replace(/^export\s+/, '')));
}
} else if (ts.isVariableStatement(statement)) {
for (const declaration of statement.declarationList.declarations) {
result.set(
declaration.name.getText(loaded.file),
sha256(statement.getText(loaded.file).replace(/^export\s+/, '')),
);
}
}
}
return result;
}
const contracts = declarationMap('api/src/channels/channels.contracts.ts');
for (const expected of manifest.contracts) {
if (contracts.get(expected.name) !== expected.sha256) {
throw new Error(`Channel contract changed during R5 split: ${expected.name}`);
}
}
if (contracts.size !== manifest.contracts.length) throw new Error('Unexpected channel contract found');
const helpers = declarationMap('api/src/channels/channels.helpers.ts');
for (const expected of [...manifest.helpers, ...manifest.sharedDeclarations]) {
if (helpers.get(expected.name) !== expected.sha256) {
throw new Error(`Channel helper changed during R5 split: ${expected.name}`);
}
}
if (helpers.size !== manifest.helpers.length + manifest.sharedDeclarations.length) {
throw new Error('Unexpected channel helper found');
}
const connection = loadedDomains.get('connection');
for (const token of [
'gatewayConnectionQueue',
'gatewaySubmitQueue',
'connectionTimeoutTimer',
'gatewayStartupReconnectTimer',
'gatewayReconcileTimer',
'/connections/connect',
'/connections/disconnect',
]) {
if (!connection.source.includes(token)) throw new Error(`R5 connection side-effect invariant missing: ${token}`);
}
const configuration = loadedDomains.get('configuration').source;
if (!configuration.includes('channelConnectionSettingsChanged')) {
throw new Error('R5 non-connection edit reconnect guard missing');
}
const testing = loadedDomains.get('testing').source;
const helperSource = load('api/src/channels/channels.helpers.ts').source;
if (!testing.includes('normalizeTestPhones') || !helperSource.includes('maxAttempts: 1')) {
throw new Error('R5 channel test safety invariant missing');
}
const controller = load('api/src/channels/channels.controller.ts');
for (const statement of controller.file.statements.filter(ts.isImportDeclaration)) {
if (!statement.moduleSpecifier.getText(controller.file).includes('channels.service')) continue;
const names = statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings)
? statement.importClause.namedBindings.elements.map((element) => element.name.text)
: [];
if (names.some((name) => name.endsWith('Dto'))) {
throw new Error('ChannelsController still imports DTOs from the implementation facade');
}
}
console.log(
`R5 channels facade verified: ${manifest.publicMethods.length} public methods, ${manifest.internalMethods.length} internal methods, ${manifest.contracts.length} contracts and ${manifest.helpers.length + manifest.sharedDeclarations.length} helpers/constants; side-effect invariants preserved.`,
);
@@ -0,0 +1,102 @@
import fs from 'node:fs';
import path from 'node:path';
import postcss from 'postcss';
const root = process.cwd();
const contract = JSON.parse(fs.readFileSync(path.join(root, 'docs/contracts/client-shared-styles-r11.json'), 'utf8'));
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
const failures = [];
const style = read(contract.styleFile);
const globalStyle = read(contract.globalStyleFile);
const entry = read(contract.entryFile);
const styleTree = postcss.parse(style);
const listFiles = (directory, extensions) => {
const result = [];
const visit = (current) => {
for (const item of fs.readdirSync(current, { withFileTypes: true })) {
const absolute = path.join(current, item.name);
if (item.isDirectory()) visit(absolute);
else if (extensions.some((extension) => item.name.endsWith(extension))) result.push(absolute);
}
};
visit(path.join(root, directory));
return result;
};
const clientFiles = listFiles('src/apps/client', ['.ts', '.tsx']);
const adminFiles = listFiles('src/apps/admin', ['.ts', '.tsx']);
const countUsage = (files, className) => files.filter((file) => fs.readFileSync(file, 'utf8').includes(className)).length;
const selectorClasses = (selector) => [...selector.matchAll(/\.([_a-zA-Z]+[\w-]*)/g)].map((match) => match[1]);
if (!style.includes(contract.marker)) failures.push(`${contract.styleFile} is missing its ownership marker`);
const importOrder = [
"@/styles/tokens.css",
"@/styles/reset.css",
"@/styles/shell.css",
"@/styles/global.css",
"@/styles/admin.css",
"@/styles/client.css",
"@/styles/components.css",
];
let previousIndex = -1;
for (const importPath of importOrder) {
const index = entry.indexOf(`import '${importPath}';`);
if (index < 0) failures.push(`${contract.entryFile} is missing ${importPath}`);
if (index <= previousIndex) failures.push(`${contract.entryFile} has an invalid style import order at ${importPath}`);
previousIndex = index;
}
if (entry.indexOf("import { AppRoutes }") <= previousIndex) {
failures.push('AppRoutes must load after the complete shared style dependency chain');
}
let ruleCount = 0;
let selectorCount = 0;
styleTree.walkRules((rule) => {
ruleCount += 1;
for (const selector of rule.selectors ?? [rule.selector]) {
selectorCount += 1;
const classes = selectorClasses(selector);
if (classes.length === 0 || !classes.every((className) => contract.ownedClasses.includes(className))) {
failures.push(`${contract.styleFile} contains a non-client-shared selector: ${selector}`);
}
}
});
if (ruleCount < contract.minimumRuleCount) failures.push(`${contract.styleFile} has ${ruleCount} rules; minimum is ${contract.minimumRuleCount}`);
if (selectorCount < contract.minimumSelectorCount) failures.push(`${contract.styleFile} has ${selectorCount} selectors; minimum is ${contract.minimumSelectorCount}`);
for (const [className, minimumFiles] of Object.entries(contract.usageRequirements)) {
const clientCount = countUsage(clientFiles, className);
const adminCount = countUsage(adminFiles, className);
if (clientCount < minimumFiles) failures.push(`${className} is used by ${clientCount} client files; minimum is ${minimumFiles}`);
if (adminCount !== 0) failures.push(`${className} is unexpectedly used by ${adminCount} admin files`);
const pureSelector = new RegExp(`^\\s*\\.${className}\\s*\\{`, 'm');
if (pureSelector.test(globalStyle)) failures.push(`${contract.globalStyleFile} still owns .${className}`);
}
for (const selector of contract.requiredGlobalContextSelectors) {
if (!globalStyle.includes(selector)) failures.push(`${contract.globalStyleFile} lost contextual override ${selector}`);
}
for (const [className, requirement] of Object.entries(contract.crossPortalClassesKeptGlobal)) {
if (!globalStyle.includes(`.${className}`)) failures.push(`${contract.globalStyleFile} lost cross-portal class .${className}`);
const clientCount = countUsage(clientFiles, className);
const adminCount = countUsage(adminFiles, className);
if (clientCount < requirement.minimumClientFiles) failures.push(`${className} client usage dropped to ${clientCount}`);
if (adminCount < requirement.minimumAdminFiles) failures.push(`${className} admin usage dropped to ${adminCount}`);
}
for (const className of contract.singlePageClassesKeptGlobal) {
if (!globalStyle.includes(`.${className}`)) failures.push(`${contract.globalStyleFile} lost single-page class .${className}`);
}
if (failures.length > 0) {
console.error(`R11 client shared styles verification failed:\n- ${failures.join('\n- ')}`);
process.exit(1);
}
console.log(
`R11 client shared styles verified: ${ruleCount} rule, ${selectorCount} selector, `
+ `${Object.keys(contract.crossPortalClassesKeptGlobal).length} cross-portal boundaries and single-page ownership are preserved.`,
);
@@ -0,0 +1,88 @@
import { createHash } from 'node:crypto';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import ts from '../../api/node_modules/typescript/lib/typescript.js';
const root = process.cwd();
const manifest = JSON.parse(
readFileSync(resolve(root, 'docs/contracts/admin-enterprise-signatures-r4.json'), 'utf8'),
);
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
const sha256 = (value) => createHash('sha256').update(value).digest('hex');
function load(relativePath) {
const filePath = resolve(root, relativePath);
const source = readFileSync(filePath, 'utf8');
return {
source,
file: ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX),
};
}
const componentDirectory = 'src/apps/admin/enterprise-signatures';
const loadedFiles = manifest.files.map((file) => ({
relativePath: `${componentDirectory}/${file}`,
...load(`${componentDirectory}/${file}`),
}));
const declarations = new Map();
for (const loaded of loadedFiles) {
for (const statement of loaded.file.statements) {
if (!ts.isFunctionDeclaration(statement) || !statement.name) continue;
declarations.set(statement.name.text, { statement, file: loaded.file, relativePath: loaded.relativePath });
}
}
for (const expected of manifest.movedFunctions) {
const actual = declarations.get(expected.name);
if (!actual) throw new Error(`R4 frontend function missing: ${expected.name}`);
const canonical = printer
.printNode(ts.EmitHint.Unspecified, actual.statement, actual.file)
.replace(/^export\s+/, '');
if (sha256(canonical) !== expected.canonicalSha256) {
throw new Error(`${expected.name} changed while moving out of the page`);
}
}
const table = declarations.get('EnterpriseSignaturesTable');
if (!table?.statement.body) throw new Error('EnterpriseSignaturesTable not found');
const tableReturn = table.statement.body.statements.find(ts.isReturnStatement);
if (!tableReturn?.expression) throw new Error('EnterpriseSignaturesTable return expression not found');
if (
sha256(printer.printNode(ts.EmitHint.Unspecified, tableReturn.expression, table.file)) !==
manifest.tableJsxSha256
) {
throw new Error('Enterprise signatures table JSX changed during R4 extraction');
}
const page = load('src/apps/admin/AdminEnterpriseSignaturesPage.tsx');
const pageFunction = page.file.statements.find(
(statement) => ts.isFunctionDeclaration(statement) && statement.name?.text === 'AdminEnterpriseSignaturesPage',
);
if (!pageFunction || !ts.isFunctionDeclaration(pageFunction) || !pageFunction.body) {
throw new Error('AdminEnterpriseSignaturesPage container not found');
}
const pageText = pageFunction.getText(page.file);
for (const apiCall of manifest.apiCalls) {
if (!pageText.includes(`adminApi.${apiCall}`)) {
throw new Error(`R4 page container API coordination missing: ${apiCall}`);
}
}
for (const stateName of manifest.stateNames) {
if (!new RegExp(`\\b${stateName}\\b`).test(pageText)) {
throw new Error(`R4 page container state missing: ${stateName}`);
}
}
if (!pageText.includes('<EnterpriseSignaturesTable')) {
throw new Error('AdminEnterpriseSignaturesPage does not delegate table rendering');
}
if (page.source.split(/\r?\n/).length >= manifest.originalLines) {
throw new Error('AdminEnterpriseSignaturesPage was not reduced by R4 extraction');
}
for (const loaded of loadedFiles) {
if (loaded.source.split(/\r?\n/).length > 360) {
throw new Error(`${loaded.relativePath} exceeds the R4 component size boundary`);
}
}
console.log(
`R4 enterprise-signatures page verified: ${manifest.movedFunctions.length} functions, table JSX, ${manifest.apiCalls.length} real API calls and ${manifest.stateNames.length} page states preserved.`,
);
@@ -0,0 +1,101 @@
import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
const root = process.cwd();
const contract = JSON.parse(
fs.readFileSync(path.join(root, 'docs/contracts/foundation-styles-r11.json'), 'utf8'),
);
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
const failures = [];
function normalizeSelector(selector) {
return selector.replace(/\s+/g, '').trim();
}
function normalizeDeclarations(declarations) {
return declarations
.split(';')
.map((declaration) => declaration.trim())
.filter(Boolean)
.join(';');
}
function extractFlatRules(source) {
const withoutComments = source.replace(/\/\*[\s\S]*?\*\//g, '');
return [...withoutComments.matchAll(/([^{}]+)\{([^{}]*)\}/g)].map((match) => ({
selector: normalizeSelector(match[1]),
declarations: normalizeDeclarations(match[2]),
}));
}
const entry = read(contract.entry);
let previousImportIndex = -1;
for (const importPath of contract.importOrder) {
const statement = `import '${importPath}';`;
const importIndex = entry.indexOf(statement);
if (importIndex < 0) {
failures.push(`${contract.entry} is missing ${statement}`);
} else if (importIndex <= previousImportIndex) {
failures.push(`${importPath} is not loaded in the required foundation-to-component order`);
}
previousImportIndex = importIndex;
}
const applicationImportIndex = entry.indexOf(contract.applicationImport);
if (applicationImportIndex < 0) {
failures.push(`${contract.entry} is missing the application route import`);
} else if (applicationImportIndex <= previousImportIndex) {
failures.push('AppRoutes must enter the dependency graph after the ordered foundation/component styles');
}
const tokens = read(contract.tokensFile);
const tokenNames = [...tokens.matchAll(/^\s*(--[a-z0-9-]+)\s*:/gim)].map((match) => match[1]);
if (tokenNames.length < contract.minimumTokenCount) {
failures.push(`${contract.tokensFile} has ${tokenNames.length} tokens; expected at least ${contract.minimumTokenCount}`);
}
for (const token of contract.requiredTokens) {
if (!tokenNames.includes(token)) failures.push(`${contract.tokensFile} is missing ${token}`);
}
const tokenRules = extractFlatRules(tokens);
if (tokenRules.length !== 1 || tokenRules[0].selector !== ':root') {
failures.push(`${contract.tokensFile} must remain a single :root design-token scope`);
}
const reset = read(contract.resetFile);
const resetRules = extractFlatRules(reset);
const expectedSelectors = Object.keys(contract.resetRuleHashes);
const actualSelectors = resetRules.map((rule) => rule.selector);
for (const selector of expectedSelectors) {
const matches = resetRules.filter((rule) => rule.selector === selector);
if (matches.length !== 1) {
failures.push(`${contract.resetFile} must contain exactly one ${selector} rule`);
continue;
}
const actualHash = crypto.createHash('sha256').update(matches[0].declarations).digest('hex');
if (actualHash !== contract.resetRuleHashes[selector]) {
failures.push(`${contract.resetFile} changed declarations for ${selector}`);
}
}
for (const selector of actualSelectors) {
if (!expectedSelectors.includes(selector)) {
failures.push(`${contract.resetFile} contains non-foundation selector ${selector}`);
}
}
const sourceRules = extractFlatRules(read(contract.sourceFile));
const remainingSelectors = new Set(sourceRules.map((rule) => rule.selector));
for (const selector of expectedSelectors) {
if (remainingSelectors.has(selector)) {
failures.push(`${contract.sourceFile} still owns reset selector ${selector}`);
}
}
if (failures.length > 0) {
console.error(`R11 foundation styles verification failed:\n- ${failures.join('\n- ')}`);
process.exit(1);
}
console.log(
`R11 foundation styles verified: ${tokenNames.length} design tokens, `
+ `${resetRules.length} reset/base rules and deterministic import order preserved.`,
);
+202
View File
@@ -0,0 +1,202 @@
package main
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"strings"
)
type declaration struct {
Name string `json:"name"`
Kind string `json:"kind"`
File string `json:"file"`
SHA256 string `json:"sha256"`
}
type manifest struct {
Version string `json:"version"`
Source string `json:"source"`
Declarations []declaration `json:"declarations"`
}
var requiredFiles = []string{
"server.go",
"authentication.go",
"submit.go",
"sessions.go",
"delivery.go",
"acknowledgement.go",
"pending_recovery.go",
"protocol_log.go",
"transport.go",
}
var requiredTests = []string{
"TestInboundServerAuthenticatesAndSubmits",
"TestInboundServerForwardsLongMessageFragmentsWithoutUDHAndAcknowledgesEachSubmit",
"TestSubmitResponsePrecedesQueuedFailureReceipt",
"TestDailyLimitRejectsSubmitSynchronouslyWithoutPendingReceipt",
"TestInboundServerNegotiatesCMPP2AndUsesAuthenticatedAccountForSubmit",
"TestNormalizeInboundSubmitSupportsCMPP2AndCMPP3",
"TestDownstreamDeliveryRequiresAcknowledgement",
"TestReceiptLookupDoesNotFallbackToAccountBeforeSubmitMappingExists",
"TestDownstreamDeliveryReportsAckTimeout",
"TestRecoverPendingCandidatesWritesWaitingConnectionStatus",
"TestSubmitResponseProtocolLoggerEmitsActualPacketDirection",
"TestDownstreamDeliverProtocolLoggerEmitsReceiptPacket",
}
func main() {
root, err := os.Getwd()
must(err)
manifestPath := filepath.Join(root, "docs", "contracts", "inbound-r6-declarations.json")
payload, err := os.ReadFile(manifestPath)
must(err)
var contract manifest
must(json.Unmarshal(payload, &contract))
if contract.Version != "R6" {
fail("unexpected contract version %q", contract.Version)
}
inboundDir := filepath.Join(root, "gateway", "internal", "inbound")
actual := map[string]declaration{}
for _, name := range requiredFiles {
path := filepath.Join(inboundDir, name)
source, err := os.ReadFile(path)
must(err)
if !bytes.HasPrefix(source, []byte("package inbound")) {
fail("%s must remain in package inbound", name)
}
for _, item := range declarationsInFile(path, source) {
key := item.Kind + ":" + item.Name
if previous, exists := actual[key]; exists {
fail("duplicate declaration %s in %s and %s", key, previous.File, item.File)
}
actual[key] = item
}
}
for _, expected := range contract.Declarations {
key := expected.Kind + ":" + expected.Name
item, ok := actual[key]
if !ok {
fail("missing declaration %s expected in %s", key, expected.File)
}
if item.File != expected.File {
fail("%s moved to %s, expected %s", key, item.File, expected.File)
}
if item.SHA256 != expected.SHA256 {
fail("%s implementation changed: %s != %s", key, item.SHA256, expected.SHA256)
}
}
serverSource, err := os.ReadFile(filepath.Join(inboundDir, "server.go"))
must(err)
if lines := bytes.Count(serverSource, []byte{'\n'}); lines > 100 {
fail("stable server.go grew to %d lines", lines)
}
assertContains(serverSource, "func (s Server) ListenAndServe() error")
assertContains(serverSource, "s.handleLogin")
assertContains(serverSource, "s.handleSubmit")
assertContains(serverSource, "s.handleActivity")
assertContains(serverSource, "s.handleConnectionClosed")
controlSource, err := os.ReadFile(filepath.Join(root, "gateway", "internal", "control", "server.go"))
must(err)
for _, entry := range []string{
"inbound.DisconnectAccount(",
"inbound.PushReceiptWithResult(",
"inbound.PushUplinkWithResult(",
} {
assertContains(controlSource, entry)
}
var tests bytes.Buffer
for _, name := range []string{"server_test.go", "protocol_log_test.go", "presence_test.go", "recovery_test.go"} {
source, err := os.ReadFile(filepath.Join(inboundDir, name))
must(err)
tests.Write(source)
}
for _, test := range requiredTests {
assertContains(tests.Bytes(), "func "+test+"(")
}
fmt.Printf(
"R6 inbound facade verified: %d declarations across %d focused files; stable entries and %d critical tests preserved.\n",
len(contract.Declarations), len(requiredFiles), len(requiredTests),
)
}
func declarationsInFile(path string, source []byte) []declaration {
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, path, source, parser.ParseComments)
must(err)
var result []declaration
for _, decl := range file.Decls {
if gen, ok := decl.(*ast.GenDecl); ok && gen.Tok == token.IMPORT {
continue
}
name, kind := declarationName(decl)
start := decl.Pos()
switch typed := decl.(type) {
case *ast.FuncDecl:
if typed.Doc != nil {
start = typed.Doc.Pos()
}
case *ast.GenDecl:
if typed.Doc != nil {
start = typed.Doc.Pos()
}
}
code := strings.TrimSpace(string(source[fset.Position(start).Offset:fset.Position(decl.End()).Offset]))
hash := sha256.Sum256([]byte(strings.ReplaceAll(code, "\r\n", "\n")))
result = append(result, declaration{
Name: name, Kind: kind, File: filepath.Base(path), SHA256: hex.EncodeToString(hash[:]),
})
}
return result
}
func declarationName(decl ast.Decl) (string, string) {
switch typed := decl.(type) {
case *ast.FuncDecl:
return typed.Name.Name, "func"
case *ast.GenDecl:
if len(typed.Specs) == 0 {
fail("empty declaration")
}
switch spec := typed.Specs[0].(type) {
case *ast.TypeSpec:
return spec.Name.Name, "type"
case *ast.ValueSpec:
return spec.Names[0].Name, strings.ToLower(typed.Tok.String())
}
}
fail("unsupported declaration %T", decl)
return "", ""
}
func assertContains(source []byte, fragment string) {
if !bytes.Contains(source, []byte(fragment)) {
fail("required invariant not found: %s", fragment)
}
}
func must(err error) {
if err != nil {
panic(err)
}
}
func fail(format string, args ...any) {
fmt.Fprintf(os.Stderr, "R6 verification failed: "+format+"\n", args...)
os.Exit(1)
}
+96
View File
@@ -0,0 +1,96 @@
import { createHash } from 'node:crypto';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import ts from 'typescript';
const root = process.cwd();
const contract = JSON.parse(readFileSync(resolve(root, 'docs/contracts/operations-r2-methods.json'), 'utf8'));
function sha(value) {
return createHash('sha256').update(value).digest('hex');
}
function loadSource(relativePath) {
const path = resolve(root, relativePath);
const source = readFileSync(path, 'utf8');
return { source, file: ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS) };
}
function classMethods(relativePath, className) {
const { source, file } = loadSource(relativePath);
const declaration = file.statements.find(
(statement) => ts.isClassDeclaration(statement) && statement.name?.text === className,
);
if (!declaration || !ts.isClassDeclaration(declaration)) throw new Error(`${className} not found`);
return new Map(
declaration.members.filter(ts.isMethodDeclaration).map((method) => {
if (!method.body) throw new Error(`${className}.${method.name.getText(file)} has no body`);
return [method.name.getText(file), {
signatureSha256: sha(source.slice(method.getStart(file), method.body.getStart(file))),
bodySha256: sha(method.body.getText(file)),
}];
}),
);
}
const queryClasses = {
messages: ['api/src/operations/queries/messages.queries.ts', 'OperationsMessageQueries'],
uplink: ['api/src/operations/queries/uplink.queries.ts', 'OperationsUplinkQueries'],
dashboard: ['api/src/operations/queries/dashboard.queries.ts', 'OperationsDashboardQueries'],
quality: ['api/src/operations/queries/quality.queries.ts', 'OperationsQualityQueries'],
logs: ['api/src/operations/queries/logs.queries.ts', 'OperationsLogQueries'],
downstream: ['api/src/operations/queries/downstream.queries.ts', 'OperationsDownstreamQueries'],
trace: ['api/src/operations/queries/trace.queries.ts', 'OperationsTraceQueries'],
};
const queryMethods = new Map();
for (const [group, [path, className]] of Object.entries(queryClasses)) {
for (const [name, hashes] of classMethods(path, className)) {
if (queryMethods.has(name)) throw new Error(`Duplicate R2 query method: ${name}`);
queryMethods.set(name, { group, ...hashes });
}
}
const facadeMethods = classMethods('api/src/operations/operations.service.ts', 'OperationsService');
for (const expected of contract.methods) {
const queryMethod = queryMethods.get(expected.name);
if (!queryMethod) throw new Error(`R2 query method missing: ${expected.name}`);
if (queryMethod.group !== expected.group) throw new Error(`${expected.name}: moved to unexpected group`);
if (queryMethod.signatureSha256 !== expected.signatureSha256) throw new Error(`${expected.name}: query signature changed`);
if (queryMethod.bodySha256 !== expected.bodySha256) throw new Error(`${expected.name}: query behavior changed`);
if (!expected.isPrivate) {
const facadeMethod = facadeMethods.get(expected.name);
if (!facadeMethod) throw new Error(`OperationsService facade method missing: ${expected.name}`);
if (facadeMethod.signatureSha256 !== expected.signatureSha256) throw new Error(`${expected.name}: facade signature changed`);
}
}
if (queryMethods.size !== contract.methods.length) throw new Error('Unexpected R2 query methods found');
if (facadeMethods.size !== contract.methods.filter((method) => !method.isPrivate).length) {
throw new Error('Unexpected OperationsService facade methods found');
}
const contractsSource = loadSource('api/src/operations/operations.contracts.ts').file;
const actualContracts = new Map(
contractsSource.statements.filter(ts.isInterfaceDeclaration)
.map((statement) => [statement.name.text, sha(statement.getText(contractsSource))]),
);
for (const [name, expectedHash] of Object.entries(contract.contracts)) {
if (actualContracts.get(name) !== expectedHash) throw new Error(`Operations contract changed: ${name}`);
}
const helpersSource = loadSource('api/src/operations/operations.helpers.ts').file;
const actualHelpers = new Map();
for (const statement of helpersSource.statements) {
if (ts.isFunctionDeclaration(statement) && statement.name && statement.body) {
actualHelpers.set(statement.name.text, sha(statement.body.getText(helpersSource)));
} else if (ts.isVariableStatement(statement)) {
for (const declaration of statement.declarationList.declarations) {
actualHelpers.set(declaration.name.getText(helpersSource), sha(declaration.initializer?.getText(helpersSource) ?? ''));
}
}
}
for (const [name, expectedHash] of Object.entries(contract.helpers)) {
if (actualHelpers.get(name) !== expectedHash) throw new Error(`Operations helper changed: ${name}`);
}
if (actualHelpers.size !== Object.keys(contract.helpers).length) throw new Error('Unexpected Operations helpers found');
console.log(`R2 operations facade verified: ${facadeMethods.size} public methods, ${queryMethods.size - facadeMethods.size} private methods, ${actualHelpers.size} helpers; signatures and implementations unchanged.`);
+63
View File
@@ -0,0 +1,63 @@
import { existsSync, readFileSync } from 'node:fs';
import { resolve } from 'node:path';
const root = process.cwd();
const manifestPath = resolve(root, 'docs/contracts/refactoring-r0-manifest.json');
function assert(condition, message) {
if (!condition) {
throw new Error(message);
}
}
function read(relativePath) {
const absolutePath = resolve(root, relativePath);
assert(existsSync(absolutePath), `R0 baseline file is missing: ${relativePath}`);
return readFileSync(absolutePath, 'utf8');
}
const manifest = JSON.parse(read('docs/contracts/refactoring-r0-manifest.json'));
assert(manifest.version === 'r0', 'R0 manifest version must be r0');
assert(/^[0-9a-f]{40}$/.test(manifest.baselineCommit), 'R0 baselineCommit must be a full Git commit');
for (const contract of manifest.contracts) {
for (const path of [contract.schema, contract.validator, ...(contract.examples ?? []), ...(contract.tests ?? [])].filter(Boolean)) {
read(path);
}
assert((contract.invariants?.length ?? contract.examples?.length ?? 0) > 0, `${contract.kind}: no frozen examples or invariants`);
}
const facadeChecks = [
['api/src/send-chain/send-chain.service.ts', 'export class SendChainService'],
['api/src/channels/channels.service.ts', 'export class ChannelsService'],
['api/src/operations/operations.service.ts', 'export class OperationsService'],
['api/src/sms-config/sms-config.service.ts', 'export class SmsConfigService'],
['api/src/report-materials/report-materials.service.ts', 'export class ReportMaterialsService'],
['src/api/adminApi.ts', 'export const adminApi'],
['gateway/internal/inbound/server.go', 'type Server struct'],
['gateway/internal/upstream/manager.go', 'type Manager struct'],
];
for (const [path, stableExport] of facadeChecks) {
assert(read(path).includes(stableExport), `${path}: stable facade not found: ${stableExport}`);
}
const characterizationChecks = [
['api/src/send-chain/send-chain.service.spec.ts', 'allows only one retry submit when three long-message failure receipts race'],
['api/src/send-chain/send-chain.service.spec.ts', 'creates and sends only one downstream final receipt under concurrent completion'],
['api/src/send-chain/send-chain.service.spec.ts', 'atomically claims a downstream manual requeue so concurrent requests only call Gateway once'],
['api/src/billing/billing.service.spec.ts', 'serializes and replays concurrent refunds with one balance mutation'],
['gateway/internal/inbound/server_test.go', 'TestDownstreamDeliveryRequiresAcknowledgement'],
['gateway/internal/upstream/connection_loss_test.go', 'TestReconnectDelayUsesCappedBackoffAndSlowAuthenticationRetry'],
['gateway/internal/upstream/reconnect_integration_test.go', 'TestFailedSupplierConnectionReconnectsWhenEndpointRecovers'],
['gateway/internal/cmpp/gocmpp_integration_test.go', 'TestGocmppDeliverReceiptPackAndUnpack'],
];
for (const [path, behavior] of characterizationChecks) {
assert(read(path).includes(behavior), `${path}: required characterization test is missing: ${behavior}`);
}
read('docs/refactoring/r0-responsibility-index.md');
read('docs/refactoring/r0-release-gate.md');
console.log(`R0 refactoring baseline verified: ${manifest.contracts.length} contract groups, ${facadeChecks.length} stable facades, ${characterizationChecks.length} critical behaviors.`);
@@ -0,0 +1,163 @@
import { createHash } from 'node:crypto';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import ts from '../../api/node_modules/typescript/lib/typescript.js';
const root = process.cwd();
const manifest = JSON.parse(
readFileSync(resolve(root, 'docs/contracts/report-materials-r4-methods.json'), 'utf8'),
);
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
const sha256 = (value) => createHash('sha256').update(value).digest('hex');
function loadSource(relativePath) {
const filePath = resolve(root, relativePath);
const source = readFileSync(filePath, 'utf8');
return {
source,
file: ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS),
};
}
function loadClass(relativePath, className) {
const loaded = loadSource(relativePath);
const declaration = loaded.file.statements.find(
(statement) => ts.isClassDeclaration(statement) && statement.name?.text === className,
);
if (!declaration || !ts.isClassDeclaration(declaration)) {
throw new Error(`${className} not found in ${relativePath}`);
}
return {
...loaded,
methods: new Map(
declaration.members.filter(ts.isMethodDeclaration).map((method) => [
method.name.getText(loaded.file),
method,
]),
),
};
}
const reverseReplacements = {
officialExport: {
'this.pending.findPendingItems': 'this.findPendingItems',
},
importReview: {
'this.importParser.saveImportProfile': 'this.saveImportProfile',
},
batchGeneration: {
'this.operations.claimBatchOperation': 'this.claimBatchOperation',
'this.operations.completeBatchOperation': 'this.completeBatchOperation',
'this.operations.failBatchOperation': 'this.failBatchOperation',
'this.channelExport.exportChannelBatch': 'this.exportChannelBatch',
},
};
function normalizedBody(domain, method, file) {
let printed = printer.printNode(ts.EmitHint.Unspecified, method.body, file);
for (const [current, original] of Object.entries(reverseReplacements[domain] ?? {})) {
printed = printed.split(current).join(original);
}
printed = printed
.split("ReportBatchGenerationService['preflightBatch']")
.join("ReportMaterialsService['preflightBatch']")
.split("ReportBatchGenerationService['prepareBatchItem']")
.join("ReportMaterialsService['prepareBatchItem']");
return printed;
}
const domainMethods = new Map();
for (const [domain, definition] of Object.entries(manifest.domains)) {
const loaded = loadClass(definition.file, definition.className);
for (const name of definition.methods) {
const method = loaded.methods.get(name);
if (!method?.body) throw new Error(`Missing R4 domain method: ${domain}.${name}`);
if (domainMethods.has(name)) throw new Error(`Duplicate R4 domain method: ${name}`);
domainMethods.set(name, { domain, method, file: loaded.file });
}
if (loaded.methods.size !== definition.methods.length) {
throw new Error(`Unexpected method found in R4 domain: ${domain}`);
}
}
const expectedMethods = [...manifest.publicMethods, ...manifest.internalMethods];
for (const expected of expectedMethods) {
const actual = domainMethods.get(expected.name);
if (!actual) throw new Error(`R4 implementation missing: ${expected.name}`);
if (actual.domain !== expected.domain) throw new Error(`${expected.name} moved to unexpected R4 domain`);
if (sha256(normalizedBody(actual.domain, actual.method, actual.file)) !== expected.canonicalBodySha256) {
throw new Error(`${expected.name} implementation changed during R4 split`);
}
}
if (domainMethods.size !== expectedMethods.length) {
throw new Error('Unexpected R4 implementation method found');
}
const facade = loadClass('api/src/report-materials/report-materials.service.ts', 'ReportMaterialsService');
for (const expected of manifest.publicMethods) {
const method = facade.methods.get(expected.name);
if (!method?.body) throw new Error(`ReportMaterialsService facade method missing: ${expected.name}`);
const signature = facade.source.slice(method.getStart(facade.file), method.body.getStart(facade.file)).trim();
if (signature !== expected.signature) throw new Error(`${expected.name} facade signature changed`);
}
if (facade.methods.size !== manifest.publicMethods.length) {
throw new Error('Unexpected ReportMaterialsService facade method found');
}
const contracts = loadSource('api/src/report-materials/report-materials.contracts.ts');
const actualContracts = new Map(
contracts.file.statements
.filter((statement) => ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement))
.map((statement) => [
statement.name.text,
sha256(statement.getText(contracts.file).replace(/^export\s+/, '')),
]),
);
for (const expected of manifest.contracts) {
if (actualContracts.get(expected.name) !== expected.sha256) {
throw new Error(`Report-materials contract changed during R4 split: ${expected.name}`);
}
}
if (actualContracts.size !== manifest.contracts.length) {
throw new Error('Unexpected report-materials contract found');
}
const helpers = loadSource('api/src/report-materials/report-materials.helpers.ts');
const actualHelpers = new Map();
for (const statement of helpers.file.statements) {
if (ts.isFunctionDeclaration(statement) || ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement)) {
if (statement.name) {
actualHelpers.set(statement.name.text, sha256(statement.getText(helpers.file).replace(/^export\s+/, '')));
}
} else if (ts.isVariableStatement(statement)) {
for (const declaration of statement.declarationList.declarations) {
actualHelpers.set(
declaration.name.getText(helpers.file),
sha256(statement.getText(helpers.file).replace(/^export\s+/, '')),
);
}
}
}
for (const expected of manifest.helpers) {
if (actualHelpers.get(expected.name) !== expected.sha256) {
throw new Error(`Report-materials helper changed during R4 split: ${expected.name}`);
}
}
if (actualHelpers.size !== manifest.helpers.length) {
throw new Error('Unexpected report-materials helper found');
}
const controller = loadSource('api/src/report-materials/report-materials.controller.ts');
for (const statement of controller.file.statements.filter(ts.isImportDeclaration)) {
if (!statement.moduleSpecifier.getText(controller.file).includes('report-materials.service')) continue;
const names = statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings)
? statement.importClause.namedBindings.elements.map((element) => element.name.text)
: [];
if (names.some((name) => name.endsWith('Dto') || name.endsWith('Mapping'))) {
throw new Error('ReportMaterialsController still imports contracts from the implementation facade');
}
}
console.log(
`R4 report-materials facade verified: ${manifest.publicMethods.length} public methods, ${manifest.internalMethods.length} internal methods, ${manifest.contracts.length} contracts and ${manifest.helpers.length} helpers; implementations unchanged.`,
);
+110
View File
@@ -0,0 +1,110 @@
import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import ts from 'typescript';
const root = process.cwd();
const contract = JSON.parse(
fs.readFileSync(path.join(root, 'docs/contracts/send-chain-r10-completion.json'), 'utf8'),
);
const parse = (filePath, source) =>
ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
const methodsByName = (classNode) => new Map(
classNode.members
.filter((member) => ts.isMethodDeclaration(member) && member.name && ts.isIdentifier(member.name))
.map((member) => [member.name.text, member]),
);
const digest = (value) =>
crypto.createHash('sha256').update(value.replace(/\r\n/g, '\n')).digest('hex');
const normalizeFacadeSeams = (value) => value.replaceAll('this.facade.', 'this.');
const facadePath = path.join(root, 'api/src/send-chain/send-chain.service.ts');
const facadeSource = fs.readFileSync(facadePath, 'utf8');
const facadeFile = parse(facadePath, facadeSource);
const facadeClass = facadeFile.statements.find(
(statement) => ts.isClassDeclaration(statement) && statement.name?.text === 'SendChainService',
);
const completionPath = path.join(root, 'api/src/send-chain/send-completion.service.ts');
const completionSource = fs.readFileSync(completionPath, 'utf8');
const completionFile = parse(completionPath, completionSource);
const completionClass = completionFile.statements.find(
(statement) => ts.isClassDeclaration(statement) && statement.name?.text === 'SendCompletionService',
);
if (!facadeClass || !completionClass) throw new Error('R10 facade is missing');
const facadeMethods = methodsByName(facadeClass);
const completionMethods = methodsByName(completionClass);
const domainSources = new Map();
const domainMethods = new Map();
for (const domain of contract.domains) {
const filePath = path.join(root, 'api/src/send-chain', domain.file);
const source = fs.readFileSync(filePath, 'utf8');
const file = parse(filePath, source);
const classNode = file.statements.find(
(statement) => ts.isClassDeclaration(statement) && statement.name?.text === domain.className,
);
if (!classNode) throw new Error(`R10 domain class missing: ${domain.className}`);
const methods = methodsByName(classNode);
const expectedCount = contract.methods.filter((method) => method.file === domain.file).length;
if (expectedCount !== domain.methodCount) throw new Error(`R10 domain count changed: ${domain.file}`);
domainSources.set(domain.file, { source, file });
domainMethods.set(domain.file, methods);
}
for (const expected of contract.methods) {
const domain = domainSources.get(expected.file);
const migrated = domainMethods.get(expected.file)?.get(expected.name);
if (!domain || !migrated?.body) throw new Error(`R10 migrated method missing: ${expected.name}`);
if (digest(normalizeFacadeSeams(migrated.body.getText(domain.file))) !== expected.bodySha256) {
throw new Error(`R10 migrated method body changed: ${expected.name}`);
}
const completion = completionMethods.get(expected.name);
if (!completion?.body || !completion.body.getText(completionFile).includes(`.${expected.name}(`)) {
throw new Error(`R10 completion facade does not delegate: ${expected.name}`);
}
const facade = facadeMethods.get(expected.name);
if (!facade?.body || !facade.body.getText(facadeFile).includes(`this.completion.${expected.name}(`)) {
throw new Error(`R10 stable facade does not delegate: ${expected.name}`);
}
}
if (facadeMethods.size !== 98) {
throw new Error(`R10 changed the stable SendChainService method count: ${facadeMethods.size}`);
}
const combined = [...domainSources.values()].map((item) => item.source).join('\n');
const gatewaySubmitSource = fs.readFileSync(
path.join(root, 'api/src/send-chain/send-gateway-submit.service.ts'),
'utf8',
);
for (const invariant of [
'retryOfSubmitRecordId: sourceAttempt.id',
'where: { retryOfSubmitRecordId }',
"error.code === 'P2002'",
'isCurrentAttempt',
'sms-reservation-release:${message.messageId}',
'sms-refund:${message.messageId}',
'receiptEventKey(data, logicalChannelId)',
"dedupeKey = data.deliveryType === 'receipt'",
"status: 'manual_requeueing'",
'gatewaySubmitRequeueKey',
'recordReceiptSegment',
'aggregateReceiptSegments',
]) {
if (!(combined + gatewaySubmitSource).includes(invariant)) {
throw new Error(`R10 accident invariant missing: ${invariant}`);
}
}
if (combined.includes('deleteMany(')) {
throw new Error('R10 must not delete receipt, retry, accounting or downstream history');
}
const moduleSource = fs.readFileSync(path.join(root, 'api/src/send-chain/send-chain.module.ts'), 'utf8');
if (!moduleSource.includes('providers: [SendChainService]') || moduleSource.includes('SendCompletionService,')) {
throw new Error('R10 changed the public NestJS provider boundary');
}
console.log(
`R10 send completion verified: ${contract.methods.length} methods across ${contract.domains.length} domains; `
+ '98 stable facade methods and accident invariants preserved.',
);
+113
View File
@@ -0,0 +1,113 @@
import fs from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';
import ts from 'typescript';
const root = process.cwd();
const contract = JSON.parse(fs.readFileSync(path.join(root, 'docs', 'contracts', 'send-chain-r8-pure-logic.json'), 'utf8'));
const servicePath = path.join(root, 'api', 'src', 'send-chain', 'send-chain.service.ts');
const contractsPath = path.join(root, 'api', 'src', 'send-chain', 'send-chain.contracts.ts');
const helpersPath = path.join(root, 'api', 'src', 'send-chain', 'send-chain.helpers.ts');
const service = fs.readFileSync(servicePath, 'utf8');
const contracts = fs.readFileSync(contractsPath, 'utf8');
const helpers = fs.readFileSync(helpersPath, 'utf8');
const implementationSources = [
service,
...fs.readdirSync(path.dirname(servicePath))
.filter((fileName) => /^send-.*\.service\.ts$/.test(fileName) && fileName !== 'send-chain.service.ts')
.map((fileName) => fs.readFileSync(path.join(path.dirname(servicePath), fileName), 'utf8')),
].join('\n');
const parse = (filePath, source) => ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
const contractFile = parse(contractsPath, contracts);
const helperFile = parse(helpersPath, helpers);
const serviceFile = parse(servicePath, service);
const digest = (text) => crypto.createHash('sha256').update(text.replace(/\r\n/g, '\n')).digest('hex');
const normalizedText = (source, file, node) => source.slice(node.getStart(file), node.end).trim().replace(/^export\s+/, '');
const contractDeclarations = new Map();
for (const statement of contractFile.statements) {
if (ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement)) {
contractDeclarations.set(statement.name.text, statement);
}
}
for (const expected of contract.contracts) {
const node = contractDeclarations.get(expected.name);
if (!node) throw new Error(`R8 contract missing: ${expected.name}`);
const actualHash = digest(normalizedText(contracts, contractFile, node));
if (actualHash !== expected.sha256) {
throw new Error(`R8 contract changed: ${expected.name}`);
}
}
const helperDeclarations = new Map();
for (const statement of helperFile.statements) {
if (ts.isFunctionDeclaration(statement) && statement.name) {
helperDeclarations.set(statement.name.text, statement);
} else if (ts.isVariableStatement(statement)) {
const name = statement.declarationList.declarations[0]?.name;
if (name && ts.isIdentifier(name)) helperDeclarations.set(name.text, statement);
}
}
for (const expected of contract.helpers) {
const node = helperDeclarations.get(expected.name);
if (!node) throw new Error(`R8 helper missing: ${expected.name}`);
const actualHash = digest(normalizedText(helpers, helperFile, node));
if (actualHash !== expected.sha256) {
throw new Error(`R8 helper changed: ${expected.name}`);
}
}
const serviceClass = serviceFile.statements.find(
(statement) => ts.isClassDeclaration(statement) && statement.name?.text === 'SendChainService',
);
if (!serviceClass) throw new Error('R8 stable SendChainService missing');
const serviceMethods = serviceClass.members.filter(ts.isMethodDeclaration);
if (serviceMethods.length < 60) throw new Error(`R8 service lost orchestration methods: ${serviceMethods.length}`);
for (const forbidden of serviceFile.statements) {
if (ts.isFunctionDeclaration(forbidden) || ts.isInterfaceDeclaration(forbidden) || ts.isTypeAliasDeclaration(forbidden)) {
throw new Error('R8 service still contains movable top-level declarations');
}
}
for (const invariant of [
'this.prisma.$transaction',
'publishGatewaySubmitCommand',
'getSendQueue()',
'getGatewayQueue()',
'retryMessageIfAllowed',
'recordSubmitSegments',
'recordReceiptSegment',
]) {
if (!implementationSources.includes(invariant)) throw new Error(`R8 side-effect invariant missing: ${invariant}`);
}
for (const helper of [
'selectChannelCandidate',
'aggregateReceiptSegmentState',
'isSameUpstreamEndpointIdentity',
'receiptEventKey',
]) {
if (!helperDeclarations.has(helper)) throw new Error(`R8 extracted policy missing: ${helper}`);
}
for (const controller of [
'admin-send-chain.controller.ts',
'client-send-chain.controller.ts',
'gateway-events.controller.ts',
]) {
const source = fs.readFileSync(path.join(root, 'api', 'src', 'send-chain', controller), 'utf8');
if (!source.includes("from './send-chain.contracts'")) {
throw new Error(`R8 controller does not use contracts: ${controller}`);
}
}
const spec = fs.readFileSync(path.join(root, 'api', 'src', 'send-chain', 'send-chain.helpers.spec.ts'), 'utf8');
for (const phrase of ['prefers an approved online province channel', 'keeps a segmented message non-terminal', 'generates a stable receipt event key']) {
if (!spec.includes(phrase)) throw new Error(`R8 pure policy test missing: ${phrase}`);
}
console.log(
`R8 send-chain pure logic verified: ${contract.contracts.length} contracts, ${contract.helpers.length} migrated declarations, `
+ `${serviceMethods.length} orchestration methods and 4 extracted policies preserved.`,
);
+113
View File
@@ -0,0 +1,113 @@
import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import ts from 'typescript';
const root = process.cwd();
const servicePath = path.join(root, 'api/src/send-chain/send-chain.service.ts');
const submissionPath = path.join(root, 'api/src/send-chain/send-submission.service.ts');
const contract = JSON.parse(
fs.readFileSync(path.join(root, 'docs/contracts/send-chain-r9-submission.json'), 'utf8'),
);
const serviceSource = fs.readFileSync(servicePath, 'utf8');
const submissionSource = fs.readFileSync(submissionPath, 'utf8');
const parse = (filePath, source) =>
ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
const serviceFile = parse(servicePath, serviceSource);
const submissionFile = parse(submissionPath, submissionSource);
const serviceClass = serviceFile.statements.find(
(statement) => ts.isClassDeclaration(statement) && statement.name?.text === 'SendChainService',
);
const submissionClass = submissionFile.statements.find(
(statement) => ts.isClassDeclaration(statement) && statement.name?.text === 'SendSubmissionService',
);
if (!serviceClass || !submissionClass) throw new Error('R9 send-chain facade or submission domain is missing');
const methodsByName = (classNode) => new Map(
classNode.members
.filter((member) => ts.isMethodDeclaration(member) && member.name && ts.isIdentifier(member.name))
.map((member) => [member.name.text, member]),
);
const facadeMethods = methodsByName(serviceClass);
const submissionMethods = methodsByName(submissionClass);
const domainSources = new Map();
const domainMethods = new Map();
for (const domain of contract.domains) {
const filePath = path.join(root, 'api/src/send-chain', domain.file);
const source = fs.readFileSync(filePath, 'utf8');
const file = parse(filePath, source);
const classNode = file.statements.find(
(statement) => ts.isClassDeclaration(statement) && statement.name?.text === domain.className,
);
if (!classNode) throw new Error(`R9 domain class missing: ${domain.className}`);
const methods = methodsByName(classNode);
const migratedCount = contract.methods.filter((method) => method.file === domain.file).length;
if (migratedCount !== domain.methodCount) {
throw new Error(`R9 domain contract count changed: ${domain.file}`);
}
domainSources.set(domain.file, { source, file });
domainMethods.set(domain.file, methods);
}
const combinedDomainSource = [...domainSources.values()].map((item) => item.source).join('\n');
const digest = (value) =>
crypto.createHash('sha256').update(value.replace(/\r\n/g, '\n')).digest('hex');
const normalizeFacadeSeams = (value) => value.replaceAll('this.facade.', 'this.');
for (const expected of contract.methods) {
const domain = domainSources.get(expected.file);
const migrated = domainMethods.get(expected.file)?.get(expected.name);
if (!migrated?.body) throw new Error(`R9 migrated method missing: ${expected.name}`);
if (digest(normalizeFacadeSeams(migrated.body.getText(domain.file))) !== expected.bodySha256) {
throw new Error(`R9 migrated method body changed: ${expected.name}`);
}
const internalFacade = submissionMethods.get(expected.name);
if (!internalFacade?.body || !internalFacade.body.getText(submissionFile).includes(`.${expected.name}(`)) {
throw new Error(`R9 internal submission facade does not delegate ${expected.name}`);
}
const facade = facadeMethods.get(expected.name);
if (!facade?.body) throw new Error(`R9 stable facade method missing: ${expected.name}`);
const body = facade.body.getText(serviceFile);
if (!body.includes(`this.submission.${expected.name}(`)) {
throw new Error(`R9 facade does not delegate ${expected.name}`);
}
}
for (const retained of [
'handleSubmitSegmentResult',
'handleSubmitResult',
'intakeReceipt',
'handleReceipt',
'retryMessageIfAllowed',
'recordCmppFailureReceipt',
'refundMessage',
'queueAndTryDownstreamDelivery',
]) {
const method = facadeMethods.get(retained);
if (!method?.body || method.body.getText(serviceFile).includes('this.submission.')) {
throw new Error(`R9 moved R10 behavior out of the facade: ${retained}`);
}
}
for (const invariant of [
"new Logger('SendChainService')",
'this.prisma.$transaction',
'this.callbacks.releaseMessageReservation',
'this.callbacks.recordCmppFailureReceipt',
'GATEWAY_SUBMIT_STREAM',
'await this.facade.publishGatewaySubmitCommand(command);',
'new Worker<SendJob>(',
]) {
if (!combinedDomainSource.includes(invariant)) {
throw new Error(`R9 submission invariant missing: ${invariant}`);
}
}
const moduleSource = fs.readFileSync(path.join(root, 'api/src/send-chain/send-chain.module.ts'), 'utf8');
if (!moduleSource.includes('providers: [SendChainService]') || moduleSource.includes('SendSubmissionService,')) {
throw new Error('R9 changed the public NestJS provider boundary');
}
console.log(
`R9 send submission verified: ${contract.methods.length} methods across ${contract.domains.length} domains; `
+ `${facadeMethods.size} stable facade methods, unchanged bodies and R10 boundaries preserved.`,
);
@@ -0,0 +1,113 @@
import fs from 'node:fs';
import path from 'node:path';
import postcss from 'postcss';
const root = process.cwd();
const contract = JSON.parse(
fs.readFileSync(path.join(root, 'docs/contracts/shared-components-r11.json'), 'utf8'),
);
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
const failures = [];
function mediaOf(rule) {
let parent = rule.parent;
while (parent) {
if (parent.type === 'atrule' && parent.name === 'media') return parent.params;
parent = parent.parent;
}
return undefined;
}
function normalizedDeclarations(rule) {
return rule.nodes
.filter((node) => node.type === 'decl')
.map((node) => `${node.prop}:${node.value}${node.important ? ' !important' : ''}`)
.join(';');
}
function selectorClasses(selector) {
return (selector.match(/\.([a-zA-Z0-9_-]+)/g) ?? []).map((value) => value.slice(1));
}
function isPureSharedSelector(selector) {
const classes = selectorClasses(selector);
const generic = new Set(contract.genericClasses);
const states = new Set(contract.allowedStateClasses);
const ownsSharedClass = classes.some((className) => className.startsWith('ui-') || generic.has(className));
return classes.length > 0
&& ownsSharedClass
&& classes.every((className) => className.startsWith('ui-') || generic.has(className) || states.has(className));
}
const entry = read(contract.entry);
const globalImport = entry.indexOf(`import '@/styles/global.css';`);
const componentImport = entry.indexOf(`import '@/styles/components.css';`);
if (globalImport < 0 || componentImport < 0 || globalImport >= componentImport) {
failures.push(`${contract.componentsFile} must load after ${contract.legacyFile}`);
}
const componentsSource = read(contract.componentsFile);
const markerIndexes = [
componentsSource.indexOf(contract.compatibilityMarker),
componentsSource.indexOf(contract.canonicalMarker),
componentsSource.indexOf(contract.responsiveMarker),
];
if (markerIndexes.some((index) => index < 0) || !(markerIndexes[0] < markerIndexes[1] && markerIndexes[1] < markerIndexes[2])) {
failures.push('compatibility, canonical and responsive component sections are missing or out of order');
}
const componentsRoot = postcss.parse(componentsSource, { from: contract.componentsFile });
const componentRules = [];
componentsRoot.walkRules((rule) => componentRules.push(rule));
const selectorCount = componentRules.reduce((total, rule) => total + rule.selectors.length, 0);
if (componentRules.length < contract.minimumRuleCount) {
failures.push(`${contract.componentsFile} has ${componentRules.length} rules; expected at least ${contract.minimumRuleCount}`);
}
if (selectorCount < contract.minimumSelectorCount) {
failures.push(`${contract.componentsFile} has ${selectorCount} selectors; expected at least ${contract.minimumSelectorCount}`);
}
for (const requirement of contract.requiredRuleFragments) {
const matches = componentRules.filter(
(rule) => rule.selectors.includes(requirement.selector) && mediaOf(rule) === requirement.media,
);
if (matches.length === 0) {
failures.push(
`${contract.componentsFile} is missing ${requirement.selector}`
+ `${requirement.media ? ` in @media ${requirement.media}` : ' outside media queries'}`,
);
continue;
}
const declarations = matches.map(normalizedDeclarations).join(';');
for (const fragment of requirement.includes) {
if (!declarations.includes(fragment)) {
failures.push(`${requirement.selector} is missing declaration ${fragment}`);
}
}
}
const legacyRoot = postcss.parse(read(contract.legacyFile), { from: contract.legacyFile });
legacyRoot.walkRules((rule) => {
for (const selector of rule.selectors) {
if (isPureSharedSelector(selector)) {
failures.push(`${contract.legacyFile} still owns shared component selector ${selector}`);
}
}
});
for (const [file, classNames] of Object.entries(contract.requiredComponentBindings)) {
const source = read(file);
for (const className of classNames) {
if (!source.includes(className)) failures.push(`${file} is missing component class ${className}`);
}
}
if (failures.length > 0) {
console.error(`R11 shared components verification failed:\n- ${[...new Set(failures)].join('\n- ')}`);
process.exit(1);
}
console.log(
`R11 shared components verified: ${componentRules.length} rules, ${selectorCount} selectors, `
+ `${contract.genericClasses.length} generic class families and desktop/mobile ownership are isolated.`,
);
+191
View File
@@ -0,0 +1,191 @@
import { createHash } from 'node:crypto';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import ts from '../../api/node_modules/typescript/lib/typescript.js';
const root = process.cwd();
const manifest = JSON.parse(
readFileSync(resolve(root, 'docs/contracts/sms-config-r3-methods.json'), 'utf8'),
);
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
const sha256 = (value) => createHash('sha256').update(value).digest('hex');
function loadSource(relativePath) {
const filePath = resolve(root, relativePath);
const source = readFileSync(filePath, 'utf8');
const file = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
return { source, file };
}
function loadClass(relativePath, className) {
const { source, file } = loadSource(relativePath);
const declaration = file.statements.find(
(statement) => ts.isClassDeclaration(statement) && statement.name?.text === className,
);
if (!declaration || !ts.isClassDeclaration(declaration)) {
throw new Error(`${className} not found in ${relativePath}`);
}
return {
source,
file,
methods: new Map(
declaration.members.filter(ts.isMethodDeclaration).map((method) => [
method.name.getText(file),
method,
]),
),
};
}
const reverseReplacements = {
application: {
'this.lifecycle.markTimedOutDownstreamConnections': 'this.markTimedOutDownstreamConnections',
'this.lifecycle.getApplicationDeactivationPreview': 'this.getApplicationDeactivationPreview',
'this.lifecycle.writeOperationLog': 'this.writeOperationLog',
},
signature: {
'this.reportValidation.validateSignatureReportValues': 'this.validateSignatureReportValues',
'this.reportValidation.withReportRequirementSnapshot': 'this.withReportRequirementSnapshot',
'this.reportValidation.syncSignatureReportValues': 'this.syncSignatureReportValues',
'this.audit.createAuditRecord': 'this.createAuditRecord',
},
drainage: {
'this.reportValidation.validateDrainageReportValues': 'this.validateDrainageReportValues',
'this.reportValidation.activateDrainageReporting': 'this.activateDrainageReporting',
'this.reportValidation.suspendDrainageReporting': 'this.suspendDrainageReporting',
'this.audit.createAuditRecord': 'this.createAuditRecord',
'this.audit.reviewDrainageInfo': 'this.reviewDrainageInfo',
},
template: {
'this.audit.createAuditRecord': 'this.createAuditRecord',
},
audit: {
'this.lifecycle.writeOperationLog': 'this.writeOperationLog',
'this.reportValidation.activateDrainageReporting': 'this.activateDrainageReporting',
'this.reportValidation.suspendDrainageReporting': 'this.suspendDrainageReporting',
},
reportValidation: {
'this.applications.getApplicationReportFields': 'this.getApplicationReportFields',
},
};
function normalizeDomainBody(domain, method, file) {
let printed = printer.printNode(ts.EmitHint.Unspecified, method.body, file);
for (const [current, original] of Object.entries(reverseReplacements[domain] ?? {})) {
printed = printed.split(current).join(original);
}
if (method.name.getText(file) === 'finalizeDisablingApplication') {
printed = printed
.split("SmsApplicationLifecycleService['getApplicationDeactivationPreview']")
.join("SmsConfigService['getApplicationDeactivationPreview']");
}
return printed;
}
const domainMethods = new Map();
for (const [domain, definition] of Object.entries(manifest.domains)) {
const loaded = loadClass(definition.file, definition.className);
for (const name of definition.methods) {
const method = loaded.methods.get(name);
if (!method || !method.body) throw new Error(`Missing R3 domain method: ${domain}.${name}`);
if (domainMethods.has(name)) throw new Error(`Duplicate R3 domain method: ${name}`);
domainMethods.set(name, { domain, method, file: loaded.file, source: loaded.source });
}
if (loaded.methods.size !== definition.methods.length) {
throw new Error(`Unexpected method found in R3 domain: ${domain}`);
}
}
const expectedMethods = [...manifest.publicMethods, ...manifest.internalMethods];
for (const expected of expectedMethods) {
const actual = domainMethods.get(expected.name);
if (!actual) throw new Error(`R3 implementation missing: ${expected.name}`);
if (actual.domain !== expected.domain) {
throw new Error(`${expected.name} moved to unexpected R3 domain`);
}
const bodyHash = sha256(normalizeDomainBody(actual.domain, actual.method, actual.file));
if (bodyHash !== expected.canonicalBodySha256) {
throw new Error(`${expected.name} implementation changed during R3 split`);
}
}
if (domainMethods.size !== expectedMethods.length) {
throw new Error('Unexpected R3 implementation method found');
}
const facade = loadClass('api/src/sms-config/sms-config.service.ts', 'SmsConfigService');
const facadePublicMethods = new Map(
[...facade.methods].filter(([, method]) =>
!method.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.PrivateKeyword),
),
);
for (const expected of manifest.publicMethods) {
const method = facadePublicMethods.get(expected.name);
if (!method || !method.body) throw new Error(`SmsConfigService facade method missing: ${expected.name}`);
const signature = facade.source.slice(method.getStart(facade.file), method.body.getStart(facade.file)).trim();
if (signature !== expected.signature) throw new Error(`${expected.name} facade signature changed`);
}
if (facadePublicMethods.size !== manifest.publicMethods.length) {
throw new Error('Unexpected SmsConfigService facade method found');
}
const contracts = loadSource('api/src/sms-config/sms-config.contracts.ts');
const actualContracts = new Map(
contracts.file.statements
.filter((statement) => ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement))
.map((statement) => [statement.name.text, sha256(statement.getText(contracts.file))]),
);
for (const expected of manifest.contracts) {
if (actualContracts.get(expected.name) !== expected.sha256) {
throw new Error(`SMS config contract changed during R3 split: ${expected.name}`);
}
}
if (actualContracts.size !== manifest.contracts.length) {
throw new Error('Unexpected SMS config contract found');
}
const helpers = loadSource('api/src/sms-config/sms-config.helpers.ts');
const actualHelpers = new Map(
helpers.file.statements
.filter(
(statement) =>
ts.isInterfaceDeclaration(statement) ||
ts.isTypeAliasDeclaration(statement) ||
ts.isFunctionDeclaration(statement) ||
ts.isVariableStatement(statement),
)
.map((statement) => [
ts.isVariableStatement(statement)
? statement.declarationList.declarations[0].name.getText(helpers.file)
: statement.name.text,
sha256(statement.getText(helpers.file).replace(/^export\s+/, '')),
]),
);
for (const expected of [...manifest.helpers, ...manifest.sharedDeclarations]) {
if (actualHelpers.get(expected.name) !== expected.sha256) {
throw new Error(`SMS config helper changed during R3 split: ${expected.name}`);
}
}
if (actualHelpers.size !== manifest.helpers.length + manifest.sharedDeclarations.length) {
throw new Error('Unexpected SMS config helper found');
}
for (const controller of [
'api/src/sms-config/admin-sms-config.controller.ts',
'api/src/sms-config/client-sms-config.controller.ts',
'api/src/send-chain/gateway-events.controller.ts',
]) {
const loaded = loadSource(controller);
for (const statement of loaded.file.statements.filter(ts.isImportDeclaration)) {
if (!statement.moduleSpecifier.getText(loaded.file).includes('sms-config.service')) continue;
const names = statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings)
? statement.importClause.namedBindings.elements.map((element) => element.name.text)
: [];
if (names.some((name) => name.endsWith('Dto') || name.endsWith('ListQuery'))) {
throw new Error(`${controller} still imports DTOs from the implementation facade`);
}
}
}
console.log(
`R3 SMS configuration facade verified: ${manifest.publicMethods.length} public methods, ${manifest.internalMethods.length} internal methods, ${manifest.contracts.length} contracts and ${manifest.helpers.length + manifest.sharedDeclarations.length} helpers/constants; implementations unchanged.`,
);
+256
View File
@@ -0,0 +1,256 @@
package main
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"strings"
)
type declaration struct {
Name string `json:"name"`
Kind string `json:"kind"`
Receiver string `json:"receiver,omitempty"`
File string `json:"file"`
SHA256 string `json:"sha256"`
}
type manifest struct {
Version string `json:"version"`
Source string `json:"source"`
Declarations []declaration `json:"declarations"`
}
var requiredFiles = []string{
"manager.go",
"pool.go",
"connection.go",
"reconnect.go",
"flow_control.go",
"submit.go",
"deliver.go",
"protocol_log.go",
"transport.go",
}
var requiredTests = []string{
"TestConnectionPoolAcquiresAcrossConnections",
"TestManagerDisconnectChannelRemovesPoolAndStopsReconnects",
"TestNormalizeUpstreamConfigDefaults",
"TestFailedSupplierConnectionReconnectsWhenEndpointRecovers",
"TestHandleConnectionLossNotifiesPendingSubmitters",
"TestHeartbeatTimeoutClosesConnectionAndSchedulesReconnect",
"TestReconnectDelayUsesCappedBackoffAndSlowAuthenticationRetry",
"TestHeartbeatResponseClearsOnlyMatchingRequest",
"TestSplitSubmitContentUCS2LongMessage",
"TestAssembleLongUplinkOutOfOrder",
"TestSubmitRequestPacketUsesCMPP2PacketForCMPP20Channel",
"TestSubmitRequestPacketUsesCMPP3PacketForCMPP30Channel",
"TestHandleCMPP2DeliverReceiptPostsReceiptEvent",
"TestReceiptStatusTreatsNonDeliveredFinalStatesAsUndelivered",
"TestEmitProtocolLogPostsSafeOutboundPacketEvent",
}
func main() {
root, err := os.Getwd()
must(err)
payload, err := os.ReadFile(filepath.Join(root, "docs", "contracts", "upstream-r7-declarations.json"))
must(err)
var contract manifest
must(json.Unmarshal(payload, &contract))
if contract.Version != "R7" {
fail("unexpected contract version %q", contract.Version)
}
upstreamDir := filepath.Join(root, "gateway", "internal", "upstream")
actual := map[string]declaration{}
for _, name := range requiredFiles {
path := filepath.Join(upstreamDir, name)
source, err := os.ReadFile(path)
must(err)
if !bytes.HasPrefix(source, []byte("package upstream")) {
fail("%s must remain in package upstream", name)
}
for _, item := range declarationsInFile(path, source) {
key := declarationKey(item)
if previous, exists := actual[key]; exists {
fail("duplicate declaration %s in %s and %s", key, previous.File, item.File)
}
actual[key] = item
}
}
for _, expected := range contract.Declarations {
key := declarationKey(expected)
item, ok := actual[key]
if !ok {
fail("missing declaration %s expected in %s", key, expected.File)
}
if item.File != expected.File {
fail("%s moved to %s, expected %s", key, item.File, expected.File)
}
if item.SHA256 != expected.SHA256 {
fail("%s implementation changed: %s != %s", key, item.SHA256, expected.SHA256)
}
}
managerSource, err := os.ReadFile(filepath.Join(upstreamDir, "manager.go"))
must(err)
if lines := bytes.Count(managerSource, []byte{'\n'}); lines > 250 {
fail("stable manager.go grew to %d lines", lines)
}
for _, entry := range []string{
"type Manager struct",
"type ConnectionState struct",
"func (m *Manager) ConnectChannel(",
"func (m *Manager) DisconnectChannel(",
} {
assertContains(managerSource, entry)
}
submitSource, err := os.ReadFile(filepath.Join(upstreamDir, "submit.go"))
must(err)
assertContains(submitSource, "func (m *Manager) Submit(")
controlSource, err := os.ReadFile(filepath.Join(root, "gateway", "internal", "control", "server.go"))
must(err)
for _, entry := range []string{
"*upstream.Manager",
"s.Upstream.ConnectChannel",
"s.Upstream.DisconnectChannel",
"server.Upstream.Submit",
} {
assertContains(controlSource, entry)
}
mainSource, err := os.ReadFile(filepath.Join(root, "gateway", "cmd", "gateway", "main.go"))
must(err)
assertContains(mainSource, "&upstream.Manager{APIBaseURL: apiBaseURL}")
longMessageSource, err := os.ReadFile(filepath.Join(upstreamDir, "long_message.go"))
must(err)
for _, entry := range []string{
"func splitSubmitContent(",
"func assembleLongUplink(",
"func pruneLongUplinkAssemblies(",
} {
assertContains(longMessageSource, entry)
}
var tests bytes.Buffer
testFiles, err := filepath.Glob(filepath.Join(upstreamDir, "*_test.go"))
must(err)
for _, path := range testFiles {
source, err := os.ReadFile(path)
must(err)
tests.Write(source)
}
for _, test := range requiredTests {
assertContains(tests.Bytes(), "func "+test+"(")
}
fmt.Printf(
"R7 upstream facade verified: %d declarations across %d focused files; stable entries and %d critical tests preserved.\n",
len(contract.Declarations), len(requiredFiles), len(requiredTests),
)
}
func declarationsInFile(path string, source []byte) []declaration {
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, path, source, parser.ParseComments)
must(err)
var result []declaration
for _, decl := range file.Decls {
if gen, ok := decl.(*ast.GenDecl); ok && gen.Tok == token.IMPORT {
continue
}
name, kind, receiver := declarationIdentity(decl)
start := declarationStart(decl)
code := strings.TrimSpace(string(source[fset.Position(start).Offset:fset.Position(decl.End()).Offset]))
hash := sha256.Sum256([]byte(strings.ReplaceAll(code, "\r\n", "\n")))
result = append(result, declaration{
Name: name, Kind: kind, Receiver: receiver, File: filepath.Base(path),
SHA256: hex.EncodeToString(hash[:]),
})
}
return result
}
func declarationIdentity(decl ast.Decl) (name string, kind string, receiver string) {
switch typed := decl.(type) {
case *ast.FuncDecl:
return typed.Name.Name, "func", receiverName(typed)
case *ast.GenDecl:
if len(typed.Specs) == 0 {
fail("empty declaration")
}
switch spec := typed.Specs[0].(type) {
case *ast.TypeSpec:
return spec.Name.Name, "type", ""
case *ast.ValueSpec:
return spec.Names[0].Name, strings.ToLower(typed.Tok.String()), ""
}
}
fail("unsupported declaration %T", decl)
return "", "", ""
}
func receiverName(decl *ast.FuncDecl) string {
if decl.Recv == nil || len(decl.Recv.List) == 0 {
return ""
}
switch typed := decl.Recv.List[0].Type.(type) {
case *ast.Ident:
return typed.Name
case *ast.StarExpr:
if ident, ok := typed.X.(*ast.Ident); ok {
return ident.Name
}
}
fail("unsupported receiver for %s", decl.Name.Name)
return ""
}
func declarationStart(decl ast.Decl) token.Pos {
start := decl.Pos()
switch typed := decl.(type) {
case *ast.FuncDecl:
if typed.Doc != nil {
start = typed.Doc.Pos()
}
case *ast.GenDecl:
if typed.Doc != nil {
start = typed.Doc.Pos()
}
}
return start
}
func declarationKey(item declaration) string {
key := item.Kind + ":"
if item.Receiver != "" {
key += item.Receiver + "."
}
return key + item.Name
}
func assertContains(source []byte, fragment string) {
if !bytes.Contains(source, []byte(fragment)) {
fail("required invariant not found: %s", fragment)
}
}
func must(err error) {
if err != nil {
panic(err)
}
}
func fail(format string, args ...any) {
fmt.Fprintf(os.Stderr, "R7 verification failed: "+format+"\n", args...)
os.Exit(1)
}