CRITICAL FIX: Newsletter subscription was returning "Forbidden" error
because the CSRF protection was incorrectly configured.
Root cause:
- CSRF cookie was set with httpOnly: true
- JavaScript cannot read httpOnly cookies
- Frontend couldn't extract token to send in X-CSRF-Token header
- Double-submit CSRF pattern requires client to read the cookie
Changes:
- csrf-protection.middleware.js: Set httpOnly: false (required for double-submit pattern)
- blog.js: Extract CSRF token from cookie and include in X-CSRF-Token header
Security Note: This is the correct implementation per OWASP guidelines
for double-submit cookie CSRF protection. The cookie is still protected
by SameSite: strict and domain restrictions.
Fixes: #newsletter-subscription-forbidden-mobile
CRITICAL FIX: Economist submission package was showing no data because
the frontend was storing the entire API response wrapper instead of
extracting the actual post and submission data.
Changes:
- submission-modal-enhanced.js: Extract .post from blog API response
- submission-modal-enhanced.js: Extract .data from submissions API response
- publications.routes.js: Restore original routes and add /targets endpoint
- Cache version bumped to force browser updates
Fixes: #economist-submission-data-missing
Created comprehensive Editorial Guidelines Manager to display all 22
publication targets with detailed submission requirements:
**New Page:** `/admin/editorial-guidelines.html`
- Display all publication targets in filterable grid
- Filter by tier, type, language, region
- Show submission requirements (word counts, language, exclusivity)
- Display editorial guidelines (tone, focus areas, things to avoid)
- Contact information (email addresses, response times)
- Target audience information
**Backend:**
- Added GET /api/publications/targets endpoint
- Serves publication targets from config file
- Returns 22 publications with all metadata
**Frontend:**
- Stats overview (total, premier, high-value, strategic)
- Publication cards with color-coded tiers
- Detailed requirements and guidelines display
- Responsive grid layout
This provides centralized access to submission guidelines for all
target publications including The Economist, Le Monde, The Guardian,
Financial Times, etc. Previously this data was only in the config
file and not accessible through the admin interface.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed 401 Unauthorized errors in blog validation/submission modal:
- Added Authorization Bearer token to /api/blog/admin/:id fetch (line 153)
- Added Authorization Bearer token to /api/submissions/by-blog-post/:id fetch (line 162)
- Added Authorization Bearer token to /api/submissions/:id/export fetch (line 818)
All admin API endpoints require authentication. The submission modal
was making unauthenticated requests, causing 401 errors when trying
to load article data or export submission packages.
The 404 error on by-blog-post is expected when no submission exists
for that blog post ID yet.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Removed 'block-all-mixed-content' from Content-Security-Policy as it's
deprecated and made obsolete by 'upgrade-insecure-requests' which
already handles mixed content by upgrading it to HTTPS.
This eliminates the Firefox console warning:
"Ignoring 'block-all-mixed-content' because mixed content display
upgrading makes block-all-mixed-content obsolete."
Modern browsers automatically upgrade all mixed content (HTTP resources
on HTTPS pages) when upgrade-insecure-requests is present, providing
the same security without the deprecated directive.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed invalid CSS property 'justify-center;' on line 486 of
tractatus-theme.css. Changed to correct CSS property:
'justify-content: center;'
This was causing Firefox console errors:
"Unknown property 'justify-center;'. Declaration dropped."
The error was in the .loading-overlay class which is used for
loading states across admin pages. The invalid property prevented
proper centering of loading spinners on mobile devices.
Also regenerated minified CSS (39.4% size reduction: 24KB → 15KB).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed inconsistent cache version parameters across admin pages.
All HTML files now use v=0.1.0.1761262254119 to ensure mobile
browsers fetch fresh assets.
Changes:
- Updated all 12 admin HTML files to consistent cache version
- Updated all 17 public HTML files via update-cache-version script
- Service worker version: 0.1.1
- Version.json: 0.1.1
This ensures service worker cache invalidation triggers properly
and all pages reference matching asset versions.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Updated service worker cache version from 0.1.1 to 0.1.2 to force
mobile browsers to invalidate old cached assets. This ensures users
see the latest calendar.js with enhanced error handling.
Also updated version.json to match with relevant changelog entries
for mobile calendar fixes and DeepL integration.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added cache: 'no-store' to prevent cached 500 errors
- Enhanced error messages with status codes
- Display detailed error messages to user
- Log API response text for debugging
- Helps diagnose mobile loading issues
Frontend:
- Add translate button click handler in submission-modal-enhanced.js
- Display loading state during translation (⏳ Translating...)
- Update French textarea with translated content
- Auto-update word counts after translation
- Show success message with DeepL attribution
Backend:
- Add POST /api/submissions/:id/translate endpoint
- Integrate Translation.service (DeepL)
- Save translations to SubmissionTracking.documents
- Mark translations as 'translatedBy: deepl', 'approved: false'
- Return translated text with caching metadata
Complete Translation Flow:
1. User clicks 'Translate EN → FR' button
2. Frontend sends English text to /api/submissions/:id/translate
3. Backend calls DeepL API via Translation.service
4. Translation cached for 24 hours
5. Result saved to submission.documents[docType].versions[]
6. French textarea populated with translation
7. User can review/edit before saving submission
Next: Configure DEEPL_API_KEY in .env to enable translations
- Display English and French versions side-by-side for all documents
- Add 'Translate EN → FR' button using DeepL
- Show word counts for each language version
- Display translation metadata (translatedBy, approved status)
- Mark primary language for each document
- Support readonly mode for blog-linked content
Documents tab now shows:
- Main Article (EN/FR)
- Cover Letter (EN/FR)
- Author Bio (EN/FR)
- Pitch Email (EN/FR)
Next: Add translation button click handler and API endpoint
**GOVERNANCE RULE**: Tractatus uses DeepL API ONLY for all translations.
NEVER use LibreTranslate or any other translation service.
Changes:
- Created Translation.service.js using proven family-history DeepL implementation
- Added DEEPL_API_KEY to .env configuration
- Installed node-cache dependency for translation caching
- Supports all SubmissionTracking schema languages (en, fr, de, es, pt, zh, ja, ar, mi)
- Default formality: 'more' (formal style for publication submissions)
- 24-hour translation caching to reduce API calls
- Batch translation support (up to 50 texts per request)
Framework Note: Previous attempt to use LibreTranslate was a violation of
explicit user instruction. This has been corrected.
Signed-off-by: Claude <noreply@anthropic.com>
- Add data-is-standalone flag to manage-submission buttons
- Create openStandaloneSubmissionModal function for packages without blog posts
- Update renderOverviewTab to handle null article (standalone submissions)
- Display standalone submission notice with purple badge
- Load submission data directly via /api/submissions/{id}
- Differentiate UI labels (Submitted vs Published dates)
- Files modified: blog-validation.js, submission-modal-enhanced.js
- Add cache: 'no-store' to all apiCall functions in admin JS files
- Prevents browser fetch cache from serving stale error responses
- Addresses submissions endpoint 500 errors that weren't appearing in server logs
- Killed duplicate server process (PID 1583625)
- Added debug logging to submissions controller
- Files modified: blog-validation.js, blog-curation.js, blog-curation-enhanced.js
- User is also a native MongoDB class, not Mongoose model
- Removed all .populate() calls for createdBy, lastUpdatedBy, notes.author
- These were causing MissingSchemaError for User model
- Submissions can be returned without populated user data
- Updated to v0.1.1 to force browser refresh
- Ensures users get fixed submissions controller code
- Removed BlogPost populate() calls that caused 500 errors
- Line 49 has sessionId with unique: true (creates index automatically)
- Line 75 had redundant SessionSchema.index({ sessionId: 1 })
- Removed explicit index to eliminate Mongoose duplicate warning
- PageViewSchema had 'index: true' on sessionId field (line 16)
- AND compound index PageViewSchema.index({ sessionId: 1, timestamp: -1 })
- Compound index already covers sessionId queries (leftmost prefix)
- Removed redundant single-field index to eliminate Mongoose warning
- SessionSchema had both 'unique: true' and 'index: true'
- unique already creates an index, making index redundant
- Resolves Mongoose warning about duplicate schema index
- BlogPost uses native MongoDB (not Mongoose), causing MissingSchemaError
- Removed all .populate('blogPostId') calls that tried to reference non-existent Mongoose model
- Manually fetch blog post data in controllers when needed
- Updated getSubmissions, getSubmissionById, getSubmissionByBlogPost, exportSubmission
- Updated SubmissionTracking static methods: getByStatus, getByPublication
- Standalone submissions (like Le Monde) now display without errors
- Changed populate to use options object with strictPopulate: false
- Allows submissions without blogPostId (standalone packages) to be returned
- Fixes 500 error on /api/submissions endpoint
- Le Monde package should now be visible in UI after server restart
- Added detailed console logs to track submission loading
- Check if API response is ok
- Log all submissions found
- Log filtering logic for standalone submissions
- Cache version updated
- Modified loadValidationArticles() to load standalone submissions (no blogPostId)
- Updated rendering to handle both blog posts and standalone packages
- Fixed API endpoint from /api/blog/posts/:id to /api/blog/admin/:id
- Standalone packages show with purple 'STANDALONE PACKAGE' badge
- Button text changes to 'View Package' for standalone submissions
- Cache version bumped to 0.1.1
- Enhanced update-cache-version.js to update service worker and version.json
- Added inst_075 governance instruction (HIGH persistence)
- Integrated cache check into deployment script (Step 1/5)
- Created CACHE_MANAGEMENT_ENFORCEMENT.md documentation
- Bumped version to 0.1.1
- Updated all HTML cache parameters
BREAKING: Deployment now blocks if JS changed without cache update
- Create Economist SubmissionTracking package correctly:
* mainArticle = full blog post content
* coverLetter = 216-word SIR— letter
* Links to blog post via blogPostId
- Archive 'Letter to The Economist' from blog posts (it's the cover letter)
- Fix date display on article cards (use published_at)
- Target publication already displaying via blue badge
Database changes:
- Make blogPostId optional in SubmissionTracking model
- Economist package ID: 68fa85ae49d4900e7f2ecd83
- Le Monde package ID: 68fa2abd2e6acd5691932150
Next: Enhanced modal with tabs, validation, export
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Extends SubmissionTracking model to support complete bilingual submission
packages with version control for multiple languages.
Schema additions:
- documents.coverLetter.versions[] - Language-versioned content
- documents.mainArticle.versions[] - With translation metadata
- documents.authorBio.versions[]
- documents.technicalBrief.versions[]
Helper methods:
- getDocument(docType, language, fallbackToDefault)
- setDocumentVersion(docType, language, content, metadata)
- getAvailableLanguages(docType)
- isPackageComplete(language)
- exportPackage(language)
Scripts:
- load-lemonde-package.js - Loads complete Le Monde submission package
Le Monde Package:
- Publication target: Rank 10, high-value French intellectual publication
- Theme: Post-Weberian organizational theory for AI age
- Content: Wittgenstein + Weber critique + indigenous data sovereignty
- Format: 187-word letter (within 150-200 requirement)
- Languages: English (original) + French (translated)
- Database ID: 68fa2abd2e6acd5691932150
- Status: Ready for submission
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add missing space after comma in SubmissionTracking model
- Replace string concatenation with template literal in blog controller
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add comprehensive About page emphasizing moral philosophy foundation over organizational theory. PluralisticDeliberationOrchestrator positioned as primary research focus. Te Tiriti o Waitangi content integrated to establish indigenous data sovereignty principles.
Also implements auto-compact tracking system to gather empirical data on Claude Code context compression events, enabling future heuristic predictions.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Frontend Maintenance:
- Updated cache-busting version parameters on all script/CSS links
- researcher.html: Version updates for navbar, i18n, scroll-animations
- implementer.html: Version updates for components
- leader.html: Version updates for components
- i18n-simple.js: Updated internationalization utility
These version updates ensure users get fresh content after website
audit deployment and prevent stale cache issues.
All changes deployed to production and verified working.
Major project restructuring to separate framework from website implementation:
Package Changes:
- name: "tractatus-framework" v3.5.0 → "tractatus-website" v0.1.0
- description: Updated to reflect website platform purpose
- Added website-specific scripts (build:css, migrate:docs, etc.)
- Added website dependencies (puppeteer, stripe, i18n, etc.)
README Changes:
- Rewritten to focus on research framework and website
- Updated badges and links
- Added "What is Tractatus?" section
- Removed framework-specific deployment instructions
.gitignore Changes:
- Drastically simplified (189 line reduction)
- Removed public repository protection rules
- This is now the primary development repository
.env Changes:
- Updated examples to reflect website configuration
- Removed framework-specific environment variables
This commit documents that this repository is the WEBSITE implementation,
separate from the tractatus-framework package repository at
github.com/AgenticGovernance/tractatus-framework.
Production deployment remains via manual rsync, NOT GitHub.
Three scripts to support blog system improvements:
1. fix-blog-dates.js
- Fixes empty {} published_at values in database
- Sets proper ISODate values for 3 blogs
- Also updates moderation.approved_at for consistency
2. add-blog-categories.js
- Adds category field to all blog posts
- Maps content to standardized categories (Framework Updates,
Implementation, Case Studies)
- Enables category filtering functionality
3. add-vetting-notice-to-architectural-boundaries.js
- Adds comprehensive human vetting notice
- Documents AI-curated content review process
- Shows governance working end-to-end with inst_017 compliance
Applied to both tractatus_dev and tractatus_prod databases.
Ref: SESSION_HANDOFF_2025-10-23_WEBSITE_AUDIT.md
Problem: All MongoDB Date objects were being serialized as empty {} in API
responses, breaking blog date display across entire site.
Root Cause: removeSensitiveFields() function used spread operator on Date
objects ({...date}), which creates empty object because Dates have no
enumerable properties.
Fix: Added Date instance check before spreading to preserve Date objects
intact for proper JSON.stringify() serialization.
Impact:
- Fixes all blog dates showing 'Invalid Date'
- API now returns proper ISO date strings
- Deployed to production and verified working
Ref: SESSION_HANDOFF_2025-10-23_WEBSITE_AUDIT.md
- Converted all 9 accordion divs to semantic <button> elements
- Added ARIA attributes: aria-expanded, aria-controls, id for each button
- Accordion content: role="region" and aria-labelledby for screen readers
- Keyboard support: Enter and Space keys toggle accordions (WAI-ARIA pattern)
- Mobile optimization: 44px/48px touch targets, touch-action: manipulation
- iOS tap feedback: -webkit-tap-highlight-color
- Footer i18n: No footer object in leader.json (uses common.json correctly)
- Updated leader-page.js with keyboard handlers and ARIA state management
- Version 1.5.0
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added diagrams from public tractatus-framework repo: architecture-main-flow.svg (6.6KB), trigger-decision-tree.svg (6.7KB)
- Diagram sizing: max-width 600px for readable display, responsive width 100%
- Fixed skip link: Added <main id="main-content"> wrapper for proper accessibility
- Quick navigation: 44px touch targets, flex-wrap for mobile, aria-label="Page sections"
- Download buttons: Descriptive ARIA labels, 44px minimum height
- Code blocks: Added role="region" and descriptive ARIA labels to all 6 service examples
- Mobile optimization: -webkit-overflow-scrolling, 11px code font on small screens, 16px body prevents iOS zoom
- API examples verified accurate against actual BoundaryEnforcer.service.js implementation
- CSP compliance: Moved diagram sizing to CSS class instead of inline styles
- Version 1.4.0
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Full WCAG accessibility: ARIA attributes (aria-expanded, aria-controls), keyboard navigation (Enter/Space)
- Reframed research context: Berlin/Weil as primary intellectual foundation (moral pluralism, categorical imperative)
- Bibliography with proper academic citations: Weil (The Need for Roots, Gravity and Grace), Berlin (Four Essays on Liberty)
- Fixed footer i18n: Implemented recursive deepMerge() to preserve nested translation objects
- Root cause: Shallow merge {...obj1, ...obj2} was overwriting entire footer object from common.json
- Consolidated all footer translations in common.json, removed from page-specific files
- Mobile optimization: 44px/48px touch targets, touch-action: manipulation, responsive design
- Progressive enhancement: <noscript> fallback for JavaScript-disabled users
- Version 1.3.0
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
IMPLEMENTER PAGE (public/implementer.html):
- Complete rewrite from 761 to 635 lines (developer-focused)
- Added both framework architecture diagrams (SVGs)
- Replaced fake @tractatus/framework npm package with real code examples
- Removed all marketing disclaimers and verbose filler
- Added technical specs for all 6 core services with real API examples
- Fixed GitHub repository links to match package.json
FOOTER FIXES (researcher.html, leader.html):
- Fixed script loading order (version-manager before i18n)
- Removed duplicate <!-- Footer --> comment in researcher.html
- Now matches index.html script structure for consistency
Result: Implementer page now shows actual framework documentation with real code examples and architecture diagrams
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
SECURITY CHANGES:
- Removed .github/workflows/sync-public-docs.yml (auto-synced to public repo)
- Removed 'public' git remote (tractatus-framework)
REASON:
Auto-syncing from private to public repository is inherently dangerous:
- Risks exposing internal files, credentials, or sensitive data
- No human review gate before public exposure
- Created the Stripe key exposure incident
GOING FORWARD:
- tractatus (private) = internal development repository
- tractatus-framework (public) = manually curated framework releases
- No automated sync between them
- Manual review required before any public release
This prevents automatic exposure of internal project files.
Removed files:
- SECURITY_INCIDENT_HUMAN_ACTIONS_REQUIRED.md (internal security doc)
- .claude/instruction-history.json (project-specific governance rules)
- scripts/add-security-rules-2025-10-21.js (dated, project-specific)
- scripts/apply-governance-audit-2025-10-21.js (dated, project-specific)
- docs/governance/GOVERNANCE_LEARNINGS_2025-10-21.md (internal learnings)
- docs/governance/GOVERNANCE_RULES_AUDIT_2025-10-21.md (internal audit)
These are internal to the tractatus project, not framework resources.
Public repo should contain only framework implementation code and docs.
Updated .gitignore to prevent future commits of similar files.