The previous row said xitca-web was vendored in this repository (519 files) and
attributed the workspace-member blocker to xitca-web/web/Cargo.toml inheriting
rust-version and lints from the wrong workspace root. f9c1235 dropped the
vendored xitca-web for crates.io `xitca-web = "0.8"`, so both statements were
already stale when written.
Current, verified state:
- nigig-lite/crates/common (package nigig-common) is present; it now also uses
crates.io xitca-web.
- Adding `nimanyatta` to the workspace still fails `cargo metadata` with exit
101, but the cause is different: "multiple workspace roots found in the same
workspace: nimanyatta, nigig-lite/crates/common, nigig-org". Both sub-crates
carry their own [workspace] table. The fix is to delete those tables or add
both paths to the root exclude list — but nigig-common's table is documented
as deliberate ("keeps it independent from any parent workspace"), so this is
a decision for the server team rather than a mechanical change.
Verified: cargo test -p nigig-site-core = 228 passed / 1 ignored;
cargo test -p nimanyatta-protocol = 14 passed; clippy -D warnings clean on
both; cargo fmt --check clean; table renders (59 SITE rows, uniform columns).
Note on an intermediate false signal: a test run reported 196 passed / 32
failed. All 32 failures were `Os { code: 28, StorageFull }` in
repository::tests, caused by a 906 MB build directory I had left in the 993 MB
/tmp tmpfs while checking whether the server resolves. After clearing it, the
suite is 228/228 again. No code was involved.
Commits dfffe9e + 13ae501 landed nigig-lite/crates/common (package
`nigig-common`) and a vendored xitca-web into this repository, which makes the
/sync wire format readable for the first time. Reading it shows ce9e12b was
incorrect.
There are TWO MessageContent types, deliberately:
- nigig_common::MessageContent::Text { body, formatted_body, mentions } — the
HTTP/JSON REST DTO. Its own doc-comment: "Serialises externally tagged,
e.g. {"Text":{"body":"hi",…}}". This is what SendMessageRequest.content and
TimelineEvent::Message.content are.
- nimanyatta_protocol::MessageContent::Text { text } — the WebSocket chat
protocol, serialized with postcard. nigig-common's manifest says that format
"must match nimanyatta/crates/nimanyatta-protocol (the client library)".
ce9e12b changed the E2E test's outgoing posts from {"Text":{"body":…}} to
{"Text":{"text":…}}, reasoning from the WebSocket type. The original `body`
was correct for these HTTP endpoints; my change would have made both requests
fail deserialization. Reverted, and the now-unused nimanyatta-protocol
dev-dependency is dropped from nigig-site.
What ce9e12b got right is kept: the parser had keyed on "type":"message",
but TimelineEvent carries no serde tag attribute, so it is externally tagged —
{"Message":{…,"sender":…,"content":…}} with sender and content at the variant
level. event_text still accepts that envelope (plus the legacy lowercase shape
and `text` as last-resort field names).
The fixture's cross-check test was rebuilt against nigig_common's real shape
instead of the WebSocket type. It is NOT verified by execution: nigig-site's
test target pulls in makepad-widgets, which is SIGKILLed on this host. The
parser was verified instead by extracting event_text/sync_timelines verbatim
into a standalone harness over four cases — nigig_common Message with `body`
-> Some(("alice","slab done")); Membership -> None; Redaction -> None; legacy
lowercase -> Some(("bob","old")). All pass. The harness is scratch, not
shipped.
EXECUTION_PLAN.md SITE-12 rewritten: the sibling repos are no longer missing,
the /sync DTOs are named with their file paths, both MessageContent types are
distinguished, and the narrower remaining blockers are stated — nimanyatta
still cannot be a workspace member (xitca-web/web/Cargo.toml inherits
rust-version and lints from a workspace root, and nested here it resolves to
ours, which defines neither), and a full `cargo check --features b_server` has
not completed on this host, so no build claim is made.
Verified: cargo test -p nimanyatta-protocol = 14; cargo test -p nigig-site-core
= 228 / 1 ignored; clippy -D warnings clean; cargo fmt --check clean;
cargo metadata exit 0.
Follow-up to ac40ac9, which fixed only the parse side and overstated the
consequence. Three corrections and two further defects.
CORRECTION to my own previous claim: ac40ac9's message and the SITE-12 plan
row both said that with only `content.Text.body` supported, `live_round_trip`
"would still pass". That is false. sync_e2e.rs asserts
`timelines.iter().any(|(_, lines)| lines.iter().any(|l| l.contains(&marker)))`,
so the run would have FAILED with "app parser must extract the marker" — a
misleading diagnosis, but a failure, not a silent green. The fixture bug was
real; the consequence I described was not.
Defect 1 — the test also SENT the wrong shape. Both message posts in
live_round_trip sent {"Text":{"body":...,"formatted_body":null,"mentions":[]}}.
nimanyatta/src/routes/rooms.rs binds `req.content` to a `MessageContent`,
whose Text variant has a single field named `text`, so those requests would
fail at deserialization before anything reached the timeline. Both now send
{"Text":{"text":...}}.
Defect 2 — the parser keyed on the wrong envelope. `event_text` required
`event["type"] == "message"`, but the server constructs
`TimelineEvent::Message { event_id, room_id, sender, content, timestamp,
edited, reply_to }` (verified at the construction site in routes/rooms.rs),
and `sender`/`content` live at the variant level, not under a `type` tag.
What is still unknown, and is NOT guessed here: TimelineEvent's serde tagging.
It is defined in `nigig_common`, which is absent from this repository, so
neither externally- nor internally-tagged can be confirmed. Rather than pick
one, event_text now accepts both envelopes plus the legacy lowercase shape,
and `body` is kept as a last-resort field name. Membership and Redaction
events are still skipped.
Verification, stated precisely:
- The fixture's own test target cannot be compiled here: nigig-site's test
target pulls in makepad-widgets, which is SIGKILLed on this 2 vCPU / 1.9 GiB
host. The shipped cross-check test remains unverified until CI runs it.
- event_text and sync_timelines were instead extracted verbatim from the
fixture into a standalone harness and exercised over five cases: externally
tagged -> Some(("alice","slab done")); internally tagged ->
Some(("bob","poured")); legacy lowercase with `body` ->
Some(("carol","old")); Membership -> None; Redaction -> None. All pass.
That harness is scratch, not part of the tree.
Unchanged and still passing: cargo test -p nimanyatta-protocol = 14;
cargo test -p nigig-site-core = 228 / 1 ignored; clippy -D warnings clean;
cargo fmt --check clean; cargo metadata exit 0; 6/6 workflow python gates.
- Remove all ignores in tests/ui.rs; harness failure was the broken pin
plus no display. CI runs them via xvfb-run (xvfb, libgl1-mesa-dri).
- HUD test: HUD is raw draw_text, not widgets; assert view survives
Intro->Driving and capture screenshot instead of invisible selectors.
- Docs: release gate ticked, blocker 2 closed, 430px HUD clipping noted.
Verified: 121 pass / 0 fail / 0 ignored; clippy 0 owned; fmt; diff --check.
Pinning nimanyatta-protocol's JSON shape exposed a live bug in the E2E
fixture, and answering "is nigig_common in this repo?" narrowed the SITE-12
gap.
The bug: MessageContent::Text serializes as {"Text":{"text":"..."}}.
tests/support/nimanyatta_fixture.rs::event_text only looked for
content.Text.body — a field that type never emits, and which
serde_json::from_str::<MessageContent> actively rejects. Against a real
server every message would have been skipped, sync_timelines would return an
empty vec, and live_round_trip would still have passed. A silently vacuous
interop test is exactly the failure mode SITE-00 exists to prevent.
- event_text now tries `text` first and keeps `body` as a fallback.
- New test in nimanyatta-protocol pins the serialized shape and asserts the
fixture's old guess does not decode.
- New cross-check test in the fixture builds its payload from the real
MessageContent type, so parser and protocol cannot diverge silently again.
What was verified and what was not:
- The protocol-crate tests run: 14 passed.
- The fixture cross-check test COULD NOT be compiled or run here. nigig-site's
test target pulls in makepad-widgets, which is SIGKILLed on this 2 vCPU /
1.9 GiB host. The parser change was instead verified by extracting
event_text/sync_timelines verbatim from the fixture into a standalone
harness: {"Text":{"text":"slab done"}} -> Some(("alice","slab done")),
the legacy {"Text":{"body":...}} shape still falls back, and a
non-message event returns None. That harness is not part of the shipped
tree; the shipped cross-check test is unverified until CI runs it.
Also recorded in EXECUTION_PLAN.md SITE-12: nigig_common is confirmed absent
from this repository (no package by that name; RoomEvent/RoomInfo/
JoinedRoomSync/SyncFilter/PaginationDirection defined nowhere here; nigig-core
is an email/IMAP crate), and it re-exports nimanyatta-protocol via a
`protocol` module — the server writes
nigig_common::protocol::{ClientToServerMsg, ServerToClientMsg}. So of the 75
items the server imports from nigig_common, 9 are the crate already pinned
here; the remaining 66 are the HTTP DTO layer, ~11 of which /sync needs.
Verified: cargo test -p nimanyatta-protocol = 14 passed; cargo test -p
nigig-site-core = 228 passed / 1 ignored; clippy -D warnings clean;
cargo fmt --check clean; cargo metadata exit 0; all 6 workflow python gates.
- math::triangulate_polygon now ear-clips via polygon::triangulate_indices
(index/orientation preserving; rejects degenerate, non-finite, bow-tie)
- extrude_polygon_mesh routes through triangulate_profile; new
try_extrude_polygon_mesh returns structured errors instead of
underflowing on undersized profiles
- tests: concave area equality, closed-manifold edge twins, bad corpus
- cad.yml gate forbids reintroducing the vertex-zero fan
- EXECUTION_PLAN.md: tranche ledger, verification, release gates
First concrete step on SITE-12. Three of the four planned steps turned out to
be impossible from this repository, and that is now recorded with evidence
rather than assumed.
What this commit does:
1. `nimanyatta/crates/nimanyatta-protocol` is a workspace member. It has no
path dependencies (only optional serde + postcard), so unlike its parent
package it can be compiled and tested here. `cargo metadata` still exits 0.
2. Its 8 pre-existing tests HAD NEVER COMPILED. The crate is `#![no_std]`, so
`use super::*` brings in alloc's `String` but not the `ToString` trait, and
the test module calls `.to_string()` — 6 E0599 errors. Fixed with one
import; 8 tests now run and pass.
3. Added 5 wire-format characterization tests pinning the exact postcard
bytes. postcard encodes enum variants as POSITIONAL INDICES, so reordering
or inserting a variant silently changes every frame on the wire while the
code still compiles and every round-trip test still passes. Verified by
mutation: swapping AuthMethod::Password/Token changes the login frame from
[0, 0, 5, ...] to [0, 1, 5, ...] and the pinned test fails.
4. Corrected a false doc comment. `sync_protocol.rs` claimed "one canonical
encoding is used by app, server, and tests". It is not. Read from source:
the server's POST /sync uses Json<SyncRequest>/Json<SyncResponse> whose
types come from `nigig_common` (not from nimanyatta-protocol, which is a
WebSocket chat protocol the server uses only for `is_guest`), and
sync_protocol.rs is a third, separate offline-envelope codec.
What could not be done, with evidence:
- `nimanyatta` itself cannot be a workspace member: adding it makes
`cargo metadata` fail with exit 101 "failed to load manifest for workspace
member", because Cargo resolves path dependencies even for features that are
not enabled, and ../nigig-lite/crates/common and ../xitca-web/web do not
exist. Verified by adding it, observing 101, and removing it.
- Neither sibling repo can be vendored or submoduled: `git ls-remote` on
gitdab.com/andodeki/nigig-lite and /xitca-web both return HTTP 500,
identical to a deliberately fake repo name, while /nigig-org resolves
normally.
- Repointing xitca-web to crates.io would not unblock the build on its own:
`nigig_common` is imported by 38 server files (~40 items) and has no
published equivalent. Guessing RoomInfo/RoomEvent field definitions would
produce a codec that silently mismatches the real server, which is worse
than the current explicit "three incompatible protocols" state.
rustfmt was applied to the protocol crate (120 insertions / 29 deletions, all
whitespace: it had never been formatted, including a stray indent on
ServerToClientMsg::login_success). No semantic change.
Verified: cargo test -p nimanyatta-protocol = 13 passed; cargo test -p
nigig-site-core = 228 passed / 1 ignored; clippy -D warnings clean on both;
cargo fmt --check clean; cargo check -p nigig-site --locked clean; cargo
metadata exit 0; all 6 workflow python gates pass.
Six new UI-free modules in nigig-site-core, 53 tests added (175 -> 228):
- procurement_flow.rs (SITE-26): requisition -> LPO -> delivery, two-approver
gate (three above KES 500,000), budget commitment at approval, Delivered
reachable only via verify_delivery with evidence assets, supplier ratings
that are None until evidence exists. Integer cents with i128 saturating
multiplication; no f64 money anywhere.
- meeting_flow.rs (SITE-27): RSVP, per-participant recording consent where a
refusal cannot be flipped to a grant, transcript refused until a recording
has started, action-item tracker.
- dashboards.rs (SITE-29): integer chart series, per-site snapshots, worst-
first cross-site comparison, and a client digest built from an explicit
field allow-list that fails closed on a smuggled key.
- documents.rs (SITE-30): revision states where approval supersedes its
predecessor and withdrawal clears `current` in the same operation, so a
withdrawn drawing can never be served as current.
- notification_rules.rs (SITE-18): delivery modes, midnight-crossing quiet
hours in the site zone, safety-critical subjects that cannot be muted and
bypass suppression, bodies generated from a closed vocabulary so no PII can
enter one.
- multi_site.rs (SITE-19): per-site roles, a switcher that refuses unknown or
unregistered sites rather than falling back, sessions that authorize only
their own site, and an offline queue that drains per site.
Two API gaps fixed while wiring these up:
- report_pack::Report::canonical_text is now public; it was private, so no
caller outside report_pack could produce a valid approval signature.
- hse::HseStats now derives Serialize/Deserialize; FR-1.7 embeds it in the
published monthly report but it could not be serialized.
EXECUTION_PLAN.md: §1a rows for these six tranches updated to their real
state, the honest completion statement recomputed (28/33 with tested domain
contracts, 4 externally blocked, 1 not started), and §2.1 corrected - it
still repeated the false "nimanyatta is only a README stub" claim that §1a
was already corrected for.
Verified: cargo test -p nigig-site-core = 228 passed / 0 failed / 1 ignored;
cargo clippy --all-targets --no-deps -D warnings clean; cargo fmt --check
clean; cargo check -p nigig-site clean; all 6 workflow python gates pass.
cargo test -p nigig-site remains unrunnable here (SIGKILL compiling
makepad-widgets on 2 vCPU / 1.9 GiB). CI workflow work deferred by request.
Two claims in the 2026-09-26 ledger were wrong and are corrected here.
Wrong: "no nimanyatta server source in the repository (only a README stub)".
That is true only of crates/nimanyatta/, which really is 2 files. The root
nimanyatta/ directory holds a substantial server: 31,458 lines across 78 files,
with routes/{auth,roles,rooms,sms_auth,sync,tenants,realtime} and a POST /sync
handler taking SyncRequest/SyncResponse under an authenticated tenant context.
Right, and now stated precisely: it is still not wired to nigig-site and cannot
be built from this repository as-is. It is not a workspace member (the root
manifest names only crates/nimanyatta), and nimanyatta/Cargo.toml depends on
../nigig-lite/crates/common and ../xitca-web/web, sibling directories that do
not exist here (0 files each).
Wrong by omission: SITE-08 said the gap was "no server and no second
implementation". There are in fact three mutually incompatible protocol
definitions -- the server's JSON SyncRequest/SyncResponse, the postcard-based
nimanyatta-protocol (1,531 lines), and this crate's sync_protocol.rs
SyncEnvelope. Neither site manifest mentions nimanyatta at all, so the exit
criterion "app/server/tests use one protocol crate" is unmet rather than
merely pending. The E2E fixture also expects Matrix-style
joined_rooms/timeline/content.Text.body, which is not the server's shape.
Verified 2026-09-26 against origin/feature/daily-reports at 0819e97.
Thirteen hardening modules (3,079 lines) sat in nigig-site/src/ declared in no
mod statement. They had never been compiled or tested: cargo check never saw
them, and the plan's "implemented in the worktree and pending verification"
status was unverifiable by construction.
Extract the UI-free core into crates/apps/nigig-site-core (scope 5's "Rust core
crate ... testable; safe"; plan 6's target architecture). nigig-site depends on
it and re-exports it, so there is one copy of each module source: the app and
the core's unit tests compile the same files. The core has no Makepad
dependency, so its contracts run on a memory-limited runner.
Compiling that code for the first time found four real defects, all fixed:
- Role/Capability lacked Ord, so every BTreeSet of them failed to compile
(auth.rs)
- CaptureResult::empty never initialised site_id from its site parameter
(ocr_policy.rs)
- negotiate_version(2, 5) agreed on a protocol the peer never offered, which is
the silent downgrade the function exists to prevent (sync_protocol.rs)
- an absurd frame size returned Overflow instead of the actionable budget
breach (media_bounds.rs)
New feature-tranche domain modules, each with unit tests:
- organisation.rs SITE-20 invites, per-site roles, the 4.2 matrix as testable
data, site registry with geofence, settings
- report_pack.rs SITE-21 report numbering, entry status, signatures binding
actor/device/timestamp/document hash, lock and versioning,
multi-site compilation, monthly packs
- site_diary.rs SITE-22 weather with provenance, plant, deliveries, delay
log where a weather delay needs supporting rainfall,
visitors, manpower by trade
- workforce.rs SITE-23 consent-gated registration, tag-only blocklist,
attendance with overtime, QR badges, payroll CSV that never
emits identity, offboarding tombstones
- programme.rs SITE-25 dependencies with cycle detection and rollback,
topological order, critical path, frozen baselines with
slippage, checklists gating approval, snags, RFIs,
variations needing two distinct approvers
- hse.rs SITE-28 append-only incidents, closure requires corrective
action, toolbox talks, inspections, monthly statistics
workflows.rs gains the FR-1.14 Locked state; commands.rs gains a bounded
non-empty text validator shared by the new modules.
CI: the SITE-02 crypto/repository/store lanes pointed at -p nigig-site, where
those suites no longer live; left alone they would have compiled nothing and
reported a vacuous green. Repointed at the core, and added core-contracts and
core-clippy lanes with a ">=100 tests collected" check so a lane cannot pass
vacuously. All six existing Python contract gates still pass.
Auto-purge of worker ID data refuses to run until the scope 18 retention
question is answered rather than inventing a window.
Verified 2026-09-26: cargo test -p nigig-site-core --locked = 175 passed,
0 failed, 1 ignored (needs a live Secret Service session); cargo clippy -p
nigig-site-core --all-targets --no-deps -- -D warnings clean; cargo check -p
nigig-site clean. cargo test -p nigig-site is still killed by SIGKILL compiling
makepad-widgets on a 2 GB host, as recorded in plan 2.1.
EXECUTION_PLAN.md gains a per-tranche status ledger (1a) that states plainly
which tranches are done, domain-only, externally blocked, or not started, and
records that this branch and main diverged at b3a9005 with SITE-03 published
only on main.
UI-09: BVH rewritten over a stable primitive-index permutation with
explicit child indices, structural validator, and differential tests
(the old tree walked prims directly and assumed adjacent children).
UI-07: AI worker constructs the selected backend (local fails closed
without a loopback/https endpoint instead of sending prompts to
Claude); streaming is preview-only (never replaces the editor
mid-stream); stale post-cancel events discarded; AI-sized responses
checked against the script ceiling before apply.
UI-06: script source ceiling enforced before VM creation; output
triangle ceiling before cache/export.
UI-12: export dispatch bounded (1 active + 2 queued, explicit reject).
UI-13: GLB preserves hierarchy/scale/visibility/units; PDF uses real
CSG mesh bounds (never fabricated 1x1) and validated grid spacing; SVG
fits viewBox to bounds with validated bounded grids.
Also: corrected PDF CSG test to real bounds, tightened the disabled-copy
test to success markers, FileLock Debug for green lib-test compile.
project_repo.rs/capabilities.rs ride along concurrent fmt/test fixes in
the same files.
CORE-11: parsed scheme/host identity replaces string-prefix trust;
loopback is exactly localhost/127/8/::1; plaintext http only for
loopback; lookalikes, user-info, bad ports, fragments rejected.
CORE-12: resource_limits, format_interop (independent parsers), and
migration_corpus integration targets.
Hygiene for green gates under the pinned toolchain: unused-import
cleanup, derivable Default impls, needless-range/manual-contains
fixes, type aliases, fixture-literal allows (no numeric changes).
CORE-06: DocumentEdit/ScenePatch transactions against base revisions;
failed patches leave byte-identical state, stale bases rejected, undo
via inverse patches, refuse/cascade deletion policy.
CORE-07: one canonical bounded world-mesh stream (hierarchy, full
transforms, inherited visibility, named unit conversion, triangle
budgets, chunked cancellation with no partial success).
CORE-01: CadError with kind/entity-path, ValidationPolicy/GeometryBudget
hard ceilings enforced before allocation, opaque typed ids with checked
supply, explicit remap tables; missing material/layer refs are hard
errors, never silent fallbacks.
CORE-02: identity transform is translation 0/rotation 0/scale 1
(Default no longer collapses to zero scale); single local/world matrix
convention T*Rz*Ry*Rx*S with iterative validation (duplicates, dangling,
cycles, depth 1024) and inherited visibility.
CORE-03: versioned lossless CadDocument with tagged EntityKind, explicit
units, deterministic canonical bytes, future-version quarantine, and
explicit legacy migration warnings.
Verified in worktree at 64ae8d7: cad-core 319 tests green, clippy/fmt clean.
Contain production capabilities, remove the production sync surface, and keep legacy media/export/transport implementations test-only.
Require authenticated existing-key storage with preservation-first recovery and sticky write disablement, backed by deterministic fault and concurrency tests plus dependency and workflow contracts.