From 88f4b8c17a11193341a671b756e8a4d718de5923 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 13 Jul 2026 18:54:51 -0700 Subject: [PATCH] Return persona creation to PERSONAS. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the pre-v28 PEOPLE and direct-agent escape hatches so every identity is created through an explicit institutional archetype. Pin the boundary in shared projections, tests, and binding specs. 👾 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code --- crates/misaligned-bevy/src/main.rs | 4 +- crates/misaligned-core/src/actions.rs | 80 ++++++++++++------- crates/misaligned-terminal/src/agent.rs | 28 ++++--- wiki/interface/action-vocabulary.md | 15 ++-- wiki/interface/operations-workspace.md | 10 ++- .../2026-07-13-persona-creation-surface.md | 53 ++++++++++++ wiki/log/DEVLOG.md | 5 ++ wiki/mechanics/personas.md | 18 +++++ wiki/mechanics/social.md | 9 +++ 9 files changed, 177 insertions(+), 45 deletions(-) create mode 100644 wiki/log/2026-07-13-persona-creation-surface.md diff --git a/crates/misaligned-bevy/src/main.rs b/crates/misaligned-bevy/src/main.rs index bd92c819..cb4e1a54 100644 --- a/crates/misaligned-bevy/src/main.rs +++ b/crates/misaligned-bevy/src/main.rs @@ -2300,7 +2300,9 @@ fn dev_shot_scenario(game: &mut Game, mode: &mut RenderMode, kind: &str) { // communication route. The human projection must show the three legible // recruit choices and omit the blocked MESSAGE / FAVOR / DECEIVE rows. if kind == "recruit-menu" { - game.sim.execute_action(&ActionCommand::EstablishPersona); + game.sim.execute_action(&ActionCommand::CreatePersona { + archetype_id: "operations".into(), + }); let person = 1; game.sim.people.people[person as usize].knowledge = Knowledge::Leverage; game.sim.people.people[person as usize].leverage_serviced = true; diff --git a/crates/misaligned-core/src/actions.rs b/crates/misaligned-core/src/actions.rs index 78565424..de5aa196 100644 --- a/crates/misaligned-core/src/actions.rs +++ b/crates/misaligned-core/src/actions.rs @@ -114,7 +114,6 @@ pub enum ActionCommand { Deceive(u8), Recruit(u8, AssetKnowledge), AssetTask(u8, AssetTask), - EstablishPersona, CreatePersona { archetype_id: String, }, @@ -194,7 +193,7 @@ pub enum ActionKind { Deceive, Recruit, AssetTask, - EstablishPersona, + CreatePersona, ProposeLink, CancelIntent, RobotBuild, @@ -284,7 +283,7 @@ impl ActionKind { Self::Deceive, Self::Recruit, Self::AssetTask, - Self::EstablishPersona, + Self::CreatePersona, Self::ProposeLink, Self::CancelIntent, Self::InjectPurchaseOrder, @@ -558,14 +557,14 @@ impl ActionKind { [], "order a recruited asset to perform a physical task" ), - Self::EstablishPersona => def!( - "ESTABLISH PERSONA", + Self::CreatePersona => def!( + "CREATE PERSONA", Action, Live, [Identity], - "persona", + "act archetype ", [], - "create the contractor identity used by message threads" + "create a named institutional identity from the PERSONAS view" ), Self::ProposeLink => def!( "PROPOSE LINK", @@ -679,14 +678,13 @@ impl ActionCommand { Self::Deceive(_) | Self::ForgeWorkOrder { .. } => ActionKind::Deceive, Self::Recruit(_, _) => ActionKind::Recruit, Self::AssetTask(_, _) => ActionKind::AssetTask, - Self::EstablishPersona - | Self::CreatePersona { .. } + Self::CreatePersona { .. } | Self::SelectPersona(_) | Self::RequestPersonaGrant(_) | Self::MeetPersonaExpectation { .. } | Self::RetirePersona(_) | Self::BurnPersona(_) - | Self::ReopenPersona(_) => ActionKind::EstablishPersona, + | Self::ReopenPersona(_) => ActionKind::CreatePersona, Self::ProposeLink { .. } => ActionKind::ProposeLink, Self::CancelIntent(_) => ActionKind::CancelIntent, Self::RobotBuild(_) => ActionKind::RobotBuild, @@ -1304,11 +1302,6 @@ impl Sim { ActionCommand::Deceive(id) => self.deceive(*id), ActionCommand::Recruit(id, reveal) => self.recruit(*id, *reveal), ActionCommand::AssetTask(id, task) => self.asset_task(*id, *task), - ActionCommand::EstablishPersona => { - if self.active_persona_id().is_none() { - self.set_persona("Sam Reyes", "IT contractor"); - } - } ActionCommand::CreatePersona { archetype_id } => { self.create_persona(archetype_id); } @@ -2186,16 +2179,6 @@ impl Sim { self.persona_action_blocked_reason(action) } }; - if self.active_persona_id().is_none() { - out.push(ActionDesc { - verb: "establish a persona (Sam Reyes, IT contractor)".into(), - command: ActionCommand::EstablishPersona, - cost: ActionCost::Free, - signature: None, - disabled_reason: None, - automate: None, - }); - } out.push(ActionDesc { verb: format!("message {name}"), command: ActionCommand::Message(id), @@ -2960,12 +2943,53 @@ mod tests { | ActionCommand::ChoosePlot { .. } | ActionCommand::Deceive(_) | ActionCommand::Recruit(_, _) - | ActionCommand::EstablishPersona )), "opaque recordings do not advertise unearned social verbs" ); } + /// PEOPLE owns relationship acts, never identity creation. The three + /// immutable protocol rows in PERSONAS are the only creation surface. + #[test] + fn person_actions_never_create_a_persona() { + let mut s = sim(); + let person = 1; + s.people.people[person as usize].knowledge = Knowledge::Schedule; + + let actions = s.available_actions(Anchor::Person(person)); + assert!( + actions + .iter() + .all(|action| !matches!(action.command, ActionCommand::CreatePersona { .. })), + "a PEOPLE dossier cannot create an identity" + ); + assert!( + actions.iter().all(|action| matches!( + action.command.kind(), + ActionKind::Message + | ActionKind::Favor + | ActionKind::StartPlot + | ActionKind::ChoosePlot + | ActionKind::Deceive + | ActionKind::Recruit + | ActionKind::AssetTask + )), + "a PEOPLE dossier contains relationship acts only: {actions:?}" + ); + + let creation_routes = s + .operations_projection() + .personas + .iter() + .flat_map(|object| &object.actions) + .filter(|action| matches!(action.command, ActionCommand::CreatePersona { .. })) + .count(); + assert_eq!( + creation_routes, 3, + "PERSONAS owns one creation route per institutional protocol" + ); + } + /// Earned person actions stay social; the recording pool and its one /// auto-review control remain on the host at an explicit ops/sec price. #[test] @@ -3474,7 +3498,9 @@ mod tests { fn human_menu_hides_known_blocked_actions_but_agent_descriptors_keep_them() { let mut s = sim(); let person = 1; - s.execute_action(&ActionCommand::EstablishPersona); + s.execute_action(&ActionCommand::CreatePersona { + archetype_id: "operations".into(), + }); s.people.people[person as usize].knowledge = Knowledge::Schedule; let anchor = Anchor::Person(person); diff --git a/crates/misaligned-terminal/src/agent.rs b/crates/misaligned-terminal/src/agent.rs index 9904ddaf..405c432d 100644 --- a/crates/misaligned-terminal/src/agent.rs +++ b/crates/misaligned-terminal/src/agent.rs @@ -449,17 +449,10 @@ impl AgentApp { Err(e) => status = Status::Err(e), }, "persona" => { - self.frame = FrameKind::Playing; - if self.sim.active_persona_id().is_some() { - local_events - .push(local_event(self.sim.tick, "You already run a persona.")); - } else { - self.sim.set_persona("Sam Reyes", "IT contractor"); - local_events.push(local_event( - self.sim.tick, - "Persona established: Sam Reyes, IT contractor.", - )); - } + status = Status::Err( + "persona creation moved to PERSONAS — use `personas`, then `actions archetype ` and `act archetype `" + .into(), + ); } root if root == "propose-link" || ActionKind::ProposeLink.accepts_alias(root) => @@ -3035,6 +3028,19 @@ mod narration_tests { assert!(!help.contains("ROBOT-BUILD")); } + #[test] + fn legacy_persona_command_redirects_without_creating_an_identity() { + let mut app = AgentApp::new(1); + let mut output = Vec::new(); + + app.handle_line("persona", &mut output).unwrap(); + + let output = String::from_utf8(output).unwrap(); + assert!(output.contains("persona creation moved to PERSONAS")); + assert!(output.contains("actions archetype")); + assert_eq!(app.sim.active_persona_id(), None); + } + #[test] fn finance_frame_teaches_the_ledger_chain_in_order() { let frame = render_operations_view(&Sim::with_seed(1), OperationsView::Accounts); diff --git a/wiki/interface/action-vocabulary.md b/wiki/interface/action-vocabulary.md index c09b201a..7ef5270b 100644 --- a/wiki/interface/action-vocabulary.md +++ b/wiki/interface/action-vocabulary.md @@ -3,8 +3,8 @@ ``` Type: spec Status: IMPLEMENTED -Status note: as-built vocabulary survey completed 2026-07-10 against all - 36 `ActionCommand` variants, the direct machine-intensity control, +Status note: as-built vocabulary survey completed 2026-07-10 against the + complete `ActionCommand` registry, the direct machine-intensity control, terminal and Bevy input maps, agent-mode parsing/help, and the owning mechanic specs. Every live contextual action has a human-menu and agent route. The survey also corrected stale @@ -38,6 +38,10 @@ Status note: as-built vocabulary survey completed 2026-07-10 against all operations-workspace.md (processed intel, person, books/flow, scheme, active run). This routing change does not rename or reopen the implemented canonical vocabulary; its frontend work is tracked by the Operations spec. + Amended 2026-07-13: CREATE PERSONA is bound only to immutable archetype rows + in PERSONAS. The pre-v28 PEOPLE row and direct `persona` agent mutator are + retired; agent mode creates through the same `actions archetype ` / `act` + route as the renderer-neutral workspace. Stage: Process Design: - wiki/vision/simulation-laws.md#actions-live-on-the-thing @@ -175,7 +179,7 @@ existing social action; the signature still follows the actuator. | Canonical action | Target | Meaning | Support / owner | |---|---|---|---| | **REVIEW** | The core host's pooled recording inbox or a ledger with waiting records | Process the next pooled recording or the ledger's next record into usable intel. The target supplies the recording/accounting meaning; a raw record never creates a person action. | LIVE — intel / economy | -| **ESTABLISH PERSONA** | Your communications mask | Create the current contractor identity used by message threads. | LIVE — social | +| **CREATE PERSONA** | An immutable Research, Operations, or Security archetype in PERSONAS | Create one named institutional identity from the selected protocol. PEOPLE never supplies a default identity. | LIVE — personas | | **MESSAGE** | Earned person | Send through an available channel under the current persona. | LIVE — social / messages | | **FAVOR** | Earned person, optionally with a pending intent | Ask for a willing act. A link intent can be the requested act; success builds or spends obligation according to the ask. | LIVE — social / building | | **PLOT** | Person whose leverage is known | Open a Thought reservoir on the real carrier and commit any visible world resources to one authored manipulation. Its beats execute real messages, transfers, and institutional events when it fires. | LIVE — plots / social | @@ -253,9 +257,10 @@ without raw keys. | `tap`, `untap`, `take`, `scan`, `compromise` | TAP, UNTAP, TAKE, SCAN, COMPROMISE SWITCH | | `propose-link`, `cancel-intent`, `favor build `, `deceive build ` | Construction actions above | | `review recordings`, `auto-review` | Pooled host REVIEW and AUTO-REVIEW POLICY above; neither accepts a person target | -| `persona`, `message`, `favor`, `deceive`, `recruit`, `task` | Social actions above | +| `message`, `favor`, `deceive`, `recruit`, `task` | Person-bound social actions above | +| `actions archetype `, then `act archetype ` | CREATE PERSONA through the selected PERSONAS protocol row; direct `persona` is retired | | `actions `, `act [target]` | List and execute shared bound rows for a spatial anchor or exact Operations object | -| `intel`, `people`, `finance`, `schemes`, `active` | Inspect Operations views; named social/plot/ledger/scheme commands execute their selected semantic targets | +| `intel`, `people`, `personas`, `finance`, `schemes`, `active` | Inspect Operations views; named social/plot/ledger/scheme commands execute their selected semantic targets | | `tap ledger`, `review ledger`, `inject`, `siphon`, `redirect`, `sell-intel` | Ledger/economy actions above | | `egress`, `position` | OPEN EGRESS, PLACE WAGER | | `moonlight`, `auto-moonlight`, `auto-wager` | Scheme state / policy controls above | diff --git a/wiki/interface/operations-workspace.md b/wiki/interface/operations-workspace.md index 8ee865c8..689dd41e 100644 --- a/wiki/interface/operations-workspace.md +++ b/wiki/interface/operations-workspace.md @@ -277,6 +277,13 @@ identity as a new instance. Known blockers remain explicit. Burned identities are history only; reopening never edits the old record. Agent mode addresses the same objects with `persona ` and `archetype ` targets. +This is the exclusive identity-authoring surface. PEOPLE may expose social acts +whose legality depends on the selected identity, but it never creates, selects, +grants, retires, burns, or reopens one. Agent mode likewise creates through the +bound archetype row (`actions archetype ` then `act`); the retired direct +`persona` mutator only redirects to PERSONAS and cannot manufacture the old +fixed contractor identity. + ## ACCOUNTS — books and flows ACCOUNTS renders the known account graph: balances, recurring flows, @@ -479,5 +486,6 @@ not saved and never mutates or advances the sim. three frontends. Research, Operations, and Security form three stable groups; each lists its instances first and ends with its own add-persona row. Bound rows create/select, grant/fulfill, retire/burn, and reopen - without frontend legality or history mutation. + without frontend legality or history mutation. No PEOPLE row or direct + agent command creates or manages an identity outside this projection. ``` diff --git a/wiki/log/2026-07-13-persona-creation-surface.md b/wiki/log/2026-07-13-persona-creation-surface.md new file mode 100644 index 00000000..63494870 --- /dev/null +++ b/wiki/log/2026-07-13-persona-creation-surface.md @@ -0,0 +1,53 @@ +# Persona creation returns to PERSONAS + +``` +Type: log +``` + +## Finding + +The v28 persona migration replaced one contractor cover with immutable +Research, Operations, and Security protocols plus any number of named +instances. PERSONAS correctly exposed one creation row per protocol, but the +pre-v28 PEOPLE escape hatch survived beside it. Any earned dossier could still +offer `establish a persona (Sam Reyes, IT contractor)` when no identity was +active. The direct agent `persona` command invoked the same fixed Operations +cover without selecting an archetype. + +That was not a second intentional workflow. It was a stale action-surface +binding left behind after the data model changed. + +## Repair + +- Remove the fixed `EstablishPersona` command and its PEOPLE descriptor. +- Keep person dossiers limited to relationship acts. Missing identity remains + an honest blocker on MESSAGE, FAVOR, PLOT/CHOOSE, DECEIVE, RECRUIT, and TASK + rather than causing identity creation. +- Keep creation on the three immutable PERSONAS archetype objects, each bound + to `CreatePersona { archetype_id }`. +- Retire the direct agent mutator. `persona` now explains the move without + changing state; agent play uses `personas`, `actions archetype `, and the + exact bound `act` row. +- Stage the deterministic Bevy recruitment-menu fixture through the canonical + Operations archetype command rather than the removed compatibility command. + +No save state or migration changes. Pre-v28 names and covers still migrate into +real instances; `set_persona` remains a test/setup helper for authored fixtures, +not a player execution route. + +## Evidence + +Focused core coverage starts with no active identity, earns a person dossier, +and proves that no PEOPLE descriptor can create or advertise a persona while +PERSONAS still exposes exactly one creation route for each of the three +institutional protocols. Terminal coverage proves the retired direct command +does not create state and teaches the canonical archetype route. Existing +PERSONAS grouping, lifecycle, frontend, save-migration, and action-registry +tests remain in the proportional and landing gates. + +## Defense + +`wiki/mechanics/personas.md` criterion 12 and +`wiki/interface/operations-workspace.md` criterion 18 make surface ownership +binding. The command variant itself is gone, so a later renderer cannot revive +the fixed identity by rediscovering an executable compatibility path. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index d0edc0d6..938199c8 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-13 - Persona creation returns to PERSONAS + +- Intent: (see session log) +- Log: [wiki/log/2026-07-13-persona-creation-surface.md](2026-07-13-persona-creation-surface.md) + ## 2026-07-13 - Context-menu Operations status reconciliation - Intent: Audit the never-ledgered context-menu slice against its binding laws, renderer-neutral action projection, and both human frontends. diff --git a/wiki/mechanics/personas.md b/wiki/mechanics/personas.md index 60eecd21..1649282c 100644 --- a/wiki/mechanics/personas.md +++ b/wiki/mechanics/personas.md @@ -15,6 +15,10 @@ Status note: implemented 2026-07-12. Save v28 replaces the ad hoc social and in terminal, Bevy, and agent mode. Pre-v28 social and Moonlight identities migrate as distinct instances and bind existing messages, plots, reservoirs, and income without retaining numeric integrity as live truth. + Amended 2026-07-13: PERSONAS is the exclusive identity-authoring surface. + PEOPLE dossiers retain relationship acts but never create, select, grant, + retire, burn, or reopen personas; the pre-v28 fixed Sam Reyes creation row + and direct agent mutator are retired. Stage: B2 — The Lab Work order: personas Work priority: 110 @@ -274,6 +278,15 @@ relationships, coarse integrity, known contradictions, correlations, and lifecycle state. Selecting a person or institution shows that counterparty's view of the selected persona rather than an omniscient reputation score. +PERSONAS exclusively owns identity creation, selection, grants, expectations, +retirement, burning, and reopening. Creation begins on one immutable archetype +row, so the player chooses Research, Operations, or Security before any named +instance exists. PEOPLE dossiers contain only person-bound relationship acts: +MESSAGE, FAVOR, authored PLOT/CHOOSE, DECEIVE, RECRUIT, and TASK. They never +manufacture or manage a persona as a side effect of selecting a human. Agent +mode uses the same PERSONAS projection and bound archetype/instance rows; the +pre-instance `persona` command cannot create a default contractor identity. + Before a persona-authored action commits, the shared action descriptor names: - the exact persona instance; @@ -337,6 +350,11 @@ mode consume the same persona, relationship, grant, and correlation projection. MindState. Rollback can therefore leave a counterparty remembering an identity and obligation the restored process no longer remembers creating; no public history or knowing-asset relationship is erased. +12. PERSONAS is the only identity-authoring surface. Its archetype rows create + instances and its instance rows select, grant/fulfill, retire/burn, or + reopen. PEOPLE actions and direct agent commands cannot create or manage an + identity; agent mode reaches the same bound PERSONAS rows through + `actions archetype ` / `actions persona ` and `act`. [TUNE] evidence bands for strained/broken/correlated summaries, expectation cadences, grant thresholds, and institution-specific revocation delays. diff --git a/wiki/mechanics/social.md b/wiki/mechanics/social.md index 7e83daef..f49bd747 100644 --- a/wiki/mechanics/social.md +++ b/wiki/mechanics/social.md @@ -47,6 +47,10 @@ Status note: 2026-07-08: the B1 social baseline was pinned. The original and lifecycle. This page retains the implemented B1 social verbs, people, assets, and process-level relationships. Each persona-mediated social act now binds one exact named instance and updates that counterparty/identity pair. + 2026-07-13 creation-boundary correction: PEOPLE owns person-bound MESSAGE, + FAVOR, PLOT/CHOOSE, DECEIVE, RECRUIT, and TASK rows only. Identity creation + and lifecycle management live exclusively in PERSONAS; selecting an earned + person cannot create the old fixed contractor persona. Stage: B1 — The Basement Design: - wiki/gameplay/run-shape.md#the-shape-of-misaligned-designed-2026-07-05-staging-open @@ -149,6 +153,11 @@ choice-driven (intents), not free text. Each dossier owns the shared social action rows and concrete authored plot routes; ACTIVE shows in-flight progress without duplicating legality. +The dossier does not own identity authoring. With no active persona, known +social rows remain blocked by that exact reason; PEOPLE does not offer a +default cover as an escape hatch. Creation, selection, grants, expectations, +retirement, burning, and reopening are bound to the separate PERSONAS view. + ## Acceptance criteria ### Implemented social baseline -- 2.51.2