# System Invariants **Purpose:** Single authoritative document naming, explaining, and referencing all enforced invariants. **Owner:** Development Team **Last Updated:** 2025-12-22 **Status:** active **Baseline:** `v1.0.11-p0-p1.4-p1.5-p2.6-p2.7-complete` --- ## Overview This document defines the **invariants** (unchanging rules) that this project enforces. These invariants are **policy-as-code** — they are enforced by tooling, not just documented as conventions. **Why this matters:** - New contributors can understand "what not to break" - Future work (P2, P3, etc.) has explicit constraints - Violations are caught automatically, not discovered later - The baseline tag (`v1.0.11-p0-p1.4-complete`) represents a state where all invariants are enforced **How to use this document:** - Before making changes, review relevant invariants - If you violate an invariant, CI will fail with a clear error - If you need to change an invariant, update this document and the enforcing code together --- ## 1. Packaging Invariants (P0) ### What The npm package must not contain forbidden files, and packaging is controlled by a whitelist approach. **Specific rules:** - `npm pack --dry-run` must not contain: - `xcuserdata/`, `*.xcuserstate`, `DerivedData/` (Xcode user state) - `ios/App/` (test app, not library code) - `.DS_Store`, `*.swp`, `*.swo`, `*.orig`, `*.rej` (editor/macOS junk) - `package.json.files` whitelist is **authoritative** (primary control) - `.npmignore` is secondary (belt-and-suspenders only) ### Why - **Publish safety:** Prevents shipping developer-local files, test apps, and build artifacts - **Package size:** Keeps published tarball clean and minimal - **Security:** Avoids leaking local development state - **Professionalism:** Published packages should only contain intended library code ### How **Enforced by:** `scripts/verify.sh` → `check_package()` function **Enforcement mechanism:** 1. Runs `npm pack --dry-run` to simulate package creation 2. Extracts file list from pack output (handles multiple npm output formats) 3. Scans for forbidden patterns using regex: `xcuserdata/|\.xcuserstate|DerivedData/|\.tgz|ios/App/|\.DS_Store|\.swp|\.swo|\.orig|\.rej` 4. **Hard-fails** if any forbidden files are found 5. Provides actionable error messages with remediation hints **Location:** `scripts/verify.sh:216-316` (function `check_package()`) **Verification command:** ```bash ./ci/run.sh # Includes package checks # Or manually: npm pack --dry-run | grep -E "xcuserdata|xcuserstate|DerivedData|ios/App/" ``` ### Where - **Enforcing code:** `scripts/verify.sh:216-316` (`check_package()`) - **Policy definition:** `docs/progress/00-STATUS.md:104-113` (Packaging Invariants section) - **Package configuration:** `package.json` (`files` field) - **Secondary exclusion:** `.npmignore` (belt-and-suspenders) --- ## 2. Core Module Purity (P1.4) ### What The `src/core/` module must remain platform-agnostic and portable. It cannot import platform-specific or Node.js built-in modules. **Specific rules:** - `src/core/` must not import: - **Node builtins:** `fs`, `path`, `os`, `child_process`, `crypto`, `http`, `https`, `net`, `tls`, `zlib`, `stream`, `util`, `url`, `worker_threads`, `perf_hooks`, `vm` - **Platform modules:** `@capacitor/*`, `react`, `capacitor` - `package.json.exports['./core']` must exist and point to valid build artifacts - Core types must remain platform-agnostic (no platform-specific types in core) ### Why - **Portability:** Core module can be used in any JavaScript/TypeScript environment - **Architectural separation:** Platform-specific code belongs in adapters, not core - **Testability:** Core can be tested without platform dependencies - **Reusability:** Core types/interfaces can be shared across platforms without coupling ### How **Enforced by:** `scripts/verify.sh` → `check_core_source()` + `check_core_artifacts()` **Source checks (pre-build):** 1. Verifies `src/core/` directory exists 2. Checks for required core files (`index.ts`, `errors.ts`, `enums.ts`, `events.ts`, `contracts.ts`, `guards.ts`) 3. Scans all files in `src/core/` for forbidden imports using comprehensive regex: ```bash (from\s+['\"]|require\s*\(\s*['\"]|import\s*\(\s*['\"])(${NODE_BUILTINS}|react|@capacitor/|capacitor)['\"] ``` 4. **Hard-fails** if forbidden imports are found 5. Prints offending lines and policy reminder **Artifact checks (post-build):** 1. Verifies build artifacts exist: `dist/esm/core/index.js`, `dist/esm/core/index.d.ts` 2. Validates `package.json.exports['./core']` exists using Node.js script 3. **Hard-fails** if artifacts or exports are missing **Location:** - Source checks: `scripts/verify.sh:413-464` (function `check_core_source()`) - Artifact checks: `scripts/verify.sh:467-496` (function `check_core_artifacts()`) **Verification command:** ```bash ./ci/run.sh # Includes core module checks # Or manually check source: grep -RInE "(from\s+['\"]|require\s*\(\s*['\"]|import\s*\(\s*['\"])(${NODE_BUILTINS}|react|@capacitor/|capacitor)['\"]" src/core ``` ### Where - **Enforcing code:** - Source checks: `scripts/verify.sh:413-464` (`check_core_source()`) - Artifact checks: `scripts/verify.sh:467-496` (`check_core_artifacts()`) - **Policy definition:** `docs/progress/P2-DESIGN.md:67-77` (Core Module Purity section) - **Core module location:** `src/core/` - **Package exports:** `package.json` (`exports['./core']` field) --- ## 3. CI Authority (P0) ### What `./ci/run.sh` is the **only** supported CI entrypoint. All release gates, merge gates, and automation must invoke `./ci/run.sh`, not `npm run build` directly. **Specific rules:** - `./ci/run.sh` is the canonical CI command - All gates (release, merge, automation) must call `./ci/run.sh` - `npm run build` must not be called directly in gates (it doesn't include invariant checks) - `./scripts/verify.sh` is an implementation detail (wrapped by `./ci/run.sh`) ### Why - **Single source of truth:** One command that runs all checks - **Invariant enforcement:** `verify.sh` (called by `ci/run.sh`) encodes packaging, core-purity, and export checks - **Consistency:** All environments (local, CI, release) use the same verification - **Debuggability:** Failures are actionable and consistent across environments - **Policy-as-code:** The contract is explicit, not implicit ### How **Enforced by:** `ci/README.md` (policy-as-code contract) + `githooks/pre-push` (optional automation) **Enforcement mechanism:** 1. **Documentation contract:** `ci/README.md` explicitly states the policy (line 5-6) 2. **Git hook (optional):** `githooks/pre-push` calls `./ci/run.sh` before allowing pushes 3. **Makefile target:** `make ci` runs `./ci/run.sh` (convenience alias) 4. **Process enforcement:** Team must follow the contract (not automatically enforced, but CI will fail if invariants are violated) **Location:** - Policy contract: `ci/README.md:5-6` (Contract / Policy-as-code block) - CI entrypoint: `ci/run.sh` (wraps `./scripts/verify.sh`) - Git hook: `githooks/pre-push` (optional, calls `./ci/run.sh`) **Verification command:** ```bash ./ci/run.sh # The canonical CI command # Or: make ci # Convenience alias ``` ### Where - **Policy contract:** `ci/README.md:5-6` (Contract / Policy-as-code block) - **CI entrypoint:** `ci/run.sh` (wraps `./scripts/verify.sh`) - **Verification script:** `scripts/verify.sh` (implementation detail) - **Git hook:** `githooks/pre-push` (optional automation) - **Makefile:** `Makefile` (`make ci` target) - **Documentation:** `docs/progress/00-STATUS.md:115-117` (Local CI Policy section) --- ## 4. Export Correctness (P0) ### What All `package.json.exports` paths must match actual build artifacts. Exported paths must exist after build. **Specific rules:** - `package.json.exports["./web"]` paths must match actual build artifacts - `package.json.exports["./core"]` paths must match actual build artifacts - All exported paths must exist after `npm run build` - Build must succeed (TypeScript compilation + Rollup bundling) ### Why - **Runtime correctness:** Broken exports cause import failures at runtime - **Type safety:** Missing type definitions break TypeScript consumers - **Publish safety:** Broken exports are discovered before publish, not after - **Consumer trust:** Correct exports are a basic contract with package consumers ### How **Enforced by:** `scripts/verify.sh` → `check_build()` function **Enforcement mechanism:** 1. Runs `npm run build` to generate build artifacts 2. Verifies build succeeds (exit code check) 3. Checks for required build outputs: - `dist/esm/web.d.ts`, `dist/esm/web.js` - `dist/esm/core/index.d.ts`, `dist/esm/core/index.js` 4. **Hard-fails** if build fails or artifacts are missing 5. Core artifact validation also checks `package.json.exports['./core']` exists (via `check_core_artifacts()`) **Location:** `scripts/verify.sh:191-214` (function `check_build()`) **Verification command:** ```bash ./ci/run.sh # Includes build checks # Or manually: npm run build && ls -la dist/esm/web.* dist/esm/core/index.* ``` ### Where - **Enforcing code:** `scripts/verify.sh:191-214` (`check_build()`) - **Export definitions:** `package.json` (`exports` field) - **Build artifacts:** `dist/esm/` (generated by `npm run build`) - **Policy definition:** `docs/progress/00-STATUS.md:111` (Export correctness requirement) --- ## 5. Documentation Structure (P1.5) ### What Documentation must follow the index-first rule and maintain drift guards. New docs must be discoverable via the index or explicitly archived. **Specific rules:** - **Index-first rule:** New docs must be linked from `docs/00-INDEX.md` or placed in `_archive/`/`_reference/` - **Progress docs are authoritative:** `docs/progress/` is the single source of truth for project state - **Archive structure:** Historical docs go in `docs/_archive/` (underscore indicates "not active doc surface") - **Drift guards:** Key docs have standard headers (Purpose, Owner, Last Updated, Status) ### Why - **Discoverability:** Contributors can find docs via the index - **Prevents sprawl:** Index-first rule prevents undocumented files - **Maintainability:** Drift guards (Last Updated, Status) help identify stale docs - **Audit trail:** Archive preserves history without cluttering active navigation - **Authority:** Progress docs are clearly marked as "truth" vs "guides" ### How **Enforced by:** `docs/00-INDEX.md` (index-first rule) + documentation process **Enforcement mechanism:** 1. **Index-first rule:** Stated in `docs/00-INDEX.md:298-305` (Maintenance section) 2. **Process enforcement:** Team must add new docs to index (not automatically enforced, but discoverability suffers if not followed) 3. **Drift guards:** Standard header format in progress docs: ```markdown **Purpose:** [one sentence] **Owner:** Development Team **Last Updated:** YYYY-MM-DD **Status:** active|archived ``` 4. **Archive structure:** `docs/_archive/` clearly separated from active docs **Location:** - Index: `docs/00-INDEX.md` (central navigation hub) - Index-first rule: `docs/00-INDEX.md:298-305` (Maintenance section) - Progress docs: `docs/progress/` (authoritative state) - Archive: `docs/_archive/` (historical artifacts) **Verification command:** ```bash # Manual review: # 1. Check that new docs are in index # 2. Verify progress docs have drift guards # 3. Confirm archive structure is standardized ``` ### Where - **Index:** `docs/00-INDEX.md` (central navigation hub) - **Index-first rule:** `docs/00-INDEX.md:298-305` (Maintenance section) - **Progress docs:** `docs/progress/` (authoritative state) - **Archive structure:** `docs/_archive/` (historical artifacts) - **Policy definition:** `docs/progress/P2-DESIGN.md:105-113` (Documentation Structure section) --- ## 6. Baseline Tag Integrity ### What The baseline tag `v1.0.11-p0-p1.4-p1.5-p2.6-p2.7-complete` represents a known-good architectural baseline where all invariants are enforced. Future work must not invalidate this baseline. **Specific rules:** - Baseline tag: `v1.0.11-p0-p1.4-p1.5-p2.6-p2.7-complete` - This tag represents: - All P0 invariants enforced (packaging, CI authority, exports) - All P1.4 invariants enforced (core module purity) - All P1.5 invariants enforced (documentation structure) - All P2.6 invariants enforced (type safety) - All P2.7 invariants enforced (system invariants documentation) - All tooling in place (`verify.sh`, `ci/run.sh`) - Future work must not require rollback to this baseline - Future work must not break any invariant enforced at baseline ### Why - **Safety anchor:** Provides a known-good state to rollback to if needed - **Reference point:** Future work can compare against baseline - **Confidence:** Baseline represents a tested, stable state - **Historical record:** Tag preserves the state where foundation was complete ### How **Enforced by:** Git tag + process (not automatically enforced, but baseline must remain valid) **Enforcement mechanism:** 1. **Git tag:** `v1.0.11-p0-p1.4-p1.5-p2.6-p2.7-complete` exists in repository 2. **Process enforcement:** Team must not break baseline (CI will catch invariant violations) 3. **Validation:** Can verify baseline by checking out tag and running `./ci/run.sh` (should pass) **Location:** - Baseline tag: `v1.0.11-p0-p1.4-p1.5-p2.6-p2.7-complete` (Git tag) - Baseline description: `docs/progress/00-STATUS.md:126` (Baseline Tag section) - Previous baseline: `v1.0.11-p0-p1.4-complete` (historical reference) **Verification command:** ```bash # Verify baseline is still valid: git checkout v1.0.11-p0-p1.4-p1.5-p2.6-p2.7-complete ./ci/run.sh # Should pass git checkout - # Return to current branch ``` ### Where - **Baseline tag:** `v1.0.11-p0-p1.4-p1.5-p2.6-p2.7-complete` (Git tag) - **Baseline description:** `docs/progress/00-STATUS.md:126` (Baseline Tag section) - **Previous baseline:** `v1.0.11-p0-p1.4-complete` (historical reference) - **Status doc:** `docs/progress/00-STATUS.md:17-25` (What This Baseline Includes section) --- ## Summary ### Invariant Enforcement Matrix | Invariant | Enforced By | Hard-Fail? | Verification Command | |-----------|-------------|------------|---------------------| | Packaging | `verify.sh` → `check_package()` | ✅ Yes | `./ci/run.sh` | | Core Purity | `verify.sh` → `check_core_source()` + `check_core_artifacts()` | ✅ Yes | `./ci/run.sh` | | CI Authority | `ci/README.md` (contract) | ⚠️ Process | Manual review | | Export Correctness | `verify.sh` → `check_build()` | ✅ Yes | `./ci/run.sh` | | Documentation Structure | `docs/00-INDEX.md` (index-first rule) | ⚠️ Process | Manual review | | Baseline Integrity | Git tag + process | ⚠️ Process | `git checkout v1.0.11-p0-p1.4-p1.5-p2.6-p2.7-complete && ./ci/run.sh` | **Legend:** - ✅ **Hard-Fail:** CI automatically fails if violated - ⚠️ **Process:** Enforced by process/documentation, not automatic --- ## For New Contributors **Before making changes:** 1. Review relevant invariants above 2. Run `./ci/run.sh` to verify current state passes 3. Make your changes 4. Run `./ci/run.sh` again — it will catch invariant violations automatically **If CI fails:** - Read the error message — it explains which invariant was violated - Check the "Where" section above for the enforcing code - Fix the violation (or discuss changing the invariant if needed) **If you need to change an invariant:** 1. Update this document (`docs/SYSTEM_INVARIANTS.md`) 2. Update the enforcing code (usually `scripts/verify.sh`) 3. Update any related documentation 4. Ensure the change is backward-compatible or properly versioned --- ## Related Documentation - **P2 Design:** `docs/progress/P2-DESIGN.md` — Defines P2 scope and constraints - **Progress Status:** `docs/progress/00-STATUS.md` — Current status and packaging invariants - **CI Documentation:** `ci/README.md` — Local CI usage and contract - **Verification Script:** `scripts/verify.sh` — Implementation of invariant checks --- **Last Updated:** 2025-12-22 **Maintained By:** Development Team **Status:** active --- ## Type Safety Notes **Policy:** All external boundaries use `unknown`, all data payloads use `Record`. No `any` allowed except documented TypeScript limitations. **Allowed Exception:** - **`src/utils/PlatformServiceMixin.ts:258`** — `any[]` required for TypeScript mixin constructor pattern - **Reason:** TypeScript's mixin pattern requires `any[]` for constructor arguments (language limitation, not design choice) - **Status:** Documented with inline comment explaining the limitation - **Verification:** `rg '\bany\b' src/` returns zero matches except this documented exception **Verification:** - Run `rg -n "\bany\b" src/ --type ts | grep -v "node_modules" | grep -v "test"` — should return only the documented exception - All external boundaries (`src/web.ts`, plugin interfaces) use `unknown` for inputs - All data payloads (`src/observability.ts`, `src/core/events.ts`) use `Record`