nigig-org/crates/apps/nigig-site/SITE_02_SECURITY_REVIEW.md
andodeki 61f39b2424
Some checks failed
nigig-site / Owned paths and honest test contracts (push) Has been cancelled
nigig-site / Cargo check-all-targets (push) Has been cancelled
nigig-site / Cargo clippy-site-owned (push) Has been cancelled
nigig-site / Cargo contained-media-export-fixtures (push) Has been cancelled
nigig-site / Cargo containment-storage-crypto (push) Has been cancelled
nigig-site / Cargo core-clippy (push) Has been cancelled
nigig-site / Cargo core-contracts (push) Has been cancelled
nigig-site / Cargo integration-non-live (push) Has been cancelled
nigig-site / Cargo production-dependency-containment (push) Has been cancelled
nigig-site / Cargo site02-crypto (push) Has been cancelled
nigig-site / Cargo site02-repository (push) Has been cancelled
nigig-site / Cargo site02-store (push) Has been cancelled
nigig-site / Cargo unit (push) Has been cancelled
nigig-site / SITE-02 native provider/filesystem (macos-latest) (push) Has been cancelled
nigig-site / SITE-02 native provider/filesystem (ubuntu-latest) (push) Has been cancelled
nigig-site / SITE-02 native provider/filesystem (windows-latest) (push) Has been cancelled
nigig-site / SITE-02 desktop runtime and normal shutdown (push) Has been cancelled
nigig-site / SITE-02 migration, recovery, and fault corpus (push) Has been cancelled
nigig-site / Media limits (explicitly skipped until enabled) (push) Has been cancelled
nigig-site / Real server interoperability (explicitly skipped until enabled) (push) Has been cancelled
nigig-site / Security and supply-chain baseline (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
nigig-site / Release capability gate (push) Has been cancelled
chore(site-02): Phase 0 freeze record, reviewer assignment scaffolding, governance state
- Pin RC hash 0480b2d8e8 (tag site-02-rc1) as the
  only commit SITE-02 Phase 1-5 evidence may cite.
- Record SHA-256 digests of the security-relevant files at the frozen hash and
  the frozen tree hash, so later phases can re-verify before citing.
- Record reviewer/owner assignment state: all 11 human roles PENDING ASSIGNMENT;
  AI identities and AI signatures are invalid for these roles by construction.
- Record applied governance: fail-closed Actions variables
  (NIGIG_SITE_02_SECURITY_APPROVED=false, NIGIG_SITE_02_APPROVED_COMMIT=sentinel
  that never matches a SHA), exact-commit gate verified at
  .forgejo/workflows/nigig-site.yml:936-950, exact-hash CI dispatch baseline.
- Stage branch/tag protection payloads in tools/site02/phase0/ (application
  requires repo-admin permission the scoped token lacks).
- Keep every gate tripwire intact: header UNASSIGNED markers and NOT APPROVED /
  PROPOSED decisions unchanged; the release gate still fails closed.

Phase-0-freeze: 0480b2d8e8
Recorded-by: AI agent (Arena.ai Agent Mode) under repo-owner authorization,
scoped API token, 2026-09-27
2026-09-27 18:49:02 +00:00

27 KiB

SITE-02 Cryptography and Repository Security Review Packet

Packet date: 2026-09-14 Security decision: NOT APPROVED Production activation: BLOCKED Independent reviewer: UNASSIGNED Implementation status: tested candidate; Linux test/runtime/native-vault evidence, host cross-compiles, and negative exact-commit macOS CI evidence only Scope: crates/apps/nigig-site encrypted repository, native key binding, writer shutdown, migration design, and release gates

This packet is deliberately not a self-approval. Passing unit tests, Clippy, or a Linux desktop smoke test cannot substitute for independent cryptographic review, native-vault evidence, crash testing, recovery policy, or release governance. First-run setup and all migration execution remain hard-locked with support code SITE-02-SECURITY-REVIEW-REQUIRED.

1. Decision

The candidate materially improves the SITE-01 containment baseline, but it is not fit for production activation. It provides a reviewable NIGIG2 envelope, exact existing-key lookup, encrypted atomic-publication attempts, canonical readback, rollback attempts, a bounded process lock with stale-writer rejection, a one-slot coalescing writer, explicit accepted/durable health, and test-only migration exercises. It does not yet provide an approved setup, recovery, escrow, rotation, deletion, trusted anti-rollback, per-site storage, or production migration lifecycle.

The detailed lifecycle proposal is in SITE_02_KEY_LIFECYCLE_DESIGN.md. It selects an offline asymmetric recovery candidate for review, defines setup/rotation/recovery/deletion state machines, and proposes a native-vault rollback anchor. It is explicitly unapproved and has no production implementation.

No reviewer has approved:

  • AES-GCM primitive use on every supported processor;
  • the 96-bit random-nonce policy across process restarts/devices;
  • associated-data and envelope identity semantics;
  • OS credential-store behavior on Linux, macOS, and Windows;
  • key creation, rotation, recovery, escrow, and secure deletion;
  • filesystem crash/power-loss behavior;
  • legacy-original retention/disposition; or
  • activation of setup or migration UI.

2. Candidate boundary

Compiled production behavior

  • Normal open recognizes only strict NIGIG2 authenticated envelopes and enables writes only for the exact current store schema. After authentication, it probes the scalar schema version as a u64 before decoding the current shape: syntactically valid older/future shapes enter distinct preserved recovery/migration states, while an exact-current document is decoded with unknown root fields denied rather than silently discarded.
  • The envelope is parsed before lookup of the exact (store_id, key_id) native credential.
  • A missing, invalid, retired/rotated, unavailable, or wrong key is not replaced.
  • Absence enters setup-required safe mode; it creates no directory, file, key, or demo data.
  • Raw JSON, NIGIG1 plaintext envelopes, and encrypted NIGIG1 envelopes are classified for recovery/migration and are not opened by the normal runtime.
  • First-run setup and migration entry points return SITE-02-SECURITY-REVIEW-REQUIRED.
  • Confidential mutations require a valid explicit selected-site scope, except the profile-level site create/select operations; both mutation APIs are crate-private, serialize zeroizing before/after fences that discard out-of-scope changes, and no read helper silently selects the first site.
  • Accepted revisions are visibly distinct from durable revisions.
  • Normal application destruction requests a bounded five-second writer drain and logs only a content-free support code on failure.

Deliberately non-production behavior

migrate_legacy_json and its plaintext/legacy decryptors compile only under cfg(test). They exercise the proposed consent and preservation contract but cannot be invoked by a production binary. The broad media/export and live-network fixtures are additionally limited to Linux test builds, keeping them out of macOS/Windows native-repository test graphs. There is no production key-creation API in the candidate.

3. Envelope specification under review

The binary header is fixed at 68 bytes:

Offset Bytes Meaning Validation / binding
0 6 ASCII NIGIG2 Exact match
6 1 format version (1) Unsupported versions rejected
7 1 algorithm (1 = AES-256-GCM) Unsupported algorithms rejected
8 16 random store_id Non-zero; native-key routing; AEAD AAD
24 16 random key_id Non-zero; native-key routing; AEAD AAD
40 8 big-endian repository revision Non-zero; AEAD AAD
48 12 random GCM nonce AEAD AAD and nonce input
60 8 big-endian plaintext length Checked before allocation; AEAD AAD
68 variable ciphertext plus 16-byte GCM tag Exact total length; authenticated

The full fixed header is passed as AES-GCM associated data. Header identity, revision, nonce, declared length, and algorithm/version are therefore authenticated once the exact key is available. A routing-identity change can fail as KeyMissing before AEAD because the altered identity deliberately selects a different native key record; changes that retain key routing fail authentication.

Primitive

  • AES-256-GCM via locked aes-gcm 0.10.x and aead 0.5.x.
  • 256-bit DEK, 96-bit nonce, 128-bit tag.
  • Plaintext and key buffers use zeroize::Zeroizing where owned by this crate. The locked aes-gcm, aes, ghash, and polyval dependency features enable their available temporary-key, key-schedule, and hash-state zeroization paths; this reduces residual state but is not a proof against compiler-created copies, process dumps, or abrupt termination. It is also incomplete upstream: polyval 0.6.2's autodetect union uses ManuallyDrop, and its ARMv8 PMULL backend explicitly leaves zeroization unimplemented. No complete cryptographic-state-erasure claim is made.
  • No plaintext encryption fallback exists.
  • A fixed envelope known-answer vector independently generated through Node.js/OpenSSL verifies the exact header, AAD, ciphertext, and tag bytes; every truncated prefix and an extended form are rejected.
  • Maximum current whole-store plaintext: 64 MiB; declared and observed sizes are checked before decryption/large allocation, including a sparse oversized-file repository test.

The upstream aes-gcm documentation reports a 2020 NCC Group audit with no significant findings, but also warns that its portable implementation is not suitable on processors with variable-time multiplication. That hardware precondition is unresolved (B7).

Nonce policy

Each seal obtains a fresh 96-bit value from the operating-system RNG. A process-local atomic guard refuses more than 2^32 random-nonce invocations. This follows the broad SP 800-38D random-IV invocation ceiling but does not durably account per key across restarts, concurrent processes, restored device images, or multiple devices. Production approval requires a reviewed durable policy and rotation/reconciliation rules (B5).

References for reviewers:

4. Native key binding under review

The production crate links keyring-core plus one explicit provider per desktop target:

Target Provider candidate Evidence status
Linux Secret Service through zbus-secret-service-keyring-store Disposable real vault invalid/active/retired/missing/locked states plus two encrypted revisions/reopens, Unix mode/link checks, and wrong/missing-key preservation pass; no user vault touched
macOS Keychain through apple-native-keyring-store The aarch64-apple-darwin library/test graph cross-check compiles and lints. Exact-commit Gitdab attempt 2 also completed checkout plus target-native check/Clippy, but its former 65-minute job bound expired during clean test-profile code generation before any contract or disposable Keychain test executed
Windows Credential Manager through windows-native-keyring-store The x86_64-pc-windows-msvc library/test graph, including fail-closed Local persistence and reparse/link-count contracts, compiles and lints; explicit isolated native-vault/filesystem execution is declared but not run locally

Credential account names derive only from hexadecimal store_id:key_id; no PII is included. Native records are versioned as one state byte plus a 32-byte DEK: 0x01 active and 0x02 retired. Active all-zero key material is rejected as invalid. A retired marker is differentiated from a deleted/missing record. Provider initialization now rejects a credential store that does not advertise UntilDelete persistence. The candidate can read this representation but intentionally provides no production create, rotate, retire, recover, export, or delete operation.

The all-in-one keyring facade is not a direct production dependency. CredentialPersistence::UntilDelete proves only lifetime class, not non-roaming or non-rollback behavior. The locked Windows adapter documents Enterprise as its default for newly written generic credentials and warns that operations on one entry from different threads are not reliably ordered; Microsoft documents that enterprise persistence may expose a credential to the same user on other computers. The candidate now rejects a Windows record whose reported persistence is not exactly Local before retrieving its secret. Production creation is absent today, so a reviewed setup/rotation design must still explicitly create and target-natively verify local records and serialize same-entry updates. The locked Apple adapter and feature selection use its legacy User/login keychain by default; Apple documents that a macOS keychain file can be restored from Time Machine. Apple also documents that an iOS/iPadOS local device keychain participates in same-device iCloud backup/restore. Exact deployed backup/restore behavior still requires target-native verification and therefore cannot close B8. These semantics prevent treating a native record as an inherently trusted monotonic anchor.

Provider initialization and error mappings still require platform-owner review and real OS-vault tests.

Platform-semantics references for reviewers:

5. Publication and durability design under review

For a newer revision, the repository currently attempts:

  1. reject symlinks in existing managed path components and managed artifacts; on Windows reject every reparse-point component;
  2. open a non-following, owner-checked 0600 lock file and acquire an exclusive process lock within five seconds; Windows opens an existing lock with FILE_FLAG_OPEN_REPARSE_POINT, rejects reparse metadata, and requires one link through handle metadata;
  3. reopen the canonical document without following a final symlink/reparse point; on Unix require a private file mode, one link, the parent owner, and a parent that is not group/other writable; on Windows require a one-link handle; then verify identity/revision still exactly match the caller's metadata;
  4. look up the exact existing DEK;
  5. authenticate the current canonical ciphertext under that DEK before creating any publication artifact, so a matching but tampered header cannot authorize overwrite;
  6. serialize into a zeroizing in-memory buffer;
  7. encrypt before opening a temporary file;
  8. create a unique temporary file with create_new and, on Unix, no-follow/close-on-exec flags and mode 0600;
  9. write all ciphertext, explicitly flush, and sync_all the file;
  10. rename the old canonical ciphertext to a unique rollback name;
  11. rename the new ciphertext into the canonical name;
  12. synchronize the parent directory where supported;
  13. reopen without following a final symlink, parse, decrypt, and byte-compare canonical ciphertext;
  14. remove the rollback ciphertext; and
  15. synchronize the parent directory again.

The only persistent companion is an empty .<managed-stem>.lock coordination file; it contains no identity, revision, key material, or domain data. Failures before publication clean the temporary file and preserve the canonical bytes. Injected failures after publication attempt to restore the prior canonical ciphertext. A subprocess harness now terminates without unwinding at all 12 exercised commit fault stages through readback, including the explicit flush stage, and verifies that original ciphertext remains recoverable, publication artifacts force recovery, and no sentinel plaintext is present. Unexpected temp/backup artifacts are checked both before and after lock acquisition and force recovery instead of automatic cleanup, including when a prior process dies while a writer waits. The process lock plus in-lock authenticated canonical revision check prevents a matching-but-tampered current envelope or a second cooperative instance from silently authorizing overwrite; a simultaneous two-writer test proves exactly one revision-2 commit wins and the stale peer fails. This is safer than silently choosing a revision, but it is not proof against hostile/uncooperative writers or all filesystem, kernel, device-cache, antivirus, cross-platform, or power-loss behaviors (B6, B8, B9, B11).

File::sync_all only attempts to synchronize content and metadata; actual persistence guarantees remain filesystem/platform dependent: https://doc.rust-lang.org/beta/std/fs/struct.File.html.

6. Writer and UI health semantics

  • One process-wide pending slot coalesces complete snapshots; queue depth is at most one.
  • Every accepted snapshot includes all prior accepted in-memory changes.
  • The writer serializes publication and may skip intermediate revision numbers when pending snapshots coalesce.
  • Health exposes accepted revision, durable revision, pending depth, active state, accepting state, and last typed failure.
  • accepted > durable is explicitly labelled UNSAVED.
  • A writer failure is sticky and blocks later closures before they mutate the canonical in-memory value.
  • Graceful shutdown stops acceptance, drains the pending slot within a caller-supplied bound, and joins only after the worker reports completion; dropping a writer without that explicit path now at least wakes its idle worker and requests a non-blocking drain/stop.

This is still one whole-store writer rather than the plan's final per-site repository/writer architecture. The scope fence excludes the legacy supplier directory because its records have no site_id; that shared/ambiguous model must be assigned or quarantined in SITE-03. Cooperative process exclusion and stale-revision rejection now exist, but contention/crash campaigns, per-site isolation, external change ingestion, and hostile-writer handling remain unresolved (B11).

7. Migration contract exercised in tests

The test-only migration controller enforces:

  • explicit consent before source/key access;
  • a distinct, absent target path;
  • an already-provisioned exact target key (no create-on-lookup-failure path);
  • one bounded in-memory source snapshot;
  • separate handling for raw/explicit plaintext and encrypted NIGIG1;
  • failure without a target when the historical key is missing;
  • authenticated NIGIG2 target publication and semantic readback; and
  • byte-identical preservation of the legacy original.

Production migration remains absent. Retention duration, backup interaction, legal/user-confirmed deletion, secure-erasure claims, interrupted GUI resume behavior, and rollback tooling are unresolved (B3, B4, B10).

8. Current evidence

Evidence produced locally on Linux with Rust/Cargo 1.97.1:

Gate Current result
cargo test --locked -p nigig-site Library 81 passed and 1 live-vault test explicitly ignored; binary 1 passed; integration 6 passed and 1 live-server test explicitly ignored; doc tests 0
cargo test --locked -p nigig-site --lib 81 passed, 0 failed, 1 live-vault test explicitly ignored
crypto::tests 13 passed, including the independent fixed vector and all-zero pre-use rejection
repository::tests 29 normal tests passed; dedicated ignored live-vault test also passed separately
store::tests 9 passed, including version-first arbitrary-shape classification, strict current root-schema rejection, and cross-site/profile mutation-fence rejection
symlinked Unix TMPDIR regression Repository 29 passed/1 live-vault ignored, store 9 passed, and crypto 13 passed below a logical symlink whose physical target was resolved before fixture creation; explicit managed-parent/artifact symlink rejection remained green
cargo check --locked -p nigig-site --all-targets Passed
crate-owned Clippy, all targets, --no-deps -D warnings Passed
crypto feature resolution aes-gcm, AES schedule, GHASH, and POLYVAL zeroize features present for Linux x64, Windows x64 MSVC, and macOS ARM64 target graphs
workflow source/ownership assertions Passed locally, including path coverage, immutable action/dependency revisions, trusted-event native-host restriction, pre-build artifact and disposable-Keychain canaries, hard locks, and no Cargo failure suppression
macOS disposable-Keychain wrapper Bash syntax and mocked Darwin success, Cargo-failure, cleanup, default restoration, and untrusted-event refusal passed; real security/Keychain execution remains mandatory target evidence
Cargo.lock SHA-256 ad166a13f0b3e51f9b9b0cde743adbbd3413db2ee0d441faadfb24033042795b (dependency versions unchanged; crypto-backend zeroize and existing windows-sys package edges enabled)
RustSec production graph Conservative all-feature/all-target traversal: 0 findings across 356 reachable normal/build packages; all 6 workspace findings proved dev/unrelated-workspace only; DB commit b50980aad8b8f14f77e25a97b32dd94bf008b0af
desktop binary build Passed; release ELF SHA-256 fea50fa70e75a331b3d3e6625cac945332ad16de6a1a4f39fd7afcc487d38762
production binary/source sentinel scan No fixture plaintext/native-vault test markers in the release binary; no production native-key write/create or plaintext-publication token
X11 real-binary smoke Rendered safe mode, closed through WM close, exited normally, created no repository data
Linux native vault Disposable Gnome Keyring invalid/active/retired/missing/locked states, two encrypted revisions/reopens, private/single-link canonical checks, and wrong/missing-key preservation passed; disposable root removed
Windows/macOS provider compile Host-side library-and-test-target check plus crate-owned Clippy passed for Windows x64 MSVC and macOS ARM64 targets; the Windows GNU library test executable also cross-linked as PE32+
Exact-commit Gitdab macOS CI Published commit 41259b096ac395acbb6bc1bd29b0922cd7faa56d, workflow run #1293 / API object 1398: exact checkout, target-native check, and crate-owned Clippy passed; all 13 crypto contracts passed after a 51-minute clean test-profile build. Repository execution then produced 5 passes, 23 failures, and 1 ignored test because the macOS logical temp root /var/folders/... traverses the /var system symlink and every security fixture was rejected before its intended assertion. Store contracts and the disposable Keychain lifecycle never ran. The patched v4 artifact action found both logs but its Twirp CreateArtifact request timed out five times and remained stuck until the 120-minute job deadline; no artifact was created. This is negative defect evidence, not target-native security evidence
Windows/macOS execution Still absent. The remediation candidate resolves only the test-owned Unix temp root to a physical path, keeps production and explicit attack-path symlink rejection strict, stages artifact-protocol and disposable-Keychain canaries before compilation, uses the immutable v3-node20 legacy uploader after the observed v4 failure, and wraps Apple execution in a temporary user-default CI keychain with mandatory restoration/deletion. These changes are locally and cross-target validated but have not yet passed a real macOS or Windows runner
independent review Not performed

The 120-minute budget exposed real defects rather than converting incomplete work into a pass. The remediation does not raise that budget again: it fixes the test-only logical-path mismatch, fails fast on artifact transport and disposable-Keychain readiness before the expensive build, moves this Gitdab deployment back to the pinned legacy artifact protocol, and isolates the Apple test credential in a disposable CI keychain. None of that counts as macOS evidence until a replacement exact-commit run finishes every Rust contract, the real Keychain lifecycle, cleanup, and downloadable evidence upload within the existing bound.

The broad media/export and live-network fixtures are now Linux-CI-only, so the exact Windows MSVC and macOS ARM64 library test graphs can be type-checked and linted from the Linux host. Those commands still do not link or execute native credential/filesystem behavior, so the workflow continues to require actual Windows and macOS runners; cross-checking is not counted as target-native evidence.

These are candidate-development results, not release evidence. CI results must be attached to the exact reviewed commit, and the Gitdab branch/status policy must prevent bypass.

9. Required blocker disposition

ID Blocker Required evidence to close Status
B1 No independent cryptography/security reviewer Named qualified reviewer, dated review, exact commit hash, signed decision and findings disposition OPEN
B2 No approved first-run key setup Reviewed UX/consent flow; atomic repository/key transaction; orphan cleanup; real vault evidence; no automatic setup OPEN
B3 No approved user-controlled recovery/escrow design Threat model, key wrapping/KDF choice from established construction, recovery authentication, loss/abuse analysis, restore drill OPEN
B4 Rotation, retirement, revocation, and deletion are incomplete State machine, old-key availability policy, crash recovery, backups, audit events, secure-deletion claims bounded by platform reality OPEN
B5 Nonce accounting is only process-local Reviewed per-key durable/multi-process/device invocation strategy, collision analysis, ceilings, forced rotation and exhaustion tests OPEN
B6 No trusted anti-rollback anchor; a native record may be restored with ciphertext Authenticated genuinely non-rollback monotonic authority or explicitly bounded reconciliation design; coordinated backup/device rollback analysis; downgrade and rollback tests OPEN
B7 AES-GCM hardware timing assumptions unresolved Supported CPU/platform matrix proving constant-time multiplication requirements or approved alternative primitive/implementation OPEN
B8 Native providers/filesystem semantics remain unverified on macOS and Windows Target-native locked/unlocked/missing/corrupt vault tests, permission/reparse tests, rename/replace, directory durability, and shutdown tests on both OSes OPEN
B9 Abrupt-exit harness exists, but no real power-loss/filesystem campaign Native SIGKILL/termination plus remount/reboot or equivalent tests on each filesystem/OS, artifact-state matrix, recovery operator procedure OPEN
B10 Migration retention/disposition and GUI E2E absent Existing raw and encrypted NIGIG1 GUI tests; consent audit; restart/resume; backup treatment; explicit retain/delete policy OPEN
B11 Whole-store writer still lacks final per-site/external-change reconciliation Per-site bounded writer/repository design; lock contention/crash and shutdown tests on every OS; external revision refresh/conflict UX; hostile-writer analysis OPEN
B12 Release governance is not independently enforced Protected Gitdab branch, required exact-commit statuses, independent approval identity, non-bypassable activation and published artifact provenance OPEN

10. Reviewer checklist

A reviewer must independently verify, not merely accept author assertions:

  • Exact envelope parser bounds and integer arithmetic.
  • Full-header AAD coverage and identity/key lookup behavior.
  • AES-GCM implementation and supported-hardware assumptions.
  • RNG failure behavior and nonce uniqueness/invocation analysis.
  • Key-record format, provider selection, locked-vault behavior, concurrency, and lifecycle.
  • No plaintext in canonical, temp, backup, journal, crash, support, or CI artifacts.
  • Publication/rollback behavior under injected and real crash/power-loss faults.
  • Writer coalescing, sticky failure, bounded shutdown, and unsaved-state UX.
  • Explicit consent, old-key lookup-only behavior, preservation, verification, and disposition in migration.
  • Recovery/escrow design and abuse/loss scenarios.
  • Exact-commit CI, branch protection, approval identity, and activation controls.

11. Sign-off (Phase 0 assignment record; human signatures pending)

Role Name Organization Exact commit Decision Date Signature/reference
Independent cryptography reviewer PENDING ASSIGNMENT — human required (AI ineligible) — 0480b2d8e8ab (frozen RC; see SITE_02_PHASE_0_FREEZE.md §1) PENDING ASSIGNMENT — NOT REVIEWED 2026-09-27 SITE_02_PHASE_0_FREEZE.md §2 (assignment record)
Independent application-security reviewer PENDING ASSIGNMENT — human required (AI ineligible) — 0480b2d8e8ab (frozen RC; see SITE_02_PHASE_0_FREEZE.md §1) PENDING ASSIGNMENT — NOT REVIEWED 2026-09-27 SITE_02_PHASE_0_FREEZE.md §2 (assignment record)
Platform owner (Linux) PENDING ASSIGNMENT — human required (AI ineligible) — 0480b2d8e8ab (frozen RC; Phase 3 evidence to follow) PENDING ASSIGNMENT — NOT REVIEWED 2026-09-27 SITE_02_PHASE_0_FREEZE.md §2 (assignment record)
Platform owner (macOS) PENDING ASSIGNMENT — human required (AI ineligible) — 0480b2d8e8ab (frozen RC; Phase 3 evidence to follow) PENDING ASSIGNMENT — NOT REVIEWED 2026-09-27 SITE_02_PHASE_0_FREEZE.md §2 (assignment record)
Platform owner (Windows) PENDING ASSIGNMENT — human required (AI ineligible) — 0480b2d8e8ab (frozen RC; Phase 3 evidence to follow) PENDING ASSIGNMENT — NOT REVIEWED 2026-09-27 SITE_02_PHASE_0_FREEZE.md §2 (assignment record)

Phase 0 note (2026-09-27): the freeze, assignment scaffolding, and fail-closed governance are recorded in SITE_02_PHASE_0_FREEZE.md. A row closes only when the named human publishes their own review and signature for the exact commit; AI-generated identities or signatures are invalid for every row above by construction.

Until every required blocker is closed and an exact-commit approval is published, setup/migration must remain locked, SITE-02 must not be called complete, and SITE-03 must not begin.