53 KiB
nigig-site — Security and Reliability Remediation Plan
Plan date: 2026-09-12
Audit baseline: e899a271c69efca0e11ae274b879378d2f26485f (main)
Scope: crates/apps/nigig-site, its domain/storage/media/export code, and its protocol boundary with nimanyatta
Status: Execution in progress. SITE-00 and SITE-01 are complete and published; SITE-02 through SITE-19 remain incomplete.
Published evidence: SITE-00 f49d8b16ac8f1245ba1e80ac19588825f91a79f4; SITE-01 5d2d890f701444a3ff7e9c227e41e4608406ee89.
Release posture: Do not enable current multi-device sync. Do not call current storage confidential. Do not claim real OCR, safe media processing, or lossless photo reports.
This is the authoritative forward execution plan for this crate. README.md and NIGIG_SITE_ASSESSMENT_AND_PLAN.md contain useful history but do not satisfy the security, migration, and release gates here.
1. Executive verdict
nigig-site handles high-risk construction and identity data—worker ID numbers, names, phones, client/site details, reports, meetings, approvals, locations, photos, and chat—but its storage and sync designs are not safe for that data.
The two most urgent defects are destructive storage recovery and plaintext whole-store sync:
- If the keystore is unavailable, saving falls back to plaintext by design.
- If the data key is missing, a new key can be created, decryption fails, the file is classified as corrupt, and the app backs it up then seeds a demo site. Tampering, key loss, unsupported data, and JSON corruption all collapse into the same destructive path.
- Save APIs return no failure, discard write/sync/rename errors, queue unbounded full-store clones, and production never drains the queue on shutdown.
- Sync serializes the entire multi-site store—including worker identity/contact data—into ordinary text messages in the selected site's normal chat room. HTTPS protects a network hop; it does not hide plaintext from the chat server, room members, logs, backups, or moderation systems.
- Snapshot chunks split raw UTF-8, grow after JSON escaping, have no encoded/decompressed size contract, integrity, trusted sender identity, site binding, replay defense, tombstones, or reliable concurrent request correlation.
The rest of the app repeats the same architectural pattern: public mutable structs and screen-level code bypass central authorization, state transitions, reference validation, audit history, finite numeric rules, and site scoping. Media processing trusts stored paths, decodes before limits, buffers every frame repeatedly, and writes collision-prone files non-atomically. OCR does no text recognition. PDF/DOCX print photo paths instead of images, contain Unicode panic paths, and DOCX assembly unwraps production errors. AI constructs Claude while labelling it OpenAI, can accept a timed-out partial response as success, and has no request IDs.
Current grade: security F, privacy F, data durability F, sync correctness F, domain integrity D-, media safety F, export fidelity D-, architecture D, test credibility D+.
2. Evidence and reproducible baseline
2.1 Observed validation
| Check | Result at baseline | Honest interpretation |
|---|---|---|
cargo check --locked -p nigig-site |
Passed | Compilation only. |
cargo test --locked -p nigig-site |
Inconclusive; rustc was killed by signal 9 while compiling makepad-widgets |
No suite result may be claimed. Retry in low-memory CI with one job and stripped dev/test debug data. |
| Site-specific Forgejo workflow | Absent | Security and lifecycle regressions have no crate-owned CI. |
tests/sync_e2e.rs without NIMANYATTA_E2E_URL |
Returns success without testing | A silent early return is not an executed skip or pass. |
In-repository nimanyatta server |
Absent; only a README stub exists | App/server protocol cannot be reproduced from this repository. |
2.2 Primary evidence locations
src/store.rs,src/crypto.rs: destructive load fallback, plaintext downgrade, swallowed write errors, unbounded snapshot queue, global whole-store lock/cache.src/sync.rs,src/nimanyatta_client.rs,tests/sync_e2e.rs,crates/nimanyatta/README.md: cross-site plaintext snapshots, unsafe chunking/merge/replay, singleton pending slots, protocol drift, absent server.src/domain/{site,daily_report,workers,procurement,approvals,meetings}.rs: stringly IDs, unrestricted mutable states, missing role/audit/reference/finite/date invariants, PII duplication.src/site_frame/screens/*.rs: implicit first-site fallback, stale/local caches, direct global mutations, broad disclosure, dead or weakly correlated actions.src/{gif,video,ocr,report_pdf,doc_export,ai_refine,scheduler}.rs: unbounded decode/encode, fake OCR, lossy exports, Unicode panic, provider/cancellation defects, and reminder ordering/time-zone defects.
3. Immediate threat statement
Protected data
Worker identity numbers and images; names, phone/email; exact site/location data; attendance; reports and photographs; approvals; procurement/supplier data; meetings/transcripts; chat; credentials; device/site encryption keys; audit history.
Adversaries and failures in scope
- Curious or compromised sync/chat server and its operators/backups/logs.
- Unauthorized or removed room/site member.
- Malicious member/device sending forged, replayed, oversized, future-dated, conflicting, or malformed events.
- Network attacker where TLS validation/configuration is weakened.
- Lost/stolen device and copied local files.
- Corrupt filesystem, disk full, power loss, process crash, missing/locked keystore, and downgrade/future-version binaries.
- Crafted images, text, PDFs/DOCX inputs/options, and sync payloads intended to exhaust resources or trigger panics.
- Normal concurrent use, out-of-order responses, project/site switching, and stale background jobs.
Explicit limitation
End-to-end encryption cannot prevent an authorized endpoint from copying data, and a malicious server can withhold/reorder traffic. Availability and endpoint malware are not solved cryptographically. These limitations must appear in product documentation.
4. Non-negotiable invariants
- No plaintext downgrade. If confidential persistence cannot encrypt, mutation is blocked or retained only in an explicitly volatile safe mode. It is never silently/plainly persisted.
- No destructive recovery. Missing key, authentication failure, corrupt payload, unsupported version, I/O error, and valid empty are distinct states. None seeds demo data over user state.
- Durable success only. Save success is a
Resultafter serialization, encryption, write, flush, sync, atomic replace, and manifest/directory durability as supported. - Explicit site context. Every domain command, query, export, asset, reminder, and sync event takes typed
SiteId; no “selected or first” fallback authorizes data access. - Central commands and authorization. Screens cannot mutate workflow fields directly. Identity, site role, transition, reference, finite value, revision, and audit checks occur in one service boundary.
- Data minimization. Sync/export/AI/OCR receives only named required records/fields for one site and operation. The process-wide store is never serialized into a room.
- Authenticated site-scoped E2EE. Server sees ciphertext and necessary routing metadata only. Envelopes bind protocol/version/site/key epoch/sender/sequence/content and reject forgery/replay/cross-site substitution.
- One shared protocol implementation. App, server, and tests import the same typed protocol crate and endpoint semantics.
- Convergent explicit replication. Edits and deletes replicate via operation IDs/revisions/tombstones and declared conflict rules. Attacker-controlled wall-clock strings do not decide authority.
- Correlated async work. Every request/result has operation ID, site ID, base revision, and cancellation status. Stale results cannot mutate current state.
- Owned assets, not arbitrary paths. Domain records refer to
AssetId. Ingestion validates/copies bytes into a site-scoped managed store before decode/use. - Bound before allocation. Encoded bytes, dimensions, pixels, frames, decompressed bytes, text, pages, output, recursion, and queue depths are checked with overflow-safe arithmetic.
- No partial success. AI timeout, media truncation, export failure, sync subset, and notification registration failure are explicit states.
- PII lifecycle is enforceable. Access, purpose, retention, deletion/anonymization, backup, export, and audit rules exist for identity data and images.
- Demo data is opt-in. Fixtures are visibly marked and created only by an explicit development/demo action.
5. Severity-ranked findings
P0 — emergency release blockers
| ID | Finding | Concrete impact |
|---|---|---|
| SITE-P0-01 | crypto::seal falls back to a plaintext envelope when keyring/encryption is unavailable. |
Worker/site PII is stored unencrypted while the app continues operating. |
| SITE-P0-02 | load_or_create_dek may create a new key for an existing encrypted store; any decrypt/parse failure then enters backup-and-reseed. |
Key loss/tampering can make user data disappear from the app and be replaced by a demo site. |
| SITE-P0-03 | save/save_to discard directory, serialization, encryption fallback, write, sync, permission, rename, and directory-sync errors. |
UI/state can report success although no durable update exists. |
| SITE-P0-04 | Async persistence uses an unbounded channel of full SiteStore clones and is never drained on production exit. |
Memory amplification and lost final writes. |
| SITE-P0-05 | Sync puts the complete multi-site plaintext store into one site's ordinary chat room. | Cross-site disclosure of identity, contacts, reports, meetings, procurement, reminders, and chat to server/room members. |
| SITE-P0-06 | Sync envelopes have no cryptographic authenticity, site binding, trusted device identity, sequence/replay control, or decompression/total size limit. | Injection, replay, cross-site substitution, memory denial of service, and state poisoning. |
| SITE-P0-07 | Raw JSON is split at arbitrary bytes then lossy-converted to UTF-8; escaped envelope size can exceed transport limits; serialization failure becomes {}. |
Corrupt/incomplete snapshots can look valid; Unicode and transport limits are violated. |
| SITE-P0-08 | Lexically greatest attacker-chosen snapshot ID wins; LWW accepts arbitrary future updated_at; add-only merges cannot propagate most edits/deletes. |
Permanent eclipsing/poisoning and non-convergent devices. |
| SITE-P0-09 | Static Makepad request IDs plus singleton PENDING_* slots correlate concurrent room/send/fetch operations. |
A response can be attributed to the wrong site/room; concurrent messages overwrite pending context. |
| SITE-P0-10 | AI timeout returns nonempty partial output as success and result events lack request/site/revision identity. | Partial or stale sensitive text overwrites a newer field/site. |
| SITE-P0-11 | Image/GIF/video paths decode arbitrary supplied files before pixel/byte limits and buffer every frame/copy. | Path abuse, decompression bombs, process OOM, and UI/job starvation. |
| SITE-P0-12 | AVI size/rate/count casts and multiplication are unchecked; all output is accumulated in memory. | Truncated headers, overflow, corrupt video, or memory exhaustion. |
P1 — major security, correctness, and privacy defects
| ID | Finding | Consequence |
|---|---|---|
| SITE-P1-01 | Domain IDs are aliases to String; parent/site/supplier/meeting/worker references are not centrally validated. |
Cross-site/dangling references and collisions survive into exports/sync. |
| SITE-P1-02 | Report/task/inspection/meeting/site status fields are publicly mutable; roles and legal transition order are not enforced. | Anyone/path can approve, inspect, complete, reopen, or rewrite history. |
| SITE-P1-03 | No immutable audit event records actor, reason, revision, device, and prior/new state. | Approval and PII actions are not accountable. |
| SITE-P1-04 | Worker scans duplicate complete ID numbers/names/phones into daily tables and expose them broadly. Captured ID images have no retention/delete lifecycle. | Excessive PII replication and disclosure. |
| SITE-P1-05 | Quantities, latitude/longitude, dates, durations, IDs, and uniqueness accept invalid/non-finite/inconsistent values. | NaN output, impossible schedules/locations, duplicate reports/scans, and invalid references. |
| SITE-P1-06 | Implicit first-site fallback substitutes another site when selection is absent/stale. | Wrong-site mutation/export/disclosure. |
| SITE-P1-07 | OCR performs corner detection/enhancement only and always returns no recognized text. | Product wording/integration overstates ID scanning capability. |
| SITE-P1-08 | PDF and DOCX print local media paths instead of embedding photos. | Reports are not portable and leak local filesystem structure. |
| SITE-P1-09 | PDF preview and meeting summary truncate UTF-8 strings with byte slicing. | Valid Unicode text can panic the app/export. |
| SITE-P1-10 | DOCX ZIP assembly uses production unwrap() and returns bytes instead of Result. |
Recoverable file/codec failures crash and can lose unsaved work. |
| SITE-P1-11 | AI constructs ClaudeApiChatProvider with ProviderKind::OpenAi, uses environment credentials, and sends report text without a clear consent boundary. |
Wrong provider behavior and undisclosed remote processing of site data. |
| SITE-P1-12 | Reminder fire time assumes fixed EAT regardless of site; invalid hour can panic; OS scheduling happens before durable state. | Wrong/duplicate/missing reminders and unrecoverable OS/app divergence. |
| SITE-P1-13 | Reminder is marked fired immediately after asynchronous post is spawned, even if delivery fails. | Missed reminders are recorded as delivered. |
| SITE-P1-14 | Sync E2E test silently returns success without a server and uses endpoint/method shapes that differ from the app. | CI claims nothing about real interoperability. |
P2 — maintainability and performance debt
- One global clone-heavy store makes every mutation/write proportional to all sites and PII.
- Screens repeatedly clone whole state and cache derived site values without typed revision invalidation.
- Network code hand-parses loosely typed JSON and tolerates malformed success bodies as empty IDs/events.
- PDF/DOCX/media encoders accumulate full output and lack cancellation/metrics.
- Static/global environment manipulation makes tests serial, fragile, and unlike production configuration.
- No Site-owned workflow means dependency, security, test, and resource-limit regressions can merge unnoticed.
6. Target architecture
SiteApp / Makepad screens
│ intents + explicit SiteContext
▼
SiteSessionController
├─ AuthSession { UserId, DeviceId, credentials/capabilities }
├─ SiteContext { SiteId, role/capabilities, revision }
├─ DomainCommandService (validation + authorization + audit)
├─ SiteRepository (encrypted, versioned, atomic, recoverable)
├─ AssetRepository (site-scoped, content-addressed, quota/retention)
├─ JobCoordinator (bounded, correlated, cancellable)
└─ SyncEngine
├─ local operation journal / tombstones
├─ shared typed protocol
├─ established/reviewed group key protocol
└─ authenticated encrypted transport envelopes
Server
├─ authentication and site membership/capabilities
├─ opaque encrypted event log + cursors + bounded snapshot blobs
└─ no application plaintext or client-side key material
Persistence layout
nigig-site/
profile/manifest # store ID, schema, key IDs; no PII plaintext
sites/<site-id>/manifest # encrypted aggregate pointers/revisions
sites/<site-id>/aggregates/... # encrypted envelopes
sites/<site-id>/journal/... # encrypted local operations
sites/<site-id>/assets/<id> # encrypted or platform-protected asset blobs
recovery/... # encrypted/preserved originals; never demo state
Selection and UI preferences are device-local metadata and are not replicated as domain state.
7. Resource and performance budgets
These are initial hard safety ceilings. Lower platform-specific quotas are allowed; unbounded operation is not.
| Resource | Initial limit | Enforcement point |
|---|---|---|
| Legacy whole-store input | 64 MiB | Before read-to-end/decrypt/decode. |
| One aggregate plaintext | 16 MiB | Repository envelope decoder. |
| Sites per profile | 1,000 | Repository/query pagination. |
| Records per aggregate kind/site | 100,000 | Decode/command validation. |
| One sync operation plaintext | 256 KiB | Journal command codec. |
| Sync wire event | <= 48 KiB after encoding | Shared protocol before send. |
| Snapshot plaintext | 32 MiB | Streaming snapshot builder/reader. |
| Snapshot chunks | 1,024; unique indexes only | Reassembly before allocation. |
| Compression expansion | <= 20:1 and <=32 MiB | Streaming decompressor. |
| Concurrent network requests | 32 process-wide; 1 sync stream/site | Request manager. |
| Pending persistence | 1 coalesced revision/site + bounded journal | Repository writer. |
| Image encoded bytes | 20 MiB | Ingestion stream before decode. |
| Image dimensions/pixels | <= 8,192 each; <= 40 MP | Decoder header/limits before allocation. |
| Animation frames | <= 120 and <= 30 seconds | Manifest/request preflight. |
| Total decoded animation pixels | <= 30M pixels (~90 MiB RGB) | Shared media budget. |
| GIF output | 50 MiB | Counting writer. |
| Video output | 200 MiB and below format's checked size ceiling | Counting writer/container preflight. |
| Report text | 1 MiB/report; 64 KiB/field | Domain constructors. |
| Meeting transcript | 5 MiB | Ingest/editor/AI boundary. |
| PDF/DOCX | 500 pages and 100 MiB output | Paginated counting writer. |
| AI request/response | 64 KiB / 16 KiB; 30 s; 1 active/site | AI coordinator. |
| Undo/offline journal | 10,000 ops or 64 MiB/site before compaction | Sync/repository. |
| UI event | p95 < 8 ms desktop / 12 ms mobile | No disk/network/decode/export in handler. |
| Site switch | p95 < 100 ms for metadata; content loads incrementally | Runtime benchmark. |
All count/size/rate arithmetic is checked. Limit errors preserve the prior durable revision and delete incomplete temporary output.
8. Dependency-ordered implementation tranches
Each tranche is a separately tested commit and push. Security containment must not wait for broad refactoring.
SITE-00 — Site-owned truthful CI
Priority: P0 Effort: 2–3 person-days Depends on: none; may proceed in parallel with SITE-01
Change
- Add
.forgejo/workflows/nigig-site.ymlcovering Site-owned files, shared protocol/server, Cargo manifests/lock, and relevant dependencies. - Use one build job and disabled test/dev debug info on memory-limited runners.
- Split check, unit, integration, runtime UI, sync interoperability, media limits, migration, clippy, and security/supply-chain jobs.
- Make missing server/config an explicit skipped job status in development and a hard failure in release CI.
- Preserve full logs and peak RSS/timeout artifacts.
Tests / exit
- Obtain a definitive test result; signal 9 is reported as infrastructure/resource failure.
- A test that returns before assertions cannot count as pass.
- Every path filter and source scan proves it matched owned files.
- Cargo/clippy failures are never hidden by shell filtering or
|| true.
Rollback: runner tuning may change; mandatory ownership and honest status remain.
SITE-01 — Emergency containment without destroying data
Priority: P0 immediate Effort: 1–3 person-days Depends on: none
Change
- Remove/disable Push/Pull and all automatic current sync code in production builds.
- On any non-successful store open, enter
RecoveryRequiredread-only UI; do not callSiteStore::default, seed Muthaiga Villas, save, or mutate the original. - Disable confidential writes when encryption/keyring is unavailable; show explicit safe-mode state.
- Disable GIF/video generation, remote AI, and path-based export/OCR entry points until their limit/privacy tranches land; manual text/photo capture may continue only through safe asset ingestion.
- Preserve original files and display recovery/export-support actions without decrypting/logging content.
Tests / exit
- Missing key, wrong key, tamper, malformed JSON, future version, permission error, and empty file cannot create/overwrite a store.
- Default production build has no reachable sync send/pull path.
- A feature flag/config cannot bypass containment accidentally.
- Existing valid encrypted data remains readable and byte-identical until explicit mutation.
Rollback: unsafe capabilities stay disabled. There is no acceptable rollback to plaintext sync or destructive seeding.
SITE-02 — Fail-closed recoverable encrypted repository
Priority: P0 Effort: 8–12 person-days Depends on: SITE-01
Change
- Replace
load_or_create_dekwith distinct setup and existing-store open flows. An existing envelope namesstore_idandkey_id; missing key isKeyUnavailable, never “create another key.” - Make encryption/decryption/sealing return typed
Result; eliminate plaintext fallback. - Bind envelope header/version/store/aggregate/key ID to AEAD associated data and validate nonce/key-length/version exactly.
- Add user-controlled recovery/key escrow design, rotation, and secure deletion after security review; do not invent password crypto ad hoc.
- Replace
savewith an injectedSiteRepositoryreturning durable revision results. Use unique temp names, restrictive permissions at creation, checked write/flush/sync/rename/directory sync, and cleanup. - Replace unbounded full-store channel with bounded coalescing per-site writers and explicit
flush_and_shutdown. - Expose persistence health and last durable revision to UI.
Tests / exit
- Fault injection at key lookup, random generation, serialization, encryption, create, chmod, write, flush, sync, rename, and shutdown.
- Wrong/missing/rotated key and modified header/ciphertext are distinct recoverable errors.
- No plaintext bytes/known PII appear in data, temp, backup, or journal files.
- Queue depth is bounded; final accepted mutation either flushes on graceful shutdown or remains explicitly unsaved.
- Independent crypto/security review approves primitive use, nonce policy, associated data, key lifecycle, and recovery design.
Migration: legacy plaintext is read only after explicit user consent and key setup, then encrypted to a new location and verified before original disposition. Existing encrypted NIGIG1 input is preserved and migrated without invoking key creation on lookup failure.
Rollback: use the prior encrypted reader against preserved originals; never emit plaintext or reseed.
SITE-03 — Site-scoped versioned aggregates and explicit context
Priority: P0 Effort: 7–11 person-days Depends on: SITE-02
Change
- Split monolithic
SiteStoreinto typed versioned aggregates per site plus device-local preferences. - Add opaque
SiteId,ReportId,TaskId,WorkerId,ScanId,SupplierId,MeetingId,AssetId,ReminderId,UserId, andDeviceIdnewtypes. - Introduce
SiteContext; removeselected_or_firstfrom command/query authorization. - Add repository query pagination/indexes and aggregate revision compare-and-swap.
- Move selected site, transient screen state, and chat presentation cache out of replicated domain payloads.
Tests / exit
- APIs cannot compile a site mutation/query/export without
SiteContext. - A/B sentinel tests cover every aggregate, cache, asset, export, reminder, and job.
- Missing/stale selection is
NoSiteSelected, never substitution with first site. - A one-site mutation does not clone/serialize unrelated sites or PII.
- Duplicate/wrong-type/cross-site IDs are rejected.
Migration: split legacy whole store by site_id; global suppliers/ambiguous records require explicit assignment or quarantined shared-directory policy. Keep mapping and original encrypted bytes.
Rollback: compatibility reader may expose old data read-only; new writes never return to a global blob.
SITE-04 — Domain command service, validation, and immutable audit
Priority: P0/P1 Effort: 8–12 person-days Depends on: SITE-03
Change
- Make domain fields private where mutation needs invariants.
- Add
DomainCommandServicewith expected revision, actor/device, site context, reason, timestamp source, validation, atomic aggregate update, and audit event. - Validate uniqueness, parent/reference ownership, finite/range values, latitude/longitude, date order, duration, one-report-per-site/day, scan deduplication, and record/text limits.
- Use fixed-decimal quantity plus typed unit where commercial/measured quantities matter.
- Persist append-only audit events with hash/integrity linkage appropriate to the repository threat model; audit is not user-editable history.
Tests / exit
- Every public mutation has success, unauthorized, invalid transition/reference/value, stale revision, and rollback tests.
- NaN/inf, negative quantities, impossible coordinates/dates, duplicate IDs/scans/reports, cross-site parents/suppliers/assets fail.
- Failed command leaves aggregate, revision, audit, and sync journal unchanged.
- Audit identifies actor/device/command/target/prior-new revision/reason without logging secret payloads.
Rollback: aggregates remain read-only if command migration fails; no public-field bypass.
SITE-05 — Authentication, site roles, and PII policy
Priority: P0 before sync Effort: 8–14 person-days plus privacy/security review Depends on: SITE-03, SITE-04
Change
- Define authenticated user/device session and site membership/roles/capabilities. Server and client enforce the same policy at different trust boundaries.
- Map commands to capabilities: report author/submit/approve, worker PII read/write/export, task inspect, procurement edit, meeting manage, site admin, key/device management.
- Separate stable worker/person record from attendance scan; daily tables reference
WorkerIdrather than duplicating full ID/contact data. - Define purpose, masking, search, export, retention, correction, deletion/anonymization, backup expiration, and ID-image handling.
- Redact PII/secrets from logs, errors, crash reports, analytics, filenames, and notification bodies.
Tests / exit
- Deny-by-default role matrix at command, query, export, sync, and server endpoints.
- Removed member/device loses future key access and server authorization according to documented revocation limits.
- Worker ID is masked by default and omitted from broad report/export roles.
- Retention/deletion/anonymization propagates to indexes, assets, sync operations, and backups under stated policy.
- Privacy/security review and Kenya/target-market legal review approve processing flows.
Rollback: disable remote/PII workflows or make them local read-only; never rely on screen hiding as authorization.
SITE-06 — Enforced report/task/inspection/procurement/meeting workflows
Priority: P1 Effort: 10–16 person-days Depends on: SITE-04, SITE-05
Change
- Define legal state machines and commands for site, report, construction task, inspection, procurement line, meeting, and minutes.
- Require submitter/approver separation if policy requires; approval/rejection reason and immutable approved revision.
- Validate task hierarchy, dates, quantities/units, inspections/assignees/outcomes, supplier references, meeting attendees/duration/status, and minutes approval.
- Replace byte-truncated “auto-summary” with no summary until real summarization is accepted.
- Define reopen/amendment behavior instead of mutating approved history.
Tests / exit
- Transition tables test every allowed/forbidden edge and role.
- Approved reports/minutes are immutable; amendment produces linked revision and audit.
- Parent/site, supplier/site, inspection/task, minutes/meeting, and attendee/user references validate.
- Unicode transcript of any valid text never panics.
- Templates are explicit user actions and visibly identified, not recovery seeds.
Rollback: block transitions and preserve drafts; do not permit arbitrary status mutation.
SITE-07 — Managed asset ingestion and lifecycle
Priority: P0 media prerequisite Effort: 7–11 person-days Depends on: SITE-02, SITE-03, SITE-05
Change
- Replace stored filesystem strings with site-scoped
AssetIdplus media metadata, content digest, ownership, source, creation, retention, and encryption key epoch. - Ingest picker/camera handles by streaming into a quarantine temp file under quota; sniff/decode declared format, check encoded bytes and header dimensions before full allocation, then atomically publish.
- Use decoder allocation limits and checked pixel/channel arithmetic; reject decompression bombs and unsupported metadata.
- Generate bounded thumbnails separately. Strip EXIF/location by default; attach GPS only after explicit product/user policy.
- Prevent symlink/path races by never reopening an arbitrary caller path as authority.
- Implement reference counting or explicit ownership for deletion/retention.
Tests / exit
- Truncated, polyglot, extension-spoofed, huge-dimension, high-compression, symlink-swap, duplicate-content, quota, disk-full, and cancellation corpus.
- Domain/export/OCR/media APIs accept
AssetId, not raw path strings. - Delete/retention removes all derived thumbnails/transcodes and respects approved-record retention rules.
- Assets from Site A are inaccessible under Site B context even with guessed IDs.
Rollback: retain original external path only in migration quarantine/read-only UI; new captures use managed assets.
SITE-08 — Shared sync protocol and threat-model ADR
Priority: P0 Effort: 6–10 person-days Depends on: SITE-01, SITE-03, SITE-05
Change
- Write an ADR/protocol specification with threat model, metadata leakage, identity/membership, key epochs, event fields, size limits, replay/order, snapshot/compaction, conflict, deletion, revocation, recovery, and version negotiation.
- Create one workspace protocol crate used by app, server, and tests; no hand-written parallel JSON shims.
- Define dedicated opaque sync endpoints/event types; do not piggyback application snapshots as ordinary chat text.
- Define canonical byte encoding and test vectors across implementations/versions.
- Select an established audited group encryption/key-management protocol or maintained implementation (for example MLS where platform support is viable). Any simpler design requires independent cryptographic review before code.
Tests / exit
- Protocol crate rejects unknown critical versions/fields, wrong site, oversized lengths/counts, duplicate indexes, invalid canonical encoding, and unsupported algorithm suites before large allocation.
- App/server compile against the same request/response types and HTTP methods.
- Threat model and crypto choice receive independent security review.
- Version negotiation cannot silently downgrade confidentiality/integrity.
Rollback: sync remains disabled; no “temporary” plaintext protocol ships.
SITE-09 — Authenticated site-scoped E2EE and device/key lifecycle
Priority: P0 Effort: 12–20 person-days plus external review Depends on: SITE-05, SITE-08
Change
- Provision per-site group context/key epochs through the selected reviewed protocol; bind authenticated user devices to site membership.
- Encrypt application payload client-side; include only minimum routing/version metadata outside ciphertext.
- Authenticate sender/device and bind site ID, protocol version, key epoch, operation ID, sequence, and payload digest as protected content/associated data.
- Implement device add/verify/remove, member add/remove, epoch rotation, lost-device recovery, backup/recovery, and key zeroization according to chosen library guarantees.
- Pin production TLS/security policy in addition to E2EE; allow loopback HTTP only in explicit development builds.
Tests / exit
- Known-answer vectors plus wrong site/key/epoch/device, modified header/body, nonce reuse guard, replay, reordering, duplicate, removed device, downgrade, and lost-key recovery tests.
- Server/database/log capture contains no worker/report/application plaintext or client group keys.
- A member without Site A membership cannot decrypt/inject A even if sharing another chat room.
- External security assessment has no unresolved critical/high findings.
Rollback: disable sync and retain encrypted local journal; never downgrade to TLS-only/plain room messages.
SITE-10 — Operation journal, tombstones, and deterministic convergence
Priority: P0 Effort: 10–16 person-days Depends on: SITE-04, SITE-08, SITE-09
Change
- Replicate validated domain commands/operations for one site, not whole mutable stores.
- Give operations globally unique ID, device sequence, base/entity revision, actor, schema, and cryptographically authenticated ordering metadata.
- Add tombstones with retention/compaction rules; edits/deletes must propagate.
- Define per-aggregate conflict rules. Use server cursor/log order, revision preconditions, CRDTs where justified, or explicit user conflict; do not let arbitrary future wall clocks win.
- Exclude chat cache, selection, secrets, keys, temporary files, and sync envelopes from snapshots.
- Build streaming encrypted snapshots for bootstrap/compaction with hash tree/digest, byte/record limits, and acknowledgement before journal truncation.
Tests / exit
- Multi-replica property tests randomly reorder/duplicate/drop/retry operations and prove convergence after eventual delivery.
- Edit/edit, edit/delete, delete/recreate, stale base, offline long fork, future/old clock, device reset, and compaction boundary cases.
- Syncing once does not make the next snapshot grow recursively.
- A malicious operation cannot bypass domain authorization/invariants merely because it decrypts.
- Journal/snapshot stays within §7 and interruption never discards unacknowledged ops.
Rollback: retain local encrypted journal and require manual export; do not apply ambiguous remote state.
SITE-11 — Correlated bounded network client
Priority: P0 Effort: 5–8 person-days Depends on: SITE-08
Change
- Replace static request IDs/singleton pending slots with
RequestManagerkeyed by unique operation ID and carrying endpoint, site, room if applicable, base revision, deadline, retry/idempotency, and cancellation. - Bound concurrent/pending requests and response body bytes; validate status/content type/typed body and required nonempty IDs.
- Use cursor-based pagination; do not fetch an entire room history to discover snapshots.
- Redact tokens and bodies from errors/logs; credentials use secure storage and explicit session lifecycle.
- Deliver response only to matching live controller/context.
Tests / exit
- 100 concurrent out-of-order create/send/fetch/sync operations correlate exactly.
- Timeout/cancel/site switch/retry/duplicate response/oversized body/invalid JSON/empty success ID tests.
- Idempotent retry cannot create duplicate room/events.
- Queue saturation is an explicit error/backpressure state.
Rollback: network features remain disabled; no global pending-slot fallback.
SITE-12 — Reproducible real server interoperability
Priority: P0 release prerequisite for sync Effort: 8–15 person-days depending on server import Depends on: SITE-08 through SITE-11
Change
- Vendor/import the actual supported
nimanyattaserver/protocol history or depend on a reproducibly pinned source available to CI. - Add migrations/test server launcher and typed endpoints for auth, membership, opaque events, cursors, snapshots, limits, rate limiting, and idempotency.
- Replace
sync_e2eearly-return behavior with explicit ignored developer test plus mandatory CI integration job that launches the pinned server. - Test two users, three devices, two sites, removal/revocation, offline edits, and server restart/database migration.
Tests / exit
- Fresh checkout can build and launch app protocol tests with no sibling repository.
- App and server use identical methods/routes/types; protocol drift is a compile/test failure.
- Database inspection verifies ciphertext-only application payload.
- Authz, rate/size limits, cursor semantics, idempotency, revocation, and schema migration pass.
- A required server job cannot be classified as pass when not executed.
Rollback: deploy no sync client UI; local encrypted operation journal remains intact.
SITE-13 — Private, correct, correlated AI refinement
Priority: P0/P1 Effort: 5–8 person-days Depends on: SITE-04, SITE-05, SITE-11
Change
- Instantiate the chosen provider with matching
ProviderKind, model, endpoint, and credential source. - Add operation/site/report/field/base revision IDs to request and result.
- Enforce request/response/time budgets; timeout/cancel/error/function call/partial stream is failure.
- Require explicit consent and show provider/data fields/region-retention link before remote processing. Minimize/redact input.
- Stream into preview only; apply as a domain command after complete response validation and explicit acceptance.
- Store credentials in platform secret storage; never mutate process environment as product settings.
Tests / exit
- Fake providers prove selection, data minimization, complete success, partial timeout, stale result, cancellation, oversized output, malformed/function response, and site switch.
- Original report is unchanged on all failures and before acceptance.
- Role/privacy policy gates who may submit data and persist refined text.
- Logs/artifacts contain no prompt, key, ID number, or report body.
Rollback: AI stays disabled; manual text remains fully functional.
SITE-14 — Real OCR or truthful capture-only flow
Priority: P1 privacy Effort: 6–12 person-days plus platform/license review Depends on: SITE-05, SITE-07
Change
- Rename current behavior to crop/enhance if no OCR engine is shipped.
- If OCR is enabled, integrate a maintained on-device engine with pinned model/version/license, language coverage, confidence/field extraction, and no network by default.
- Process managed
AssetIdunder media budgets in a bounded worker; correlate result to site/worker/base revision. - Present suggestions with confidence for user confirmation; never auto-approve identity.
- Define ID-image deletion immediately after verified extraction unless retention has explicit lawful purpose/consent.
Tests / exit
- Representative permitted ID fixtures measure field precision/recall and false-positive behavior; synthetic fixtures alone are insufficient.
- Rotated, blurred, glare, Unicode names, no text, adversarial image, cancel, and stale/site-switch tests.
- UI never says OCR found data when suggestions are
None. - Privacy/license/security review passes and no image leaves device absent explicit remote-OCR consent.
Rollback: capture/manual entry only, with no OCR claim.
SITE-15 — Streaming bounded GIF/video processing
Priority: P0 media Effort: 8–12 person-days Depends on: SITE-07
Change
- Accept managed assets and a
MediaBudget, preflight encoded bytes/dimensions/frame count/duration/fps/output/container limits before decoding. - Decode/resize/encode one bounded frame at a time; do not retain source images, resized images, RGB copies, JPEGs,
movi, and final output simultaneously. - Use checked arithmetic and fallible reservations/conversions for every pixel/RIFF/GIF/JPEG count.
- Prefer a maintained encoder/container library. If AVI remains, cap below 32-bit RIFF limits, include/validate needed indexing, and independently test playback.
- Use collision-resistant asset IDs, unique temp output, atomic publish, cancellation cleanup, and quotas.
Tests / exit
- Huge headers, decompression bombs, 0/too-high fps, >120 frames, mismatched dimensions, maximum counts, JPEG/output expansion, integer boundaries, disk full, cancel, and duplicate simultaneous jobs.
- Peak RSS stays under the declared decoded/output budget; test records metrics.
- Independent GIF/video decoders parse and play every golden output with expected frame count/duration/dimensions.
- No arbitrary path open and no second-resolution filename remains.
Rollback: disable animation/video; preserve original managed photos.
SITE-16 — Portable panic-free PDF/DOCX exports
Priority: P1 Effort: 8–13 person-days Depends on: SITE-04 through SITE-07, SITE-11 for job correlation
Change
- Return
Result<ExportArtifact, ExportError>throughout; remove productionunwrapand partial success. - Iterate/truncate/wrap by Unicode grapheme/glyph boundaries, not byte offsets; embed a reviewed Unicode font with fallback.
- Resolve authorized
AssetIds and embed compressed photos/thumbnails with captions/location policy. Never print local paths. - Build valid DOCX image parts, relationships, content types, dimensions, and alt text using a maintained OOXML layer where feasible.
- Stream/paginate under page/byte/image budgets in a bounded export coordinator and deliver only after final picker/write completion.
- Apply role/PII minimization and watermark/status policy to compiled multi-site reports.
Tests / exit
- Emoji, combining marks, CJK/Arabic/long unbroken text, 500-page boundary, missing/deleted/unauthorized/corrupt photos, write failure, and cancellation never panic.
- Independent PDF parser/render and LibreOffice/Word-compatible DOCX open validate pages, text, images, relationships, and no repair warnings.
- Extracted package/PDF contains image bytes and no source filesystem paths.
- Export from Site A cannot include Site B without an explicit authorized multi-site scope.
Rollback: export text-only with explicit omitted-image warnings or disable the format; never claim photo inclusion.
SITE-17 — Durable site-time-zone reminder state machine
Priority: P1 Effort: 5–8 person-days Depends on: SITE-03 through SITE-05
Change
- Store IANA site time zone and validate local working-hour/calendar inputs; use device zone only by explicit policy.
- Make fire-time calculation return
Result, handle DST ambiguous/nonexistent times according to documented policy, and remove panic/fallback-to-UTC guessing. - Persist
PendingRegistrationwith idempotency key before OS call; then persistRegistered/RegistrationFailedand platform handle. - Implement update/cancel/reschedule/reconcile on startup and permissions/time-zone/clock changes.
- Mark
Deliveredonly on confirmed/successful post semantics available from platform; otherwise record attempted/unknown/failed honestly. - Deduplicate app/OS paths by stable tag and state.
Tests / exit
- Nairobi and DST zones, midnight/year/month boundaries, invalid hour, past/future, clock rollback/forward, restart between each state, denied permission, OS failure, duplicate tick, cancel/reschedule.
- Site A zone never drives Site B.
- Repository and fake OS scheduler fault injection prove recoverable reconciliation.
- Notification body follows PII minimization policy.
Rollback: in-app due list only; no unreliable OS scheduling claims.
SITE-18 — Screen/controller isolation, responsive UX, and observability
Priority: P1/P2 Effort: 10–16 person-days Depends on: SITE-03 through enabled feature tranches
Change
- Replace direct
SiteStorereads/mutations in screens with controllers and immutable revisioned view models. - Route every screen with explicit
SiteContext; invalidate/cancel old cache/jobs on switch. - Remove static singleton action payloads and dead/inert actions.
- Virtualize large reports/workers/tasks/procurement/meeting lists; do not clone whole profile in draw.
- Add accessible labels/focus/touch targets/error/recovery/offline/conflict states.
- Instrument persistence revisions/latency/queue, sync encrypted bytes/cursor/retries/conflicts, media bytes/pixels/frames/RSS, AI duration, export pages/bytes, and frame/event latency. Never record content/PII.
Tests / exit
- Automated A/B route/switch suite checks every screen, dialog, async completion, cache, and export.
- Draw/event instrumentation detects disk/network/decode/export work and fails.
- 100k-record virtualized fixtures and site switch meet §7 budgets.
- Metrics cardinality/content review proves no IDs, names, paths, prompt/report text, tokens, or coordinates leak.
- Desktop/mobile lifecycle and accessibility tests exercise actual actions after app startup.
Rollback: presentation pieces may roll back; explicit site/controller/domain boundaries may not.
SITE-19 — Migration, adversarial, interoperability, and release evidence
Priority: P0 release gate Effort: 8–14 person-days plus external/device testing Depends on: every enabled-feature tranche
Change
- Build sanitized legacy corpus: plaintext/raw JSON, plaintext envelope, valid encrypted, missing key, wrong key, tampered/truncated/future, multi-site/PII/media paths, recursive sync messages.
- Add storage fault, domain property, authorization, protocol fuzz, multi-replica convergence, media bomb, Unicode export, AI stale/cancel, reminder, and real runtime journeys.
- Run multi-device/server restart/migration/revocation tests and long offline/online soak.
- Perform independent security/privacy/crypto review and remediate critical/high findings.
- Publish capability matrix and release evidence with commit, toolchain, server schema, platform/device, limits, and known limitations.
Tests / exit
- Migration operates on copies, verifies encrypted outputs, and demonstrates rollback without data invention/loss.
- Zero unexplained/expired ignored tests; required server/device jobs actually execute.
- No PII plaintext appears in local encrypted artifacts, wire/server storage, logs, or CI artifacts.
- A/B isolation, role matrix, convergence, replay/forgery, resource limits, and forced failures all pass.
- Peak memory/latency/output meet §7 on supported devices.
Rollback: release is blocked or the failing capability remains disabled through SITE-01.
9. Detailed legacy migration and recovery policy
- Never open destructively. Read bounded original bytes once, hash them, and work on a copy.
- Classify before action: absent, zero length, raw legacy JSON, explicit plaintext envelope, encrypted known version/key ID, encrypted missing key, authentication failure, corrupt payload, or unsupported future version.
- Do not create a key while opening encrypted data. Setup creates keys only for a confirmed new profile/store ID.
- Require encryption before migration writes. If key setup fails, retain read-only recovery state; do not write plaintext.
- Split by site. Reports/workers/tasks/procurement/meetings/directories/assets map by validated site ID. Dangling and global records are quarantined for explicit resolution.
- Do not replicate device state. Selected site and cached chat presentation are excluded.
- Import assets. Canonicalize only for one-time migration, enforce byte/pixel limits, copy into managed asset storage, and replace path with
AssetId. Missing/unsafe files become warnings, not arbitrary reads. - Preserve PII policy. ID images are not imported automatically without a retention decision.
- Write new aggregates atomically, reopen/decrypt/validate, compare record counts/digests, then activate new manifest.
- Keep originals encrypted/restricted for a stated recovery window. Deletion requires explicit confirmation and backup policy.
- Never seed demo data as migration/recovery. A new empty profile is a valid explicit user choice.
- No automatic downgrade. Newer-schema data stays read-only for an old binary.
10. Required CI command matrix
Memory-constrained baseline commands:
CARGO_BUILD_JOBS=1 CARGO_PROFILE_DEV_DEBUG=0 cargo check --locked -p nigig-site --all-targets
CARGO_BUILD_JOBS=1 CARGO_PROFILE_TEST_DEBUG=0 cargo test --locked -p nigig-site --lib -- --test-threads=1
CARGO_BUILD_JOBS=1 CARGO_PROFILE_TEST_DEBUG=0 cargo test --locked -p nigig-site --tests -- --test-threads=1
cargo fmt -p nigig-site -- --check
CARGO_BUILD_JOBS=1 CARGO_PROFILE_DEV_DEBUG=0 cargo clippy --locked -p nigig-site --all-targets -- -D warnings
git diff --check
Required named jobs as tranches land:
cargo test --locked -p nigig-site --test storage_recovery -- --test-threads=1
cargo test --locked -p nigig-site --test project_site_isolation -- --test-threads=1
cargo test --locked -p nigig-site --test authorization_matrix -- --test-threads=1
cargo test --locked -p nigig-site --test protocol_adversarial -- --test-threads=1
cargo test --locked -p nigig-site --test sync_convergence -- --test-threads=1
cargo test --locked -p nigig-site --test media_limits -- --test-threads=1
cargo test --locked -p nigig-site --test export_interop -- --test-threads=1
cargo test --locked -p nigig-site --test runtime_ui -- --test-threads=1
cargo test --locked -p nigig-site --test sync_e2e_required -- --test-threads=1
Release CI launches the pinned server itself; NIMANYATTA_E2E_URL cannot be an optional condition for the required job. Fuzzers and independent parsers run with fixed wall-clock/memory ceilings and retain crashing inputs.
11. Release gates
Local/offline release
- Current sync is absent from production build.
- Missing/wrong key, tamper, corruption, future version, empty, and I/O failures are distinct non-destructive states.
- No plaintext fallback exists; durable save errors are surfaced.
- Repository queues/writes are bounded, atomic, and flushed/reconciled on lifecycle exit.
- Every command/query/export/job uses explicit typed site context.
- Domain transitions, references, finite/range rules, roles, revisions, and audit are centrally enforced.
- Worker PII and ID-image lifecycle policy is approved and tested.
- All enabled media uses managed assets and passes encoded/pixel/frame/output/peak-memory limits.
- OCR is real and measured or labelled capture-only.
- PDF/DOCX are Unicode-safe, embed authorized photos, return errors, and pass independent consumers.
- AI is disabled or passes provider/privacy/correlation/acceptance gates.
- Reminders pass time-zone/durability/idempotency/reconciliation gates or remain in-app only.
- Site-owned CI has a definitive green result with real runtime assertions.
Additional sync release gates
- Shared protocol and threat-model ADR are approved.
- App/server/tests use one protocol crate and reproducible in-repo/pinned server.
- Authenticated site-scoped E2EE passes external review with no open critical/high findings.
- Device/member lifecycle, revocation, recovery, replay, downgrade, cross-site, and ciphertext-at-server tests pass.
- Operation journal/tombstones/conflicts converge under randomized multi-replica tests.
- Network client correlates bounded concurrent requests and validates all responses.
- Required E2E server job actually executes; no silent return/skip counts as pass.
- Product copy accurately states metadata/endpoint/availability limitations.
12. Delivery, push safety, and rollback protocol
For each SITE-NN tranche:
- Record starting SHA and keep a clean worktree.
- Add regression/adversarial tests that fail against the parent commit.
- Run the tranche matrix under memory/resource limits plus
git diff --check. - Commit only that independently reviewable change using its tranche ID.
- Immediately before push, run
git fetch origin mainand compare remote/merge base. - If remote moved, inspect all incoming changes and rebase/merge without discarding them; rerun tests after resolution.
- Push without force and verify the exact commit exists remotely before starting the next pushed tranche.
- If authentication is unavailable, report the exact blocker and preserve the tested local commit. Never claim success, expose credentials, or overwrite remote work later.
Rollback defaults to feature off + preserved encrypted data. Never roll back to plaintext storage/sync, first-site fallback, destructive demo seeding, unrestricted path decode, public workflow mutation, or stale async acceptance.
13. Critical path and cross-plan dependencies
SITE-01 containment → SITE-02 encrypted repository → SITE-03 site aggregates/context
├→ SITE-04 commands/audit → SITE-05 auth/privacy → SITE-06 workflows
└→ SITE-07 managed assets → SITE-14/15/16
SITE-03 + SITE-05 → SITE-08 protocol → SITE-09 E2EE → SITE-10 convergence
└────────→ SITE-11 client → SITE-12 real server
SITE-04/05/11 → SITE-13 AI
SITE-03/04/05 → SITE-17 reminders
All enabled paths + SITE-00 → SITE-18 → SITE-19
- Build/Traffic/CAD work does not justify delaying Site's emergency containment.
- Future integration with
nigig-buildrequires an authenticated mapping between Build project ID and Site ID; never sync one app's global store into the other. - Shared chat rooms may coexist with sync, but sync payloads must use dedicated opaque protocol events and keys, not visible text bodies.
- A safe release may omit sync, AI, OCR, video, DOCX, or OS reminders. It may not ship unsafe substitutes for them.