Files
daily-notification-plugin/docs/SYSTEM_INVARIANTS.md
Matthew Raymer 6b5b886951 feat(ios): complete P2.1 schema versioning and P2.2 combined edge case tests
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.
2025-12-22 12:59:40 +00:00

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-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.shcheck_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:

./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.shcheck_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:
    (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:

./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:

./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.shcheck_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:

./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:
    **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:

# 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:

# 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.shcheck_package() Yes ./ci/run.sh
Core Purity verify.shcheck_core_source() + check_core_artifacts() Yes ./ci/run.sh
CI Authority ci/README.md (contract) ⚠️ Process Manual review
Export Correctness verify.shcheck_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

  • 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:258any[] 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<string, unknown>