P2.1: iOS Schema Versioning Strategy
- Added SCHEMA_VERSION constant and checkSchemaVersion() method in PersistenceController
- Version stored in NSPersistentStore metadata (observability contract, not migration gate)
- CoreData auto-migration remains authoritative; version mismatches logged, not blocked
- Documentation added to ios/Plugin/README.md with migration contract
P2.2: Combined Edge Case Tests
- Added 3 resilience test scenarios to DailyNotificationRecoveryTests.swift:
- test_combined_dst_boundary_duplicate_delivery_cold_start()
- test_combined_rollover_duplicate_delivery_cold_start()
- test_combined_schema_version_cold_start_recovery()
- All tests labeled with @resilience @combined-scenarios comments
- Tests verify idempotency and correctness under combined stressors
P2.3: Android Combined Tests Design
- Created P2.3-DESIGN.md with scope, invariants, and acceptance criteria
- Created P2.3-IMPLEMENTATION-CHECKLIST.md with step-by-step execution plan
- Design ready for implementation to achieve parity with iOS P2.2
Documentation Updates
- Fixed parity matrix: iOS invalid data handling now correctly shows "✅ Recovery tested" with test references
- Updated progress docs (00-STATUS.md, 01-CHANGELOG-WORK.md, 03-TEST-RUNS.md, 04-PARITY-MATRIX.md)
- Updated P2-DESIGN.md to reflect P2.3 scope (Android combined tests)
- Updated SYSTEM_INVARIANTS.md baseline tag references
Baseline Tag
- Created and pushed v1.0.11-p2-complete tag
- Tag represents P2.x completion (schema versioning + combined resilience tests)
All invariants preserved. CI passes. Tests runnable via xcodebuild on macOS.
17 KiB
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-runmust 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.fileswhitelist is authoritative (primary control).npmignoreis 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:
- Runs
npm pack --dry-runto simulate package creation - Extracts file list from pack output (handles multiple npm output formats)
- Scans for forbidden patterns using regex:
xcuserdata/|\.xcuserstate|DerivedData/|\.tgz|ios/App/|\.DS_Store|\.swp|\.swo|\.orig|\.rej - Hard-fails if any forbidden files are found
- Provides actionable error messages with remediation hints
Location: scripts/verify.sh:216-316 (function check_package())
Verification command:
./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(filesfield) - 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
- Node builtins:
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):
- Verifies
src/core/directory exists - Checks for required core files (
index.ts,errors.ts,enums.ts,events.ts,contracts.ts,guards.ts) - Scans all files in
src/core/for forbidden imports using comprehensive regex:(from\s+['\"]|require\s*\(\s*['\"]|import\s*\(\s*['\"])(${NODE_BUILTINS}|react|@capacitor/|capacitor)['\"] - Hard-fails if forbidden imports are found
- Prints offending lines and policy reminder
Artifact checks (post-build):
- Verifies build artifacts exist:
dist/esm/core/index.js,dist/esm/core/index.d.ts - Validates
package.json.exports['./core']exists using Node.js script - Hard-fails if artifacts or exports are missing
Location:
- Source checks:
scripts/verify.sh:413-464(functioncheck_core_source()) - Artifact checks:
scripts/verify.sh:467-496(functioncheck_core_artifacts())
Verification command:
./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())
- Source checks:
- 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.shis the canonical CI command- All gates (release, merge, automation) must call
./ci/run.sh npm run buildmust not be called directly in gates (it doesn't include invariant checks)./scripts/verify.shis an implementation detail (wrapped by./ci/run.sh)
Why
- Single source of truth: One command that runs all checks
- Invariant enforcement:
verify.sh(called byci/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:
- Documentation contract:
ci/README.mdexplicitly states the policy (line 5-6) - Git hook (optional):
githooks/pre-pushcalls./ci/run.shbefore allowing pushes - Makefile target:
make ciruns./ci/run.sh(convenience alias) - 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:
./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 citarget) - 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 artifactspackage.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:
- Runs
npm run buildto generate build artifacts - Verifies build succeeds (exit code check)
- Checks for required build outputs:
dist/esm/web.d.ts,dist/esm/web.jsdist/esm/core/index.d.ts,dist/esm/core/index.js
- Hard-fails if build fails or artifacts are missing
- Core artifact validation also checks
package.json.exports['./core']exists (viacheck_core_artifacts())
Location: scripts/verify.sh:191-214 (function check_build())
Verification command:
./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(exportsfield) - Build artifacts:
dist/esm/(generated bynpm 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.mdor 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:
- Index-first rule: Stated in
docs/00-INDEX.md:298-305(Maintenance section) - Process enforcement: Team must add new docs to index (not automatically enforced, but discoverability suffers if not followed)
- Drift guards: Standard header format in progress docs:
**Purpose:** [one sentence] **Owner:** Development Team **Last Updated:** YYYY-MM-DD **Status:** active|archived - 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:
# 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:
- Git tag:
v1.0.11-p0-p1.4-p1.5-p2.6-p2.7-completeexists in repository - Process enforcement: Team must not break baseline (CI will catch invariant violations)
- 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:
# 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:
- Review relevant invariants above
- Run
./ci/run.shto verify current state passes - Make your changes
- Run
./ci/run.shagain — 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:
- Update this document (
docs/SYSTEM_INVARIANTS.md) - Update the enforcing code (usually
scripts/verify.sh) - Update any related documentation
- 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<string, unknown>. 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
- Reason: TypeScript's mixin pattern requires
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) useunknownfor inputs - All data payloads (
src/observability.ts,src/core/events.ts) useRecord<string, unknown>