name: nigig-site # Truthful Site CI. Optional capabilities are visibly skipped until their # dedicated harness/server exists; a release-gate invocation fails instead of # treating missing infrastructure as a pass. on: push: paths: - 'crates/apps/nigig-site/**' - 'crates/nimanyatta/**' - 'crates/nigig-core/**' - 'crates/nigig-uikit/**' - 'crates/matrix_client/**' - 'crates/robius-notification/**' - 'crates/apps/doc/**' - 'crates/apps/pdf/**' - 'crates/apps/nigig_doc_scanner/**' - 'Cargo.toml' - 'Cargo.lock' - 'rust-toolchain.toml' - '.forgejo/workflows/nigig-site.yml' pull_request: paths: - 'crates/apps/nigig-site/**' - 'crates/nimanyatta/**' - 'crates/nigig-core/**' - 'crates/nigig-uikit/**' - 'crates/matrix_client/**' - 'crates/robius-notification/**' - 'crates/apps/doc/**' - 'crates/apps/pdf/**' - 'crates/apps/nigig_doc_scanner/**' - 'Cargo.toml' - 'Cargo.lock' - 'rust-toolchain.toml' - '.forgejo/workflows/nigig-site.yml' workflow_dispatch: inputs: enforce_release_gates: description: 'Enforce exact SITE-02 approval plus all later release capabilities' required: true type: boolean default: false permissions: contents: read # Gitdab run 1293 proved that the patched v4 artifact action's Twirp # CreateArtifact request times out on this deployment and leaves the runner # process stuck. The immutable v3-node20 pin below uses the supported legacy # artifact protocol while retaining a current Node runtime. env: CARGO_BUILD_JOBS: '1' CARGO_INCREMENTAL: '0' CARGO_PROFILE_DEV_DEBUG: '0' CARGO_PROFILE_TEST_DEBUG: '0' CARGO_TERM_COLOR: always RUST_BACKTRACE: '1' NIGIG_SITE_CI_TIMEOUT_SECONDS: '3300' jobs: ownership-and-contracts: name: Owned paths and honest test contracts runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Every declared path filter must match repository files run: | set -euo pipefail python3 - <<'PY' from collections import Counter from pathlib import Path import glob, re workflow = Path('.forgejo/workflows/nigig-site.yml').read_text() trigger_block = workflow.split('\npermissions:', 1)[0] filters = re.findall(r"^ - '([^']+)'$", trigger_block, re.MULTILINE) counts = Counter(filters) assert filters, 'workflow path-filter scan matched nothing' assert all(count == 2 for count in counts.values()), ( 'push and pull_request path filters must be identical', counts ) for pattern in sorted(counts): matches = [Path(item) for item in glob.glob(pattern, recursive=True)] files = [item for item in matches if item.is_file()] assert files, f'path filter matches no repository files: {pattern}' print(f'{pattern}: {len(files)} file(s)') PY - name: Prove Site-owned source paths were scanned run: | set -euo pipefail mapfile -d '' files < <(find crates/apps/nigig-site \ -type f \( -name '*.rs' -o -name 'Cargo.toml' -o -name '*.sh' \) \ -print0) if [ "${#files[@]}" -lt 10 ]; then echo "ERROR: Site source scan matched only ${#files[@]} files." >&2 exit 1 fi printf 'matched %d Site-owned source/manifest/tool files\n' "${#files[@]}" test -f crates/apps/nigig-site/src/store.rs test -f crates/apps/nigig-site/src/repository.rs test -f crates/apps/nigig-site/SITE_02_SECURITY_REVIEW.md test -f crates/apps/nigig-site/SITE_02_KEY_LIFECYCLE_DESIGN.md test -f crates/apps/nigig-site/tests/sync_e2e.rs test -f crates/apps/nigig-site/tools/ci-cargo.sh test -x crates/apps/nigig-site/tools/check-production-deps.sh test -x crates/apps/nigig-site/tools/runtime-smoke.sh test -x crates/apps/nigig-site/tools/native-keyring-smoke.sh test -x crates/apps/nigig-site/tools/macos-native-keyring-smoke.sh test -x crates/apps/nigig-site/tools/audit-production-deps.py test -f .forgejo/workflows/nigig-site.yml - name: SITE-01 production containment is structural and fail-closed run: | set -euo pipefail python3 - <<'PY' from pathlib import Path import re, tomllib root = Path('crates/apps/nigig-site') lib = (root / 'src/lib.rs').read_text() for module in ('doc_export', 'gif', 'ocr', 'report_pdf', 'video'): pattern = ( rf'#\[cfg\(all\(test, target_os = "linux"\)\)\]' rf'\s+pub mod {module};' ) assert re.search(pattern, lib), ( f'{module} must remain Linux-CI-only test code' ) manifest = tomllib.loads((root / 'Cargo.toml').read_text()) features = set(manifest.get('features', {})) assert not features, ( 'feature configuration could bypass SITE-01 containment', features ) production_deps = manifest.get('dependencies', {}) assert not manifest.get('dev-dependencies'), ( 'broad fixture dependencies must not enter every native test target' ) linux_dev = manifest.get('target', {}).get( 'cfg(target_os = "linux")', {} ).get('dev-dependencies', {}) for dependency in ( 'makepad-test', 'nigig-core', 'nigig-pdf-cos', 'nigig-pdf-document', 'nigig-pdf-graphics', 'nigig_doc_scanner', 'image', 'weezl', 'zip', 'reqwest', ): assert dependency in linux_dev, ( f'Linux-owned fixture dependency missing: {dependency}' ) for dependency in ( 'nigig-core', 'nigig-uikit', 'doc-ui', 'reqwest', 'makepad-ai-hub', 'makepad-system-speech', 'robius-location', 'robius-notification', 'nigig-pdf-cos', 'nigig-pdf-document', 'nigig-pdf-graphics', 'nigig-pdf-makepad', 'nigig_doc_scanner', 'doc-engine', 'image', 'weezl', 'zip', 'quick-xml', ): assert dependency not in production_deps, ( f'contained dependency leaked into production: {dependency}' ) for path in (root / 'src/nimanyatta_client.rs', root / 'src/sync.rs'): assert not path.exists(), f'test-only transport/codec leaked into src: {path}' for module in ('nimanyatta_client', 'sync'): assert not re.search(rf'pub mod {module};', lib), ( f'production transport/codec module exported: {module}' ) assert (root / 'tests/support/nimanyatta_fixture.rs').is_file() assert (root / 'tests/support/sync_fixture.rs').is_file() assert 'nigig_uikit::script_mod' not in lib assert 'pub use nigig_uikit::shared::*' not in lib assert 'doc_ui::script_mod' not in lib for module in ('persistence', 'location', 'tile_service'): assert not re.search(rf'pub mod {module}\s*\{{', lib), ( f'unsafe compatibility re-export restored: {module}' ) active = '\n'.join( path.read_text() for path in ( root / 'src/main.rs', root / 'src/scheduler.rs', root / 'src/site_frame/screens/chat.rs', root / 'src/site_frame/screens/meetings.rs', root / 'src/site_frame/screens/more_hub.rs', root / 'src/site_frame/screens/report_editor.rs', root / 'src/site_frame/screens/reports.rs', root / 'src/site_frame/screens/sites.rs', root / 'src/site_frame/screens/workers.rs', ) ) for token in ( 'CameraWidget', 'get_latest_location', 'makepad_system_speech', 'robius_notification::', 'photos_to_gif_file', 'photos_to_clip_file', 'request_send_text', 'demo-site', 'Muthaiga Villas', ): assert token not in active, f'reachable contained capability found: {token}' main = (root / 'src/main.rs').read_text() assert re.search( r'app_shell\s*:=\s*View\s*\{.*?visible:\s*false', main, re.DOTALL, ) assert re.search( r'recovery_page\s*:=\s*View\s*\{.*?visible:\s*true', main, re.DOTALL, ) assert 'schedule_daily_eod' not in main and 'check_and_fire_due' not in main scheduler = (root / 'src/scheduler.rs').read_text() assert 'register_os_schedule' not in scheduler assert 'check_and_fire_due' not in scheduler chat = (root / 'src/site_frame/screens/chat.rs').read_text() assert 'TextInput' not in chat, 'confidential chat input restored' editor = (root / 'src/site_frame/screens/report_editor.rs').read_text() assert 'submit_btn' not in editor, 'unreviewed report submission restored' reports = (root / 'src/site_frame/screens/reports.rs').read_text() assert '.approve(' not in reports and '.reject(' not in reports daily_report = (root / 'src/domain/daily_report.rs').read_text() assert 'refine_with_ai' not in daily_report store = (root / 'src/store.rs').read_text() assert 'pub fn save(' not in store and 'pub fn save_async(' not in store assert 'pub fn load()' not in store crypto = (root / 'src/crypto.rs').read_text() assert 'set_password' not in crypto and 'load_or_create' not in crypto # SITE-02 candidate: strict envelope, exact native-key lookup, # bounded/coalesced persistence, explicit health, and hard locks. repository = (root / 'src/repository.rs').read_text() manifest_targets = manifest.get('target', {}) assert 'keyring' not in production_deps, 'all-in-one keyring facade restored' assert 'keyring-core' in production_deps and 'zeroize' in production_deps aes_gcm = production_deps.get('aes-gcm', {}) assert aes_gcm.get('default-features') is False assert {'aes', 'alloc', 'zeroize'} <= set(aes_gcm.get('features', [])) for dependency in ('aes', 'ghash', 'polyval'): configured = production_deps.get(dependency, {}) assert 'zeroize' in configured.get('features', []), ( f'crypto backend zeroization feature missing: {dependency}' ) target_text = (root / 'Cargo.toml').read_text() for provider in ( 'zbus-secret-service-keyring-store', 'apple-native-keyring-store', 'windows-native-keyring-store', ): assert provider in target_text, f'explicit native provider missing: {provider}' assert 'windows-sys' in target_text and 'Win32_Storage_FileSystem' in target_text for token in ( 'NIGIG2', 'Payload {', 'aad:', 'MAX_PLAINTEXT_BYTES', 'NonceInvocationLimit', 'AuthenticationFailed', 'cipher_for', 'envelope_matches_independent_aes_gcm_known_answer', ): assert token in crypto, f'SITE-02 crypto contract missing: {token}' for token in ( 'CredentialPersistence::UntilDelete', 'create_new(true)', 'O_NOFOLLOW', 'try_lock()', 'read_expected_current', 'flush()', 'FlushFailed', 'sync_all()', 'std::fs::rename', 'CanonicalReadbackFailed', 'rollback_publication', 'pending: Option>', 'pending_depth', 'flush_and_shutdown', 'impl Drop for RepositoryWriter', 'abrupt_process_termination_is_fail_closed_at_every_commit_stage', 'concurrent_writers_serialize_and_exactly_one_stale_commit_fails', 'SchemaVersionProbe', 'open_versioned_json', 'linux_native_provider_real_vault_lifecycle', 'apple_windows_native_provider_real_vault_lifecycle', 'attributes.get("persistence")', 'value == "Local"', 'FILE_ATTRIBUTE_REPARSE_POINT', 'GetFileInformationByHandle', 'nNumberOfLinks', 'windows-reparse-real', 'validate_canonical_permissions', 'reject_symlink_chain', ): assert token in repository, f'SITE-02 repository contract missing: {token}' for token in ( 'mutate_scoped', 'profile_mutation_fence', 'site_mutation_fence', 'PersistenceHealth', 'accepted_revision', 'durable_revision', 'deny_unknown_fields', 'open_versioned_json::(STORE_VERSION)', 'OlderVersionMigrationRequired', 'SecurityReviewRequired', 'setup_review_locked', 'migration_review_locked', ): assert token in store, f'SITE-02 runtime contract missing: {token}' assert '#[cfg(test)]\n fn migrate_legacy_json' in repository, ( 'migration execution harness must remain test-only while review is pending' ) assert 'pub(crate) mod repository;' in lib assert 'pub mod repository;' not in lib assert 'impl Drop for SiteStandaloneApp' in main assert 'persistence_health' in main and 'flush_and_shutdown(5_000)' in main assert 'SITE-02-SECURITY-REVIEW-REQUIRED' in store workflow = Path('.forgejo/workflows/nigig-site.yml').read_text() assert 'cargo metadata --locked --format-version 1 --all-features' in workflow, ( 'RustSec production-graph audit must retain conservative feature resolution' ) # Every confidential screen mutation is explicitly scoped. The only # broad mutations left are profile-level site create/select actions. for screen in ('approvals.rs', 'meetings.rs', 'procurement.rs', 'report_editor.rs'): text = (root / 'src/site_frame/screens' / screen).read_text() assert 'SiteStore::mutate(' not in text, f'unscoped mutation in {screen}' assert 'SiteStore::mutate_profile' not in text, f'profile mutation in {screen}' assert 'SiteStore::mutate_scoped' in text, f'no scoped mutation in {screen}' sites = (root / 'src/site_frame/screens/sites.rs').read_text() assert 'SiteStore::mutate_profile' in sites assert 'pub fn mutate(' not in store assert 'selected_or_first' not in store print('SITE-01 containment and SITE-02 hard-lock contracts passed') PY - name: Live E2E must be explicitly ignored, never early-return green run: | set -euo pipefail python3 - <<'PY' from pathlib import Path p = Path('crates/apps/nigig-site/tests/sync_e2e.rs') s = p.read_text() assert '#![cfg(target_os = "linux")]' in s, ( 'live transport fixture must not enter native provider test builds' ) assert '#[ignore = ' in s, 'live test must be an explicit ignored test' assert 'NIMANYATTA_E2E_URL' in s, 'live test must name required config' assert 'fn live_round_trip()' in s, 'live test entry point missing' body = s[s.index('fn live_round_trip()'):] assert 'return;' not in body, 'live test may not early-return as a pass' print('live E2E is explicit: normal CI reports ignored; dedicated CI runs --ignored') PY - name: Third-party actions must be pinned to full commit SHAs run: | set -euo pipefail python3 - <<'PY' from pathlib import Path import re p = Path('.forgejo/workflows/nigig-site.yml') workflow = p.read_text() bad = [] references = [] for number, line in enumerate(workflow.splitlines(), 1): m = re.search(r'\buses:\s*([^\s#]+)', line) if not m: continue ref = m.group(1) references.append(ref) if ref.startswith('./'): continue if not re.search(r'@[0-9a-f]{40}$', ref): bad.append((number, ref)) assert not bad, f'unpinned action references: {bad}' artifact = ( 'forgejo/upload-artifact@' '97a0fba1372883ab732affbe8f94b823f91727db' ) assert references.count(artifact) == 8, ( 'every evidence upload plus the native transport canary must use ' 'the pinned v3-node20 legacy protocol' ) assert all( not ref.startswith('forgejo/upload-artifact@') or ref == artifact for ref in references ), 'mixed or obsolete artifact protocols are forbidden' native = workflow.split('\n native-platform-contracts:', 1)[1].split( '\n runtime-ui:', 1 )[0] assert "github.event_name == 'workflow_dispatch'" in native assert "github.event_name == 'push'" in native assert "github.ref == 'refs/heads/main'" in native compile_index = native.index('Compile the target-native provider') assert native.index('Verify artifact transport') < compile_index assert native.index('Verify disposable Apple Keychain') < compile_index assert '--preflight' in native assert 'macos-native-keyring-smoke.sh' in native assert 'if-no-files-found: error' in native wrapper = Path( 'crates/apps/nigig-site/tools/macos-native-keyring-smoke.sh' ).read_text() for token in ( 'security create-keychain', 'security unlock-keychain', 'security default-keychain -d user -s "$keychain_path"', 'security default-keychain -d user -s "$original_default"', 'security delete-keychain', "trap cleanup EXIT", 'refs/heads/main', 'workflow_dispatch', '--preflight', '--ignored --exact --test-threads=1', ): assert token in wrapper, f'macOS keychain isolation contract missing: {token}' print('all third-party action and native-host references are immutable and fail-closed') PY cargo-gates: name: Cargo ${{ matrix.label }} runs-on: ubuntu-latest timeout-minutes: 65 strategy: fail-fast: false matrix: include: - label: check-all-targets artifact: check command: cargo check --locked -p nigig-site --all-targets - label: production-dependency-containment artifact: production-dependencies command: crates/apps/nigig-site/tools/check-production-deps.sh /tmp/nigig-site-ci/production-tree.txt - label: unit artifact: unit command: cargo test --locked -p nigig-site --lib -- --test-threads=1 - label: integration-non-live artifact: integration command: cargo test --locked -p nigig-site --tests -- --test-threads=1 - label: containment-storage-crypto artifact: containment-storage command: cargo test --locked -p nigig-site --lib containment::tests -- --test-threads=1 && cargo test --locked -p nigig-site --lib ai_refine::tests -- --test-threads=1 && cargo test --locked -p nigig-site --lib crypto::tests -- --test-threads=1 && cargo test --locked -p nigig-site --lib repository::tests -- --test-threads=1 && cargo test --locked -p nigig-site --lib store::tests -- --test-threads=1 - label: site02-crypto artifact: site02-crypto command: cargo test --locked -p nigig-site --lib crypto::tests -- --test-threads=1 - label: site02-repository artifact: site02-repository command: cargo test --locked -p nigig-site --lib repository::tests -- --test-threads=1 - label: site02-store artifact: site02-store command: cargo test --locked -p nigig-site --lib store::tests -- --test-threads=1 - label: contained-media-export-fixtures artifact: contained-media command: cargo test --locked -p nigig-site --lib gif::tests -- --test-threads=1 && cargo test --locked -p nigig-site --lib video::tests -- --test-threads=1 && cargo test --locked -p nigig-site --lib ocr::tests -- --test-threads=1 && cargo test --locked -p nigig-site --lib report_pdf::tests -- --test-threads=1 && cargo test --locked -p nigig-site --lib doc_export::tests -- --test-threads=1 - label: clippy-site-owned artifact: clippy command: cargo clippy --locked -p nigig-site --all-targets --no-deps -- -D warnings steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Install pinned toolchain and native packages run: crates/apps/nigig-site/tools/ci-setup-ubuntu.sh - name: Run ${{ matrix.label }} with timeout and RSS evidence env: SITE_GATE_COMMAND: ${{ matrix.command }} run: | crates/apps/nigig-site/tools/ci-cargo.sh \ "${{ matrix.artifact }}" bash -lc "$SITE_GATE_COMMAND" - name: Upload full command log and resource evidence if: always() uses: forgejo/upload-artifact@97a0fba1372883ab732affbe8f94b823f91727db # v3-node20 with: name: nigig-site-${{ matrix.artifact }}-${{ github.sha }} path: /tmp/nigig-site-ci/ if-no-files-found: error retention-days: 14 native-platform-contracts: name: SITE-02 native provider/filesystem (${{ matrix.os }}) # Never execute pull-request- or branch-controlled code on privileged host # runners that can reach a native credential store. Exact main pushes and # explicitly dispatched revisions still exercise every target vault. if: >- github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/main') runs-on: ${{ matrix.os }} # A clean macOS host-executor run proved checkout plus check/Clippy in # 20 minutes, then spent the remainder of the former 65-minute bound doing # single-job test-profile code generation. Keep this bounded, but allow a # clean native build to reach the contracts, real vault test, and artifact. timeout-minutes: 120 strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Create native evidence transport canary shell: bash run: | set -euo pipefail mkdir -p /tmp/nigig-site-native-evidence printf 'commit=%s\nrunner_os=%s\n' "$GITHUB_SHA" "$RUNNER_OS" > \ /tmp/nigig-site-native-evidence/transport.txt - name: Verify artifact transport before the expensive native build uses: forgejo/upload-artifact@97a0fba1372883ab732affbe8f94b823f91727db # v3-node20 with: name: nigig-site-native-transport-${{ runner.os }}-${{ github.sha }} path: /tmp/nigig-site-native-evidence/transport.txt if-no-files-found: error retention-days: 14 - name: Verify disposable Apple Keychain before the expensive native build if: runner.os == 'macOS' shell: bash env: NIGIG_SITE_LIVE_KEYRING_TEST: isolated-ci-native-v1 run: | set -euo pipefail crates/apps/nigig-site/tools/macos-native-keyring-smoke.sh \ --preflight 2>&1 | \ tee /tmp/nigig-site-native-evidence/macos-keychain-preflight.log - name: Install Linux native build and disposable-vault dependencies if: runner.os == 'Linux' shell: bash env: NIGIG_SITE_INSTALL_NATIVE_KEYRING: '1' run: crates/apps/nigig-site/tools/ci-setup-ubuntu.sh - name: Compile the target-native provider and repository shell: bash run: | set -euo pipefail mkdir -p /tmp/nigig-site-native-evidence { rustc --version --verbose cargo --version --verbose cargo check --locked -p nigig-site --tests cargo clippy --locked -p nigig-site --tests --no-deps -- -D warnings } 2>&1 | tee /tmp/nigig-site-native-evidence/compile.log - name: Execute target-native crypto/repository/store contracts shell: bash run: | set -euo pipefail { cargo test --locked -p nigig-site --lib crypto::tests -- --test-threads=1 cargo test --locked -p nigig-site --lib repository::tests -- --test-threads=1 cargo test --locked -p nigig-site --lib store::tests -- --test-threads=1 } 2>&1 | tee /tmp/nigig-site-native-evidence/contracts.log - name: Exercise a disposable real Secret Service vault if: runner.os == 'Linux' shell: bash run: | set -euo pipefail crates/apps/nigig-site/tools/native-keyring-smoke.sh \ /tmp/nigig-site-native-keyring 2>&1 | \ tee /tmp/nigig-site-native-evidence/linux-secret-service.log test ! -e /tmp/nigig-site-native-keyring - name: Exercise a disposable Apple native vault if: runner.os == 'macOS' shell: bash env: NIGIG_SITE_LIVE_KEYRING_TEST: isolated-ci-native-v1 run: | set -euo pipefail crates/apps/nigig-site/tools/macos-native-keyring-smoke.sh 2>&1 | \ tee /tmp/nigig-site-native-evidence/native-vault.log - name: Exercise a disposable Windows native vault if: runner.os == 'Windows' shell: bash env: NIGIG_SITE_LIVE_KEYRING_TEST: isolated-ci-native-v1 run: | set -euo pipefail cargo test --locked -p nigig-site --lib \ repository::tests::apple_windows_native_provider_real_vault_lifecycle -- \ --ignored --exact --test-threads=1 2>&1 | \ tee /tmp/nigig-site-native-evidence/native-vault.log - name: Upload target-native evidence if: always() uses: forgejo/upload-artifact@97a0fba1372883ab732affbe8f94b823f91727db # v3-node20 with: name: nigig-site-native-${{ runner.os }}-${{ github.sha }} path: /tmp/nigig-site-native-evidence/ if-no-files-found: error retention-days: 14 runtime-ui: name: SITE-02 desktop runtime and normal shutdown runs-on: ubuntu-latest timeout-minutes: 65 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - run: crates/apps/nigig-site/tools/ci-setup-ubuntu.sh - name: Build the real desktop binary run: | crates/apps/nigig-site/tools/ci-cargo.sh runtime-build \ cargo build --locked -p nigig-site --bin nigig-site - name: Render safe mode, close normally, and prove startup wrote no repository run: | set -euo pipefail crates/apps/nigig-site/tools/runtime-smoke.sh \ target/debug/nigig-site /tmp/nigig-site-ci - if: always() uses: forgejo/upload-artifact@97a0fba1372883ab732affbe8f94b823f91727db # v3-node20 with: name: nigig-site-runtime-ui-${{ github.sha }} path: /tmp/nigig-site-ci/ if-no-files-found: error retention-days: 14 migration-recovery: name: SITE-02 migration, recovery, and fault corpus runs-on: ubuntu-latest timeout-minutes: 65 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - run: crates/apps/nigig-site/tools/ci-setup-ubuntu.sh - name: Execute strict envelope and locked migration/recovery tests run: | set -euo pipefail crates/apps/nigig-site/tools/ci-cargo.sh migration-recovery bash -lc ' cargo test --locked -p nigig-site --lib crypto::tests -- --test-threads=1 cargo test --locked -p nigig-site --lib repository::tests -- --test-threads=1 cargo test --locked -p nigig-site --lib store::tests -- --test-threads=1 ' - if: always() uses: forgejo/upload-artifact@97a0fba1372883ab732affbe8f94b823f91727db # v3-node20 with: name: nigig-site-migration-${{ github.sha }} path: /tmp/nigig-site-ci/ if-no-files-found: error retention-days: 14 media-limits: name: Media limits (explicitly skipped until enabled) if: ${{ vars.NIGIG_SITE_MEDIA_LIMITS_ENABLED == 'true' }} runs-on: ubuntu-latest timeout-minutes: 65 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - run: crates/apps/nigig-site/tools/ci-setup-ubuntu.sh - name: Execute adversarial media limits run: | set -euo pipefail test -f crates/apps/nigig-site/tests/media_limits.rs crates/apps/nigig-site/tools/ci-cargo.sh media-limits \ cargo test --locked -p nigig-site --test media_limits -- --test-threads=1 - if: always() uses: forgejo/upload-artifact@97a0fba1372883ab732affbe8f94b823f91727db # v3-node20 with: name: nigig-site-media-limits-${{ github.sha }} path: /tmp/nigig-site-ci/ if-no-files-found: error retention-days: 14 sync-interoperability: name: Real server interoperability (explicitly skipped until enabled) if: ${{ vars.NIGIG_SITE_SYNC_E2E_ENABLED == 'true' }} runs-on: ubuntu-latest timeout-minutes: 65 env: NIMANYATTA_E2E_URL: ${{ secrets.NIMANYATTA_E2E_URL }} steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - run: crates/apps/nigig-site/tools/ci-setup-ubuntu.sh - name: Require pinned server source and execute ignored live test run: | set -euo pipefail test -n "$NIMANYATTA_E2E_URL" test -d crates/nimanyatta/src crates/apps/nigig-site/tools/ci-cargo.sh sync-e2e \ cargo test --locked -p nigig-site --test sync_e2e -- \ --ignored --exact live_round_trip --test-threads=1 - if: always() uses: forgejo/upload-artifact@97a0fba1372883ab732affbe8f94b823f91727db # v3-node20 with: name: nigig-site-sync-e2e-${{ github.sha }} path: /tmp/nigig-site-ci/ if-no-files-found: error retention-days: 14 security-supply-chain: name: Security and supply-chain baseline runs-on: ubuntu-latest timeout-minutes: 35 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Lockfile resolves without mutation run: | set -euo pipefail test -f Cargo.lock # Resolve a conservative all-feature superset as well as every target # predicate; the Site crate itself has no feature bypass surface. cargo metadata --locked --format-version 1 --all-features \ >/tmp/nigig-site-metadata.json git diff --exit-code -- Cargo.lock - name: Audit the Site production graph against RustSec run: | set -euo pipefail cargo install cargo-audit --version 0.22.2 --locked set +e cargo audit --file Cargo.lock --json > /tmp/nigig-site-audit.json audit_status=$? set -e test "$audit_status" -eq 0 || test "$audit_status" -eq 1 test -s /tmp/nigig-site-audit.json crates/apps/nigig-site/tools/audit-production-deps.py \ /tmp/nigig-site-metadata.json \ /tmp/nigig-site-audit.json \ /tmp/nigig-site-rustsec-report.txt echo "workspace cargo-audit exit=${audit_status}; scoped report is authoritative for this production graph" - name: Every live git dependency has a full immutable revision run: | set -euo pipefail python3 - <<'PY' from pathlib import Path import json, re, tomllib failures = [] def walk(value, where): if isinstance(value, dict): if 'git' in value: rev = value.get('rev') if not isinstance(rev, str) or not re.fullmatch(r'[0-9a-f]{40}', rev): failures.append((where, value.get('git'), rev)) for key, child in value.items(): walk(child, f'{where}.{key}') elif isinstance(value, list): for index, child in enumerate(value): walk(child, f'{where}[{index}]') metadata = json.loads(Path('/tmp/nigig-site-metadata.json').read_text()) packages = {package['id']: package for package in metadata['packages']} manifests = {Path('Cargo.toml').resolve()} manifests.update( Path(packages[member]['manifest_path']) for member in metadata['workspace_members'] ) assert manifests, 'live workspace manifest scan matched nothing' for manifest in sorted(manifests): with manifest.open('rb') as fh: data = tomllib.load(fh) walk(data, str(manifest)) assert not failures, f'unpinned git dependencies: {failures}' print(f'checked {len(manifests)} live manifests; all git dependencies use full revs') PY - name: No plaintext sync/store exception may be hidden in workflow shell run: | set -euo pipefail if grep -nE 'cargo (check|test|clippy).*(\|\| true|; true)' \ .forgejo/workflows/nigig-site.yml; then echo 'ERROR: Cargo failure suppression found in Site workflow.' >&2 exit 1 fi echo 'no Cargo failure suppression found' - name: SITE-02 review packet and plaintext policy are explicit run: | set -euo pipefail python3 - <<'PY' from pathlib import Path root = Path('crates/apps/nigig-site') review = (root / 'SITE_02_SECURITY_REVIEW.md').read_text() lifecycle = (root / 'SITE_02_KEY_LIFECYCLE_DESIGN.md').read_text() assert '**Decision status:** `PROPOSED — NOT APPROVED`' in lifecycle assert '**Production implementation:** `ABSENT / BLOCKED`' in lifecycle assert '**Independent cryptography reviewer:** `UNASSIGNED`' in lifecycle pending = '**Security decision:** `NOT APPROVED`' in review approved = '**Security decision:** `APPROVED`' in review assert pending != approved, 'review decision must be exactly pending or approved' if pending: assert '**Production activation:** `BLOCKED`' in review else: assert '**Production activation:** `APPROVED`' in review assert '**Independent reviewer:** `UNASSIGNED`' not in review assert '**Decision status:** `APPROVED`' in lifecycle assert '**Production implementation:** `IMPLEMENTED / ENABLED`' in lifecycle assert '**Independent cryptography reviewer:** `UNASSIGNED`' not in lifecycle for blocker in [f'B{i}' for i in range(1, 13)]: assert f'| {blocker} |' in review, f'missing review blocker {blocker}' crypto = (root / 'src/crypto.rs').read_text() repository = (root / 'src/repository.rs').read_text() production_repository = repository.split('\n#[cfg(test)]\nmod tests {', 1)[0] assert len(production_repository) < len(repository), 'test boundary not found' assert 'const ALG_PLAINTEXT:' not in crypto assert 'plaintext fallback' in crypto.lower() assert 'write_all(envelope)' in production_repository assert 'write_all(&plaintext)' not in production_repository assert 'set_password' not in production_repository assert 'set_secret' not in production_repository assert 'fn create_key' not in production_repository print('pending review is explicit; production has no plaintext/key-creation path') PY - name: Upload RustSec evidence if: always() uses: forgejo/upload-artifact@97a0fba1372883ab732affbe8f94b823f91727db # v3-node20 with: name: nigig-site-rustsec-${{ github.sha }} path: | /tmp/nigig-site-audit.json /tmp/nigig-site-rustsec-report.txt if-no-files-found: warn retention-days: 14 release-capability-gate: name: Release capability gate if: always() needs: - ownership-and-contracts - cargo-gates - native-platform-contracts - runtime-ui - migration-recovery - media-limits - sync-interoperability - security-supply-chain runs-on: ubuntu-latest timeout-minutes: 10 env: OWNERSHIP_RESULT: ${{ needs.ownership-and-contracts.result }} CARGO_RESULT: ${{ needs.cargo-gates.result }} NATIVE_PLATFORM_RESULT: ${{ needs.native-platform-contracts.result }} RUNTIME_RESULT: ${{ needs.runtime-ui.result }} MIGRATION_RESULT: ${{ needs.migration-recovery.result }} SECURITY_RESULT: ${{ needs.security-supply-chain.result }} MEDIA_RESULT: ${{ needs.media-limits.result }} SYNC_RESULT: ${{ needs.sync-interoperability.result }} ENFORCE_RELEASE_GATES: ${{ inputs.enforce_release_gates }} SITE02_APPROVED: ${{ vars.NIGIG_SITE_02_SECURITY_APPROVED }} SITE02_APPROVED_COMMIT: ${{ vars.NIGIG_SITE_02_APPROVED_COMMIT }} MEDIA_ENABLED: ${{ vars.NIGIG_SITE_MEDIA_LIMITS_ENABLED }} SYNC_ENABLED: ${{ vars.NIGIG_SITE_SYNC_E2E_ENABLED }} NIMANYATTA_E2E_URL: ${{ secrets.NIMANYATTA_E2E_URL }} steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Development status or hard release gate run: | set -euo pipefail for status in \ "$OWNERSHIP_RESULT" "$CARGO_RESULT" "$NATIVE_PLATFORM_RESULT" \ "$RUNTIME_RESULT" "$MIGRATION_RESULT" "$SECURITY_RESULT"; do test "$status" = success done release=false case "${GITHUB_REF:-}" in refs/tags/*) release=true ;; esac if [ "${ENFORCE_RELEASE_GATES:-false}" = true ]; then release=true; fi if [ "$release" != true ]; then echo 'Development CI capability status:' echo ' runtime-ui=mandatory job' echo ' migration/recovery=mandatory locked-design job' echo " site02-security-approved=${SITE02_APPROVED:-false}" echo " media-limits=${MEDIA_ENABLED:-false} (later tranche; disabled job is skipped)" echo " sync-e2e=${SYNC_ENABLED:-false} (later tranche; disabled job is skipped)" exit 0 fi # Two independent facts are required: a repository variable naming # the exact reviewed commit, and a signed-off packet in that commit. test "${SITE02_APPROVED:-false}" = true test -n "${SITE02_APPROVED_COMMIT:-}" test "${SITE02_APPROVED_COMMIT}" = "${GITHUB_SHA}" grep -Fq '**Security decision:** `APPROVED`' \ crates/apps/nigig-site/SITE_02_SECURITY_REVIEW.md ! grep -Fq '**Independent reviewer:** `UNASSIGNED`' \ crates/apps/nigig-site/SITE_02_SECURITY_REVIEW.md grep -Fq '**Decision status:** `APPROVED`' \ crates/apps/nigig-site/SITE_02_KEY_LIFECYCLE_DESIGN.md grep -Fq '**Production implementation:** `IMPLEMENTED / ENABLED`' \ crates/apps/nigig-site/SITE_02_KEY_LIFECYCLE_DESIGN.md ! grep -Fq '**Independent cryptography reviewer:** `UNASSIGNED`' \ crates/apps/nigig-site/SITE_02_KEY_LIFECYCLE_DESIGN.md # Full application release still requires later-tranche capabilities # to be enabled and to have actually succeeded for this exact run. test "$MEDIA_RESULT" = success test "$SYNC_RESULT" = success test "${MEDIA_ENABLED:-false}" = true test "${SYNC_ENABLED:-false}" = true test -n "$NIMANYATTA_E2E_URL" test -f crates/apps/nigig-site/tests/media_limits.rs test -d crates/nimanyatta/src echo 'reviewed commit and all later release capabilities are configured; jobs still decide pass/fail'