- Create src/globalVariables/helpers.js (315 lines) - Extract 11 pure utility functions from proxy.js - Reduce proxy.js from 752 to 493 lines (35% reduction) - Load helpers via vm.Script with same isolation pattern - Update constitution to document helper extraction pattern Extracted functions: - generateRequestId, validateDocumentId, validateDocumentCount - escapeXml, mapDriveErrorToHttp - toSitemapEntry, transformDocumentsToSitemapEntries - generateSitemapXML, generateSitemap - parseRoute, DocumentCountExceededError class Architecture: - helpers.js loaded via vm.Script (IIFE returning object) - Injected as 'helpers' global object into VM context - proxy.js accesses via helpers.functionName() pattern - Maintains zero-import isolation pattern Constitution version: 1.16.0 → 1.17.0 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
30 KiB
Proxy Scripts Constitution
Core Principles
I. Monolithic Architecture (NON-NEGOTIABLE)
ALL business logic, data processing, authentication, and request handling MUST exist within the src/proxyScripts/proxy.js file. Pure utility/helper functions MAY be extracted to src/globalVariables/helpers.js if they improve code organization. The server.js file should ONLY handle:
- HTTP server setup
- Configuration loading
- Global object injection into isolated context
- Loading src/proxyScripts/proxy.js via
vm.Scriptandvm.createContext - Loading src/globalVariables/helpers.js via
vm.Script(optional) - Per-request context creation with all necessary globals
Implementation via vm.Script:
src/proxyScripts/proxy.js MUST be loaded using Node.js vm.Script and executed in isolated contexts created per-request with vm.createContext. This ensures:
- Complete isolation from server.js module system
- All dependencies provided explicitly through context objects
- Zero ability to import/export modules
- Pure functional execution with injected dependencies
Rationale: Monolithic architecture enables simple packaging as a single IVA Studio proxy script and prevents fragmentation of business logic across multiple files. Using vm.Script enforces architectural boundaries at runtime, making it impossible for src/proxyScripts/proxy.js to access Node.js module system or file system, ensuring ALL functionality exists in one isolated, dependency-injected file.
Helper Functions Pattern: Pure utility functions (XML escaping, validation, formatting, routing) MAY be extracted to src/globalVariables/helpers.js to improve readability and maintainability. The helpers module:
- MUST be loaded via
vm.Script(same isolation as proxy.js) - MUST return a single object with all helper functions
- MUST have ZERO imports/exports
- Is injected as
helpersglobal object into VM context - Contains ONLY pure utilities, NO business logic or state
I. Zero External Imports or Exports from src/proxyScripts/proxy.js (NON-NEGOTIABLE)
src/proxyScripts/proxy.js MUST have ZERO import statements. All dependencies MUST be provided through vm.createContext by server.js.
src/proxyScripts/proxy.js MUST have ZERO export statements. The file MUST be pure JavaScript code executed in an isolated VM context.
File system access from src/proxyScripts/proxy.js is ABSOLUTELY PROHIBITED under any circumstances. The fs module MUST NOT be accessible.
External libraries (axios, jwt, googleapis, etc.) MUST NOT be imported. Dependencies are injected through VM context by server.js.
Rationale: Using vm.Script and vm.createContext enforces architectural boundaries at the VM level. src/proxyScripts/proxy.js runs in an isolated context with NO access to Node.js module system, file system, or process globals. ALL dependencies must be explicitly injected per-request through the context object, ensuring src/proxyScripts/proxy.js contains ONLY pure business logic with zero capability for I/O operations.
For data files that src/proxyScripts/proxy.js needs (service account keys, certificates, secrets):
- Place JSON files in
src/globalVariables/directory - server.js loads them at startup using
loadGlobalObjects() - server.js injects them into VM context per-request via
vm.createContext - src/proxyScripts/proxy.js accesses them as simple variables in context (e.g.,
google_drive_settings)
Example:
- File:
src/globalVariables/google_drive_settings.json - Loading: server.js reads and assigns to
globalVariableContext.google_drive_settings - Injection: server.js adds
google_drive_settings: globalVariableContext.google_drive_settingsto context - Access in src/proxyScripts/proxy.js: Direct variable access
google_drive_settings.serviceAccount
Enforcement:
- src/proxyScripts/proxy.js MUST have NO
importstatements (file should start with comments, then code) - src/proxyScripts/proxy.js MUST have NO
exportstatements (no module.exports, no export keyword) - Any
importorexportin src/proxyScripts/proxy.js MUST be rejected immediately - server.js MUST load src/proxyScripts/proxy.js using
vm.Scriptconstructor - server.js MUST execute via
script.runInContext(context)with fresh context per request - All dependencies injected through
vm.createContext({ ... })context object - VM isolation prevents access to require(), import(), fs, process, and Node.js globals
I.I What MUST Be in src/proxyScripts/proxy.js
The following MUST be implemented in src/proxyScripts/proxy.js (or extracted to helpers.js if pure utilities):
- Authentication: Service Account JWT, OAuth flows, token management (MUST be in proxy.js)
- Business Logic: All request handling, routing, and processing (MUST be in proxy.js)
- Data Transformation: Document parsing, XML generation, data mapping (MUST be in proxy.js or helpers.js)
- API Integration: Drive API queries, error mapping, response handling (MUST be in proxy.js)
- Request Queue: FIFO queue for sequential processing (MUST be in proxy.js)
- Utility Functions: Request ID generation, validation, XML escaping, date formatting (MAY be in helpers.js)
- Error Handling: All error mapping and HTTP status code logic (MAY be in helpers.js)
Helper Extraction Guidelines:
- ✅ CAN extract: Pure functions, validators, formatters, XML utilities, error mappers, route parsers
- ❌ MUST NOT extract: Authentication, API calls, request queue, cached state, business decisions
NO EXCEPTIONS for business logic - Even complex authentication (OAuth 2.0, JWT) must be in proxy.js.
I.II What Can Be Separate Files
ONLY the following infrastructure modules may exist outside src/proxyScripts/proxy.js:
- src/logger.js: Structured logging with console replacement (ONLY logging, no business logic)
- src/server.js: HTTP server bootstrap and configuration (ONLY server setup, no business logic)
- config/: JSON configuration files (data files, not code)
- src/globalVariables/: JSON data files AND helpers.js module
*.json: Runtime data loaded at startup (credentials, settings)helpers.js: Pure utility functions loaded via vm.Script (OPTIONAL)
- src/proxyScripts/: Directory containing the main proxy script (proxy.js)
Test files are exempt - Test utilities may exist solely for test compatibility if needed, but MUST NOT be imported by production code.
File Structure:
src/
├── proxyScripts/
│ └── proxy.js # Main business logic (authentication, API, queue)
├── globalVariables/
│ ├── *.json # Data files for VM context
│ └── helpers.js # Pure utility functions (OPTIONAL)
├── logger.js # Structured logging
└── server.js # HTTP server bootstrap
config/
└── default.json # Infrastructure settings
helpers.js Pattern:
- MUST be loaded using
vm.Script(same isolation as proxy.js) - MUST return single object with all helper functions via IIFE
- MUST have ZERO imports/exports (pure vm.Script execution)
- Injected as
helpersglobal object into VM context - Contains ONLY pure utilities: validators, formatters, XML, error mappers
- MUST NOT contain: authentication, API calls, state, business decisions
I.III Enforcement
During code review and planning:
- ANY file in
src/proxyScripts/besidesproxy.jsMUST be challenged - ANY file in
src/globalVariables/besideshelpers.jsand*.jsonMUST be challenged - ANY file in
src/besidesproxyScripts/,globalVariables/,logger.js,server.jsMUST be challenged - Authentication, even if complex, MUST be in
src/proxyScripts/proxy.js(never in helpers.js) - Business logic MUST be in
src/proxyScripts/proxy.js(never in helpers.js) - Exceptions require explicit constitutional justification with measurable trade-offs
- When in doubt about helpers extraction, keep it in
src/proxyScripts/proxy.js
RED FLAGS to reject immediately:
- Separate files for: auth, database, utilities, helpers, services, controllers, models
- Any file containing business logic or domain knowledge
- Multiple files "organizing" the codebase
I.IV Configuration
- Configuration for the Node.js web server infrastructure should be stored as JSON in
config/default.json. config/default.jsonMUST contain ONLY infrastructure settings: server (host, port), logging levelconfig/default.jsonMUST NOT contain authentication credentials, secrets, API keys, or behavioral configuration- Authentication credentials, secrets, and ALL behavioral configuration MUST be stored in
src/globalVariables/directory as JSON files - Global JSON files are automatically loaded by server.js and made available as global objects
- server.js should validate both configuration from
config/default.jsonAND global objects fromsrc/globalVariables/directory
I.V Global Objects Provided by server.js
The server.js file MUST inject the following objects into VM context for use by src/proxyScripts/proxy.js:
VM Context Injection Pattern:
server.js uses a spread operator pattern for cleaner context creation:
// Define static VM context (libraries and built-ins)
const globalVMContext = {
URLSearchParams,
console: logger,
crypto,
axios,
uuidv4,
jwt,
xmlBuilder,
};
// Load dynamic data from src/globalVariables/ directory into globalVariableContext
let globalVariableContext = {}; // Populated by loadGlobalObjects()
// Load helpers module via vm.Script
const helpersScript = new vm.Script(fs.readFileSync('./src/globalVariables/helpers.js', 'utf8'));
const helpersContext = vm.createContext({
crypto,
console: logger
});
const helpers = helpersScript.runInContext(helpersContext);
// Per-request: Create fresh context with all dependencies
const context = vm.createContext({
...globalVMContext, // Spread static dependencies
...globalVariableContext, // Spread dynamic data (e.g., google_drive_settings)
helpers, // Helpers object with utility functions
req, // Fresh request object
res // Fresh response object
});
script.runInContext(context);
Note: src/proxyScripts/proxy.js accesses these as direct variables (e.g., google_drive_settings, not globalThis["google_drive_settings"]). The VM context makes all properties available as top-level variables.
Core Infrastructure Context Variables:
-
console - Custom logger from
logger.js- Purpose: Structured JSON logging
- Usage:
console.info(),console.debug(),console.error() - Injected from:
globalVMContext.console(set tologger)
-
crypto - Web Crypto API (built-in)
- Purpose: UUID generation, cryptographic operations
- Usage:
crypto.randomUUID(), etc. - Injected from:
globalVMContext.crypto - Note: Web Crypto API available by default in Node.js
-
axios - HTTP client library
- Purpose: Making HTTP requests to external APIs
- Usage:
axios.get(url),axios.post(url, data) - Package:
axios - Injected from:
globalVMContext.axios
-
uuidv4 - UUID v4 generator
- Purpose: Generate RFC4122 compliant UUIDs
- Usage:
uuidv4()returns string like "110ec58a-a0f2-4ac4-8393-c866d813b8d1" - Package:
uuid(v4 function only) - Injected from:
globalVMContext.uuidv4
-
jwt - JSON Web Token library
- Purpose: Creating and verifying JWTs for authentication
- Usage:
jwt.sign(payload, secret),jwt.verify(token, secret) - Package:
jsonwebtoken - Injected from:
globalVMContext.jwt
-
xmlBuilder - XML builder/generator
- Purpose: Constructing XML documents programmatically
- Usage:
xmlBuilder({ root: { child: 'value' } }) - Package:
xmlbuilder2(create function) - Injected from:
globalVMContext.xmlBuilder
Built-in Web APIs:
-
URLSearchParams - URL query string parser (built-in)
- Purpose: Parse and manipulate URL query strings
- Usage:
new URLSearchParams(queryString) - Injected from:
globalVMContext.URLSearchParams
-
URL - URL parser (built-in)
- Purpose: Parse and manipulate URLs
- Usage:
new URL(urlString) - Injected from:
globalVMContext.URL - Note: Currently not included in globalVMContext but available in Node.js by default
Dynamic Data Context Variables:
- Dynamic JSON objects from src/globalVariables/ directory
- Purpose: Authentication credentials, secrets, API keys, and behavioral configuration
- Pattern: Each
src/globalVariables/filename.jsonloaded by server.js → added toglobalVariableContext→ spread into VM context - Examples:
src/globalVariables/google_drive_settings.json→ context variablegoogle_drive_settings(consolidated service account, scopes, drive query, sitemap config)src/globalVariables/api-keys.json→ context variableapi_keys(API keys and secrets)src/globalVariables/custom-config.json→ context variablecustom_config(behavioral settings)
- Usage in src/proxyScripts/proxy.js: Direct variable access
const settings = google_drive_settings; - Loading: By server.js at startup using
loadGlobalObjects()function - Injection: Via spread operator
...globalVariableContextinvm.createContext() - Note: ALL authentication, secrets, and behavioral configuration MUST be in src/globalVariables/, NEVER in config/default.json
Helper Functions Module:
- helpers - Pure utility functions object (OPTIONAL)
- Purpose: Extracted helper functions for code organization
- Source:
src/globalVariables/helpers.jsloaded viavm.Script - Pattern: IIFE returning object with all helper functions
- Loading: server.js loads via
vm.Scriptat startup (same as proxy.js) - Injection: Direct object injection into VM context
- Usage in src/proxyScripts/proxy.js:
helpers.functionName()(e.g.,helpers.generateRequestId()) - Contains: Pure utilities only (validators, formatters, XML, error mappers, route parsers)
- MUST NOT contain: Authentication, API calls, state, business logic
- Example functions:
helpers.generateRequestId()- UUID generationhelpers.validateDocumentId(id)- Document ID validationhelpers.escapeXml(str)- XML character escapinghelpers.generateSitemap(docs, baseUrl)- Sitemap generationhelpers.mapDriveErrorToHttp(error)- Error mappinghelpers.parseRoute(method, url)- Route parsinghelpers.DocumentCountExceededError- Custom error class
Request/Response Objects:
-
req - HTTP IncomingMessage
-
req - HTTP IncomingMessage
- Purpose: Access request data (URL, method, headers, body)
- Injected fresh: Per-request from
http.createServer((req, res) => ...)
-
res - HTTP ServerResponse
- Purpose: Send response to client
- Injected fresh: Per-request from
http.createServer((req, res) => ...)
Rationale: Using vm.createContext with spread operator pattern for dependency injection achieves:
- Runtime-enforced isolation - src/proxyScripts/proxy.js physically cannot access Node.js module system or file system
- Zero imports possible - VM context has no
require()orimport()capability - Explicit dependencies - All available objects must be explicitly listed in globalVMContext or globalVariableContext
- Clean organization - Static dependencies (globalVMContext) separated from dynamic data (globalVariableContext)
- Per-request isolation - Fresh context per request prevents cross-request state leakage
- Testing simplicity - Mock entire context object instead of individual module imports
- Clear contracts - Context spread pattern documents every dependency src/proxyScripts/proxy.js uses
- Security boundaries - VM sandbox prevents escape to underlying system
- DRY principle - Spread operators eliminate repetitive property declarations
I.VI Logging
Modify server.js to replace the global console object with the logger export from logger.js. This will make all console.log, console.info, console.error calls throughout the application use the custom logger.
Logging should use logger.js module that has the following functions:
- log - which defaults to the 'info' function
- info - which writes to stdout
- debug - which prefixes the output with "[DEBUG]" written in red font and writes to stdout
- error - which prefixes the output with "[ERROR]" written in red font and writes to stderr
II. API-First Design
Every feature MUST expose a clear, documented API before implementation begins. APIs MUST follow RESTful principles where applicable, use consistent naming conventions, and include comprehensive error handling with meaningful status codes and messages.
Rationale: API-first design ensures contracts are stable, enables parallel front-end/back-end work, facilitates integration testing, and produces naturally documented systems.
III. Test-First Development (NON-NEGOTIABLE)
Test-Driven Development is MANDATORY for all production code. The cycle MUST be:
- Write failing tests
- Obtain user approval of test scenarios
- Implement minimum code to pass tests
- Refactor while maintaining green tests
Unit tests MUST achieve minimum 80% code coverage. Integration tests MUST cover all API contracts and critical user flows.
Rationale: TDD catches defects early, documents expected behavior, enables confident refactoring, and ensures all code paths are exercised.
IV. Security & Privacy by Default
All user data MUST be treated as sensitive. OAuth tokens, credentials, and personal information MUST be encrypted at rest and in transit. The principle of least privilege MUST govern all access controls. Audit logging MUST track all data access and modifications.
Rationale: Privacy violations damage trust and carry legal liability. Security must be foundational, not retrofitted.
V. Observability & Debuggability
All operations MUST emit structured logs with appropriate severity levels (DEBUG, INFO, WARN, ERROR). Errors MUST include context (request IDs, user IDs, operation names) sufficient for diagnosis. Performance-critical paths MUST expose metrics (latency, throughput, error rates).
Rationale: Production issues are inevitable. Observable systems reduce mean time to resolution and enable proactive problem detection.
VI. Semantic Versioning & Change Management
All public APIs MUST follow semantic versioning (MAJOR.MINOR.PATCH):
- MAJOR: Breaking changes that require consumer updates
- MINOR: Backward-compatible feature additions
- PATCH: Backward-compatible bug fixes
Breaking changes MUST include migration guides and deprecation notices for at least one MINOR version before removal.
Rationale: Clear versioning communicates impact, enables safe upgrades, and respects downstream consumers' need for stability.
VII. Simplicity, Minimal Dependencies & YAGNI
Implement only features with demonstrated need. Choose the simplest solution that satisfies current requirements. Reject premature optimization and speculative features. Complexity MUST be explicitly justified with measurable benefits.
Dependency Minimization: Prefer Node.js built-in modules over external npm packages. Each external dependency MUST be justified by:
- Significant functionality that would take >2 days to implement correctly
- Active maintenance and security track record
- Clear, documented benefit that outweighs maintenance risk
Prohibited without explicit approval:
- Utility libraries for functionality Node.js provides natively (fs, path, crypto, http, etc.)
- Heavy framework dependencies when lightweight alternatives exist
- Multiple packages solving the same problem
Rationale: External dependencies introduce supply chain risk, increase bundle size, complicate auditing, and create maintenance burden. Node.js built-ins are stable, well-tested, and maintained by the platform.
API Design Standards
All external APIs MUST:
- Accept and return JSON for structured data
- Use standard HTTP methods (GET, POST, PUT, DELETE, PATCH) semantically
- Return appropriate HTTP status codes (2xx success, 4xx client errors, 5xx server errors)
- Include rate limiting headers where applicable
- Version endpoints explicitly (e.g., /v1/, /v2/)
- Document all parameters, responses, and error codes using OpenAPI/Swagger
Response formats MUST be consistent and include:
- Timestamp of response generation
- Request correlation ID for tracing
- Pagination metadata for list operations
- Clear error messages with actionable guidance
Security & Data Protection
Authentication & Authorization MUST:
- Never log or expose credentials, tokens, or API keys
- Validate all input to prevent injection attacks
- Apply rate limiting to prevent abuse
Data Handling MUST:
- Minimize data retention—delete temporary files promptly
- Encrypt sensitive data using industry-standard algorithms (AES-256 or equivalent)
- Sanitize all user-supplied content before processing
- Implement CSRF protection for web interfaces
Development Workflow
Code Reviews MUST:
- Verify alignment with all constitutional principles
- Check test coverage meets minimum thresholds
- Validate API contracts match documentation
- Confirm security best practices are followed
Quality Gates (ALL must pass before merge):
- All tests pass (unit, integration, end-to-end)
- Code coverage ≥ 80%
- No critical security vulnerabilities (use automated scanning)
- Documentation updated for API/behavior changes
- Performance regression checks pass
Deployment MUST:
- Use automated CI/CD pipelines
- Include smoke tests post-deployment
- Support rollback within 5 minutes
- Include release notes documenting all changes
Technology Stack
Platform: Node.js (LTS version or later)
Mandatory Baseline:
- Use Node.js built-in modules as first choice (fs, path, crypto, http, https, stream, util, url, querystring, etc.)
- DO NOT use 'events' EventEmitter - implement simple patterns directly (e.g., Promise-based queues)
- Plain JavaScript (ES2022+) without TypeScript
- JSDoc comments for type documentation where needed
- JavaScript tooling (ESLint, Prettier) does not count against dependency budget
- Native test runner (node:test) or minimal test framework
Dependency Approval Process: Any external npm package (excluding JavaScript tooling like ESLint and Prettier) MUST be justified in the feature specification with:
- Functionality gap: What Node.js built-ins cannot do
- Implementation cost: Estimated effort to build vs. maintain dependency
- Risk assessment: Package security, maintenance history, download stats
- Alternatives considered: Why alternatives were rejected
Examples of acceptable dependencies (when justified):
- xmlbuilder2
- axios
- uuid
Examples of prohibited dependencies (use Node.js built-ins or inline implementations instead):
- lodash/underscore (use native Array/Object methods)
- moment/date-fns (use native Date, Intl.DateTimeFormat)
- rimraf (use fs.rm with recursive: true)
- mkdirp (use fs.mkdir with recursive: true)
- EventEmitter from 'events' (implement simple queue classes directly - no need for event system)
- express/fastify (use native http/https for simple servers
Node.js built-in modules to prefer:
- Use 'node:' prefix for clarity:
import crypto from 'node:crypto' - Acceptable built-ins: fs, path, crypto, http, https, stream, util, url, querystring, etc.
- NOT acceptable: 'events' EventEmitter - implement patterns directly without event system
IMPORTANT : All dependencies that are not acceptable must be approved when running plan and task agents
Governance
This constitution supersedes all other development practices and guidelines. When conflicts arise between this document and team conventions, the constitution takes precedence.
Amendments to this constitution require:
- Documented justification explaining the need for change
- Impact analysis of affected systems and workflows
- Approval from project maintainers
- Migration plan for any breaking changes
- Update of version number following semantic versioning rules
All pull requests, code reviews, and design discussions MUST verify compliance with constitutional principles. Exceptions MUST be rare, explicitly justified with measurable trade-offs, and documented in the relevant specification or plan.
For runtime development guidance, refer to .github/prompts/ and .github/agents/ files which operationalize these principles into agent workflows.
Version: 1.14.0 | Ratified: 2026-03-05 | Last Amended: 2026-03-07