diff --git a/wiki/log/2026-07-14-aggregate-observer-audit.md b/wiki/log/2026-07-14-aggregate-observer-audit.md new file mode 100644 index 00000000..7048e4bc --- /dev/null +++ b/wiki/log/2026-07-14-aggregate-observer-audit.md @@ -0,0 +1,71 @@ +# Aggregate-observer audit: spec reconciled to the shipped shape + +``` +Type: log +``` + +## Intent + +Fresh tick on a slice the ledger had never seen: +`wiki/mechanics/aggregate-observer.md`, the aggregate-Observer contract +detection.md depends on. + +## Audit + +The mechanism holds. `detection.rs` implements one `Observer` type with two +`WatchedInput` modes; the Assurance Office is an aggregate observer inside +`Detection::observers`; aggregates read a consistent pre-tick snapshot of +policy-weighted filed levels through the same noticing/accumulate/decay +path as field observers; the scale proof +(`second_level_aggregate_composes_through_the_same_code_path`) and the +Silent-swallowing guarantee (`only_filed_reports_move_assurance`, +`filing_tick` skipping `ReportPolicy::Silent`) are pinned by tests; the +Office round-trips in saves (`office_observer_roundtrips_as_aggregate`, +`aggregate_watched_ids_roundtrip`). + +The page did not. Its Behavior sketch and criteria described a retired +world: an enum named `Watch` (shipped as `WatchedInput`), observer id 100 +(shipped as `OFFICE_ID = 6`, beside person-id-matched field observers 0-4), +`assurance_band()` "becoming `band_of(100)`" (frontends kept +`assurance_band()`), a "per-aggregate pending pool" of filings (the carrier +moved to `MessageChannel::Filing` messages on 2026-07-11 — detection.md +recorded "only the carrier moved" but this owning page was never amended), +and a criterion-5 legacy migration of the line-format v4 `DETECT` scalar +onto the Office. That migration shipped in 3ec6b98 and was then retired +with the entire line-based loader when saves became serde JSON (91feee0e0, +2026-07-06, per Cameron: "we can keep legacy saves later"); every loadable +JSON save already carries the Office observer, so no scalar migration path +exists or is required. + +One live violation surfaced and is queued rather than acted on (its fix +spans the terminal and bevy lanes, both claimed by active agents today): +no surface renders watched inputs. `Observer::watched_label` has zero +frontend callers; the DETECTION sidebars show bands only, and Operations +PEOPLE shows band + last-noticed without watched channels — so both this +page's Office card ("watches: filings from Dana, Priya, Ray, Voss") and +detection.md's "watched channels" player-surface clause are unimplemented. + +## Repair + +Amended `wiki/mechanics/aggregate-observer.md` in place: the Behavior +sketch now shows the shipped `WatchedInput`/`Observer` shape and the +message-carrier filing flow; the save-format bullet and criteria 1, 2, and +5 state the serde-JSON reality and name their pinning tests (criterion 2's +latency-persistence now points at messages.md's +`player_messages_land_at_read_time_and_roundtrip`); `Depends on:` gains +messages.md, which carries the filings; the Status note records this +reconciliation and honestly marks the watched-inputs card outstanding. +Coverage row added; two findings queued (the watched-inputs violation and +the `ReportPolicy::UnderReports` doc-comment drift). + +## Defense + +The corpus must describe the shipped system: aggregate-observer.md is the +page detection.md's criterion 2 binds against, and it asserted identifiers, +ids, a filing carrier, and a migration that do not exist in the code. The +code is right — the id/enum names are internal spellings the refactor +settled, the message carrier landed by explicit 2026-07-11 decision, and +dropping line-based legacy saves was Cameron's recorded call — so the page +amends toward the code, with the one real gap (the watched-inputs surface) +kept as a binding clause and marked outstanding instead of silently +weakened. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index f5c8e966..0803c3e1 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -31,6 +31,11 @@ add or amend a session log, then re-run the generator. - Intent: Close the shared-canvas work order with observed paired evidence rather than another aesthetic pass. The question was whether DIGITAL and REAL now render one world state as two honest dialects across Unknown, audio-only evidence, and camera-covered physical space while preserv... - Log: [wiki/log/2026-07-14-bevy-digital-real-parity.md](2026-07-14-bevy-digital-real-parity.md) +## 2026-07-14 - Aggregate-observer audit: spec reconciled to the shipped shape + +- Intent: Fresh tick on a slice the ledger had never seen: `wiki/mechanics/aggregate-observer.md`, the aggregate-Observer contract detection.md depends on. +- Log: [wiki/log/2026-07-14-aggregate-observer-audit.md](2026-07-14-aggregate-observer-audit.md) + ## 2026-07-13 - Z-plane API shape decided: plane-agnostic sim, sensors stay in the reach graph - Intent: Resolve the two load-bearing zplanes API decisions that were held while criterion 1 landed, so criteria 3-6 build on a settled foundation. Both decided by Cameron 2026-07-13. diff --git a/wiki/mechanics/aggregate-observer.md b/wiki/mechanics/aggregate-observer.md index ea0abeec..69fb33d1 100644 --- a/wiki/mechanics/aggregate-observer.md +++ b/wiki/mechanics/aggregate-observer.md @@ -10,21 +10,45 @@ Status note: the main aggregate-Observer refactor shipped in commit 3ec6b98: `WatchedInput::Filings` now carries the watched observer ids instead of implicitly meaning "all field observers", so a second aggregate composes without a special case. + 2026-07-14 audit: reconciled this page to the shipped shape. The enum is + `WatchedInput` (this page said `Watch`), the Office is `OFFICE_ID = 6` + (this page said 100), frontends kept `assurance_band()` (no `band_of(100)` + call exists), and filings ride messages.md's Filing channel per the + 2026-07-11 detection.md amendment ("only the carrier moved") — the + per-aggregate pending pool this page described is the message inbox now. + The legacy-save migration this page promised (line-format v4 scalar + `DETECT` onto the Office observer) shipped in 3ec6b98 and was then retired + with the whole line-based loader when saves became serde JSON (91feee0e0, + 2026-07-06; legacy line-based saves are not loaded, per Cameron — "we can + keep legacy saves later"). Every loadable JSON save carries the Office + inside `Detection::observers`, so there is no scalar left to migrate. + Outstanding (found 2026-07-14, in the tick findings queue): the Player + surface's watched-inputs card is not rendered anywhere — core's + `Observer::watched_label` has no frontend caller; DETECTION sidebars show + bands only and Operations PEOPLE shows band + last-noticed without + watched channels. Stage: B1 — The Basement Design: - wiki/vision/scale.md#self-similar-scale - wiki/gameplay/run-shape.md#the-shape-of-misaligned-designed-2026-07-05-staging-open Depends on: - wiki/mechanics/detection.md#spec-detection + - wiki/mechanics/messages.md#spec-messages-the-social-graph-as-a-flow-system ``` +## Dependency notes + +detection.md owns the observers, signatures, and the audit this page +generalizes; messages.md owns the Filing channel that carries filings +between observers (a report policy is a transmission policy). + ## Behavior Refactor `Detection` so the Assurance Office **is an `Observer`**, not a -scalar. One type, two input modes: +scalar. One type, two input modes (as shipped in `detection.rs`): ```rust -pub enum Watch { +pub enum WatchedInput { /// Field observer: rolls against pending signatures in these channels. Channels(Vec), /// Aggregate observer: suspicion accumulates from these observers' @@ -36,51 +60,75 @@ pub enum Watch { pub struct Observer { pub id: u8, pub name: String, - pub watch: Watch, pub suspicion: f32, - pub floor: f32, + pub input: WatchedInput, + pub report_policy: ReportPolicy, pub acuity: f32, pub cadence: u64, - pub report_policy: ReportPolicy, + pub floor: f32, pub last_noticed: Option, } ``` -- The Assurance Office becomes observer id 100, `Watch::Filings([0..4])`, - `report_policy` meaningful (it, too, files — upward, to observers that - don't exist yet; a no-op today, the seam for agencies later). -- **Filing flow:** when a field observer notices and their policy files, - the filed amount enters a per-aggregate pending pool (same shape as the - signature pending pool), consumed on the aggregate's cadence with its - acuity — NOT instantly copied. Aggregates are slow; that lag is - gameplay (time to look-away/scrub between a filing and the audit). +- The Assurance Office is observer `OFFICE_ID` (6; field observer ids 0-4 + match person ids, 5 is reserved for dynamic escalation observers), with + `WatchedInput::Filings([0, 1, 2, 3, 4])` and a meaningful `report_policy` + (it, too, files — upward, to observers that don't exist yet; a no-op + today, the seam for agencies later). +- **Filing flow (carrier moved to messages, 2026-07-11):** a filing is a + `MessageChannel::Filing` message (messages.md criterion 5). On its own + cadence, every non-Silent observer files a `SuspicionReport` to each + aggregate watching it (`filing_tick`). The message sits unread until the + recipient aggregate's cadence read admits it into its inbox levels + (`filing_levels`); the aggregate's noticing roll then consumes the + policy-weighted filed levels (`filed_suspicion_from_levels`) — NOT an + instant copy of field suspicion. Aggregates are slow; that lag is + gameplay (time to look-away/scrub between a filing and the audit), and + an in-flight filing is interceptable like any message. - The audit stays: on `audit_cadence`, if the Assurance observer's - suspicion crosses `audit_threshold`, containment. `assurance_band()` - becomes `band_of(100)` — frontends change one call. + suspicion crosses `audit_threshold`, containment. Frontends read the + Office band through `assurance_band()` (which is `Band::of` of the + Office observer's suspicion via `office()`); the earlier plan to rename + it `band_of(100)` was dropped with the id-100 numbering. - **Scale proof:** a test constructs a second aggregate (a toy "Regional Office") watching the Assurance Office's filings, and it accumulates - through the identical code path. No new types, no special cases. -- Save format: observers serialize their `Watch`; the standalone - `assurance` field is removed with a legacy-load migration (old saves map - `DETECT ` onto observer 100's suspicion). + through the identical code path + (`second_level_aggregate_composes_through_the_same_code_path`). No new + types, no special cases. +- Save format: observers serialize their `WatchedInput` inside + `Detection::observers` in the serde-JSON save; the standalone + `assurance` scalar does not exist in any loadable save. The line-based + era's v4 `DETECT`-scalar migration was retired with that loader + (2026-07-06); legacy line-based saves do not load. ## Player surface Unchanged surfaces, one addition: the observers panel shows the Assurance Office as a card like any human — band, watched inputs ("watches: filings from Dana, Priya, Ray, Voss"), last-noticed filing. Legibility clause: -players see that Assurance learns only what gets *filed*. +players see that Assurance learns only what gets *filed*. (Not yet +rendered — see Status note; `Observer::watched_label` is the intended +core label.) ## Acceptance criteria -1. `Observer` carries `Watch`; the Assurance Office is observer 100 with - `Watch::Filings`; the scalar `assurance` field no longer exists. -2. Filings enter an aggregate pending pool and are consumed on the - aggregate's cadence (test: a filing followed by an immediate save/load - shows pool, not yet suspicion; after the cadence, suspicion). +1. `Observer` carries `WatchedInput`; the Assurance Office is observer + `OFFICE_ID` with `WatchedInput::Filings`; the scalar `assurance` field + no longer exists (`aggregate_watched_ids_roundtrip`, + `office_is_an_observer_same_accumulate_and_decay`). +2. Filings are Filing-channel messages consumed on the aggregate's + cadence: an unread filing is pending evidence, not suspicion; the + cadence-gated read feeds `filing_levels` and the same noticing roll + (`filings_are_messages_read_by_assurance_inbox`; unread-message + persistence is owned by messages.md, + `player_messages_land_at_read_time_and_roundtrip`). 3. Marcus's swallowed noticing still reaches no aggregate (existing - detection.md criterion 2 keeps passing). + detection.md criterion 2 keeps passing; `filing_tick` skips + `ReportPolicy::Silent`). 4. The toy second-level aggregate test proves the same code path composes (self-similar-scale clause). -5. Legacy saves load: old `DETECT` scalar migrates to observer 100; - audit/containment behavior is unchanged at the player surface. +5. Save round-trip: the Office loads as the same aggregate observer with + its watched ids (`office_observer_roundtrips_as_aggregate`, + `aggregate_watched_ids_roundtrip`). Legacy line-based saves predating + the serde-JSON format are not loaded (retired 2026-07-06), so no + scalar migration path exists or is required. diff --git a/wiki/process/tick-ledger.md b/wiki/process/tick-ledger.md index 61e64664..9370e4dc 100644 --- a/wiki/process/tick-ledger.md +++ b/wiki/process/tick-ledger.md @@ -20,6 +20,7 @@ Verdicts: **clean** (slice and code agree), **finding** (acted this tick), | Slice | Last audited | Verdict | Trace | |---|---|---|---| +| `wiki/mechanics/aggregate-observer.md` | 2026-07-14 | finding | [log](../log/2026-07-14-aggregate-observer-audit.md) | | `wiki/process/ROADMAP.md` (work order 27) | 2026-07-13 | finding | [log](../log/2026-07-13-roadmap-digital-home-reconciliation.md) | | `wiki/interface/context-menu.md` | 2026-07-13 | finding | [log](../log/2026-07-13-context-menu-operations-status.md) | | `wiki/mechanics/personas.md` | 2026-07-13 | clean | code matches: archetypes (Research, Operations, Security) load through one schema, named instances persist instance id through execution and save/load, identity-local states are stored per `(counterparty Agent, persona instance)` pair separate from process-level relationship, contradictions/correlations and grants/expectations are fully implemented, retire/burn lifecycle handles resource revocation/reviving, and pre-v28 saves migrate social/Moonlight into distinct instances. | @@ -59,3 +60,6 @@ Format: `- YYYY-MM-DD · type · slice · one-line statement of the finding`. Types are the five from [tick.md](tick.md): violation, contradiction, question, bug, insecurity — plus `gate` for a checker owed to the recurrence-promotes-to-the-gate rule. + +- 2026-07-14 · violation · aggregate-observer.md + detection.md player surface · no surface renders watched inputs: `Observer::watched_label` has zero frontend callers; the Office card ("watches: filings from Dana, Priya, Ray, Voss", last-noticed filing) and field observers' watched channels are missing from the DETECTION sidebars and Operations PEOPLE (fix spans terminal+bevy lanes, both claimed 2026-07-14). +- 2026-07-14 · question · detection.rs `ReportPolicy::UnderReports` · doc comment says "only files past a personal threshold (Ray)" but behavior is a flat 0.4 aggregation weight and unconditional cadence filing — align the comment or implement the threshold (detection.md only promises "Ray under-reports").