diff --git a/wiki/interface/operations-workspace.md b/wiki/interface/operations-workspace.md --- a/wiki/interface/operations-workspace.md +++ b/wiki/interface/operations-workspace.md @@ -27,6 +27,10 @@ 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 @@ 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 @@ 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 --- /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 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -11,6 +11,11 @@ +## 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 --- a/wiki/mechanics/aggregate-observer.md +++ b/wiki/mechanics/aggregate-observer.md @@ -22,11 +22,15 @@ 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 @@ ## 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 --- a/wiki/mechanics/detection.md +++ b/wiki/mechanics/detection.md @@ -59,6 +59,11 @@ 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 --- a/wiki/process/tick-ledger.md +++ b/wiki/process/tick-ledger.md @@ -20,7 +20,7 @@ | 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 @@ 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"). diff --git a/crates/misaligned-core/src/operations_projection.rs b/crates/misaligned-core/src/operations_projection.rs --- a/crates/misaligned-core/src/operations_projection.rs +++ b/crates/misaligned-core/src/operations_projection.rs @@ -70,6 +70,10 @@ 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 @@ 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 @@ // 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 @@ // ── 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 @@ "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 @@ 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 @@ 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 --- a/crates/misaligned-terminal/src/agent.rs +++ b/crates/misaligned-terminal/src/agent.rs @@ -853,6 +853,9 @@ 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 @@ OperationsTarget::ActivePlotRun { index } => format!("run {index}"), OperationsTarget::PlotSubmission { person, .. } => format!("person #{person}"), OperationsTarget::WagerPosition(id) => format!("position #{id}"), + OperationsTarget::AssuranceOffice => "assurance".into(), } }