Addresses the documentation-layer gap after Phase A/B moved the git REMOTE from
GitHub to Codeberg but left ~100 project-self GitHub URLs embedded in markdown,
HTML, JS, and Python files. The remote-layer migration was generalised as
"GitHub is gone from the codebase" without verifying the content layer.
22 files swept in this commit. 27 additional files hold pre-existing inst_016/017/018
or inst_084 debt that would transfer on touch (hook whole-file scan). Those
await a companion hygiene-first commit before their GitHub->Codeberg flip
can land cleanly.
Sweep scope this commit:
- README.md, SECURITY.md
- 3 For-Claude-Web bundle files (GitHub URLs noted as "separate concern" in
today's earlier licence-swap commits)
- docs/markdown/deployment-guide.md
- docs/AUTOMATED_SYNC_SETUP, PLURALISM_CHECKLIST, github/AGENT_LIGHTNING_README
- docs/business-intelligence/governance-bi-tools
- docs/outreach/EXECUTIVE-BRIEF-BI-GOVERNANCE (+ v2)
- docs/research/ARCHITECTURAL-SAFEGUARDS-*
- email-templates/README.md, base-template.html
- 3 scripts/seed-*-blog-post.js (blog-seeding scripts)
- scripts/upload-document.js
- SESSION_HANDOFF_2025-10-23_FRAMEWORK_ANALYSIS.md
- SECURITY_INCIDENT_POST_MORTEM_2025-10-21.md
Pattern swaps (longest-first):
github.com/AgenticGovernance/tractatus-framework/issues -> codeberg.org/mysovereignty/tractatus-framework/issues
github.com/AgenticGovernance/tractatus-framework/discussions -> .../issues (Codeberg has no discussions feature)
github.com/AgenticGovernance/tractatus-framework.git -> codeberg.org/mysovereignty/tractatus-framework.git
github.com/AgenticGovernance/tractatus-framework -> codeberg.org/mysovereignty/tractatus-framework
git@github.com:AgenticGovernance/... -> git@codeberg.org:mysovereignty/...
github.com/AgenticGovernance/tractatus (old org/repo path) -> codeberg.org/mysovereignty/tractatus-framework
AgenticGovernance/tractatus-framework (bare) -> mysovereignty/tractatus-framework
Hook validator update (scripts/hook-validators/validate-credentials.js):
PROTECTED_VALUES.github_org: 'AgenticGovernance' -> 'mysovereignty'
PROTECTED_VALUES.license: 'Apache License 2.0' -> EUPL-1.2 long form
URL detection regex: /github\.com\/.../ -> /codeberg\.org\/.../
Placeholder checks + error messages updated to reflect Codeberg as
authoritative post-migration host. Key names (e.g. `github_org`) retained
for backward compatibility with validate-file-edit.js.
Held back from this commit (27 files total, documented reasons):
11 historical session handoffs / closedown docs / incident reports
(2025-10 through 2026-02) — modifying them rewrites the record to contain
URLs that did not exist at the time of writing, AND ownership of their
pre-existing inst_084 exposures transfers on touch.
8 live-content docs with pre-existing inst_084 debt (port/API-endpoint/
file-path exposures): docs/markdown/case-studies.md, technical-architecture,
introduction-to-the-tractatus-framework, implementation-guide-v1.1,
docs/plans/integrated-implementation-roadmap-2025, docs/governance/*,
docs/ANTHROPIC_*, docs/GOVERNANCE_SERVICE_*, docs/RESEARCH_DOCUMENTATION_*,
deployment-quickstart/*.
8 live-content docs with pre-existing inst_016/017/018 debt:
CHANGELOG.md, CONTRIBUTING.md, docs/LAUNCH_ANNOUNCEMENT, LAUNCH_CHECKLIST,
PHASE_4_REPOSITORY_ANALYSIS, PHASE_6_SUMMARY, docs/plans/research-enhancement-
roadmap-2025, docs/case-studies/pre-publication-audit-oct-2025.
Also NOT in this commit (separate concerns):
- scripts/add-inst-084-github-url-protection.js (detection-rule logic needs
framework-level decision on post-migration semantics).
- .claude/* (framework state).
- docs/PRODUCTION_DOCUMENTS_EXPORT.json (DB dump).
- package-lock.json (npm sponsor URLs, third-party).
- .git/config embedded credentials (requires out-of-band rotation on both
remote hosts + auth-strategy decision; user-action task).
Context: today's EUPL-1.2 sweep closed the licence-text-content layer
(5c386d0d / 6d49bfbf / ab0a6af4 / 4c1a26e8). This commit starts closing the
matching vendor-URL-content layer. Next: hygiene-first pass on the 16
live-content docs held back, then a second URL-flip pass on them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
221 lines
6.9 KiB
JavaScript
221 lines
6.9 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Credential & Attribution Validator
|
|
*
|
|
* Prevents unauthorized changes to:
|
|
* - Copyright attribution
|
|
* - GitHub repository URLs
|
|
* - Contact emails
|
|
* - Domain names
|
|
* - API keys/credentials
|
|
* - Legal entity names
|
|
*
|
|
* Architectural enforcement to prevent Claude from arbitrarily changing
|
|
* important credentials without human confirmation.
|
|
*
|
|
* Created: 2025-10-18
|
|
* Incident: Footer component overwrote copyright from "John G Stroh" to "Tractatus AI Safety Framework"
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
// IMMUTABLE CREDENTIALS - These values MUST NOT be changed without human approval
|
|
const PROTECTED_VALUES = {
|
|
copyright_holder: 'John G Stroh',
|
|
// Key names retained for backward compatibility; values updated to reflect
|
|
// the post-migration authoritative host (Codeberg) and licence (EUPL-1.2).
|
|
github_org: 'mysovereignty',
|
|
github_repo: 'tractatus-framework',
|
|
primary_email: 'hello@agenticgovernance.digital',
|
|
support_email: 'support@agenticgovernance.digital',
|
|
domain: 'agenticgovernance.digital',
|
|
license: 'European Union Public Licence, Version 1.2 (EUPL-1.2)',
|
|
};
|
|
|
|
// File patterns that commonly contain credentials
|
|
const CREDENTIAL_FILES = [
|
|
'public/js/components/footer.js',
|
|
'LICENSE',
|
|
'package.json',
|
|
'public/**/*.html',
|
|
'docs/**/*.md',
|
|
];
|
|
|
|
/**
|
|
* Validate that protected values haven't been arbitrarily changed
|
|
*/
|
|
function validateCredentials(filePath, fileContent) {
|
|
const violations = [];
|
|
|
|
// Check for incorrect copyright attribution
|
|
const copyrightRegex = /©.*?(\d{4})\s+([^.\n<]+)/g;
|
|
let match;
|
|
while ((match = copyrightRegex.exec(fileContent)) !== null) {
|
|
const holder = match[2].trim();
|
|
|
|
// Allow "John G Stroh" or "John G. Stroh" (with period)
|
|
if (!holder.includes('John G') && !holder.includes(PROTECTED_VALUES.copyright_holder)) {
|
|
violations.push({
|
|
type: 'COPYRIGHT_MISMATCH',
|
|
line: getLineNumber(fileContent, match.index),
|
|
found: holder,
|
|
expected: PROTECTED_VALUES.copyright_holder,
|
|
message: `Copyright holder must be "${PROTECTED_VALUES.copyright_holder}" (from LICENSE file)`
|
|
});
|
|
}
|
|
}
|
|
|
|
// Check for incorrect Codeberg URLs (authoritative host post-migration)
|
|
const repoRegex = /codeberg\.org\/([^\/\s"']+)\/([^\/\s"'<]+)/g;
|
|
while ((match = repoRegex.exec(fileContent)) !== null) {
|
|
const org = match[1];
|
|
const repo = match[2];
|
|
|
|
// Check if it's supposed to be our repo but has wrong values
|
|
if ((org !== PROTECTED_VALUES.github_org || repo !== PROTECTED_VALUES.github_repo) &&
|
|
(org.includes('tractatus') || repo.includes('tractatus'))) {
|
|
violations.push({
|
|
type: 'REPO_URL_INCORRECT',
|
|
line: getLineNumber(fileContent, match.index),
|
|
found: `${org}/${repo}`,
|
|
expected: `${PROTECTED_VALUES.github_org}/${PROTECTED_VALUES.github_repo}`,
|
|
message: `Repository reference must be "${PROTECTED_VALUES.github_org}/${PROTECTED_VALUES.github_repo}" on codeberg.org`
|
|
});
|
|
}
|
|
}
|
|
|
|
// Check for placeholder repo URLs
|
|
if (fileContent.includes('codeberg.org/yourusername') ||
|
|
fileContent.includes('codeberg.org/your-org')) {
|
|
violations.push({
|
|
type: 'REPO_PLACEHOLDER',
|
|
message: 'Repository URL contains placeholder - must use actual repository URL',
|
|
expected: `https://codeberg.org/${PROTECTED_VALUES.github_org}/${PROTECTED_VALUES.github_repo}`
|
|
});
|
|
}
|
|
|
|
// Check for incorrect domain references
|
|
const domainRegex = /https?:\/\/([a-z0-9-]+\.)+[a-z]{2,}/gi;
|
|
while ((match = domainRegex.exec(fileContent)) !== null) {
|
|
const url = match[0];
|
|
// Skip CDNs, external services, etc.
|
|
if (url.includes('tractatus') && !url.includes(PROTECTED_VALUES.domain)) {
|
|
violations.push({
|
|
type: 'DOMAIN_INCORRECT',
|
|
line: getLineNumber(fileContent, match.index),
|
|
found: url,
|
|
expected: `https://${PROTECTED_VALUES.domain}`,
|
|
message: `Domain must be "${PROTECTED_VALUES.domain}"`
|
|
});
|
|
}
|
|
}
|
|
|
|
return violations;
|
|
}
|
|
|
|
/**
|
|
* Get line number from character index
|
|
*/
|
|
function getLineNumber(content, index) {
|
|
return content.substring(0, index).split('\n').length;
|
|
}
|
|
|
|
/**
|
|
* Main validation function
|
|
*/
|
|
function validate(filePath, operation) {
|
|
// Only validate files that might contain credentials
|
|
const shouldValidate = CREDENTIAL_FILES.some(pattern => {
|
|
if (pattern.includes('*')) {
|
|
const regex = new RegExp(pattern.replace(/\*/g, '.*'));
|
|
return regex.test(filePath);
|
|
}
|
|
return filePath.includes(pattern);
|
|
});
|
|
|
|
if (!shouldValidate) {
|
|
return { valid: true };
|
|
}
|
|
|
|
// Read file content
|
|
let content;
|
|
try {
|
|
content = fs.readFileSync(filePath, 'utf-8');
|
|
} catch (error) {
|
|
// File doesn't exist yet (being created)
|
|
return { valid: true };
|
|
}
|
|
|
|
// Validate credentials
|
|
const violations = validateCredentials(filePath, content);
|
|
|
|
if (violations.length > 0) {
|
|
return {
|
|
valid: false,
|
|
violations,
|
|
message: formatViolations(filePath, violations)
|
|
};
|
|
}
|
|
|
|
return { valid: true };
|
|
}
|
|
|
|
/**
|
|
* Format violations for human-readable output
|
|
*/
|
|
function formatViolations(filePath, violations) {
|
|
let message = `\n❌ CREDENTIAL VIOLATION in ${filePath}\n\n`;
|
|
message += `PROTECTED CREDENTIALS ENFORCEMENT:\n`;
|
|
message += `The following values are IMMUTABLE and require human approval to change:\n\n`;
|
|
|
|
for (const violation of violations) {
|
|
message += ` ⚠️ ${violation.type}:\n`;
|
|
if (violation.line) {
|
|
message += ` Line: ${violation.line}\n`;
|
|
}
|
|
if (violation.found) {
|
|
message += ` Found: "${violation.found}"\n`;
|
|
}
|
|
message += ` Expected: "${violation.expected}"\n`;
|
|
message += ` Reason: ${violation.message}\n\n`;
|
|
}
|
|
|
|
message += `\n`;
|
|
message += `RESOLUTION:\n`;
|
|
message += `1. This change requires EXPLICIT human approval\n`;
|
|
message += `2. Verify the change is intentional and authorized\n`;
|
|
message += `3. If correcting an error, update to the protected values above\n`;
|
|
message += `4. If legitimately changing credentials, update PROTECTED_VALUES in:\n`;
|
|
message += ` scripts/hook-validators/validate-credentials.js\n`;
|
|
message += `\n`;
|
|
message += `Protected values are defined in LICENSE and package.json.\n`;
|
|
message += `Copyright holder: John G Stroh (LICENSE:189)\n`;
|
|
message += `Repository: mysovereignty/tractatus-framework (codeberg.org — authoritative host post-migration; US-vendor hosts forbidden per vendor policy)\n`;
|
|
|
|
return message;
|
|
}
|
|
|
|
// Export for use as module
|
|
module.exports = { validate, validateCredentials, PROTECTED_VALUES };
|
|
|
|
// CLI usage
|
|
if (require.main === module) {
|
|
const args = process.argv.slice(2);
|
|
|
|
if (args.length < 2) {
|
|
console.error('Usage: validate-credentials.js <file-path> <operation>');
|
|
process.exit(1);
|
|
}
|
|
|
|
const [filePath, operation] = args;
|
|
const result = validate(filePath, operation);
|
|
|
|
if (!result.valid) {
|
|
console.error(result.message);
|
|
process.exit(1);
|
|
}
|
|
|
|
process.exit(0);
|
|
}
|