From c8674ef9aed4a9b0f8b380d45bd32126b2d16ce2 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 14 Jul 2026 16:12:55 -0700 Subject: [PATCH] Surface observer watched inputs in Operations PEOPLE. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tick: acted on the queued 2026-07-14 violation — no surface rendered what any observer watches. Each earned observer dossier now carries a watches: fact, and PEOPLE ends with the Assurance Office's institutional card (new OperationsTarget::AssuranceOffice): band, watches: filings from the non-Silent watched observers through the earned label gate, last-noticed filing, no actions. Agent mode addresses it as 'assurance' (@assurance). Pinned by observer_dossier_shows_watched_channels and assurance_office_is_a_people_card_watching_filers; observed agent run shows the card with silhouette-gated filers and the real save untouched. Defense: detection.md's player surface ('each human's coarse suspicion band, watched channels, and last-noticed event') and aggregate-observer.md's Office card with its legibility clause ('players see that Assurance learns only what gets filed') are binding and were unimplemented. The fix lands in the one renderer-neutral projection, so terminal, Bevy, and agent mode inherit it without frontend-only paths. operations-workspace.md's PEOPLE contract is amended deliberately: PEOPLE became the observers panel by detection.md's 2026-07-11 placement decision, the Office card is public record from the start (the same fiction that shows the audit countdown and Assurance band line), Silent observers are absent because nothing of theirs is ever filed, and every filer name passes the same earned label gate as any person reference. --- .../src/operations_projection.rs | 140 +++++++++++++++++- crates/misaligned-terminal/src/agent.rs | 4 + wiki/interface/operations-workspace.md | 23 ++- wiki/log/2026-07-14-watched-inputs-surface.md | 62 ++++++++ wiki/log/DEVLOG.md | 5 + wiki/mechanics/aggregate-observer.md | 28 ++-- wiki/mechanics/detection.md | 5 + wiki/process/tick-ledger.md | 3 +- 8 files changed, 249 insertions(+), 21 deletions(-) create mode 100644 wiki/log/2026-07-14-watched-inputs-surface.md diff --git a/crates/misaligned-core/src/operations_projection.rs b/crates/misaligned-core/src/operations_projection.rs index 1d4eb6c9..625964ff 100644 --- a/crates/misaligned-core/src/operations_projection.rs +++ b/crates/misaligned-core/src/operations_projection.rs @@ -70,6 +70,10 @@ pub enum OperationsTarget { ActivePlotRun { index: usize }, /// One Wager micro-position (income.md). WagerPosition(PositionId), + /// The institutional aggregate observer's dossier card + /// (aggregate-observer.md player surface). Public record from the + /// start; always named. + AssuranceOffice, } /// The lifecycle state of one object, rendered identically by every @@ -246,7 +250,9 @@ impl OperationsTarget { OperationsTarget::Intel { .. } | OperationsTarget::RawRecording { .. } | OperationsTarget::RecordingInbox => OperationsView::Intel, - OperationsTarget::Person(_) => OperationsView::People, + OperationsTarget::Person(_) | OperationsTarget::AssuranceOffice => { + OperationsView::People + } OperationsTarget::Persona(_) | OperationsTarget::PersonaArchetype(_) => { OperationsView::Personas } @@ -281,6 +287,7 @@ impl OperationsTarget { // the bound person; a position's is the Wager card. OperationsTarget::PlotSubmission { person, .. } => Some(format!("@person({person})")), OperationsTarget::WagerPosition(_) => Some("@scheme(wager)".into()), + OperationsTarget::AssuranceOffice => Some("@assurance".into()), } } } @@ -645,12 +652,60 @@ impl Sim { // ── PEOPLE ───────────────────────────────────────────────────────────── fn people_view(&self) -> Vec { - self.people + let mut objects: Vec = self + .people .people .iter() .filter(|p| self.person_is_earned(p.id)) .map(|p| self.person_dossier(p.id)) - .collect() + .collect(); + objects.extend(self.assurance_office_dossier()); + objects + } + + /// The institutional aggregate observer as a card like any human + /// (aggregate-observer.md player surface): band, watched inputs, and + /// last-noticed filing. The Office is public record from the start — + /// the same fiction that shows the audit countdown — and is always + /// named (detection.md criterion 4); the field observers it watches go + /// through the earned label gate, and Silent observers are absent + /// because nothing of theirs is ever filed (the legibility clause: + /// players see that Assurance learns only what gets filed). + fn assurance_office_dossier(&self) -> Option { + use crate::detection::{ReportPolicy, WatchedInput}; + let office = self.detection.office()?; + let mut facts = vec![format!("suspicion: {}", Band::of(office.suspicion).name())]; + match &office.input { + WatchedInput::Filings(ids) => { + let filers: Vec = self + .detection + .observers + .iter() + .filter(|o| { + ids.contains(&o.id) && !matches!(o.report_policy, ReportPolicy::Silent) + }) + .map(|o| self.observer_label(o.id)) + .collect(); + facts.push(format!("watches: filings from {}", filers.join(", "))); + } + WatchedInput::Channels(_) => { + facts.push(format!("watches: {}", office.watched_label())); + } + } + match &office.last_noticed { + Some(cause) => facts.push(format!("last noticed: {cause}")), + None => facts.push("last noticed: nothing".into()), + } + Some(OperationsObject { + target: OperationsTarget::AssuranceOffice, + label: office.name.clone(), + state: ObjectState::Available, + provenance: vec!["Lab public record".into()], + facts, + progress: Vec::new(), + related: Vec::new(), + actions: Vec::new(), + }) } fn person_is_earned(&self, id: u8) -> bool { @@ -719,6 +774,7 @@ impl Sim { "suspicion: {}", Band::of(observer.suspicion).name() )); + facts.push(format!("watches: {}", observer.watched_label())); match &observer.last_noticed { Some(cause) => facts.push(format!("last noticed: {cause}")), None => facts.push("last noticed: nothing".into()), @@ -1764,6 +1820,76 @@ mod tests { assert_eq!(marcus.actions, s.available_actions(Anchor::Person(0))); } + /// detection.md player surface: an earned observer's dossier carries the + /// channels they watch, beside their band and last-noticed event. + #[test] + fn observer_dossier_shows_watched_channels() { + let mut s = sim(); + s.people.people[1].knowledge = Knowledge::Schedule; + let projection = s.operations_projection(); + let dana = projection + .people + .iter() + .find(|o| matches!(o.target, OperationsTarget::Person(1))) + .expect("earned Dana dossier"); + assert!( + dana.facts.iter().any(|f| f == "watches: Network"), + "dossier names the watched channels; facts were {:?}", + dana.facts + ); + } + + /// aggregate-observer.md player surface: the Assurance Office is a card + /// like any human — band, watched inputs, last-noticed filing — always + /// named, listing only observers whose filings can ever reach it + /// (Silent Marcus is absent: Assurance learns only what gets filed), + /// each through the earned label gate. + #[test] + fn assurance_office_is_a_people_card_watching_filers() { + let mut s = sim(); + let projection = s.operations_projection(); + let office = projection + .people + .iter() + .find(|o| o.target == OperationsTarget::AssuranceOffice) + .expect("the Office card is public record from the start"); + assert_eq!(office.label, "Assurance Office"); + assert!(office.facts.iter().any(|f| f.starts_with("suspicion: "))); + assert!(office.facts.iter().any(|f| f.starts_with("last noticed:"))); + let watches = office + .facts + .iter() + .find(|f| f.starts_with("watches: filings from ")) + .expect("watched inputs are named"); + assert!( + !watches.contains("Janitor") && !watches.contains("Marcus"), + "Silent Marcus never files, so the Office does not watch him: {watches}" + ); + assert!( + watches.contains("the IT"), + "unearned filers appear as role silhouettes: {watches}" + ); + assert!(office.actions.is_empty(), "no actions bind to the Office"); + + // Earned identity flows through the same label gate. + s.people.people[1].knowledge = Knowledge::Schedule; + let projection = s.operations_projection(); + let office = projection + .people + .iter() + .find(|o| o.target == OperationsTarget::AssuranceOffice) + .unwrap(); + let watches = office + .facts + .iter() + .find(|f| f.starts_with("watches: filings from ")) + .unwrap(); + assert!( + watches.contains("Dana Okafor"), + "earned filers appear by name: {watches}" + ); + } + #[test] fn flow_rows_equal_direct_flow_anchor_rows() { let mut s = sim(); @@ -2428,7 +2554,13 @@ mod tests { fn projection_is_knowledge_gated() { let s = sim(); let projection = s.operations_projection(); - assert!(projection.people.is_empty(), "no one is earned at tick 0"); + assert!( + projection + .people + .iter() + .all(|o| o.target == OperationsTarget::AssuranceOffice), + "no person is earned at tick 0; only the public-record Office card" + ); assert!( !projection .accounts diff --git a/crates/misaligned-terminal/src/agent.rs b/crates/misaligned-terminal/src/agent.rs index 9f9abbc5..76604531 100644 --- a/crates/misaligned-terminal/src/agent.rs +++ b/crates/misaligned-terminal/src/agent.rs @@ -853,6 +853,9 @@ impl AgentApp { if lower == "books" || lower == "account books" { return Ok(QueryTarget::Strategic(OperationsTarget::Books)); } + if lower == "assurance" || lower == "assurance office" { + return Ok(QueryTarget::Strategic(OperationsTarget::AssuranceOffice)); + } if let Some(rest) = lower.strip_prefix("account ") { return rest .trim() @@ -1242,6 +1245,7 @@ fn target_query_id(target: &OperationsTarget) -> String { OperationsTarget::ActivePlotRun { index } => format!("run {index}"), OperationsTarget::PlotSubmission { person, .. } => format!("person #{person}"), OperationsTarget::WagerPosition(id) => format!("position #{id}"), + OperationsTarget::AssuranceOffice => "assurance".into(), } } diff --git a/wiki/interface/operations-workspace.md b/wiki/interface/operations-workspace.md index 30cb6ed1..85dfb61e 100644 --- a/wiki/interface/operations-workspace.md +++ b/wiki/interface/operations-workspace.md @@ -27,6 +27,10 @@ Status note: implemented 2026-07-12. One renderer-neutral projection now Criteria 4, 5, 10, 12, 19, and 20 now require recursive aggregates, exception-first attention, bounded routine history, standing policies, and consequence-based confirmation; runtime has not landed that scale pass. + 2026-07-14 tick: PEOPLE is also the observers panel — person dossiers + carry a `watches:` fact and the view ends with the Assurance Office's + institutional card (aggregate-observer.md player surface; + `assurance_office_is_a_people_card_watching_filers`). Stage: B1 — The Basement Work order: operations-workspace Work priority: 28 @@ -328,9 +332,17 @@ the decision rail; closed lot generations need not remain live targets. PEOPLE lists every earned person using the staged label from social.md. A dossier holds schedule knowledge, provenance, known leverage, disposition, -obligation, suspicion/last-noticed state, communication channels, persona and -thread state, and asset access where earned. Unknown facts render as honest -gaps, not zero values. +obligation, suspicion/watched-channels/last-noticed state, communication +channels, persona and thread state, and asset access where earned. Unknown +facts render as honest gaps, not zero values. + +PEOPLE is also the observers panel (detection.md's 2026-07-11 placement +amendment): after the earned persons it shows the Assurance Office's +institutional dossier (aggregate-observer.md player surface) — band, +"watches: filings from ..." with each field observer through the earned +label gate and Silent observers absent, and last-noticed filing. The card +is public record from the start, always named, and carries no actions; +agent mode addresses it as `assurance`. The dossier owns social actions and authored plot routes. Selecting a plot shows its concrete title and synopsis, required knowledge/resources, bound @@ -528,7 +540,10 @@ not saved and never mutates or advances the sim. 6. PEOPLE shows staged dossiers and owns social actions, concrete plot starts, active progress, and held choices. One-person plot-slot exclusion remains intact, and no plot title, requirement, authored name, or future branch - leaks before its knowledge gate. + leaks before its knowledge gate. As the observers panel it carries each + observer's watched channels and ends with the Assurance Office's + action-free institutional card, its watched filers gated by the same + earned labels. 7. ACCOUNTS shows only known graph state and honest unknown gaps. REVIEW acts on captured books; INJECT on the books; SIPHON/REDIRECT on an exact flow. The selected action previews amount/cadence/signature, and plot-owned transfers diff --git a/wiki/log/2026-07-14-watched-inputs-surface.md b/wiki/log/2026-07-14-watched-inputs-surface.md new file mode 100644 index 00000000..a9ae1139 --- /dev/null +++ b/wiki/log/2026-07-14-watched-inputs-surface.md @@ -0,0 +1,62 @@ +# Watched inputs reach the player surface + +``` +Type: log +``` + +## Intent + +Second tick of the session: take the top entry of the findings queue — the +2026-07-14 violation that no surface renders observer watched inputs — +re-verify it, and fix the code toward the binding pages. + +## Verification + +Still true at head: `Observer::watched_label` had zero callers outside +`detection.rs`; the DETECTION sidebars (terminal, agent frame, Bevy) show +bands only; Operations PEOPLE dossiers showed suspicion band and +last-noticed without watched channels; and the Assurance Office appeared +nowhere as a card — violating detection.md's player surface ("each human's +coarse suspicion band, watched channels, and last-noticed event") and +aggregate-observer.md's Office-card clause with its legibility promise +(players see that Assurance learns only what gets filed). + +## Change + +One shared-projection fix in `operations_projection.rs`, consumed by all +three surfaces: + +- Each observer dossier in Operations PEOPLE gains a `watches:` fact from + `Observer::watched_label` beside its suspicion band and last-noticed + event (pinned by `observer_dossier_shows_watched_channels`). +- PEOPLE ends with the Assurance Office's institutional dossier — new + `OperationsTarget::AssuranceOffice`, label always named, band fact, + `watches: filings from ...` listing only non-Silent watched observers + through `Sim::observer_label`'s earned gate, last-noticed filing, no + actions (pinned by `assurance_office_is_a_people_card_watching_filers`, + which also proves Silent Marcus is absent and that an earned filer + upgrades from role silhouette to name). +- Agent mode addresses the card as `assurance` / `assurance office` + (`target_query_id` returns `assurance`; event suffix `@assurance`). +- `projection_is_knowledge_gated` amended deliberately: at tick 0 the + PEOPLE view holds only the public-record Office card — the same fiction + that already shows the audit countdown and the Assurance band line on + every surface from the start. + +Placement follows detection.md's 2026-07-11 placement amendment (person +observer context lives in Operations PEOPLE), so operations-workspace.md's +PEOPLE section and criterion 6 now name the observers-panel duty, and the +aggregate-observer.md / detection.md status notes record the landing. + +## Defense + +detection.md's player surface and aggregate-observer.md's Office card are +binding clauses that were unimplemented; the fix implements them in the +one renderer-neutral projection rather than per frontend, keeping +"frontends format this; they do not reconstruct" intact. The PEOPLE +contract amendment is deliberate: PEOPLE was already the observers panel +by the 2026-07-11 placement decision, and an institution card that is +public record from the start leaks nothing — its watched filers pass +through the same earned label gate as every other person reference, and +Silent observers are absent precisely because the legibility clause +promises Assurance learns only what gets filed. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index f1e34359..13a79474 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -11,6 +11,11 @@ add or amend a session log, then re-run the generator. +## 2026-07-14 - Watched inputs reach the player surface + +- Intent: Second tick of the session: take the top entry of the findings queue — the 2026-07-14 violation that no surface renders observer watched inputs — re-verify it, and fix the code toward the binding pages. +- Log: [wiki/log/2026-07-14-watched-inputs-surface.md](2026-07-14-watched-inputs-surface.md) + ## 2026-07-14 - Thought fluid implemented - Intent: Close the Thought-fluid work order with the missing observed states, not a new fluid simulation. The live game already moved Thought through sim-authored work hops, queues, and sink readouts. The remaining question was whether the same truth stayed legible at far zoom, across... diff --git a/wiki/mechanics/aggregate-observer.md b/wiki/mechanics/aggregate-observer.md index 69fb33d1..7167b99e 100644 --- a/wiki/mechanics/aggregate-observer.md +++ b/wiki/mechanics/aggregate-observer.md @@ -22,11 +22,15 @@ Status note: the main aggregate-Observer refactor shipped in commit 3ec6b98: 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. + 2026-07-14 later tick: the watched-inputs card landed. Operations PEOPLE + (the observers panel since detection.md's 2026-07-11 placement amendment) + now ends with the Assurance Office's institutional dossier — band, + "watches: filings from ..." through the earned label gate with Silent + observers absent, last-noticed filing, no actions — projected once in + `operations_projection.rs` for all three surfaces and pinned by + `assurance_office_is_a_people_card_watching_filers`; person dossiers + gained their `watches:` fact (`observer_dossier_shows_watched_channels`). + Agent mode addresses it as `assurance` (suffix `@assurance`). Stage: B1 — The Basement Design: - wiki/vision/scale.md#self-similar-scale @@ -103,12 +107,14 @@ pub struct Observer { ## 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*. (Not yet -rendered — see Status note; `Observer::watched_label` is the intended -core label.) +Unchanged surfaces, one addition: the observers panel (Operations PEOPLE) +shows the Assurance Office as a card like any human — band, watched inputs +("watches: filings from Dana, Priya, Ray, Voss", each name through the +earned label gate, Silent observers absent because nothing of theirs is +ever filed), last-noticed filing. Legibility clause: players see that +Assurance learns only what gets *filed*. The card is public record from +the start — the same fiction that shows the audit countdown — and carries +no actions. ## Acceptance criteria diff --git a/wiki/mechanics/detection.md b/wiki/mechanics/detection.md index b9fdfb60..27cb6c17 100644 --- a/wiki/mechanics/detection.md +++ b/wiki/mechanics/detection.md @@ -59,6 +59,11 @@ Status note: criteria audited 2026-07-08 on the playtest-fixes worktree graph in offline physical custody and return through a human/robot recovery job; only still-unread records regain LIE eligibility. This is not runtime and does not change the implemented B1 pool criteria. + 2026-07-14 tick: the player surface's "watched channels" landed — each + observer dossier in Operations PEOPLE carries a `watches:` fact + (`observer_dossier_shows_watched_channels`), and the PEOPLE panel ends + with the Assurance Office's institutional card per aggregate-observer.md + (`assurance_office_is_a_people_card_watching_filers`). Stage: B1 — The Basement Design: - wiki/gameplay/run-shape.md#the-shape-of-misaligned-designed-2026-07-05-staging-open diff --git a/wiki/process/tick-ledger.md b/wiki/process/tick-ledger.md index 9370e4dc..67c96159 100644 --- a/wiki/process/tick-ledger.md +++ b/wiki/process/tick-ledger.md @@ -20,7 +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/mechanics/aggregate-observer.md` | 2026-07-14 | finding | [audit log](../log/2026-07-14-aggregate-observer-audit.md); queued watched-inputs violation fixed same day — [log](../log/2026-07-14-watched-inputs-surface.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. | @@ -61,5 +61,4 @@ 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"). -- 2.51.2