Remove inherited Sarah infrastructure names

4b8adbc37fa2 · Christopher David · · parent fcb27de52c36

Remove inherited Sarah infrastructure names

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • modified AGENTS.md
  • modified assets/css/app.css
  • renamed assets/css/openagents.css
  • modified assets/test/voice_recording_test.mjs
  • modified assets/vendor/basecoat/README.md
  • modified config/config.exs
  • modified config/test.exs
  • modified docs/2026-08-20-integration-hardening-and-staging-readiness-recommendations.md
  • modified docs/decisions/0002-model-sarah-as-an-openagents-persona.md
  • modified docs/decisions/0005-use-basecoat-and-one-component-system.md
  • modified lib/openagents/application.ex
  • modified lib/openagents/changelog.ex
  • modified lib/openagents/changelog/backfill.ex
  • modified lib/openagents/cluster/ra_bootstrap.ex
  • modified lib/openagents/forge/boot_converge.ex
  • modified lib/openagents/forge/build_executor.ex
  • modified lib/openagents/forge/repos.ex
  • modified lib/openagents/forge/visibility.ex
  • modified lib/openagents/forge/wal.ex
  • modified lib/openagents/forge/wal/local.ex
  • modified lib/openagents/github.ex
  • modified lib/openagents/github_oauth.ex
  • modified lib/openagents/incidents/fixer.ex
  • modified lib/openagents/machines/token_vault.ex
  • modified lib/openagents/modules/metadata.ex
  • modified lib/openagents/network_status.ex
  • modified lib/openagents/observability/readback.ex
  • modified lib/openagents/observability/release_gate.ex
  • renamed lib/openagents/runtime_supervisor.ex
  • modified lib/openagents/tools/code_check.ex
  • modified lib/openagents/tools/computer_agent.ex
  • modified lib/openagents/tools/computer_devin.ex
  • modified lib/openagents/tools/computer_list.ex
  • modified lib/openagents/tools/computer_probe.ex
  • modified lib/openagents/tools/computer_run.ex
  • modified lib/openagents/tools/conversation_read.ex
  • modified lib/openagents/tools/conversation_search.ex
  • modified lib/openagents/tools/deep_work.ex
  • modified lib/openagents/tools/github_repo_list.ex
  • modified lib/openagents/tools/github_repo_read.ex
  • modified lib/openagents/tools/incident_lookup.ex
  • modified lib/openagents/tools/memory_contract.ex
  • modified lib/openagents/tools/module_discover.ex
  • modified lib/openagents/tools/repo_commit_push.ex
  • modified lib/openagents/tools/repo_edit.ex
  • modified lib/openagents/tools/repo_grep.ex
  • modified lib/openagents/tools/repo_list.ex
  • modified lib/openagents/tools/repo_read.ex
  • modified lib/openagents/tools/repo_write.ex
  • modified lib/openagents/tools/repository.ex
  • modified lib/openagents/tools/runner.ex
  • modified lib/openagents/voice/recording_vault.ex
  • modified lib/openagents_web.ex
  • modified lib/openagents_web/channels/computer_channel.ex
  • modified lib/openagents_web/component_catalog.ex
  • modified lib/openagents_web/components/core_components.ex
  • modified lib/openagents_web/components/layouts.ex
  • modified lib/openagents_web/components/tool_activity.ex
  • renamed lib/openagents_web/components/ui.ex
  • modified lib/openagents_web/controllers/changelog_controller.ex
  • modified lib/openagents_web/controllers/computers_controller.ex
  • modified lib/openagents_web/controllers/network_status_controller.ex
  • modified lib/openagents_web/live/admin_forge_live.ex
  • modified lib/openagents_web/live/admin_live.ex
  • modified lib/openagents_web/live/changelog_live.ex
  • modified lib/openagents_web/live/chat_live.ex
  • modified lib/openagents_web/live/code_blob_live.ex
  • modified lib/openagents_web/live/code_commit_live.ex
  • modified lib/openagents_web/live/code_repo_live.ex
  • modified lib/openagents_web/live/components_live.ex
  • modified lib/openagents_web/live/computers_live.ex
  • modified lib/openagents_web/live/leaderboard_live.ex
  • modified lib/openagents_web/live/network_status_live.ex
  • modified lib/openagents_web/live/ui_gallery_live.ex
  • modified lib/openagents_web/live/voice_spike_live.ex
  • modified lib/openagents_web/plugs/forge_git_auth.ex
  • modified lib/openagents_web/router.ex
  • modified mix.exs
  • added ops/ci/allowed-sarah-references.txt
  • added ops/ci/reference-check.sh
  • modified priv/icons/README.md
  • modified test/openagents/accounts_test.exs
  • modified test/openagents/admin_test.exs
  • modified test/openagents/blueprint_test.exs
  • modified test/openagents/changelog_test.exs
  • renamed test/openagents/cluster/basic_test.exs
  • modified test/openagents/cluster/chaos_test.exs
  • modified test/openagents/cluster/drain_test.exs
  • modified test/openagents/cluster/ra_cluster_test.exs
  • modified test/openagents/coding_job_test.exs
  • modified test/openagents/collective_generalizer_test.exs
  • modified test/openagents/collective_publication_test.exs
  • modified test/openagents/collective_test.exs
  • modified test/openagents/compensation_test.exs
  • modified test/openagents/computer_activity_test.exs
  • modified test/openagents/conversations_test.exs
  • modified test/openagents/data_rights/atif_export_test.exs
  • modified test/openagents/deep_work_tool_loop_test.exs
  • modified test/openagents/experience_memory_test.exs
  • modified test/openagents/forge/boot_converge_test.exs
  • modified test/openagents/forge/browse_test.exs
  • modified test/openagents/forge/builder_test.exs
  • modified test/openagents/forge/git_http_test.exs
  • modified test/openagents/forge/hot_loader_test.exs
  • modified test/openagents/forge/janitor_test.exs
  • modified test/openagents/forge/loop_v0_test.exs
  • modified test/openagents/forge/mirror_watch_test.exs
  • modified test/openagents/forge/receipt_replayer_test.exs
  • renamed test/openagents/forge/target_lifecycle_test.exs
  • modified test/openagents/forge/targets_test.exs
  • modified test/openagents/forge/visibility_test.exs
  • modified test/openagents/forge/wal_test.exs
  • modified test/openagents/github_oauth_test.exs
  • modified test/openagents/github_test.exs
  • modified test/openagents/graph_memory_test.exs
  • modified test/openagents/incidents/fixer_test.exs
  • modified test/openagents/incidents_test.exs
  • modified test/openagents/inference_test.exs
  • modified test/openagents/leaderboard_test.exs
  • added test/openagents/machines/token_vault_test.exs
  • modified test/openagents/machines_capacity_test.exs
  • modified test/openagents/machines_test.exs
  • modified test/openagents/memory/evaluation_test.exs
  • modified test/openagents/memory/evidence_test.exs
  • modified test/openagents/memory/lexical_recall_test.exs
  • modified test/openagents/memory/policy_and_redaction_test.exs
  • modified test/openagents/memory_portability_test.exs
  • modified test/openagents/modules/lifecycle_test.exs
  • modified test/openagents/modules/registry_test.exs
  • modified test/openagents/network_status_test.exs
  • modified test/openagents/observability_test.exs
  • modified test/openagents/preferences_test.exs
  • modified test/openagents/profile_memory_test.exs
  • modified test/openagents/program_lifecycle_test.exs
  • modified test/openagents/semantic_recall_test.exs
  • modified test/openagents/shadow_programs_test.exs
  • modified test/openagents/tool_step_persistence_test.exs
  • modified test/openagents/tools/computer_tools_test.exs
  • modified test/openagents/tools/conversation_recall_tools_test.exs
  • modified test/openagents/tools/github_repo_tools_test.exs
  • modified test/openagents/tools/incident_lookup_test.exs
  • modified test/openagents/tools/profile_memory_tools_test.exs
  • modified test/openagents/tools/registry_and_runner_test.exs
  • modified test/openagents/tools/repository_mutation_tools_test.exs
  • modified test/openagents/tools/repository_tools_test.exs
  • modified test/openagents/turn_memory_evidence_journeys_test.exs
  • modified test/openagents/turn_provenance_test.exs
  • modified test/openagents/turn_provider_events_test.exs
  • modified test/openagents/turn_tool_loop_test.exs
  • modified test/openagents/voice/recordings_test.exs
  • modified test/openagents/voice/release_operations_test.exs
  • modified test/openagents/voice_sessions_test.exs
  • modified test/openagents/voice_test.exs
  • modified test/openagents/work/handoff_test.exs
  • modified test/openagents/work_job_test.exs
  • modified test/openagents_web/account_chrome_test.exs
  • modified test/openagents_web/allowed_origins_test.exs
  • modified test/openagents_web/auth_controller_test.exs
  • modified test/openagents_web/auth_gate_test.exs
  • modified test/openagents_web/channels/computer_channel_test.exs
  • modified test/openagents_web/component_catalog_test.exs
  • modified test/openagents_web/controllers/atif_export_controller_test.exs
  • modified test/openagents_web/controllers/computer_agent_jobs_controller_test.exs
  • modified test/openagents_web/controllers/computers_controller_test.exs
  • modified test/openagents_web/controllers/controller_pairing_test.exs
  • modified test/openagents_web/controllers/data_controller_test.exs
  • modified test/openagents_web/controllers/health_controller_test.exs
  • modified test/openagents_web/controllers/inference_proxy_controller_test.exs
  • deleted test/openagents_web/controllers/sarah_error_html_test.exs
  • deleted test/openagents_web/controllers/sarah_error_json_test.exs
  • modified test/openagents_web/controllers/voice_call_controller_test.exs
  • modified test/openagents_web/controllers/voice_recording_controller_test.exs
  • modified test/openagents_web/controllers/voice_telemetry_controller_test.exs
  • modified test/openagents_web/icon_affordances_test.exs
  • modified test/openagents_web/icons_test.exs
  • modified test/openagents_web/live/admin_forge_live_test.exs
  • modified test/openagents_web/live/admin_live_test.exs
  • modified test/openagents_web/live/assignee_index_live_test.exs
  • modified test/openagents_web/live/changelog_live_test.exs
  • modified test/openagents_web/live/chat_delegation_rail_test.exs
  • modified test/openagents_web/live/chat_live_test.exs
  • modified test/openagents_web/live/chat_sidebar_sections_test.exs
  • modified test/openagents_web/live/code_live_test.exs
  • modified test/openagents_web/live/computers_access_test.exs
  • modified test/openagents_web/live/computers_live_test.exs
  • modified test/openagents_web/live/computers_navigation_test.exs
  • modified test/openagents_web/live/computers_pairing_errors_test.exs
  • modified test/openagents_web/live/issue_index_live_test.exs
  • modified test/openagents_web/live/issue_new_live_test.exs
  • modified test/openagents_web/live/issue_show_live_test.exs
  • modified test/openagents_web/live/label_index_live_test.exs
  • modified test/openagents_web/live/leaderboard_live_test.exs
  • modified test/openagents_web/live/milestone_index_live_test.exs
  • modified test/openagents_web/live/network_status_live_test.exs
  • modified test/openagents_web/live/project_index_live_test.exs
  • modified test/openagents_web/live/project_show_live_test.exs
  • modified test/openagents_web/live/ui_gallery_live_test.exs
  • modified test/openagents_web/tool_activity_test.exs
  • modified test/openagents_web/ui_test.exs
  • renamed test/support/channel_case.ex
  • deleted test/support/sarah_conn_case.ex
  • deleted test/support/sarah_data_case.ex
  • deleted test/support/sarah_factory.ex
  • deleted test/support/sarah_fixtures.ex
  • modified test/support/tools/test_recall.ex

Diff

205 files changed, +944 -844

AGENTS.md modified +3 -3

@@ -42,10 +42,10 @@ custom classes must fully style the input

42 42
43 43
- **Always use and maintain this import syntax** in the app.css file for projects generated with `phx.new`
44 44
- **Never** use `@apply` when writing raw css
45
- **There is exactly one component system: vendored Basecoat plus Sarah's style pack.** Basecoat lives in `assets/vendor/basecoat/components/` and carries structure (display, padding, min-height); `assets/css/sarah.css` carries OpenAgents' identity (motion tokens, radius scale, colour, the notched variant, corner frames) and must stay the last import so its declarations win.
46
  - **Reach for `OpenAgentsWeb.SarahUI` first.** It wraps that CSS in nineteen ready primitives — `button/1`, `card/1`, `badge/1`, `alert/1`, `input/1`, `textarea/1`, `label/1`, `field/1`, `avatar/1`, `menu/1`, `empty/1`, `kbd/1`, and the rest. Fall back to hand-written classes only when no primitive covers the shape.
45
- **There is exactly one component system: vendored Basecoat plus OpenAgents style pack.** Basecoat lives in `assets/vendor/basecoat/components/` and carries structure (display, padding, min-height); `assets/css/openagents.css` carries OpenAgents' identity (motion tokens, radius scale, colour, the notched variant, corner frames) and must stay the last import so its declarations win.
46
  - **Reach for `OpenAgentsWeb.UI` first.** It wraps that CSS in nineteen ready primitives — `button/1`, `card/1`, `badge/1`, `alert/1`, `input/1`, `textarea/1`, `label/1`, `field/1`, `avatar/1`, `menu/1`, `empty/1`, `kbd/1`, and the rest. Fall back to hand-written classes only when no primitive covers the shape.
47 47
  - **Variants are data attributes, not classes.** A control is `class="btn"` plus `data-variant="primary"` / `data-size="sm"` / `data-tone="danger"`. Never invent `btn-primary`-style variant classes; they defeat the whole point of the split.
48
  - **Never add a second component library — DaisyUI above all.** DaisyUI was removed deliberately. It emitted flat `.btn` rules (setting background, colour, border) from a cascade layer that outranked every `.btn[data-variant=…]` in `sarah.css`, so all eight SarahUI button variants rendered identically on staging. Any library with the same shape will do the same thing again.
48
  - **Never add a second component library — DaisyUI above all.** DaisyUI was removed deliberately. It emitted flat `.btn` rules (setting background, colour, border) from a cascade layer that outranked every `.btn[data-variant=…]` in `openagents.css`, so all eight UI button variants rendered identically on staging. Any library with the same shape will do the same thing again.
49 49
  - **Import Basecoat components individually**, one `@import "../vendor/basecoat/components/<name>.css"` per component a surface actually uses. Component CSS lands in `@layer components` and ships whether or not the class appears in markup, so the list is a budget. **Never** import `basecoat.css`, `basecoat-base.css`, or `basecoat-components.css` — each pulls in all 39 components at once.
50 50
  - The palette is the token ladder at the top of `app.css` (`--ink-void` / `--ink-surface` / `--ink-raised`, the text and line tiers, and the four semantic colours). It is dark-only. Prefer the Basecoat utility names built on it — `bg-background`, `bg-card`, `text-foreground`, `text-muted-foreground`, `border-border` — over the older `base-100`/`base-content` aliases, which are kept only so unconverted surfaces keep resolving.
51 51
- Add custom Tailwind only when the design is unique to OpenAgents and no primitive fits.
assets/css/app.css modified +15 -15

@@ -1,10 +1,10 @@

1 1
/* See the Tailwind configuration guide for advanced usage
2 2
   https://tailwindcss.com/docs/configuration */
3 3
4
/* There is exactly one component system here: Basecoat structure plus Sarah's
4
/* There is exactly one component system here: Basecoat structure plus OpenAgents
5 5
 * style pack. DaisyUI was removed (see AGENTS.md) — it emitted flat component
6 6
 * rules from its own cascade layer that outranked every `.btn[data-variant=…]`
7
 * in sarah.css, so all eight SarahUI button variants rendered identically. Do
7
 * in openagents.css, so all eight UI button variants rendered identically. Do
8 8
 * not reintroduce it, and do not add a second component library beside it.
9 9
 *
10 10
 * With DaisyUI gone the layer order Tailwind itself declares

@@ -20,10 +20,10 @@

20 20
 * basecoat-components.css. See assets/vendor/basecoat/README.md.
21 21
 *
22 22
 * These carry the structure (display, padding, min-height, background) that
23
 * sarah.css deliberately does not set — its .btn only adds gap, border,
23
 * openagents.css deliberately does not set — its .btn only adds gap, border,
24 24
 * radius, colour, font, and transition on top. With DaisyUI gone they are the
25 25
 * only source of that geometry, so nothing here is optional. This list is
26
 * Sarah's, plus `table` for `CoreComponents.table/1`. */
26
 * OpenAgents, plus `table` for `CoreComponents.table/1`. */
27 27
@import "../vendor/basecoat/base/base.css";
28 28
@import "../vendor/basecoat/components/button.css";
29 29
@import "../vendor/basecoat/components/input.css";

@@ -40,8 +40,8 @@

40 40
@import "../vendor/basecoat/components/kbd.css";
41 41
@import "../vendor/basecoat/components/table.css";
42 42
43
/* Sarah's identity. Must stay last so its declarations win. */
44
@import "./sarah.css";
43
/* OpenAgents identity. Must stay last so its declarations win. */
44
@import "./openagents.css";
45 45
@source "../css";
46 46
@source "../js";
47 47
@source "../../lib/openagents_web";

@@ -53,25 +53,25 @@

53 53
@plugin "../vendor/heroicons";
54 54
55 55
/* ── Token ladder ───────────────────────────────────────────────────────────
56
 * Ported from Sarah, where these are the two tiers of DESIGN.md "Color
56
 * Integrated into OpenAgents, where these are the two tiers of DESIGN.md "Color
57 57
 * Tokens": PRIMITIVES are the hand-picked Aiur oklch values; DERIVATIONS are
58 58
 * color-mix() steps off one ink, so the hover/focus/border grammar is
59 59
 * systematic rather than accumulated.
60 60
 *
61
 * sarah.css consumes these but does not define them — it aliases them onto
61
 * openagents.css consumes these but does not define them — it aliases them onto
62 62
 * the Basecoat names (`--background`, `--card`, `--primary`, `--border`, …)
63 63
 * in its own `:root`. Before this block existed those aliases resolved to
64 64
 * nothing and the DaisyUI theme was carrying the palette instead, which is
65
 * exactly the collision that made every SarahUI variant render identically.
65
 * exactly the collision that made every UI variant render identically.
66 66
 *
67 67
 * The three surfaces are a fixed relationship, not three loose colors: page
68 68
 * and sidebar sit on the darkest (--ink-void), the working canvas one step up
69 69
 * (--ink-surface), elevated components another step up (--ink-raised). New
70 70
 * surfaces take a rung; they do not invent a fourth.
71 71
 *
72
 * The palette is dark-only, as Sarah's is (`color-scheme: dark`). The
72
 * The palette is dark-only, as OpenAgents is (`color-scheme: dark`). The
73 73
 * `data-theme` plumbing in root.html.heex still runs, but there is no second
74
 * light palette behind it any more — DaisyUI's was the only one, and Sarah
74
 * light palette behind it any more — DaisyUI's was the only one, and OpenAgents
75 75
 * ships no light variant to replace it with.
76 76
 */
77 77
:root {

@@ -135,11 +135,11 @@

135 135
 * are still spoken by surfaces this change did not restyle (the component
136 136
 * catalog, the marketing home template, and the sidebar/docs/chat CSS at the
137 137
 * foot of this file). They are kept as aliases onto the three surface rungs so
138
 * that vocabulary keeps resolving; they are now names for Sarah tokens, not a
138
 * that vocabulary keeps resolving; they are now names for OpenAgents tokens, not a
139 139
 * second palette. Prefer Basecoat's own `background` / `card` / `foreground` /
140 140
 * `border` names in new markup.
141 141
 *
142
 * The semantic four are Sarah's, not DaisyUI's, and have no Basecoat
142
 * The semantic four are OpenAgents, not DaisyUI's, and have no Basecoat
143 143
 * equivalent — `--color-destructive` covers danger, but there is no success,
144 144
 * warning, or info in the Basecoat base. */
145 145
@theme {

@@ -167,7 +167,7 @@

167 167
168 168
/* This file is for your main application CSS */
169 169
170
/* ── Sidebar (Sarah-style layout with OpenAgents color tokens) ───────────── */
170
/* ── Sidebar (OpenAgents layout with OpenAgents color tokens) ───────────── */
171 171
.sidebar {
172 172
  display: flex;
173 173
  min-height: 0;

@@ -396,7 +396,7 @@

396 396
  padding: 32px;
397 397
}
398 398
399
/* Chat UI (Sarah-style) */
399
/* Chat UI (OpenAgents) */
400 400
401 401
.chat-transcript {
402 402
  display: flex;
assets/css/sarah.css → assets/css/openagents.css renamed +10 -10

@@ -1,10 +1,10 @@

1 1
/*
2
 * Sarah style pack
2
 * OpenAgents style pack
3 3
 *
4 4
 * Basecoat ships structure (assets/vendor/basecoat/components/*.css: layout,
5 5
 * accessibility selectors, behavior hooks - no color, no radius) separately
6 6
 * from visual style packs. We import the structure and write this pack instead
7
 * of adopting Vega or any other shadcn pack, so Sarah's identity is a
7
 * of adopting Vega or any other shadcn pack, so OpenAgents identity is a
8 8
 * first-class artifact rather than a diff against another product's defaults.
9 9
 *
10 10
 * This file is imported last. Every rule here must be traceable to DESIGN.md.

@@ -108,7 +108,7 @@

108 108
}
109 109
110 110
/* ── Token layer ───────────────────────────────────────────────────────────
111
 * Sarah's Aiur tokens (declared in app.css) are the authored source of truth.
111
 * OpenAgents Aiur tokens (declared in app.css) are the authored source of truth.
112 112
 * Basecoat's shadcn-compatible names are derived from them so both vocabularies
113 113
 * resolve to one palette. DESIGN.md's token table stays the readable contract.
114 114
 */

@@ -182,7 +182,7 @@

182 182
183 183
/* ── Surface recipes ───────────────────────────────────────────────────────
184 184
 * Shared mechanics that later phases compose by name. Each is a pattern with
185
 * one utility class, not a component: components stay in SarahWeb.UI.
185
 * one utility class, not a component: components stay in OpenAgentsWeb.UI.
186 186
 */
187 187
188 188
@layer components {

@@ -792,7 +792,7 @@

792 792
    inset: 3px;
793 793
  }
794 794
795
  /* Link buttons are Sarah's inline action: underlined text, not a boxed
795
  /* Link buttons are OpenAgents inline action: underlined text, not a boxed
796 796
     control. Size tokens must not apply. */
797 797
  .btn[data-variant="link"] {
798 798
    min-height: 0;

@@ -1229,7 +1229,7 @@

1229 1229
@layer components {
1230 1230
  /* One durable event as a disclosure row: chevron, status dot, quiet 13px
1231 1231
     one-line title, optional short status note in text, hover-revealed
1232
     timestamp. Devin's event-header primitive over Sarah's tokens; the
1232
     timestamp. Devin's event-header primitive over OpenAgents tokens; the
1233 1233
     expansion carries the bounded durable details. */
1234 1234
  .event-header {
1235 1235
    min-width: 0;

@@ -1509,7 +1509,7 @@

1509 1509
/* ── Status indicator ─────────────────────────────────────────────────────── */
1510 1510
1511 1511
@layer components {
1512
  /* Sarah's semantic state marker: a small lit dot that reinforces a text
1512
  /* OpenAgents semantic state marker: a small lit dot that reinforces a text
1513 1513
     label and never replaces it. Reserved meanings: green completed and
1514 1514
     connected, blue active and streaming, gold required attention, red
1515 1515
     failed. The halo is the state made legible at a glance, not decoration. */

@@ -1524,7 +1524,7 @@

1524 1524
  }
1525 1525
1526 1526
  /* "ended", "live", and "attention" are the sidebar status rows' lifecycle
1527
     vocabulary (DESIGN.md, Sarah shell): an ordinarily ended call is a muted
1527
     vocabulary (DESIGN.md, OpenAgents shell): an ordinarily ended call is a muted
1528 1528
     fact, a live call is blue activity, and budget/interrupted outcomes ask
1529 1529
     for gold attention. Each joins the existing hue group; no new color. */
1530 1530
  .status-indicator[data-state="idle"],

@@ -1596,7 +1596,7 @@

1596 1596
@layer components {
1597 1597
  /* Native controls, deliberately: the browser's transport is already keyboard
1598 1598
   * operable and already announced, and a hand-rolled one would have to re-earn
1599
   * both. Only the box the transport sits in is Sarah's. */
1599
   * both. Only the box the transport sits in is OpenAgents. */
1600 1600
  .audio-player {
1601 1601
    display: block;
1602 1602
    width: 100%;

@@ -1615,7 +1615,7 @@

1615 1615
/* ── Popover / menu ───────────────────────────────────────────────────────── */
1616 1616
1617 1617
@layer components {
1618
  /* Sarah keeps the native `popover` API: no Basecoat JavaScript, and the
1618
  /* OpenAgents keeps the native `popover` API: no Basecoat JavaScript, and the
1619 1619
   * control works without client-side script.
1620 1620
   *
1621 1621
   * Basecoat's own popover CSS is deliberately not imported. There, `.popover`
assets/test/voice_recording_test.mjs modified +1 -1

@@ -80,7 +80,7 @@ const build = (overrides = {}) => {

80 80
81 81
  const call = new CallRecorder({
82 82
    microphone: "mic-stream",
83
    remoteStream: "sarah-stream",
83
    remoteStream: "remote-stream",
84 84
    generation: "3",
85 85
    timesliceMs: 5000,
86 86
    mimeType: "audio/webm;codecs=opus",
assets/vendor/basecoat/README.md modified +4 -4

@@ -19,10 +19,10 @@ Copied from upstream `src/css/`:

19 19
Deliberately **not** vendored:
20 20
21 21
- `src/css/styles/*` — the Vega/Nova/Maia/Lyra/Mira/Luma/Sera/Rhea visual packs.
22
  Sarah writes its own pack at `assets/css/style-sarah.css`. Importing one of
22
  OpenAgents writes its own pack at `assets/css/openagents.css`. Importing one of
23 23
  theirs and overriding it would leave dead visual rules in the bundle and make
24
  Sarah's identity a diff against another product's defaults.
25
- `src/js/*` — no Basecoat JavaScript is loaded. The only component Sarah's
24
  OpenAgents identity a diff against another product's defaults.
25
- `src/js/*` — no Basecoat JavaScript is loaded. The only component OpenAgents
26 26
  surfaces would need it for is `dropdown-menu`, and the account menu uses the
27 27
  native `popover` API instead, which is required to work without custom
28 28
  client-side JavaScript. Taking zero Basecoat JS also removes the LiveView

@@ -34,7 +34,7 @@ Deliberately **not** vendored:

34 34
`assets/css/app.css` imports `base/base.css` plus **individually named**
35 35
component files. Component CSS lives in `@layer components` and is emitted
36 36
whether or not the class appears in markup, so importing the full set costs
37
about 13.5 KB gzip against about 7.4 KB for the components Sarah actually uses.
37
about 13.5 KB gzip against about 7.4 KB for the components OpenAgents actually uses.
38 38
Never import `basecoat.css`, `basecoat-base.css`, or `basecoat-components.css`.
39 39
40 40
Adding an import is a deliberate act tied to a surface that needs it.
config/config.exs modified +5 -12

@@ -147,8 +147,8 @@ config :openagents,

147 147
  forge_boot_converge_enabled: false,
148 148
  forge_deploy_lane_enabled: true,
149 149
  forge_data_dir: nil,
150
  forge_build_dir: "/var/lib/sarah/workspace/build",
151
  forge_repos: ["sarah"],
150
  forge_build_dir: "/var/lib/openagents/workspace/build",
151
  forge_repos: ["openagents.com"],
152 152
  forge_internal_git_url: "http://127.0.0.1:8080/git",
153 153
  forge_operator_token: nil,
154 154
  forge_mirror_urls: %{},

@@ -168,16 +168,9 @@ config :openagents,

168 168
    "OpenAgents.Scratch.",
169 169
    "OpenAgents.BuildInfo"
170 170
  ],
171
  forge_public_visibility: %{"sarah" => :l2},
172
  forge_repo_owners: %{"sarah" => "OpenAgentsInc"},
173
  forge_public_paths: %{
174
    "sarah" => [
175
      "README.md",
176
      "CHANGELOG.md",
177
      "docs/audits/2026-08-19-public-changelog-transparency-audit.md",
178
      "docs/plans/2026-08-19-transparency-spec-and-roadmap.md"
179
    ]
180
  },
171
  forge_public_visibility: %{"openagents.com" => :l3},
172
  forge_repo_owners: %{"openagents.com" => "OpenAgentsInc"},
173
  forge_public_paths: %{"openagents.com" => []},
181 174
  dns_cluster_query: nil
182 175
183 176
# Configure the endpoint
config/test.exs modified +2 -2

@@ -62,10 +62,10 @@ config :phoenix,

62 62
# `OpenAgents.Forge.HotLoader` themselves.
63 63
config :openagents,
64 64
  forge_enabled: true,
65
  forge_repos: ["sarah", "demo"],
65
  forge_repos: ["openagents.com", "demo"],
66 66
  forge_operator_token: "forge_test_operator_token_0123456789",
67 67
  forge_deploy_lane_enabled: false,
68
  forge_repo_owners: %{"sarah" => "OpenAgentsInc", "demo" => "OpenAgentsInc"}
68
  forge_repo_owners: %{"openagents.com" => "OpenAgentsInc", "demo" => "OpenAgentsInc"}
69 69
70 70
config :openagents, :migrate_on_boot, false
71 71
config :openagents, :ra_enabled, false
docs/2026-08-20-integration-hardening-and-staging-readiness-recommendations.md modified +37 -1

@@ -232,6 +232,42 @@ Add a repository check that searches source, tests, configuration, and docs for

232 232
service domains. Maintain a small allowlist of intentional Sarah-specific
233 233
locations. Fail the check on every unclassified match.
234 234
235
### Gate 2 implementation status
236
237
Completed on 2026-08-20:
238
239
- Renamed the generic runtime supervisor to `OpenAgents.RuntimeSupervisor` and
240
  the generic web macros to `openagents_live_view`, `openagents_html`, and
241
  `openagents_html_helpers`.
242
- Renamed the product component module to `OpenAgentsWeb.UI` and the style pack
243
  to `assets/css/openagents.css`. Updated the catalog, component tests, Basecoat
244
  notes, and contributor instructions with the new names.
245
- Consolidated lifted connection and data tests onto `OpenAgentsWeb.ConnCase`
246
  and `OpenAgents.DataCase`, renamed the shared channel case, removed unused
247
  lifted factory and fixture stubs, and removed duplicate lifted error tests.
248
- Replaced inherited runtime, Ra, forge, WAL, build-sidecar, test-peer, and
249
  coding-workspace names with OpenAgents names. The default forge repository is
250
  now the public `OpenAgentsInc/openagents.com` repository, represented as
251
  `openagents.com` in repository-scoped records and URLs.
252
- Extended repository validation to admit bounded domain-style names without
253
  admitting consecutive dots, trailing dots, path separators, uppercase
254
  names, or unconfigured repositories.
255
- Changed generic GitHub user agents, machine wire schemas, network and
256
  changelog schemas, observability schemas, and Git authentication labels to
257
  OpenAgents names.
258
- Versioned machine-token and voice-recording authenticated encryption. New
259
  ciphertext uses OpenAgents AAD and version 2; reads retain explicit support
260
  for legacy version-1 Sarah ciphertext so the rename does not strand existing
261
  encrypted staging data.
262
- Added `ops/ci/reference-check.sh` and a documented allowlist. `mix precommit`
263
  now rejects unclassified Sarah names, retired service domains, inherited
264
  filesystem roots, source-project symbols, builder labels, and repository
265
  names. The allowlist preserves persona language, stable behavior and data
266
  contracts, persona artifacts, historical migrations, and named historical
267
  documents.
268
- Verified the change with 15 browser tests, 1,222 default Elixir tests, and all
269
  9 isolated cluster tests, with zero failures.
270
235 271
**Exit criteria:** Every remaining Sarah reference is intentional, documented,
236 272
and specific to Sarah rather than inherited infrastructure.
237 273

@@ -1050,7 +1086,7 @@ each handoff.

1050 1086
## Final staging readiness checklist
1051 1087
1052 1088
- [x] The repository has one accurate architecture narrative.
1053
- [ ] Every remaining Sarah reference is intentional and specific.
1089
- [x] Every remaining Sarah reference is intentional and specific.
1054 1090
- [ ] All documentation links and invariant evidence resolve.
1055 1091
- [ ] The application has one Markdown parser, component system, and documented
1056 1092
      two-tier icon policy.
docs/decisions/0002-model-sarah-as-an-openagents-persona.md modified +3 -2

@@ -19,8 +19,9 @@ persona artifact IDs, behavior revisions, evaluations, visible identity, and

19 19
voice copy.
20 20
21 21
Use OpenAgents names for generic supervisors, web helpers, style packs,
22
runtime paths, build services, test cases, and configuration. Gate 2 performs
23
that semantic rename and adds an allowlist for intentional references.
22
runtime paths, build services, test cases, and configuration. Gate 2 completed
23
that semantic rename and added an executable allowlist for intentional
24
references.
24 25
25 26
## Consequences
26 27
docs/decisions/0005-use-basecoat-and-one-component-system.md modified +3 -2

@@ -18,8 +18,9 @@ component files that a surface uses. Wrap the system in one Phoenix component

18 18
module and use those components before adding surface-specific markup.
19 19
20 20
Do not add DaisyUI, another component library, remote fonts, icon fonts, or a
21
second Markdown presentation stack. Rename the generic `SarahUI` module and
22
style pack during Gate 2 without changing Sarah-specific visual copy.
21
second Markdown presentation stack. The generic component module is
22
`OpenAgentsWeb.UI`, and the product style pack is `assets/css/openagents.css`.
23
Keep Sarah-specific visual copy only where it identifies the persona.
23 24
24 25
## Consequences
25 26
lib/openagents/application.ex modified +1 -1

@@ -30,7 +30,7 @@ defmodule OpenAgents.Application do

30 30
      OpenAgents.Repo,
31 31
      {DNSCluster, query: Application.get_env(:openagents, :dns_cluster_query) || :ignore},
32 32
      {Phoenix.PubSub, name: OpenAgents.PubSub},
33
      OpenAgents.Sarah.Supervisor,
33
      OpenAgents.RuntimeSupervisor,
34 34
      # Start a worker by calling: OpenAgents.Worker.start_link(arg)
35 35
      # {OpenAgents.Worker, arg},
36 36
      # Start to serve requests, typically the last entry
lib/openagents/changelog.ex modified +1 -1

@@ -19,7 +19,7 @@ defmodule OpenAgents.Changelog do

19 19
  alias OpenAgents.Forge.{BuildReceipt, DeployReceipt, PushReceipt, Visibility}
20 20
  alias OpenAgents.Repo
21 21
22
  @schema_version "sarah.changelog.v1"
22
  @schema_version "openagents.changelog.v1"
23 23
  @cache_key {__MODULE__, :cache}
24 24
  @cache_ttl_ms 5_000
25 25
  @entry_limit 200
lib/openagents/changelog/backfill.ex modified +1 -1

@@ -14,7 +14,7 @@ defmodule OpenAgents.Changelog.Backfill do

14 14
15 15
  alias OpenAgents.Changelog
16 16
17
  @repo "sarah"
17
  @repo "openagents.com"
18 18
19 19
  @doc "Boot entrypoint: seed if configured, never raise."
20 20
  def boot do
lib/openagents/cluster/ra_bootstrap.ex modified +1 -1

@@ -33,7 +33,7 @@ defmodule OpenAgents.Cluster.RaBootstrap do

33 33
34 34
  @impl true
35 35
  def init(_opts) do
36
    data_dir = Application.get_env(:openagents, :ra_data_dir, "/tmp/sarah_ra")
36
    data_dir = Application.get_env(:openagents, :ra_data_dir, "/tmp/openagents_ra")
37 37
    expected = Application.get_env(:openagents, :ra_expected_size, 3)
38 38
39 39
    Ra.start_in(data_dir)
lib/openagents/forge/boot_converge.ex modified +1 -1

@@ -46,7 +46,7 @@ defmodule OpenAgents.Forge.BootConverge do

46 46
  end
47 47
48 48
  @doc false
49
  def converge(repo \\ "sarah") do
49
  def converge(repo \\ "openagents.com") do
50 50
    outcome =
51 51
      try do
52 52
        attempt(repo)
lib/openagents/forge/build_executor.ex modified +5 -5

@@ -4,7 +4,7 @@ defmodule OpenAgents.Forge.BuildExecutor do

4 4
  the set of changed `.beam` binaries for that commit.
5 5
6 6
  The production adapter is `OpenAgents.Forge.BuildExecutor.Sidecar`, which
7
  talks to the `sarah-builder` sidecar container through a file queue on
7
  talks to the `openagents-builder` sidecar container through a file queue on
8 8
  the shared workspace volume. Tests use `OpenAgents.Forge.FakeBuildExecutor`.
9 9
  The adapter is selected via the `:forge_build_executor` application env
10 10
  (see `OpenAgents.Forge.Builder`).

@@ -42,8 +42,8 @@ end

42 42
43 43
defmodule OpenAgents.Forge.BuildExecutor.Sidecar do
44 44
  @moduledoc """
45
  Production build adapter: talks to the `sarah-builder` sidecar container
46
  through a file queue on the shared workspace volume. The Sarah release
45
  Production build adapter: talks to the `openagents-builder` sidecar container
46
  through a file queue on the shared workspace volume. The OpenAgents release
47 47
  container runs unprivileged with no docker socket, so it cannot exec
48 48
  into the sidecar — the queue is the whole interface. Protocol (the
49 49
  watcher script lives in `ops/fleet/fleet-startup.sh`):

@@ -198,7 +198,7 @@ defmodule OpenAgents.Forge.BuildExecutor.Sidecar do

198 198
  """
199 199
  def warm? do
200 200
    build_dir =
201
      Application.get_env(:openagents, :forge_build_dir, "/var/lib/sarah/workspace/build")
201
      Application.get_env(:openagents, :forge_build_dir, "/var/lib/openagents/workspace/build")
202 202
203 203
    File.exists?(Path.join(build_dir, ".forge-manifest"))
204 204
  end

@@ -207,7 +207,7 @@ defmodule OpenAgents.Forge.BuildExecutor.Sidecar do

207 207
    Application.get_env(
208 208
      :openagents,
209 209
      :forge_build_queue_dir,
210
      "/var/lib/sarah/workspace/build-queue"
210
      "/var/lib/openagents/workspace/build-queue"
211 211
    )
212 212
  end
213 213
lib/openagents/forge/repos.ex modified +5 -5

@@ -8,17 +8,17 @@ defmodule OpenAgents.Forge.Repos do

8 8
  ever carry request data.
9 9
  """
10 10
11
  @name_pattern ~r/^[a-z0-9][a-z0-9_-]{0,63}$/
11
  @name_pattern ~r/^[a-z0-9](?:[a-z0-9_-]|\.(?=[a-z0-9])){0,63}$/
12 12
13 13
  @doc "The forge data directory (bare repos + WAL cache + beam artifacts)."
14 14
  def data_dir do
15 15
    Application.get_env(:openagents, :forge_data_dir) ||
16
      Path.join(System.tmp_dir!(), "sarah_forge_data")
16
      Path.join(System.tmp_dir!(), "openagents_forge_data")
17 17
  end
18 18
19 19
  @doc "Repositories this forge serves. Bounded, config-owned."
20 20
  def allowed_repos do
21
    Application.get_env(:openagents, :forge_repos, ["sarah"])
21
    Application.get_env(:openagents, :forge_repos, ["openagents.com"])
22 22
  end
23 23
24 24
  def valid_name?(name) when is_binary(name) do

@@ -84,7 +84,7 @@ defmodule OpenAgents.Forge.Repos do

84 84
85 85
  @doc "The WAL sequence this bare repo has applied (cache freshness marker)."
86 86
  def applied_seq(repo) do
87
    case File.read(Path.join(bare_path(repo), "sarah-wal-seq")) do
87
    case File.read(Path.join(bare_path(repo), "openagents-wal-seq")) do
88 88
      {:ok, contents} ->
89 89
        case Integer.parse(String.trim(contents)) do
90 90
          {seq, _} -> seq

@@ -97,7 +97,7 @@ defmodule OpenAgents.Forge.Repos do

97 97
  end
98 98
99 99
  def record_applied_seq!(repo, seq) when is_integer(seq) do
100
    File.write!(Path.join(bare_path(repo), "sarah-wal-seq"), Integer.to_string(seq))
100
    File.write!(Path.join(bare_path(repo), "openagents-wal-seq"), Integer.to_string(seq))
101 101
  end
102 102
103 103
  @doc "Run git with `--git-dir` pinned to the bare repo. Returns {output, status}."
lib/openagents/forge/visibility.ex modified +1 -1

@@ -84,7 +84,7 @@ defmodule OpenAgents.Forge.Visibility do

84 84
  @doc """
85 85
  The owning account for `repo` — the first path segment of its public URL,
86 86
  so a forge URL is shaped exactly like the GitHub one it replaces
87
  (`/OpenAgentsInc/sarah/blob/main/README.md`). Operator-owned config.
87
  (`/OpenAgentsInc/openagents.com/blob/main/README.md`). Operator-owned config.
88 88
  """
89 89
  def owner(repo) when is_binary(repo) do
90 90
    :openagents
lib/openagents/forge/wal.ex modified +1 -1

@@ -48,7 +48,7 @@ defmodule OpenAgents.Forge.WAL do

48 48
  @callback put_object(repo, object_key :: String.t(), payload :: binary()) ::
49 49
              {:ok, String.t()} | {:error, term}
50 50
51
  @repo_pattern ~r/^[a-z0-9][a-z0-9_-]*$/
51
  @repo_pattern ~r/^[a-z0-9](?:[a-z0-9_-]|\.(?=[a-z0-9]))*$/
52 52
  @entry_key_pattern ~r/^entries\/[0-9]{8}-[0-9a-f]{12}$/
53 53
  @artifact_key_pattern ~r/^artifacts\/[0-9a-f]{7,40}\.tar$/
54 54
lib/openagents/forge/wal/local.ex modified +2 -2

@@ -3,7 +3,7 @@ defmodule OpenAgents.Forge.WAL.Local do

3 3
  Filesystem adapter for `OpenAgents.Forge.WAL` — the development and test backend.
4 4
5 5
  Layout under the base directory (`Application.get_env(:openagents, :forge_wal_dir)`,
6
  defaulting to `sarah_forge_wal` inside `System.tmp_dir!/0`):
6
  defaulting to `openagents_forge_wal` inside `System.tmp_dir!/0`):
7 7
8 8
      <base>/<repo>/index.json
9 9
      <base>/<repo>/entries/<key>

@@ -118,6 +118,6 @@ defmodule OpenAgents.Forge.WAL.Local do

118 118
119 119
  defp base_dir do
120 120
    Application.get_env(:openagents, :forge_wal_dir) ||
121
      Path.join(System.tmp_dir!(), "sarah_forge_wal")
121
      Path.join(System.tmp_dir!(), "openagents_forge_wal")
122 122
  end
123 123
end
lib/openagents/github.ex modified +1 -1

@@ -2,7 +2,7 @@ defmodule OpenAgents.GitHub do

2 2
  @moduledoc "Server-side GitHub REST API access using the signed-in user's OAuth token."
3 3
4 4
  @github_api_version "2022-11-28"
5
  @user_agent "OpenAgents-Sarah"
5
  @user_agent "OpenAgents"
6 6
  @full_name_regex ~r/\A[A-Za-z0-9][A-Za-z0-9-]*\/[A-Za-z0-9._-]+\z/
7 7
  @maximum_file_bytes 65_536
8 8
lib/openagents/github_oauth.ex modified +1 -1

@@ -9,7 +9,7 @@ defmodule OpenAgents.GitHubOAuth do

9 9
  @default_user_url "https://api.github.com/user"
10 10
  @default_attempt_ttl_seconds 600
11 11
  @github_api_version "2022-11-28"
12
  @user_agent "OpenAgents-Sarah"
12
  @user_agent "OpenAgents"
13 13
14 14
  @type attempt :: %{required(String.t()) => String.t() | integer()}
15 15
lib/openagents/incidents/fixer.ex modified +1 -1

@@ -105,7 +105,7 @@ defmodule OpenAgents.Incidents.Fixer do

105 105
    context: #{context}
106 106
107 107
    Delegate to a coding agent on the paired computer. Reproduce the failure in
108
    the OpenAgentsInc/sarah repo if you can, diagnose the root cause, and propose
108
    the OpenAgentsInc/openagents.com repo if you can, diagnose the root cause, and propose
109 109
    a MINIMAL patch by opening a pull request. Do NOT deploy and do NOT push to
110 110
    main directly — propose only. Report what you found and what you changed.
111 111
    """
lib/openagents/machines/token_vault.ex modified +12 -5

@@ -1,7 +1,10 @@

1 1
defmodule OpenAgents.Machines.TokenVault do
2 2
  @moduledoc "AES-256-GCM sealing for machine tokens awaiting pairing claim."
3 3
4
  @aad "sarah.machine_token.v1"
4
  @version 2
5
  @legacy_version 1
6
  @aad "openagents.machine_token.v2"
7
  @legacy_aad "sarah.machine_token.v1"
5 8
  @nonce_bytes 12
6 9
  @tag_bytes 16
7 10
  @maximum_token_bytes 512

@@ -14,7 +17,7 @@ defmodule OpenAgents.Machines.TokenVault do

14 17
      {ciphertext, tag} =
15 18
        :crypto.crypto_one_time_aead(:aes_256_gcm, key, nonce, token, @aad, true)
16 19
17
      {:ok, <<1, nonce::binary, tag::binary, ciphertext::binary>>}
20
      {:ok, <<@version, nonce::binary, tag::binary, ciphertext::binary>>}
18 21
    end
19 22
  end
20 23

@@ -22,10 +25,14 @@ defmodule OpenAgents.Machines.TokenVault do

22 25
23 26
  @spec open(binary()) :: {:ok, String.t()} | {:error, atom()}
24 27
  def open(
25
        <<1, nonce::binary-size(@nonce_bytes), tag::binary-size(@tag_bytes), ciphertext::binary>>
26
      ) do
28
        <<version, nonce::binary-size(@nonce_bytes), tag::binary-size(@tag_bytes),
29
          ciphertext::binary>>
30
      )
31
      when version in [@legacy_version, @version] do
27 32
    with {:ok, key} <- key() do
28
      case :crypto.crypto_one_time_aead(:aes_256_gcm, key, nonce, ciphertext, @aad, tag, false) do
33
      aad = if version == @version, do: @aad, else: @legacy_aad
34
35
      case :crypto.crypto_one_time_aead(:aes_256_gcm, key, nonce, ciphertext, aad, tag, false) do
29 36
        token when is_binary(token) -> {:ok, token}
30 37
        :error -> {:error, :token_unsealable}
31 38
      end
lib/openagents/modules/metadata.ex modified +1 -1

@@ -40,7 +40,7 @@ defmodule OpenAgents.Modules.Metadata do

40 40
      },
41 41
      "publisher" => "OpenAgentsInc",
42 42
      "provenance" => %{
43
        "source" => "https://github.com/OpenAgentsInc/sarah",
43
        "source" => "https://github.com/OpenAgentsInc/openagents.com",
44 44
        "admission" => "reviewed_first_party_release"
45 45
      },
46 46
      "compatibility" => %{
lib/openagents/network_status.ex modified +4 -2

@@ -23,7 +23,7 @@ defmodule OpenAgents.NetworkStatus do

23 23
  up/down and the status page's slow tick call `broadcast/0`).
24 24
  """
25 25
26
  @schema "sarah.network_status.v1"
26
  @schema "openagents.network_status.v1"
27 27
  @topic "network_status"
28 28
  @cache_key {__MODULE__, :cache}
29 29
  @cache_ms 2_000

@@ -119,7 +119,9 @@ defmodule OpenAgents.NetworkStatus do

119 119
  # Every read degrades to nil/[] — forge disabled or DB down renders as
120 120
  # "no deploys yet", never an error.
121 121
  defp forge_section do
122
    repo = safely(fn -> List.first(OpenAgents.Forge.Repos.allowed_repos()) end) || "sarah"
122
    repo =
123
      safely(fn -> List.first(OpenAgents.Forge.Repos.allowed_repos()) end) || "openagents.com"
124
123 125
    targets = safely(fn -> OpenAgents.Forge.Targets.recent(repo, 10) end) || []
124 126
    deploys = safely(fn -> OpenAgents.Forge.recent_deploys(repo, 10) end) || []
125 127
lib/openagents/observability/readback.ex modified +1 -1

@@ -17,7 +17,7 @@ defmodule OpenAgents.Observability.Readback do

17 17
    voice_config = OpenAgents.Voice.Config.current!()
18 18
19 19
    %{
20
      schema: "sarah.observability.readback.v1",
20
      schema: "openagents.observability.readback.v1",
21 21
      captured_at: DateTime.utc_now() |> DateTime.to_iso8601(),
22 22
      persona_id: OpenAgents.Persona.current!().id,
23 23
      persona_digest: OpenAgents.Persona.current!().digest,
lib/openagents/observability/release_gate.ex modified +1 -1

@@ -13,7 +13,7 @@ defmodule OpenAgents.Observability.ReleaseGate do

13 13
    warnings = exceeded(integrity, @warnings)
14 14
15 15
    %{
16
      schema: "sarah.observability.release_gate.v1",
16
      schema: "openagents.observability.release_gate.v1",
17 17
      status: if(blockers == [], do: "passed", else: "blocked"),
18 18
      blockers: blockers,
19 19
      warnings: warnings,
lib/openagents/sarah/supervisor.ex → lib/openagents/runtime_supervisor.ex renamed +2 -2

@@ -1,6 +1,6 @@

1
defmodule OpenAgents.Sarah.Supervisor do
1
defmodule OpenAgents.RuntimeSupervisor do
2 2
  @moduledoc """
3
  Supervisor for the ported Sarah subsystems.
3
  Supervisor for the integrated OpenAgents runtime subsystems.
4 4
5 5
  Starts the registries and supervisors used by turns, work, voice, and
6 6
  memory workers. Heavy recovery and retention workers are gated by feature
lib/openagents/tools/code_check.ex modified +2 -2

@@ -38,10 +38,10 @@ defmodule OpenAgents.Tools.CodeCheck do

38 38
      required_authority: "code.execute",
39 39
      executor: %{
40 40
        id: "sarah.repository.self",
41
        disclosure: "Sarah's own runtime, parsing candidate code in a throwaway process"
41
        disclosure: "the OpenAgents runtime, parsing candidate code in a throwaway process"
42 42
      },
43 43
      maintainer: "OpenAgents",
44
      attribution: ["OpenAgentsInc/sarah"],
44
      attribution: ["OpenAgentsInc/openagents.com"],
45 45
      policy_facets: %{
46 46
        "privacy" => "browser_conversation",
47 47
        "residency" => "application_process",
lib/openagents/tools/computer_agent.ex modified +1 -1

@@ -59,7 +59,7 @@ defmodule OpenAgents.Tools.ComputerAgent do

59 59
      },
60 60
      maintainer: "OpenAgents",
61 61
      attribution: [
62
        "OpenAgentsInc/sarah",
62
        "OpenAgentsInc/openagents.com",
63 63
        "OpenAgentsInc/sarah-computer-controller",
64 64
        "agentclientprotocol/agent-client-protocol"
65 65
      ],
lib/openagents/tools/computer_devin.ex modified +1 -1

@@ -52,7 +52,7 @@ defmodule OpenAgents.Tools.ComputerDevin do

52 52
      },
53 53
      maintainer: "OpenAgents",
54 54
      attribution: [
55
        "OpenAgentsInc/sarah",
55
        "OpenAgentsInc/openagents.com",
56 56
        "OpenAgentsInc/sarah-computer-controller",
57 57
        "agentclientprotocol/agent-client-protocol"
58 58
      ],
lib/openagents/tools/computer_list.ex modified +1 -1

@@ -27,7 +27,7 @@ defmodule OpenAgents.Tools.ComputerList do

27 27
        disclosure: "Paired machine records for the signed-in owner"
28 28
      },
29 29
      maintainer: "OpenAgents",
30
      attribution: ["OpenAgentsInc/sarah"],
30
      attribution: ["OpenAgentsInc/openagents.com"],
31 31
      policy_facets: %{
32 32
        "privacy" => "signed_browser_owner",
33 33
        "residency" => "application_process",
lib/openagents/tools/computer_probe.ex modified +1 -1

@@ -37,7 +37,7 @@ defmodule OpenAgents.Tools.ComputerProbe do

37 37
        disclosure: "Read-only discovery on the user's paired computer"
38 38
      },
39 39
      maintainer: "OpenAgents",
40
      attribution: ["OpenAgentsInc/sarah", "OpenAgentsInc/sarah-computer-controller"],
40
      attribution: ["OpenAgentsInc/openagents.com", "OpenAgentsInc/sarah-computer-controller"],
41 41
      policy_facets: %{
42 42
        "privacy" => "signed_browser_owner",
43 43
        "residency" => "operator_machine",
lib/openagents/tools/computer_run.ex modified +1 -1

@@ -41,7 +41,7 @@ defmodule OpenAgents.Tools.ComputerRun do

41 41
        disclosure: "Command execution on the user's paired computer, bounded by its local policy"
42 42
      },
43 43
      maintainer: "OpenAgents",
44
      attribution: ["OpenAgentsInc/sarah", "OpenAgentsInc/sarah-computer-controller"],
44
      attribution: ["OpenAgentsInc/openagents.com", "OpenAgentsInc/sarah-computer-controller"],
45 45
      policy_facets: %{
46 46
        "privacy" => "signed_browser_owner",
47 47
        "residency" => "operator_machine",
lib/openagents/tools/conversation_read.ex modified +1 -1

@@ -27,7 +27,7 @@ defmodule OpenAgents.Tools.ConversationRead do

27 27
        disclosure: "Sarah conversation recall"
28 28
      },
29 29
      maintainer: "OpenAgents",
30
      attribution: ["OpenAgentsInc/sarah"],
30
      attribution: ["OpenAgentsInc/openagents.com"],
31 31
      policy_facets: %{
32 32
        "privacy" => "browser_conversation",
33 33
        "residency" => "application_postgres"
lib/openagents/tools/conversation_search.ex modified +1 -1

@@ -26,7 +26,7 @@ defmodule OpenAgents.Tools.ConversationSearch do

26 26
        disclosure: "Sarah conversation recall"
27 27
      },
28 28
      maintainer: "OpenAgents",
29
      attribution: ["OpenAgentsInc/sarah"],
29
      attribution: ["OpenAgentsInc/openagents.com"],
30 30
      policy_facets: %{
31 31
        "privacy" => "browser_conversation",
32 32
        "residency" => "application_postgres",
lib/openagents/tools/deep_work.ex modified +1 -1

@@ -36,7 +36,7 @@ defmodule OpenAgents.Tools.DeepWork do

36 36
      required_authority: "work.delegate",
37 37
      executor: %{id: "sarah.work.jobs", disclosure: "Sarah durable work jobs"},
38 38
      maintainer: "OpenAgents",
39
      attribution: ["OpenAgentsInc/sarah"],
39
      attribution: ["OpenAgentsInc/openagents.com"],
40 40
      policy_facets: %{
41 41
        "privacy" => "browser_conversation",
42 42
        "residency" => "application_postgres",
lib/openagents/tools/github_repo_list.ex modified +1 -1

@@ -24,7 +24,7 @@ defmodule OpenAgents.Tools.GitHubRepoList do

24 24
      required_authority: "github.read",
25 25
      executor: %{id: "sarah.github.api", disclosure: "GitHub API with the user's authorization"},
26 26
      maintainer: "OpenAgents",
27
      attribution: ["OpenAgentsInc/sarah"],
27
      attribution: ["OpenAgentsInc/openagents.com"],
28 28
      policy_facets: %{
29 29
        "privacy" => "signed_browser_owner",
30 30
        "residency" => "application_process",
lib/openagents/tools/github_repo_read.ex modified +1 -1

@@ -25,7 +25,7 @@ defmodule OpenAgents.Tools.GitHubRepoRead do

25 25
      required_authority: "github.read",
26 26
      executor: %{id: "sarah.github.api", disclosure: "GitHub API with the user's authorization"},
27 27
      maintainer: "OpenAgents",
28
      attribution: ["OpenAgentsInc/sarah"],
28
      attribution: ["OpenAgentsInc/openagents.com"],
29 29
      policy_facets: %{
30 30
        "privacy" => "signed_browser_owner",
31 31
        "residency" => "application_process",
lib/openagents/tools/incident_lookup.ex modified +1 -1

@@ -47,7 +47,7 @@ defmodule OpenAgents.Tools.IncidentLookup do

47 47
        disclosure: "Durable failure records for the signed-in owner"
48 48
      },
49 49
      maintainer: "OpenAgents",
50
      attribution: ["OpenAgentsInc/sarah"],
50
      attribution: ["OpenAgentsInc/openagents.com"],
51 51
      policy_facets: %{
52 52
        "privacy" => "signed_browser_owner",
53 53
        "residency" => "application_process",
lib/openagents/tools/memory_contract.ex modified +1 -1

@@ -17,7 +17,7 @@ defmodule OpenAgents.Tools.MemoryContract do

17 17
      required_authority: authority,
18 18
      executor: %{id: "sarah.postgres.profile_memory", disclosure: "Sarah profile memory"},
19 19
      maintainer: "OpenAgents",
20
      attribution: ["OpenAgentsInc/sarah", "OpenResponses/2026-04-24"],
20
      attribution: ["OpenAgentsInc/openagents.com", "OpenResponses/2026-04-24"],
21 21
      policy_facets: %{
22 22
        "privacy" => "signed_browser_owner",
23 23
        "residency" => "application_postgres",
lib/openagents/tools/module_discover.ex modified +1 -1

@@ -24,7 +24,7 @@ defmodule OpenAgents.Tools.ModuleDiscover do

24 24
      required_authority: "module.discover",
25 25
      executor: %{id: "sarah.module.discovery", disclosure: "Sarah module discovery"},
26 26
      maintainer: "OpenAgents",
27
      attribution: ["OpenAgentsInc/sarah"],
27
      attribution: ["OpenAgentsInc/openagents.com"],
28 28
      policy_facets: %{
29 29
        "privacy" => "public_catalog_metadata_only",
30 30
        "residency" => "application_process"
lib/openagents/tools/repo_commit_push.ex modified +5 -5

@@ -1,7 +1,7 @@

1 1
defmodule OpenAgents.Tools.RepoCommitPush do
2 2
  @moduledoc """
3
  Commits this job's clone and pushes to the job's own `sarah/job-<id>`
4
  branch on Sarah's own forge — never GitHub, never another ref
3
  Commits this job's clone and pushes to the job's own `openagents/job-<id>`
4
  branch on the OpenAgents forge — never GitHub, never another ref
5 5
  (SELF-EDIT-001). The commit SHA lands in the tool outcome receipt; the
6 6
  forge's WAL entry for the push is the durable artifact digest.
7 7
  """

@@ -19,7 +19,7 @@ defmodule OpenAgents.Tools.RepoCommitPush do

19 19
      version: 1,
20 20
      description:
21 21
        "Commits all changes in this coding job's clone and pushes them to this job's own " <>
22
          "branch on Sarah's forge. Pass message as the commit message. The push branch is " <>
22
          "branch on the OpenAgents forge. Pass message as the commit message. The push branch is " <>
23 23
          "fixed to this job; any other branch is refused. Returns the commit sha.",
24 24
      input_schema: %{
25 25
        "type" => "object",

@@ -36,10 +36,10 @@ defmodule OpenAgents.Tools.RepoCommitPush do

36 36
      required_authority: "repository.write",
37 37
      executor: %{
38 38
        id: "sarah.repository.self",
39
        disclosure: "Sarah's own runtime, pushing this job's branch to her own forge"
39
        disclosure: "the OpenAgents runtime, pushing this job's branch to her own forge"
40 40
      },
41 41
      maintainer: "OpenAgents",
42
      attribution: ["OpenAgentsInc/sarah"],
42
      attribution: ["OpenAgentsInc/openagents.com"],
43 43
      policy_facets: %{
44 44
        "privacy" => "browser_conversation",
45 45
        "residency" => "host",
lib/openagents/tools/repo_edit.ex modified +2 -2

@@ -39,10 +39,10 @@ defmodule OpenAgents.Tools.RepoEdit do

39 39
      required_authority: "repository.write",
40 40
      executor: %{
41 41
        id: "sarah.repository.self",
42
        disclosure: "Sarah's own runtime, editing this job's clone of her repository"
42
        disclosure: "the OpenAgents runtime, editing this job's clone of her repository"
43 43
      },
44 44
      maintainer: "OpenAgents",
45
      attribution: ["OpenAgentsInc/sarah"],
45
      attribution: ["OpenAgentsInc/openagents.com"],
46 46
      policy_facets: %{
47 47
        "privacy" => "browser_conversation",
48 48
        "residency" => "host",
lib/openagents/tools/repo_grep.ex modified +4 -4

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.Tools.RepoGrep do
2
  @moduledoc "Searches Sarah's own source for a pattern, bounded, without shelling out."
2
  @moduledoc "Searches OpenAgents source for a pattern, bounded, without shelling out."
3 3
4 4
  @behaviour OpenAgents.Tools.Tool
5 5

@@ -17,7 +17,7 @@ defmodule OpenAgents.Tools.RepoGrep do

17 17
      name: "repo_grep",
18 18
      version: 1,
19 19
      description:
20
        "Searches Sarah's own source code for a regular expression. Returns matching " <>
20
        "Searches OpenAgents source code for a regular expression. Returns matching " <>
21 21
          "path:line:text rows, bounded. Optional glob filters files (e.g. \"lib/**/*.ex\"); " <>
22 22
          "from selects \"image\" (running source, default) or \"workspace\" (this job's clone).",
23 23
      input_schema: %{

@@ -37,10 +37,10 @@ defmodule OpenAgents.Tools.RepoGrep do

37 37
      required_authority: "repository.read",
38 38
      executor: %{
39 39
        id: "sarah.repository.self",
40
        disclosure: "Sarah's own runtime, searching her running source"
40
        disclosure: "the OpenAgents runtime, searching her running source"
41 41
      },
42 42
      maintainer: "OpenAgents",
43
      attribution: ["OpenAgentsInc/sarah"],
43
      attribution: ["OpenAgentsInc/openagents.com"],
44 44
      policy_facets: %{
45 45
        "privacy" => "browser_conversation",
46 46
        "residency" => "host",
lib/openagents/tools/repo_list.ex modified +4 -4

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.Tools.RepoList do
2
  @moduledoc "Lists a directory of Sarah's own source tree, bounded."
2
  @moduledoc "Lists a directory of OpenAgents source tree, bounded."
3 3
4 4
  @behaviour OpenAgents.Tools.Tool
5 5

@@ -16,7 +16,7 @@ defmodule OpenAgents.Tools.RepoList do

16 16
      name: "repo_list",
17 17
      version: 1,
18 18
      description:
19
        "Lists files and directories in Sarah's own source tree. Pass path relative to the " <>
19
        "Lists files and directories in OpenAgents source tree. Pass path relative to the " <>
20 20
          "repository root (default: the root). from selects \"image\" (running source, " <>
21 21
          "default) or \"workspace\" (this job's clone).",
22 22
      input_schema: %{

@@ -33,10 +33,10 @@ defmodule OpenAgents.Tools.RepoList do

33 33
      required_authority: "repository.read",
34 34
      executor: %{
35 35
        id: "sarah.repository.self",
36
        disclosure: "Sarah's own runtime, listing her running source"
36
        disclosure: "the OpenAgents runtime, listing her running source"
37 37
      },
38 38
      maintainer: "OpenAgents",
39
      attribution: ["OpenAgentsInc/sarah"],
39
      attribution: ["OpenAgentsInc/openagents.com"],
40 40
      policy_facets: %{
41 41
        "privacy" => "browser_conversation",
42 42
        "residency" => "host",
lib/openagents/tools/repo_read.ex modified +4 -4

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.Tools.RepoRead do
2
  @moduledoc "Reads one file from Sarah's own source (baked image tree or the job's clone)."
2
  @moduledoc "Reads one file from OpenAgents source (baked image tree or the job's clone)."
3 3
4 4
  @behaviour OpenAgents.Tools.Tool
5 5

@@ -15,7 +15,7 @@ defmodule OpenAgents.Tools.RepoRead do

15 15
      name: "repo_read",
16 16
      version: 1,
17 17
      description:
18
        "Reads one file from Sarah's own source code. Pass path relative to the repository " <>
18
        "Reads one file from OpenAgents source code. Pass path relative to the repository " <>
19 19
          "root. from selects the tree: \"image\" (default) reads the source of the code " <>
20 20
          "currently running; \"workspace\" reads this job's editable clone.",
21 21
      input_schema: %{

@@ -33,10 +33,10 @@ defmodule OpenAgents.Tools.RepoRead do

33 33
      required_authority: "repository.read",
34 34
      executor: %{
35 35
        id: "sarah.repository.self",
36
        disclosure: "Sarah's own runtime, reading her running source"
36
        disclosure: "the OpenAgents runtime, reading her running source"
37 37
      },
38 38
      maintainer: "OpenAgents",
39
      attribution: ["OpenAgentsInc/sarah"],
39
      attribution: ["OpenAgentsInc/openagents.com"],
40 40
      policy_facets: %{
41 41
        "privacy" => "browser_conversation",
42 42
        "residency" => "host",
lib/openagents/tools/repo_write.ex modified +2 -2

@@ -30,10 +30,10 @@ defmodule OpenAgents.Tools.RepoWrite do

30 30
      required_authority: "repository.write",
31 31
      executor: %{
32 32
        id: "sarah.repository.self",
33
        disclosure: "Sarah's own runtime, writing in this job's clone of her repository"
33
        disclosure: "the OpenAgents runtime, writing in this job's clone of her repository"
34 34
      },
35 35
      maintainer: "OpenAgents",
36
      attribution: ["OpenAgentsInc/sarah"],
36
      attribution: ["OpenAgentsInc/openagents.com"],
37 37
      policy_facets: %{
38 38
        "privacy" => "browser_conversation",
39 39
        "residency" => "host",
lib/openagents/tools/repository.ex modified +6 -6

@@ -10,7 +10,7 @@ defmodule OpenAgents.Tools.Repository do

10 10
  - **Per-job clone** (`workspace_dir/1`): a working tree cloned from the
11 11
    local forge bare repo (origin is never GitHub) under the job's own
12 12
    directory. The ONLY place mutation tools may act, and pushes from it go
13
    only to that job's `sarah/job-<id>` branch. Removed when the job ends.
13
    only to that job's `openagents/job-<id>` branch. Removed when the job ends.
14 14
15 15
  Path handling fails closed: every path is resolved and verified to stay
16 16
  inside its root before any filesystem call.

@@ -19,11 +19,11 @@ defmodule OpenAgents.Tools.Repository do

19 19
  alias OpenAgents.Forge.Repos
20 20
  alias OpenAgents.Forge.Sync
21 21
22
  @repo "sarah"
22
  @repo "openagents.com"
23 23
24 24
  @doc "The baked source root (the running code's own tree)."
25 25
  def source_dir do
26
    Application.get_env(:openagents, :sarah_source_dir, File.cwd!())
26
    Application.get_env(:openagents, :source_repo_dir, File.cwd!())
27 27
  end
28 28
29 29
  @doc "The workspace root that holds all per-job clones."

@@ -31,7 +31,7 @@ defmodule OpenAgents.Tools.Repository do

31 31
    Application.get_env(
32 32
      :openagents,
33 33
      :coding_jobs_dir,
34
      Path.join(System.tmp_dir!(), "sarah-coding-jobs")
34
      Path.join(System.tmp_dir!(), "openagents-coding-jobs")
35 35
    )
36 36
  end
37 37

@@ -42,7 +42,7 @@ defmodule OpenAgents.Tools.Repository do

42 42
  def workspace_dir("work-job:" <> job_id), do: Path.join(jobs_dir(), "job-" <> job_id)
43 43
44 44
  @doc "This job's push branch — the only ref it may ever push."
45
  def job_branch("work-job:" <> job_id), do: "sarah/job-" <> job_id
45
  def job_branch("work-job:" <> job_id), do: "openagents/job-" <> job_id
46 46
47 47
  @doc """
48 48
  Ensure the per-job clone exists, cloning from the LOCAL forge bare repo

@@ -156,7 +156,7 @@ defmodule OpenAgents.Tools.Repository do

156 156
  Approval receipts for the repository mutation modules, minted for a coding
157 157
  job. Starting the coding job is the person's exact current consent for the
158 158
  reversible writes inside the job's own clone; the operator posture for the
159
  one external effect (a push to the job's own branch on Sarah's own forge)
159
  one external effect (a push to the job's own branch on the OpenAgents forge)
160 160
  is host policy: the branch is sandboxed and promotion stays operator-only
161 161
  (SELF-EDIT-001).
162 162
  """
lib/openagents/tools/runner.ex modified +1 -1

@@ -383,7 +383,7 @@ defmodule OpenAgents.Tools.Runner do

383 383
    do: "old_string matches more than once — include more surrounding context or set replace_all."
384 384
385 385
  defp error_message(:branch_refused),
386
    do: "A coding job may push only to its own sarah/job-<id> branch."
386
    do: "A coding job may push only to its own openagents/job-<id> branch."
387 387
388 388
  defp error_message(:nothing_to_commit), do: "The workspace has no changes to commit."
389 389
  defp error_message(:commit_failed), do: "git commit failed in the job workspace."
lib/openagents/voice/recording_vault.ex modified +11 -6

@@ -12,7 +12,8 @@ defmodule OpenAgents.Voice.RecordingVault do

12 12
13 13
  @nonce_bytes 12
14 14
  @tag_bytes 16
15
  @version 1
15
  @version 2
16
  @legacy_version 1
16 17
17 18
  @spec configured?() :: boolean()
18 19
  def configured?, do: match?({:ok, _key}, key())

@@ -23,7 +24,7 @@ defmodule OpenAgents.Voice.RecordingVault do

23 24
             is_integer(sequence) and sequence > 0 do
24 25
    with {:ok, key} <- key() do
25 26
      nonce = :crypto.strong_rand_bytes(@nonce_bytes)
26
      aad = aad(recording_id, sequence)
27
      aad = aad(@version, recording_id, sequence)
27 28
28 29
      {ciphertext, tag} =
29 30
        :crypto.crypto_one_time_aead(:aes_256_gcm, key, nonce, plaintext, aad, true)

@@ -36,19 +37,20 @@ defmodule OpenAgents.Voice.RecordingVault do

36 37
37 38
  @spec open(binary(), Ecto.UUID.t(), pos_integer()) :: {:ok, binary()} | {:error, atom()}
38 39
  def open(
39
        <<@version, nonce::binary-size(@nonce_bytes), tag::binary-size(@tag_bytes),
40
        <<version, nonce::binary-size(@nonce_bytes), tag::binary-size(@tag_bytes),
40 41
          ciphertext::binary>>,
41 42
        recording_id,
42 43
        sequence
43 44
      )
44
      when is_binary(recording_id) and is_integer(sequence) and sequence > 0 do
45
      when version in [@legacy_version, @version] and is_binary(recording_id) and
46
             is_integer(sequence) and sequence > 0 do
45 47
    with {:ok, key} <- key() do
46 48
      case :crypto.crypto_one_time_aead(
47 49
             :aes_256_gcm,
48 50
             key,
49 51
             nonce,
50 52
             ciphertext,
51
             aad(recording_id, sequence),
53
             aad(version, recording_id, sequence),
52 54
             tag,
53 55
             false
54 56
           ) do

@@ -60,7 +62,10 @@ defmodule OpenAgents.Voice.RecordingVault do

60 62
61 63
  def open(_sealed, _recording_id, _sequence), do: {:error, :chunk_unsealable}
62 64
63
  defp aad(recording_id, sequence),
65
  defp aad(@version, recording_id, sequence),
66
    do: "openagents.voice_recording_chunk.v2:#{recording_id}:#{sequence}"
67
68
  defp aad(@legacy_version, recording_id, sequence),
64 69
    do: "sarah.voice_recording_chunk.v1:#{recording_id}:#{sequence}"
65 70
66 71
  defp key do
lib/openagents_web.ex modified +7 -7

@@ -60,21 +60,21 @@ defmodule OpenAgentsWeb do

60 60
    end
61 61
  end
62 62
63
  def sarah_live_view do
63
  def openagents_live_view do
64 64
    quote do
65 65
      use Phoenix.LiveView
66 66
67
      unquote(sarah_html_helpers())
67
      unquote(openagents_html_helpers())
68 68
69 69
      import OpenAgentsWeb.Components.RepoHeader
70 70
    end
71 71
  end
72 72
73
  def sarah_html do
73
  def openagents_html do
74 74
    quote do
75 75
      use Phoenix.Component
76 76
77
      unquote(sarah_html_helpers())
77
      unquote(openagents_html_helpers())
78 78
    end
79 79
  end
80 80

@@ -118,12 +118,12 @@ defmodule OpenAgentsWeb do

118 118
    end
119 119
  end
120 120
121
  defp sarah_html_helpers do
121
  defp openagents_html_helpers do
122 122
    quote do
123 123
      # HTML escaping functionality
124 124
      import Phoenix.HTML
125
      # Sarah interface primitives
126
      import OpenAgentsWeb.SarahUI
125
      # OpenAgents interface primitives
126
      import OpenAgentsWeb.UI
127 127
128 128
      # Common modules used in templates
129 129
      alias Phoenix.LiveView.JS
lib/openagents_web/channels/computer_channel.ex modified +2 -2

@@ -1,6 +1,6 @@

1 1
defmodule OpenAgentsWeb.ComputerChannel do
2 2
  @moduledoc """
3
  `sarah.computer.v1` — the live channel a paired controller keeps open.
3
  `openagents.computer.v1` — the live channel a paired controller keeps open.
4 4
5 5
  Server to controller: `probe`, `run`, `agent`, `devin`, and `cancel` (each correlated
6 6
  by a server-minted `request_id`). Controller to server: `hello` (agent

@@ -27,7 +27,7 @@ defmodule OpenAgentsWeb.ComputerChannel do

27 27
            {:ok, _owner} ->
28 28
              Phoenix.PubSub.subscribe(OpenAgents.PubSub, "machine:#{machine.id}")
29 29
              Machines.record_seen(machine)
30
              {:ok, %{"protocol" => "sarah.computer.v1"}, assign(socket, :machine, machine)}
30
              {:ok, %{"protocol" => "openagents.computer.v1"}, assign(socket, :machine, machine)}
31 31
32 32
            # A prior registration (usually from a node that just died) hasn't
33 33
            # been pruned yet — refuse the join; the controller's reconnect
lib/openagents_web/component_catalog.ex modified +43 -43

@@ -14,9 +14,9 @@ defmodule OpenAgentsWeb.ComponentCatalog do

14 14
15 15
    * `OpenAgentsWeb.CoreComponents` — the Phoenix-generated set, restyled onto
16 16
      basecoat. Imported by `use OpenAgentsWeb, :live_view`.
17
    * `OpenAgentsWeb.SarahUI` — the Sarah interface primitives, imported
18
      separately via `sarah_html_helpers`. `button`, `input`, and `icon` exist
19
      in both sets, which is why the SarahUI slugs are prefixed `sarah-`.
17
    * `OpenAgentsWeb.UI` — the OpenAgents interface primitives, imported
18
      separately via `openagents_html_helpers`. `button`, `input`, and `icon` exist
19
      in both sets, which is why the UI slugs are prefixed `openagents-`.
20 20
21 21
  Icon names are drawn from the vendored Apps SDK set (`OpenAgentsWeb.Icons`).
22 22
  """

@@ -77,139 +77,139 @@ defmodule OpenAgentsWeb.ComponentCatalog do

77 77
      ]
78 78
    },
79 79
    %{
80
      title: "Sarah UI",
80
      title: "OpenAgents UI",
81 81
      items: [
82 82
        %{
83
          slug: "sarah-button",
83
          slug: "openagents-button",
84 84
          title: "Button",
85 85
          icon: "cube",
86
          source: "OpenAgentsWeb.SarahUI.button/1",
86
          source: "OpenAgentsWeb.UI.button/1",
87 87
          summary: "Eight variants, four sizes, and a danger tone."
88 88
        },
89 89
        %{
90
          slug: "sarah-text-button",
90
          slug: "openagents-text-button",
91 91
          title: "Text button",
92 92
          icon: "text",
93
          source: "OpenAgentsWeb.SarahUI.text_button/1",
93
          source: "OpenAgentsWeb.UI.text_button/1",
94 94
          summary: "Borderless action for inline and secondary affordances."
95 95
        },
96 96
        %{
97
          slug: "sarah-input",
97
          slug: "openagents-input",
98 98
          title: "Input",
99 99
          icon: "square-text",
100
          source: "OpenAgentsWeb.SarahUI.input/1",
100
          source: "OpenAgentsWeb.UI.input/1",
101 101
          summary: "Bare text input primitive, unwrapped by a form field."
102 102
        },
103 103
        %{
104
          slug: "sarah-textarea",
104
          slug: "openagents-textarea",
105 105
          title: "Textarea",
106 106
          icon: "text",
107
          source: "OpenAgentsWeb.SarahUI.textarea/1",
107
          source: "OpenAgentsWeb.UI.textarea/1",
108 108
          summary: "Multi-line text primitive."
109 109
        },
110 110
        %{
111
          slug: "sarah-label",
111
          slug: "openagents-label",
112 112
          title: "Label",
113 113
          icon: "tag",
114
          source: "OpenAgentsWeb.SarahUI.label/1",
114
          source: "OpenAgentsWeb.UI.label/1",
115 115
          summary: "Form label bound to a control by id."
116 116
        },
117 117
        %{
118
          slug: "sarah-field",
118
          slug: "openagents-field",
119 119
          title: "Field",
120 120
          icon: "file-document",
121
          source: "OpenAgentsWeb.SarahUI.field/1",
121
          source: "OpenAgentsWeb.UI.field/1",
122 122
          summary: "Wrapper that stacks a label and its control."
123 123
        },
124 124
        %{
125
          slug: "sarah-alert",
125
          slug: "openagents-alert",
126 126
          title: "Alert",
127 127
          icon: "warning",
128
          source: "OpenAgentsWeb.SarahUI.alert/1",
128
          source: "OpenAgentsWeb.UI.alert/1",
129 129
          summary: "Four variants across box, row, and notice appearances."
130 130
        },
131 131
        %{
132
          slug: "sarah-badge",
132
          slug: "openagents-badge",
133 133
          title: "Badge",
134 134
          icon: "tag",
135
          source: "OpenAgentsWeb.SarahUI.badge/1",
135
          source: "OpenAgentsWeb.UI.badge/1",
136 136
          summary: "Status pill in six variants."
137 137
        },
138 138
        %{
139
          slug: "sarah-card",
139
          slug: "openagents-card",
140 140
          title: "Card",
141 141
          icon: "square-image",
142
          source: "OpenAgentsWeb.SarahUI.card/1",
142
          source: "OpenAgentsWeb.UI.card/1",
143 143
          summary: "Content container with an optional corner frame and danger variant."
144 144
        },
145 145
        %{
146
          slug: "sarah-avatar",
146
          slug: "openagents-avatar",
147 147
          title: "Avatar",
148 148
          icon: "user",
149
          source: "OpenAgentsWeb.SarahUI.avatar/1",
149
          source: "OpenAgentsWeb.UI.avatar/1",
150 150
          summary: "Image or initials fallback in three sizes."
151 151
        },
152 152
        %{
153
          slug: "sarah-item",
153
          slug: "openagents-item",
154 154
          title: "Item",
155 155
          icon: "dot",
156
          source: "OpenAgentsWeb.SarahUI.item/1",
156
          source: "OpenAgentsWeb.UI.item/1",
157 157
          summary: "Status, label, and detail row for activity lists."
158 158
        },
159 159
        %{
160
          slug: "sarah-event-header",
160
          slug: "openagents-event-header",
161 161
          title: "Event header",
162 162
          icon: "info",
163
          source: "OpenAgentsWeb.SarahUI.event_header/1",
163
          source: "OpenAgentsWeb.UI.event_header/1",
164 164
          summary: "Titled event row with status, timestamp, and chip slot."
165 165
        },
166 166
        %{
167
          slug: "sarah-empty",
167
          slug: "openagents-empty",
168 168
          title: "Empty state",
169 169
          icon: "circle",
170
          source: "OpenAgentsWeb.SarahUI.empty/1",
170
          source: "OpenAgentsWeb.UI.empty/1",
171 171
          summary: "Placeholder for lists and panels with nothing to show."
172 172
        },
173 173
        %{
174
          slug: "sarah-kbd",
174
          slug: "openagents-kbd",
175 175
          title: "Keyboard key",
176 176
          icon: "keyboard",
177
          source: "OpenAgentsWeb.SarahUI.kbd/1",
177
          source: "OpenAgentsWeb.UI.kbd/1",
178 178
          summary: "Rendered keycap for shortcut documentation."
179 179
        },
180 180
        %{
181
          slug: "sarah-menu",
181
          slug: "openagents-menu",
182 182
          title: "Menu",
183 183
          icon: "menu",
184
          source: "OpenAgentsWeb.SarahUI.menu/1",
184
          source: "OpenAgentsWeb.UI.menu/1",
185 185
          summary: "Popover menu surface used by the account control."
186 186
        },
187 187
        %{
188
          slug: "sarah-frame",
188
          slug: "openagents-frame",
189 189
          title: "Frame",
190 190
          icon: "grid",
191
          source: "OpenAgentsWeb.SarahUI.frame/1",
191
          source: "OpenAgentsWeb.UI.frame/1",
192 192
          summary: "Corner-bracket decoration around arbitrary content."
193 193
        },
194 194
        %{
195
          slug: "sarah-status-indicator",
195
          slug: "openagents-status-indicator",
196 196
          title: "Status indicator",
197 197
          icon: "check-circle",
198
          source: "OpenAgentsWeb.SarahUI.status_indicator/1",
198
          source: "OpenAgentsWeb.UI.status_indicator/1",
199 199
          summary: "Labelled state dot, optionally decorative."
200 200
        },
201 201
        %{
202
          slug: "sarah-audio-player",
202
          slug: "openagents-audio-player",
203 203
          title: "Audio player",
204 204
          icon: "play",
205
          source: "OpenAgentsWeb.SarahUI.audio_player/1",
205
          source: "OpenAgentsWeb.UI.audio_player/1",
206 206
          summary: "Labelled audio element for recordings."
207 207
        },
208 208
        %{
209
          slug: "sarah-icon",
209
          slug: "openagents-icon",
210 210
          title: "Icon",
211 211
          icon: "sparkle",
212
          source: "OpenAgentsWeb.SarahUI.icon/1",
212
          source: "OpenAgentsWeb.UI.icon/1",
213 213
          summary: "Apps SDK glyph with an optional accessible label."
214 214
        }
215 215
      ]

@@ -276,7 +276,7 @@ defmodule OpenAgentsWeb.ComponentCatalog do

276 276
  def documented_modules do
277 277
    %{
278 278
      OpenAgentsWeb.CoreComponents => [],
279
      OpenAgentsWeb.SarahUI => [],
279
      OpenAgentsWeb.UI => [],
280 280
      OpenAgentsWeb.Layouts => [:app, :flash_group]
281 281
    }
282 282
  end
lib/openagents_web/components/core_components.ex modified +7 -7

@@ -10,15 +10,15 @@ defmodule OpenAgentsWeb.CoreComponents do

10 10
11 11
  The foundation for styling is Tailwind CSS, a utility-first CSS framework,
12 12
  on top of the vendored Basecoat component structure in
13
  `assets/vendor/basecoat/components/` and Sarah's style pack in
14
  `assets/css/sarah.css`. Basecoat expresses variants as data attributes
13
  `assets/vendor/basecoat/components/` and OpenAgents style pack in
14
  `assets/css/openagents.css`. Basecoat expresses variants as data attributes
15 15
  (`data-variant`, `data-size`), so a control is `class="btn"` plus a data
16 16
  attribute rather than a stack of variant classes. There is no second
17 17
  component library — DaisyUI was removed, and reintroducing it would put flat
18 18
  `.btn`-style rules back above `.btn[data-variant=…]`. Here are useful
19 19
  references:
20 20
21
    * `OpenAgentsWeb.SarahUI` - nineteen ready primitives over that CSS
21
    * `OpenAgentsWeb.UI` - nineteen ready primitives over that CSS
22 22
      (`button/1`, `card/1`, `badge/1`, `alert/1`, `input/1`, …). Prefer them
23 23
      to hand-written component classes.
24 24

@@ -73,7 +73,7 @@ defmodule OpenAgentsWeb.CoreComponents do

73 73
      class="fixed top-4 right-4 z-50 flex flex-col items-end gap-2"
74 74
      {@rest}
75 75
    >
76
      <%!-- Basecoat/Sarah `.alert` is a three-column grid: leading marker,
76
      <%!-- Basecoat/OpenAgents `.alert` is a three-column grid: leading marker,
77 77
      body `<section>`, trailing action. Keeping exactly three children is what
78 78
      lets the close control sit in the third column instead of wrapping. --%>
79 79
      <div

@@ -105,12 +105,12 @@ defmodule OpenAgentsWeb.CoreComponents do

105 105
  @doc """
106 106
  Renders a button with navigation support.
107 107
108
  A thin wrapper over the Basecoat/Sarah `.btn` recipe: the class is always
108
  A thin wrapper over the Basecoat/OpenAgents `.btn` recipe: the class is always
109 109
  `btn` and the look comes from `data-variant`, never from a stack of variant
110 110
  classes. `class` adds layout utilities alongside it rather than replacing it,
111 111
  so a caller cannot accidentally drop the component styling.
112 112
113
  `OpenAgentsWeb.SarahUI.button/1` is the fuller control (eight variants, three
113
  `OpenAgentsWeb.UI.button/1` is the fuller control (eight variants, three
114 114
  sizes, a danger tone). This one exists so surfaces on `CoreComponents` alone
115 115
  still get the same two shapes.
116 116

@@ -390,7 +390,7 @@ defmodule OpenAgentsWeb.CoreComponents do

390 390
    ~H"""
391 391
    <%!-- Basecoat's `.table` carries structure only — widths, alignment, and
392 392
    the row rule. Rhythm and colour are utilities here rather than a zebra
393
    variant: Sarah separates rows with the hairline, not with alternating
393
    variant: OpenAgents separates rows with the hairline, not with alternating
394 394
    fills. --%>
395 395
    <div class="table-container">
396 396
      <table class="table text-sm">
lib/openagents_web/components/layouts.ex modified +4 -4

@@ -5,7 +5,7 @@ defmodule OpenAgentsWeb.Layouts do

5 5
  """
6 6
  use OpenAgentsWeb, :html
7 7
8
  alias OpenAgentsWeb.SarahUI, as: UI
8
  alias OpenAgentsWeb.UI, as: UI
9 9
10 10
  # Embed all files in layouts/* within this module.
11 11
  # The default root.html.heex file contains the HTML

@@ -109,7 +109,7 @@ defmodule OpenAgentsWeb.Layouts do

109 109
  end
110 110
111 111
  @doc """
112
  The Sarah-style command bar: brand lockup on the left, account controls on the
112
  The OpenAgents command bar: brand lockup on the left, account controls on the
113 113
  right. `current_user` is optional because public surfaces are anonymous.
114 114
  """
115 115
  attr :aria_label, :string, required: true

@@ -190,7 +190,7 @@ defmodule OpenAgentsWeb.Layouts do

190 190
  defp account_dropdown(assigns) do
191 191
    ~H"""
192 192
    <%!-- A native <details> disclosure rather than a JavaScript dropdown, for
193
    the same reason `SarahUI.menu/1` uses the popover API: the account control
193
    the same reason `UI.menu/1` uses the popover API: the account control
194 194
    has to work before any client script has run. --%>
195 195
    <details class="relative">
196 196
      <summary class="btn list-none cursor-pointer !p-1" data-variant="ghost">

@@ -375,7 +375,7 @@ defmodule OpenAgentsWeb.Layouts do

375 375
  The `data-theme` segmented control.
376 376
377 377
  The attribute plumbing in `root.html.heex` still runs, but since DaisyUI was
378
  removed there is only one palette behind it: Sarah's token ladder is
378
  removed there is only one palette behind it: OpenAgents token ladder is
379 379
  dark-only (`color-scheme: dark` in app.css) and ships no light variant. The
380 380
  control therefore moves its indicator without repainting the page. Keep it or
381 381
  remove it deliberately — do not reintroduce a second theme to make it work.
lib/openagents_web/components/tool_activity.ex modified +9 -6

@@ -92,13 +92,16 @@ defmodule OpenAgentsWeb.ToolActivity do

92 92
  def subject("computer_devin"), do: "a Devin coding task on your computer"
93 93
  def subject("computer_agent"), do: "a coding agent task on your computer"
94 94
  def subject("module_discover"), do: "a check of what Sarah can do"
95
  def subject("repo_read"), do: "a read of Sarah's own source code"
96
  def subject("repo_grep"), do: "a search of Sarah's own source code"
97
  def subject("repo_list"), do: "a listing of Sarah's own source tree"
95
  def subject("repo_read"), do: "a read of OpenAgents source code"
96
  def subject("repo_grep"), do: "a search of OpenAgents source code"
97
  def subject("repo_list"), do: "a listing of the OpenAgents source tree"
98 98
  def subject("code_check"), do: "a syntax and compile check of candidate code"
99
  def subject("repo_edit"), do: "an edit in this job's clone of Sarah's repository"
100
  def subject("repo_write"), do: "a file written in this job's clone of Sarah's repository"
101
  def subject("repo_commit_push"), do: "a commit pushed to this job's branch on Sarah's forge"
99
  def subject("repo_edit"), do: "an edit in this job's clone of the OpenAgents repository"
100
  def subject("repo_write"), do: "a file written in this job's OpenAgents repository clone"
101
102
  def subject("repo_commit_push"),
103
    do: "a commit pushed to this job's branch on the OpenAgents forge"
104
102 105
  def subject(tool_name), do: "the #{humanized_tool(tool_name)} step"
103 106
104 107
  @doc "Bounded pretty JSON of the step's durable raw arguments, or nil."
lib/openagents_web/components/sarah_ui.ex → lib/openagents_web/components/ui.ex renamed +10 -10

@@ -1,12 +1,12 @@

1
defmodule OpenAgentsWeb.SarahUI do
1
defmodule OpenAgentsWeb.UI do
2 2
  @moduledoc """
3
  Sarah's interface primitives.
3
  OpenAgents interface primitives.
4 4
5
  Every component here wraps a vendored Basecoat class and exposes Sarah's
5
  Every component here wraps a vendored Basecoat class and exposes OpenAgents
6 6
  vocabulary rather than Basecoat's. Product surfaces build interface only from
7 7
  these components; they do not author component-level CSS classes. See
8
  `DESIGN.md`, `assets/css/style-sarah.css`, and
9
  `docs/decisions/0003-basecoat-component-library.md`.
8
  `assets/css/openagents.css` and
9
  `docs/decisions/0005-use-basecoat-and-one-component-system.md`.
10 10
11 11
  Basecoat expresses variants as data attributes (`data-variant`, `data-size`),
12 12
  so each `attr` maps straight onto the DOM with no class-merging utility.

@@ -33,7 +33,7 @@ defmodule OpenAgentsWeb.SarahUI do

33 33
  @doc """
34 34
  A boxed control.
35 35
36
  `:link` renders Sarah's inline underlined action rather than a boxed button
36
  `:link` renders OpenAgents inline underlined action rather than a boxed button
37 37
  and ignores `size`; pair it with `tone={:danger}` for a destructive inline
38 38
  action.
39 39

@@ -226,7 +226,7 @@ defmodule OpenAgentsWeb.SarahUI do

226 226
  @doc """
227 227
  A short typographic status label.
228 228
229
  Sarah states status as text; the badge carries the reserved semantic color
229
  OpenAgents states status as text; the badge carries the reserved semantic color
230 230
  and never stands in for the words.
231 231
  """
232 232
  attr :variant, :atom,

@@ -439,12 +439,12 @@ defmodule OpenAgentsWeb.SarahUI do

439 439
  Render the matching trigger with `button/1` and
440 440
  `popovertarget={id} popovertargetaction="toggle"`.
441 441
442
  Carries Sarah's own `.menu` class rather than Basecoat's `.popover`. In
442
  Carries OpenAgents own `.menu` class rather than Basecoat's `.popover`. In
443 443
  Basecoat, `.popover` is the *anchor* (`position: relative`) and the floating
444 444
  panel is `[data-popover]`, positioned by its JavaScript. Putting `.popover` on
445 445
  the panel gave it author-origin `display: inline-flex`, which outranks the
446 446
  user agent's `[popover]:not(:popover-open) { display: none }` and leaves the
447
  menu permanently open and in flow. Sarah styles the native element directly.
447
  menu permanently open and in flow. OpenAgents styles the native element directly.
448 448
  """
449 449
  attr :id, :string, required: true
450 450
  attr :label, :string, default: nil

@@ -515,7 +515,7 @@ defmodule OpenAgentsWeb.SarahUI do

515 515
  @doc """
516 516
  A native audio player for one stored recording.
517 517
518
  Basecoat has no audio component and Sarah does not ship a custom transport, so
518
  Basecoat has no audio component and OpenAgents does not ship a custom transport, so
519 519
  this wraps the browser's own `<audio controls>` rather than rebuilding play,
520 520
  pause, and volume in JavaScript. Native controls are already keyboard operable
521 521
  and already announced, which a hand-rolled transport would have to re-earn.
lib/openagents_web/controllers/changelog_controller.ex modified +2 -2

@@ -1,7 +1,7 @@

1 1
defmodule OpenAgentsWeb.ChangelogController do
2 2
  @moduledoc """
3 3
  `GET /api/changelog` — the machine-readable changelog
4
  (schema `sarah.changelog.v1`), the superset of the public `/changelog`
4
  (schema `openagents.changelog.v1`), the superset of the public `/changelog`
5 5
  page including receipt row ids so agents can cross-check every claim
6 6
  against the receipt chain. Same posture as `/api/status`: public,
7 7
  read-only, bounded, no identity state.

@@ -10,7 +10,7 @@ defmodule OpenAgentsWeb.ChangelogController do

10 10
  use OpenAgentsWeb, :controller
11 11
12 12
  def show(conn, params) do
13
    repo = Map.get(params, "repo", "sarah")
13
    repo = Map.get(params, "repo", "openagents.com")
14 14
15 15
    case OpenAgents.Changelog.projection(repo) do
16 16
      {:ok, payload} ->
lib/openagents_web/controllers/computers_controller.ex modified +1 -1

@@ -14,7 +14,7 @@ defmodule OpenAgentsWeb.ComputersController do

14 14
      |> Enum.map(&computer_projection/1)
15 15
16 16
    json(conn, %{
17
      "schema" => "sarah.computers.v1",
17
      "schema" => "openagents.computers.v1",
18 18
      "pairing_enabled" => Computer.enabled?(),
19 19
      "computers" => computers
20 20
    })
lib/openagents_web/controllers/network_status_controller.ex modified +1 -1

@@ -1,7 +1,7 @@

1 1
defmodule OpenAgentsWeb.NetworkStatusController do
2 2
  @moduledoc """
3 3
  `GET /api/status` — the full machine-readable network-state projection
4
  (schema `sarah.network_status.v1`, see `OpenAgents.NetworkStatus`). A superset of
4
  (schema `openagents.network_status.v1`, see `OpenAgents.NetworkStatus`). A superset of
5 5
  the legacy `/status` payload (`status`/`revision` keys are preserved), so
6 6
  pollers migrate here without a translation step. Public, bounded,
7 7
  content-free (STATUS-001).
lib/openagents_web/live/admin_forge_live.ex modified +2 -2

@@ -9,7 +9,7 @@ defmodule OpenAgentsWeb.AdminForgeLive do

9 9
  live off the forge PubSub topics.
10 10
  """
11 11
12
  use OpenAgentsWeb, :sarah_live_view
12
  use OpenAgentsWeb, :openagents_live_view
13 13
14 14
  alias OpenAgents.Accounts
15 15
  alias OpenAgents.Forge

@@ -86,7 +86,7 @@ defmodule OpenAgentsWeb.AdminForgeLive do

86 86
  end
87 87
88 88
  defp primary_repo do
89
    OpenAgents.Forge.Repos.allowed_repos() |> List.first() || "sarah"
89
    OpenAgents.Forge.Repos.allowed_repos() |> List.first() || "openagents.com"
90 90
  end
91 91
92 92
  defp git_store(repo, sha) do
lib/openagents_web/live/admin_live.ex modified +1 -1

@@ -16,7 +16,7 @@ defmodule OpenAgentsWeb.AdminLive do

16 16
      separate decision from cross-account listening.
17 17
  """
18 18
19
  use OpenAgentsWeb, :sarah_live_view
19
  use OpenAgentsWeb, :openagents_live_view
20 20
21 21
  alias OpenAgents.Accounts
22 22
  alias OpenAgents.Admin
lib/openagents_web/live/changelog_live.ex modified +7 -7

@@ -6,15 +6,15 @@ defmodule OpenAgentsWeb.ChangelogLive do

6 6
7 7
  Same public posture as the leaderboard and status pages (UI-001 lineage,
8 8
  TRANSPARENCY-001): read-only, mounts without a session, cannot invoke
9
  Sarah, renders only `OpenAgentsWeb.UI` primitives (UI-003), and updates live
9
  OpenAgents, renders only `OpenAgentsWeb.UI` primitives (UI-003), and updates live
10 10
  off the forge PubSub — a hot-load appears here seconds after it lands.
11 11
  """
12 12
13
  use OpenAgentsWeb, :sarah_live_view
13
  use OpenAgentsWeb, :openagents_live_view
14 14
15 15
  alias OpenAgents.Changelog
16 16
17
  @repo "sarah"
17
  @repo "openagents.com"
18 18
  @forge_topics ["forge:pushes", "forge:target", "forge:builds", "forge:deploys"]
19 19
20 20
  @impl true

@@ -31,7 +31,7 @@ defmodule OpenAgentsWeb.ChangelogLive do

31 31
32 32
    {:ok,
33 33
     socket
34
     |> assign(:page_title, "Changelog · Sarah")
34
     |> assign(:page_title, "Changelog · OpenAgents")
35 35
     |> assign(:repo, @repo)
36 36
     |> assign(:base, OpenAgents.Forge.Visibility.repo_path(@repo) || "")
37 37
     |> assign(:rows, rows)

@@ -92,7 +92,7 @@ defmodule OpenAgentsWeb.ChangelogLive do

92 92
    ~H"""
93 93
    <Layouts.app flash={@flash}>
94 94
      <main id="changelog-page" class="app-shell changelog-shell">
95
        <Layouts.command_bar aria_label="Sarah changelog" current_user={@current_user}>
95
        <Layouts.command_bar aria_label="OpenAgents changelog" current_user={@current_user}>
96 96
          <:lockup>
97 97
            <.button
98 98
              :if={@current_user}

@@ -111,9 +111,9 @@ defmodule OpenAgentsWeb.ChangelogLive do

111 111
            <div>
112 112
              <h1>Changelog</h1>
113 113
              <p>
114
                Every change to Sarah, two layers deep: what changed in plain
114
                Every change to OpenAgents, two layers deep: what changed in plain
115 115
                words, and — expand any entry — the receipts: the commit, the
116
                modules hot-loaded, and the measured push→live time. Sarah
116
                modules hot-loaded, and the measured push→live time. OpenAgents
117 117
                ships her own code through her own forge; this page is how
118 118
                you watch her do it.
119 119
              </p>
lib/openagents_web/live/chat_live.ex modified +2 -2

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgentsWeb.ChatLive do
2
  use OpenAgentsWeb, :sarah_live_view
2
  use OpenAgentsWeb, :openagents_live_view
3 3
4 4
  alias OpenAgents.{
5 5
    Accounts,

@@ -802,7 +802,7 @@ defmodule OpenAgentsWeb.ChatLive do

802 802
    ~H"""
803 803
    <Layouts.app flash={@flash} title="Chat" current_scope={@current_scope}>
804 804
      <div
805
        id="sarah-app"
805
        id="openagents-app"
806 806
        class="app-shell chat-shell"
807 807
        data-mobile-open="false"
808 808
        phx-hook=".SidebarShell"
lib/openagents_web/live/code_blob_live.ex modified +1 -1

@@ -13,7 +13,7 @@ defmodule OpenAgentsWeb.CodeBlobLive do

13 13
  the leaderboard: read-only, no session required, cannot invoke OpenAgents.
14 14
  """
15 15
16
  use OpenAgentsWeb, :sarah_live_view
16
  use OpenAgentsWeb, :openagents_live_view
17 17
18 18
  alias OpenAgents.Forge.{Browse, Visibility}
19 19
lib/openagents_web/live/code_commit_live.ex modified +1 -1

@@ -12,7 +12,7 @@ defmodule OpenAgentsWeb.CodeCommitLive do

12 12
  the other projections: read-only, sessionless, cannot invoke OpenAgents.
13 13
  """
14 14
15
  use OpenAgentsWeb, :sarah_live_view
15
  use OpenAgentsWeb, :openagents_live_view
16 16
17 17
  alias OpenAgents.Forge
18 18
  alias OpenAgents.Forge.{Browse, Visibility}
lib/openagents_web/live/code_repo_live.ex modified +2 -2

@@ -7,7 +7,7 @@ defmodule OpenAgentsWeb.CodeRepoLive do

7 7
  projections; data comes from `OpenAgents.Forge.Browse`'s bounded plumbing.
8 8
  """
9 9
10
  use OpenAgentsWeb, :sarah_live_view
10
  use OpenAgentsWeb, :openagents_live_view
11 11
12 12
  alias OpenAgents.Forge
13 13
  alias OpenAgents.Forge.{Browse, Visibility}

@@ -57,7 +57,7 @@ defmodule OpenAgentsWeb.CodeRepoLive do

57 57
    ~H"""
58 58
    <Layouts.app flash={@flash}>
59 59
      <main id="code-repo-page" class="app-shell code-shell">
60
        <Layouts.command_bar aria_label="Sarah code" current_user={@current_user}>
60
        <Layouts.command_bar aria_label="OpenAgents code" current_user={@current_user}>
61 61
          <:lockup>
62 62
            <.button
63 63
              :if={@current_user}
lib/openagents_web/live/components_live.ex modified +50 -50

@@ -12,11 +12,11 @@ defmodule OpenAgentsWeb.ComponentsLive do

12 12
  use OpenAgentsWeb, :live_view
13 13
14 14
  alias OpenAgentsWeb.ComponentCatalog
15
  alias OpenAgentsWeb.SarahUI, as: UI
15
  alias OpenAgentsWeb.UI, as: UI
16 16
17 17
  @sample_rows [
18 18
    %{id: 1, owner: "OpenAgentsInc", repo: "openagents.com", state: "open"},
19
    %{id: 2, owner: "OpenAgentsInc", repo: "sarah", state: "open"},
19
    %{id: 2, owner: "OpenAgentsInc", repo: "openagents.com", state: "open"},
20 20
    %{id: 3, owner: "OpenAgentsInc", repo: "arcade", state: "closed"}
21 21
  ]
22 22

@@ -25,8 +25,8 @@ defmodule OpenAgentsWeb.ComponentsLive do

25 25
    hero-arrow-path hero-sun-micro hero-moon-micro hero-computer-desktop-micro
26 26
  )
27 27
28
  # SarahUI.icon/1 renders the vendored Apps SDK set, not heroicons.
29
  @sarah_icons ~w(sparkle compass folder document user bell play star)
28
  # UI.icon/1 renders the vendored Apps SDK set, not heroicons.
29
  @openagents_icons ~w(sparkle compass folder document user bell play star)
30 30
31 31
  # account_control/1 and command_bar/1 read three fields off the current user.
32 32
  # A plain map is enough and keeps the catalog page free of database access.

@@ -54,7 +54,7 @@ defmodule OpenAgentsWeb.ComponentsLive do

54 54
     |> assign(:form, form)
55 55
     |> assign(:rows, @sample_rows)
56 56
     |> assign(:icons, @icons)
57
     |> assign(:sarah_icons, @sarah_icons)
57
     |> assign(:openagents_icons, @openagents_icons)
58 58
     |> assign(:demo_user, @demo_user)}
59 59
  end
60 60

@@ -109,10 +109,10 @@ defmodule OpenAgentsWeb.ComponentsLive do

109 109
      <p class="text-base-content/70 mb-8 text-pretty max-w-[68ch]">
110 110
        Live examples of every reusable function component in this repository, drawn from
111 111
        <code>OpenAgentsWeb.CoreComponents</code>
112
        (the Phoenix set, restyled onto basecoat), <code>OpenAgentsWeb.SarahUI</code>
113
        (the Sarah interface primitives), and <code>OpenAgentsWeb.Layouts</code>. <code>button</code>, <code>input</code>, and
112
        (the Phoenix set, restyled onto basecoat), <code>OpenAgentsWeb.UI</code>
113
        (the OpenAgents interface primitives), and <code>OpenAgentsWeb.Layouts</code>. <code>button</code>, <code>input</code>, and
114 114
        <code>icon</code>
115
        exist in both component sets, so the SarahUI entries are listed separately.
115
        exist in both component sets, so the UI entries are listed separately.
116 116
        A test asserts this page covers every public component in those modules.
117 117
        Planned GitHub-shaped components are listed in <code>docs/component-library.md</code>.
118 118
      </p>

@@ -156,7 +156,7 @@ defmodule OpenAgentsWeb.ComponentsLive do

156 156
  attr :form, :any, default: nil
157 157
  attr :rows, :list, default: []
158 158
  attr :icons, :list, default: []
159
  attr :sarah_icons, :list, default: []
159
  attr :openagents_icons, :list, default: []
160 160
  attr :demo_user, :map, default: nil
161 161
162 162
  defp component_demo(%{item: %{slug: "button"}} = assigns) do

@@ -284,12 +284,12 @@ defmodule OpenAgentsWeb.ComponentsLive do

284 284
    """
285 285
  end
286 286
287
  # --- Sarah UI -------------------------------------------------------------
288
  # SarahUI is imported by `sarah_html_helpers`, not by `:live_view`, so these
287
  # --- OpenAgents UI -------------------------------------------------------------
288
  # UI is imported by `openagents_html_helpers`, not by `:live_view`, so these
289 289
  # are called with the full module prefix. That also documents provenance on a
290 290
  # page whose whole job is showing where a component comes from.
291 291
292
  defp component_demo(%{item: %{slug: "sarah-button"}} = assigns) do
292
  defp component_demo(%{item: %{slug: "openagents-button"}} = assigns) do
293 293
    ~H"""
294 294
    <div class="space-y-4">
295 295
      <div class="flex flex-wrap items-center gap-3">

@@ -311,7 +311,7 @@ defmodule OpenAgentsWeb.ComponentsLive do

311 311
    """
312 312
  end
313 313
314
  defp component_demo(%{item: %{slug: "sarah-text-button"}} = assigns) do
314
  defp component_demo(%{item: %{slug: "openagents-text-button"}} = assigns) do
315 315
    ~H"""
316 316
    <div class="flex flex-wrap items-center gap-4">
317 317
      <UI.text_button>Default</UI.text_button>

@@ -321,21 +321,21 @@ defmodule OpenAgentsWeb.ComponentsLive do

321 321
    """
322 322
  end
323 323
324
  defp component_demo(%{item: %{slug: "sarah-input"}} = assigns) do
324
  defp component_demo(%{item: %{slug: "openagents-input"}} = assigns) do
325 325
    ~H"""
326 326
    <div class="space-y-3 max-w-sm">
327
      <UI.input id="sarah-input-demo" name="demo" value="Ship the catalog" />
328
      <UI.input id="sarah-input-demo-empty" name="demo_empty" placeholder="Placeholder text" />
329
      <UI.input id="sarah-input-demo-disabled" name="demo_disabled" value="Disabled" disabled />
327
      <UI.input id="openagents-input-demo" name="demo" value="Ship the catalog" />
328
      <UI.input id="openagents-input-demo-empty" name="demo_empty" placeholder="Placeholder text" />
329
      <UI.input id="openagents-input-demo-disabled" name="demo_disabled" value="Disabled" disabled />
330 330
    </div>
331 331
    """
332 332
  end
333 333
334
  defp component_demo(%{item: %{slug: "sarah-textarea"}} = assigns) do
334
  defp component_demo(%{item: %{slug: "openagents-textarea"}} = assigns) do
335 335
    ~H"""
336 336
    <div class="max-w-sm">
337 337
      <UI.textarea
338
        id="sarah-textarea-demo"
338
        id="openagents-textarea-demo"
339 339
        name="demo_body"
340 340
        value="Multi-line text primitive."
341 341
        rows="4"

@@ -344,31 +344,31 @@ defmodule OpenAgentsWeb.ComponentsLive do

344 344
    """
345 345
  end
346 346
347
  defp component_demo(%{item: %{slug: "sarah-label"}} = assigns) do
347
  defp component_demo(%{item: %{slug: "openagents-label"}} = assigns) do
348 348
    ~H"""
349 349
    <div class="space-y-2 max-w-sm">
350
      <UI.label for="sarah-label-target">Repository name</UI.label>
351
      <UI.input id="sarah-label-target" name="repo" value="openagents.com" />
350
      <UI.label for="openagents-label-target">Repository name</UI.label>
351
      <UI.input id="openagents-label-target" name="repo" value="openagents.com" />
352 352
    </div>
353 353
    """
354 354
  end
355 355
356
  defp component_demo(%{item: %{slug: "sarah-field"}} = assigns) do
356
  defp component_demo(%{item: %{slug: "openagents-field"}} = assigns) do
357 357
    ~H"""
358 358
    <div class="max-w-sm space-y-4">
359 359
      <UI.field>
360
        <UI.label for="sarah-field-title">Title</UI.label>
361
        <UI.input id="sarah-field-title" name="title" value="Ship the component catalog" />
360
        <UI.label for="openagents-field-title">Title</UI.label>
361
        <UI.input id="openagents-field-title" name="title" value="Ship the component catalog" />
362 362
      </UI.field>
363 363
      <UI.field>
364
        <UI.label for="sarah-field-body">Body</UI.label>
365
        <UI.textarea id="sarah-field-body" name="body" rows="3" />
364
        <UI.label for="openagents-field-body">Body</UI.label>
365
        <UI.textarea id="openagents-field-body" name="body" rows="3" />
366 366
      </UI.field>
367 367
    </div>
368 368
    """
369 369
  end
370 370
371
  defp component_demo(%{item: %{slug: "sarah-alert"}} = assigns) do
371
  defp component_demo(%{item: %{slug: "openagents-alert"}} = assigns) do
372 372
    ~H"""
373 373
    <div class="space-y-6">
374 374
      <div class="space-y-3">

@@ -392,7 +392,7 @@ defmodule OpenAgentsWeb.ComponentsLive do

392 392
    """
393 393
  end
394 394
395
  defp component_demo(%{item: %{slug: "sarah-badge"}} = assigns) do
395
  defp component_demo(%{item: %{slug: "openagents-badge"}} = assigns) do
396 396
    ~H"""
397 397
    <div class="flex flex-wrap items-center gap-3">
398 398
      <UI.badge :for={v <- ~w(default info success warning danger dim)a} variant={v}>

@@ -402,18 +402,18 @@ defmodule OpenAgentsWeb.ComponentsLive do

402 402
    """
403 403
  end
404 404
405
  defp component_demo(%{item: %{slug: "sarah-card"}} = assigns) do
405
  defp component_demo(%{item: %{slug: "openagents-card"}} = assigns) do
406 406
    ~H"""
407 407
    <div class="grid gap-4 sm:grid-cols-2">
408
      <UI.card id="sarah-card-default">
408
      <UI.card id="openagents-card-default">
409 409
        <p class="font-medium">Default</p>
410 410
        <p class="text-sm text-base-content/70">A plain content container.</p>
411 411
      </UI.card>
412
      <UI.card id="sarah-card-corners" frame={:corners}>
412
      <UI.card id="openagents-card-corners" frame={:corners}>
413 413
        <p class="font-medium">Corner frame</p>
414 414
        <p class="text-sm text-base-content/70">With bracket decoration.</p>
415 415
      </UI.card>
416
      <UI.card id="sarah-card-danger" variant={:danger}>
416
      <UI.card id="openagents-card-danger" variant={:danger}>
417 417
        <p class="font-medium">Danger</p>
418 418
        <p class="text-sm text-base-content/70">For destructive context.</p>
419 419
      </UI.card>

@@ -421,7 +421,7 @@ defmodule OpenAgentsWeb.ComponentsLive do

421 421
    """
422 422
  end
423 423
424
  defp component_demo(%{item: %{slug: "sarah-avatar"}} = assigns) do
424
  defp component_demo(%{item: %{slug: "openagents-avatar"}} = assigns) do
425 425
    ~H"""
426 426
    <div class="flex flex-wrap items-end gap-6">
427 427
      <UI.avatar :for={sz <- ~w(sm default lg)a} size={sz} fallback="OA" label={"size #{sz}"} />

@@ -430,7 +430,7 @@ defmodule OpenAgentsWeb.ComponentsLive do

430 430
    """
431 431
  end
432 432
433
  defp component_demo(%{item: %{slug: "sarah-item"}} = assigns) do
433
  defp component_demo(%{item: %{slug: "openagents-item"}} = assigns) do
434 434
    ~H"""
435 435
    <div class="space-y-2">
436 436
      <UI.item status="ok" label="Fleet node 1" detail="converged" />

@@ -440,10 +440,10 @@ defmodule OpenAgentsWeb.ComponentsLive do

440 440
    """
441 441
  end
442 442
443
  defp component_demo(%{item: %{slug: "sarah-event-header"}} = assigns) do
443
  defp component_demo(%{item: %{slug: "openagents-event-header"}} = assigns) do
444 444
    ~H"""
445 445
    <UI.event_header
446
      id="sarah-event-header-demo"
446
      id="openagents-event-header-demo"
447 447
      status="ok"
448 448
      title="repo_commit_push"
449 449
      status_note="completed"

@@ -457,15 +457,15 @@ defmodule OpenAgentsWeb.ComponentsLive do

457 457
    """
458 458
  end
459 459
460
  defp component_demo(%{item: %{slug: "sarah-empty"}} = assigns) do
460
  defp component_demo(%{item: %{slug: "openagents-empty"}} = assigns) do
461 461
    ~H"""
462
    <UI.empty id="sarah-empty-demo" title="No delegations yet">
462
    <UI.empty id="openagents-empty-demo" title="No delegations yet">
463 463
      Work you delegate to a paired machine will appear here.
464 464
    </UI.empty>
465 465
    """
466 466
  end
467 467
468
  defp component_demo(%{item: %{slug: "sarah-kbd"}} = assigns) do
468
  defp component_demo(%{item: %{slug: "openagents-kbd"}} = assigns) do
469 469
    ~H"""
470 470
    <p class="flex flex-wrap items-center gap-2 text-sm">
471 471
      Press

@@ -479,13 +479,13 @@ defmodule OpenAgentsWeb.ComponentsLive do

479 479
    """
480 480
  end
481 481
482
  defp component_demo(%{item: %{slug: "sarah-menu"}} = assigns) do
482
  defp component_demo(%{item: %{slug: "openagents-menu"}} = assigns) do
483 483
    ~H"""
484 484
    <div class="space-y-3">
485
      <UI.button popovertarget="sarah-demo-menu" popovertargetaction="toggle">
485
      <UI.button popovertarget="openagents-demo-menu" popovertargetaction="toggle">
486 486
        Open menu
487 487
      </UI.button>
488
      <UI.menu id="sarah-demo-menu" label="Demo menu">
488
      <UI.menu id="openagents-demo-menu" label="Demo menu">
489 489
        <UI.text_button>Profile</UI.text_button>
490 490
        <UI.text_button>Settings</UI.text_button>
491 491
        <UI.text_button tone={:danger}>Sign out</UI.text_button>

@@ -494,7 +494,7 @@ defmodule OpenAgentsWeb.ComponentsLive do

494 494
    """
495 495
  end
496 496
497
  defp component_demo(%{item: %{slug: "sarah-frame"}} = assigns) do
497
  defp component_demo(%{item: %{slug: "openagents-frame"}} = assigns) do
498 498
    ~H"""
499 499
    <UI.frame>
500 500
      <div class="p-6">

@@ -505,7 +505,7 @@ defmodule OpenAgentsWeb.ComponentsLive do

505 505
    """
506 506
  end
507 507
508
  defp component_demo(%{item: %{slug: "sarah-status-indicator"}} = assigns) do
508
  defp component_demo(%{item: %{slug: "openagents-status-indicator"}} = assigns) do
509 509
    ~H"""
510 510
    <div class="flex flex-wrap items-center gap-6">
511 511
      <UI.status_indicator state="ok" label="Healthy" />

@@ -516,11 +516,11 @@ defmodule OpenAgentsWeb.ComponentsLive do

516 516
    """
517 517
  end
518 518
519
  defp component_demo(%{item: %{slug: "sarah-audio-player"}} = assigns) do
519
  defp component_demo(%{item: %{slug: "openagents-audio-player"}} = assigns) do
520 520
    ~H"""
521 521
    <div class="space-y-2">
522 522
      <UI.audio_player
523
        id="sarah-audio-demo"
523
        id="openagents-audio-demo"
524 524
        src="/audio/does-not-exist.wav"
525 525
        label="Voice recording (demo source, nothing to play)"
526 526
      />

@@ -531,10 +531,10 @@ defmodule OpenAgentsWeb.ComponentsLive do

531 531
    """
532 532
  end
533 533
534
  defp component_demo(%{item: %{slug: "sarah-icon"}} = assigns) do
534
  defp component_demo(%{item: %{slug: "openagents-icon"}} = assigns) do
535 535
    ~H"""
536
    <ul id="sarah-demo-icons" role="list" class="flex flex-wrap gap-4">
537
      <li :for={name <- @sarah_icons} class="flex flex-col items-center gap-2 w-28">
536
    <ul id="openagents-demo-icons" role="list" class="flex flex-wrap gap-4">
537
      <li :for={name <- @openagents_icons} class="flex flex-col items-center gap-2 w-28">
538 538
        <UI.icon name={name} class="size-6" />
539 539
        <p class="text-center text-sm text-base-content/70">{name}</p>
540 540
      </li>
lib/openagents_web/live/computers_live.ex modified +1 -1

@@ -3,7 +3,7 @@ defmodule OpenAgentsWeb.ComputersLive do

3 3
  Approve controller pairing codes and manage this account's paired computers.
4 4
  """
5 5
6
  use OpenAgentsWeb, :sarah_live_view
6
  use OpenAgentsWeb, :openagents_live_view
7 7
8 8
  alias OpenAgents.Computer
9 9
  alias OpenAgents.Machines
lib/openagents_web/live/leaderboard_live.ex modified +1 -1

@@ -12,7 +12,7 @@ defmodule OpenAgentsWeb.LeaderboardLive do

12 12
  be rendered here by accident.
13 13
  """
14 14
15
  use OpenAgentsWeb, :sarah_live_view
15
  use OpenAgentsWeb, :openagents_live_view
16 16
17 17
  alias OpenAgents.Leaderboard
18 18
  alias OpenAgents.Leaderboard.Entry
lib/openagents_web/live/network_status_live.ex modified +6 -6

@@ -11,7 +11,7 @@ defmodule OpenAgentsWeb.NetworkStatusLive do

11 11
  full fleet to render — it is most useful precisely when something is down.
12 12
  """
13 13
14
  use OpenAgentsWeb, :sarah_live_view
14
  use OpenAgentsWeb, :openagents_live_view
15 15
16 16
  alias OpenAgents.NetworkStatus
17 17

@@ -33,7 +33,7 @@ defmodule OpenAgentsWeb.NetworkStatusLive do

33 33
34 34
    {:ok,
35 35
     socket
36
     |> assign(:page_title, "Network status · Sarah")
36
     |> assign(:page_title, "Network status · OpenAgents")
37 37
     |> assign(:events, [])
38 38
     |> assign_projection(NetworkStatus.projection())}
39 39
  end

@@ -250,7 +250,7 @@ defmodule OpenAgentsWeb.NetworkStatusLive do

250 250
    ~H"""
251 251
    <Layouts.app flash={@flash}>
252 252
      <main id="network-status-page" class="app-shell status-shell">
253
        <Layouts.command_bar aria_label="Sarah network status" current_user={@current_user}>
253
        <Layouts.command_bar aria_label="OpenAgents network status" current_user={@current_user}>
254 254
          <:lockup>
255 255
            <.button
256 256
              :if={@current_user}

@@ -269,7 +269,7 @@ defmodule OpenAgentsWeb.NetworkStatusLive do

269 269
            <div>
270 270
              <h1>Network status</h1>
271 271
              <p>
272
                The Sarah fleet — a quorum cluster that survives node loss and
272
                The OpenAgents fleet — a quorum cluster that survives node loss and
273 273
                upgrades in place. Owned sessions dropped per node loss or
274 274
                upgrade: 0; the release gate refuses anything that breaks that.
275 275
              </p>

@@ -360,7 +360,7 @@ defmodule OpenAgentsWeb.NetworkStatusLive do

360 360
          <.card id="status-forge">
361 361
            <h2>Rapid deploys</h2>
362 362
            <p class="status-forge__intro">
363
              Code moves through Sarah's own forge: a push is promoted by an
363
              Code moves through the OpenAgents forge: a push is promoted by an
364 364
              operator, built into just the changed modules, and hot-loaded
365 365
              across the fleet without a restart — watch it sweep the nodes
366 366
              above.

@@ -451,7 +451,7 @@ defmodule OpenAgentsWeb.NetworkStatusLive do

451 451
          <footer class="status-footer">
452 452
            <p>
453 453
              Machine-readable: <code>GET /api/status</code>
454
              (schema <code>sarah.network_status.v1</code>) ·
454
              (schema <code>openagents.network_status.v1</code>) ·
455 455
              probe: <code>GET /healthz</code>. This page updates live.
456 456
            </p>
457 457
          </footer>
lib/openagents_web/live/voice_spike_live.ex modified +1 -1

@@ -1,7 +1,7 @@

1 1
defmodule OpenAgentsWeb.VoiceSpikeLive do
2 2
  @moduledoc false
3 3
4
  use OpenAgentsWeb, :sarah_live_view
4
  use OpenAgentsWeb, :openagents_live_view
5 5
6 6
  @impl true
7 7
  def mount(_params, _session, socket) do
lib/openagents_web/plugs/forge_git_auth.ex modified +1 -1

@@ -25,7 +25,7 @@ defmodule OpenAgentsWeb.Plugs.ForgeGitAuth do

25 25
    else
26 26
      _ ->
27 27
        conn
28
        |> put_resp_header("www-authenticate", ~s(Basic realm="sarah-forge"))
28
        |> put_resp_header("www-authenticate", ~s(Basic realm="openagents-forge"))
29 29
        |> send_resp(401, "authentication required")
30 30
        |> halt()
31 31
    end
lib/openagents_web/router.ex modified +1 -1

@@ -77,7 +77,7 @@ defmodule OpenAgentsWeb.Router do

77 77
78 78
  # The public forge web UI (TRANSPARENCY-001), addressed exactly the way the
79 79
  # GitHub URL it replaces is:
80
  # openagents.com/OpenAgentsInc/sarah/blob/main/README.md.
80
  # openagents.com/OpenAgentsInc/openagents.com/blob/main/README.md.
81 81
  #
82 82
  # The owner is a LITERAL scope segment, not a `:owner` pattern: a wildcard
83 83
  # first segment would match every two-segment path on the domain (/dev/ui,
mix.exs modified +1

@@ -114,6 +114,7 @@ defmodule OpenAgents.MixProject do

114 114
        "compile --warnings-as-errors",
115 115
        "deps.unlock --unused",
116 116
        "format",
117
        "cmd ops/ci/reference-check.sh",
117 118
        "assets.test",
118 119
        "test --warnings-as-errors"
119 120
      ]
ops/ci/allowed-sarah-references.txt added +45

@@ -0,0 +1,45 @@

1
# Persona source, evaluation, and program artifacts.
2
^priv/sarah/
3
4
# Historical migration identity. Never rename a migration that may have run.
5
^priv/repo/migrations/[0-9]+_.*sarah.*\.exs:
6
7
# Dated migration plans and audits retain exact historical names and findings.
8
^docs/2026-08-19-gap-implementation-plan\.md:
9
^docs/2026-08-20-integration-hardening-and-staging-readiness-recommendations\.md:
10
^docs/2026-08-20-test-coverage-audit\.md:
11
^docs/chat-inference-plan\.md:
12
^docs/component-library\.md:
13
^docs/github-auth-plan\.md:
14
^docs/issues-projects-ui-roadmap\.md:
15
^docs/sarah-integration-plan\.md:
16
17
# Current architecture and decisions discuss the Sarah persona boundary.
18
^docs/architecture\.md:
19
^docs/decisions/0001-integrate-the-complete-public-application\.md:
20
^docs/decisions/0002-model-sarah-as-an-openagents-persona\.md:
21
^docs/decisions/0005-use-basecoat-and-one-component-system\.md:
22
23
# The invariant ledger includes Sarah-specific persona and behavior contracts.
24
^INVARIANTS\.md:
25
26
# Natural-language Sarah references describe the visible persona or her actions.
27
^(assets|lib|test)/.*:[0-9]+:.*(^|[^A-Za-z0-9_])[Ss][Aa][Rr][Aa][Hh]([^A-Za-z0-9_]|$)
28
^assets/js/voice_controller\.js:[0-9]+:.*interruptSarah
29
30
# Versioned Sarah behavior, tool, evaluation, and persisted-data identifiers are
31
# stable artifact contracts. Rename them only through an explicit data migration.
32
^(assets|lib|test)/.*:[0-9]+:.*sarah\.[A-Za-z0-9_#{}.-]+
33
34
# Persona artifact paths, names, and stable database table names.
35
^(assets|lib|test)/.*:[0-9]+:.*(priv/)?sarah/(evals|persona|programs)/
36
^(assets|lib|test)/.*:[0-9]+:.*sarah\.v1\.(md|sources\.json)
37
^(assets|lib|test)/.*:[0-9]+:.*sarah_blueprint_(facts|revisions)
38
39
# User-facing Sarah data export names and the persona's commit identity.
40
^(assets|lib|test)/.*:[0-9]+:.*sarah-(account-data|conversation-|memory-account)
41
^(assets|lib|test)/.*:[0-9]+:.*sarah@openagents\.com
42
43
# Persona-specific recording layout and source-manifest vocabulary.
44
^(assets|lib|test)/.*:[0-9]+:.*mic_left_sarah_right
45
^(assets|lib|test)/.*:[0-9]+:.*(sarah_authored_speech|(final|prepared)_sarah_script|sarah-(corpus|runbook|contracts|knowledge))
ops/ci/reference-check.sh added +46

@@ -0,0 +1,46 @@

1
#!/usr/bin/env bash
2
set -euo pipefail
3
4
repo_root=$(git rev-parse --show-toplevel)
5
cd "$repo_root"
6
7
allowlist="ops/ci/allowed-sarah-references.txt"
8
matches=$(mktemp /tmp/openagents-reference-matches.XXXXXX)
9
patterns=$(mktemp /tmp/openagents-reference-patterns.XXXXXX)
10
unclassified=$(mktemp /tmp/openagents-reference-unclassified.XXXXXX)
11
12
cleanup() {
13
  unlink "$matches" 2>/dev/null || true
14
  unlink "$patterns" 2>/dev/null || true
15
  unlink "$unclassified" 2>/dev/null || true
16
}
17
18
trap cleanup EXIT
19
20
# Generic infrastructure names are never valid in active source, tests,
21
# configuration, or assets. Historical documents are classified separately.
22
banned_in_active='OpenAgents\.Sarah(\.|$)|Sarah(UI|ConnCase|DataCase|ChannelCase|Factory|Fixtures|ClusterTest|TargetsTest)|sarah_(live_view|html|html_helpers|source_dir|forge)|(^|/)(style-)?sarah\.css|/(var/lib|tmp)/sarah|OpenAgents-Sarah|sarah[-_]builder|sarah[-_]forge|sarah-wal-seq|sarah/job-'
23
24
if rg --line-number --no-heading --color never --ignore-case \
25
  "$banned_in_active" AGENTS.md README.md assets config lib test; then
26
  echo "Unclassified Sarah infrastructure reference found in active code." >&2
27
  exit 1
28
fi
29
30
awk 'NF && $1 !~ /^#/' "$allowlist" >"$patterns"
31
32
rg --line-number --no-heading --color never --ignore-case \
33
  --glob '!ops/ci/allowed-sarah-references.txt' \
34
  --glob '!ops/ci/reference-check.sh' \
35
  'sarah|/var/lib/sarah|/tmp/sarah|pro\.openagents\.com|api\.openagents\.com' \
36
  AGENTS.md INVARIANTS.md README.md assets config docs lib ops test >"$matches" || true
37
38
grep --extended-regexp --invert-match --file="$patterns" "$matches" >"$unclassified" || true
39
40
if test -s "$unclassified"; then
41
  echo "Unclassified Sarah or retired-service references:" >&2
42
  sed -n '1,200p' "$unclassified" >&2
43
  exit 1
44
fi
45
46
echo "Sarah reference classification passed"
priv/icons/README.md modified +2 -2

@@ -13,7 +13,7 @@ Generated files. **Do not edit these by hand.**

13 13
Upstream ships one React component per glyph under
14 14
`src/components/Icon/svg/`, each drawing a `currentColor` SVG sized at `1em`.
15 15
Sarah has no React and no `node_modules`, so the components cannot be consumed
16
as published. `mix sarah.icons.vendor <path-to-apps-sdk-ui>` converts them:
16
as published. `mix openagents.icons.vendor <path-to-apps-sdk-ui>` converts them:
17 17
18 18
- JSX attribute names become their SVG spelling (`fillRule` → `fill-rule`, and
19 19
  the rest);

@@ -31,7 +31,7 @@ change fails loudly rather than emitting a subtly wrong glyph.

31 31
## Upgrading
32 32
33 33
1. Pull the upstream checkout to the new commit.
34
2. Re-run `mix sarah.icons.vendor <path>`. It clears the previous set first, so
34
2. Re-run `mix openagents.icons.vendor <path>`. It clears the previous set first, so
35 35
   glyphs deleted upstream do not linger.
36 36
3. Update the commit and date above.
37 37
4. Run `mix precommit`. The icon tests fail if a glyph a surface depends on has
test/openagents/accounts_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.AccountsTest do
2
  use OpenAgents.SarahDataCase, async: false
2
  use OpenAgents.DataCase, async: false
3 3
  alias OpenAgents.Accounts
4 4
  alias OpenAgents.Conversations
5 5
test/openagents/admin_test.exs modified +1 -1

@@ -5,7 +5,7 @@ defmodule OpenAgents.AdminTest do

5 5
  and that a call with no audio is still a row.
6 6
  """
7 7
8
  use OpenAgents.SarahDataCase, async: false
8
  use OpenAgents.DataCase, async: false
9 9
  alias OpenAgents.Accounts
10 10
  alias OpenAgents.Admin
11 11
  alias OpenAgents.Admin.Call
test/openagents/blueprint_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.BlueprintTest do
2
  use OpenAgents.SarahDataCase, async: true
2
  use OpenAgents.DataCase, async: true
3 3
  alias OpenAgents.Blueprint
4 4
  alias OpenAgents.Blueprint.Revision
5 5
  alias OpenAgents.Conversations
test/openagents/changelog_test.exs modified +18 -15

@@ -8,7 +8,7 @@ defmodule OpenAgents.ChangelogTest do

8 8
  backfill seed.
9 9
  """
10 10
11
  use OpenAgents.SarahDataCase, async: false
11
  use OpenAgents.DataCase, async: false
12 12
  alias OpenAgents.Changelog
13 13
  alias OpenAgents.Changelog.{Backfill, Entry}
14 14
  alias OpenAgents.Forge.{DeployReceipt, PushReceipt}

@@ -36,7 +36,7 @@ defmodule OpenAgents.ChangelogTest do

36 36
  defp entry_attrs(overrides) do
37 37
    Map.merge(
38 38
      %{
39
        repo: "sarah",
39
        repo: "openagents.com",
40 40
        sha: full_sha("feed0001"),
41 41
        summary: "A test entry",
42 42
        category: "ui",

@@ -53,7 +53,7 @@ defmodule OpenAgents.ChangelogTest do

53 53
      |> DeployReceipt.changeset(
54 54
        Map.merge(
55 55
          %{
56
            repo: "sarah",
56
            repo: "openagents.com",
57 57
            target_id: Ecto.UUID.generate(),
58 58
            result: "live",
59 59
            modules: ["Elixir.OpenAgents.Something"],

@@ -101,7 +101,7 @@ defmodule OpenAgents.ChangelogTest do

101 101
    end
102 102
103 103
    test "a repo configured :l2 returns entries newest first" do
104
      override_visibility(%{"sarah" => :l2})
104
      override_visibility(%{"openagents.com" => :l2})
105 105
106 106
      now = DateTime.utc_now()
107 107

@@ -117,7 +117,7 @@ defmodule OpenAgents.ChangelogTest do

117 117
      {:ok, _} =
118 118
        Changelog.record(entry_attrs(%{sha: full_sha("feed0002"), summary: "Newer entry"}))
119 119
120
      assert {:ok, [newer, older]} = Changelog.timeline("sarah", refresh: true)
120
      assert {:ok, [newer, older]} = Changelog.timeline("openagents.com", refresh: true)
121 121
      assert newer.summary == "Newer entry"
122 122
      assert older.summary == "Older entry"
123 123
      assert Enum.all?([newer, older], &(&1.kind == :entry))

@@ -137,7 +137,7 @@ defmodule OpenAgents.ChangelogTest do

137 137
          })
138 138
        )
139 139
140
      assert {:ok, [row]} = Changelog.timeline("sarah", refresh: true)
140
      assert {:ok, [row]} = Changelog.timeline("openagents.com", refresh: true)
141 141
      assert row.summary == "Security fix under embargo"
142 142
      assert row.sha == nil
143 143
      assert row.short_sha == nil

@@ -157,7 +157,7 @@ defmodule OpenAgents.ChangelogTest do

157 157
          })
158 158
        )
159 159
160
      assert {:ok, [row]} = Changelog.timeline("sarah", refresh: true)
160
      assert {:ok, [row]} = Changelog.timeline("openagents.com", refresh: true)
161 161
      assert row.sha == sha
162 162
      assert row.short_sha == String.slice(sha, 0, 12)
163 163
    end

@@ -165,7 +165,7 @@ defmodule OpenAgents.ChangelogTest do

165 165
    test "a receipted deploy with no authored entry appears as a bare :receipt row" do
166 166
      deploy = insert_deploy!(%{sha: full_sha("feed0005")})
167 167
168
      assert {:ok, [row]} = Changelog.timeline("sarah", refresh: true)
168
      assert {:ok, [row]} = Changelog.timeline("openagents.com", refresh: true)
169 169
      assert row.kind == :receipt
170 170
      assert row.summary == nil
171 171
      assert row.sha == deploy.sha

@@ -181,7 +181,7 @@ defmodule OpenAgents.ChangelogTest do

181 181
182 182
      deploy = insert_deploy!(%{sha: full_sha("feed0006"), push_to_live_ms: 5_540})
183 183
184
      assert {:ok, [row]} = Changelog.timeline("sarah", refresh: true)
184
      assert {:ok, [row]} = Changelog.timeline("openagents.com", refresh: true)
185 185
      assert row.kind == :entry
186 186
      assert row.summary == "Short-sha entry from backfill lane"
187 187
      assert row.deploy.push_to_live_ms == 5_540

@@ -197,22 +197,25 @@ defmodule OpenAgents.ChangelogTest do

197 197
      {:ok, push} =
198 198
        %PushReceipt{}
199 199
        |> PushReceipt.changeset(%{
200
          repo: "sarah",
200
          repo: "openagents.com",
201 201
          wal_seq: 1,
202 202
          principal: "operator:abc123",
203 203
          refs: %{"refs/heads/main" => %{"new" => sha}}
204 204
        })
205 205
        |> Repo.insert()
206 206
207
      assert {:ok, payload} = Changelog.projection("sarah", refresh: true)
207
      assert {:ok, payload} = Changelog.projection("openagents.com", refresh: true)
208 208
209
      assert payload["schema"] == "sarah.changelog.v1"
210
      assert payload["repo"] == "sarah"
209
      assert payload["schema"] == "openagents.changelog.v1"
210
      assert payload["repo"] == "openagents.com"
211 211
212 212
      assert [entry] = payload["entries"]
213 213
      assert entry["summary"] == "Projected entry"
214 214
      assert entry["receipt_ids"]["push"] == push.id
215
      assert entry["commit_url"] == "/OpenAgentsInc/sarah/commit/#{String.slice(sha, 0, 12)}"
215
216
      assert entry["commit_url"] ==
217
               "/OpenAgentsInc/openagents.com/commit/#{String.slice(sha, 0, 12)}"
218
216 219
      assert entry["push"]["principal_role"] == "operator"
217 220
      assert entry["push"]["wal_seq"] == 1
218 221

@@ -233,7 +236,7 @@ defmodule OpenAgents.ChangelogTest do

233 236
      Backfill.run()
234 237
      assert Repo.aggregate(Entry, :count) == seeded
235 238
236
      assert {:ok, rows} = Changelog.timeline("sarah", refresh: true)
239
      assert {:ok, rows} = Changelog.timeline("openagents.com", refresh: true)
237 240
      assert Enum.any?(rows, &(&1.source == "backfill"))
238 241
    end
239 242
  end
test/openagents/sarah_cluster_test.exs → test/openagents/cluster/basic_test.exs renamed +1 -1

@@ -1,4 +1,4 @@

1
defmodule OpenAgents.SarahClusterTest do
1
defmodule OpenAgents.Cluster.BasicTest do
2 2
  # Not async: it manipulates node-global distribution state.
3 3
  use ExUnit.Case, async: false
4 4
  alias OpenAgents.Cluster
test/openagents/cluster/chaos_test.exs modified +2 -2

@@ -29,8 +29,8 @@ defmodule OpenAgents.Cluster.ChaosTest do

29 29
30 30
  test "zero owned sessions are dropped across sequential node losses" do
31 31
    cookie = :erlang.get_cookie()
32
    {peer1, node1} = start_peer(:sarah_chaos1, cookie)
33
    {peer2, node2} = start_peer(:sarah_chaos2, cookie)
32
    {peer1, node1} = start_peer(:openagents_chaos1, cookie)
33
    {peer2, node2} = start_peer(:openagents_chaos2, cookie)
34 34
    on_exit(fn -> for p <- [peer1, peer2], do: safe_stop(p) end)
35 35
36 36
    assert eventually(fn ->
test/openagents/cluster/drain_test.exs modified +3 -3

@@ -20,8 +20,8 @@ defmodule OpenAgents.Cluster.DrainTest do

20 20
21 21
  test "a drained node leaves Raft; survivors keep quorum and keep committing" do
22 22
    cookie = :erlang.get_cookie()
23
    {peer1, node1} = start_peer(:sarah_drain1, cookie)
24
    {peer2, node2} = start_peer(:sarah_drain2, cookie)
23
    {peer1, node1} = start_peer(:openagents_drain1, cookie)
24
    {peer2, node2} = start_peer(:openagents_drain2, cookie)
25 25
    on_exit(fn -> for p <- [peer1, peer2], do: safe_stop(p) end)
26 26
27 27
    Ra.start_in(data_dir(node()))

@@ -50,7 +50,7 @@ defmodule OpenAgents.Cluster.DrainTest do

50 50
  # ── helpers ──────────────────────────────────────────────────────────────
51 51
52 52
  defp data_dir(node) do
53
    base = Path.join(System.tmp_dir!(), "sarah_drain_test")
53
    base = Path.join(System.tmp_dir!(), "openagents_drain_test")
54 54
    short = node |> Atom.to_string() |> String.replace(~r/[^a-zA-Z0-9]/, "_")
55 55
    Path.join(base, "#{short}_#{System.unique_integer([:positive])}")
56 56
  end
test/openagents/cluster/ra_cluster_test.exs modified +11 -14

@@ -22,8 +22,8 @@ defmodule OpenAgents.Cluster.RaClusterTest do

22 22
23 23
  test "3-node Raft: cross-node commit, linearizable reads, fence, and quorum survival" do
24 24
    cookie = :erlang.get_cookie()
25
    {peer1, node1} = start_peer(:sarah_ra_peer1, cookie)
26
    {peer2, node2} = start_peer(:sarah_ra_peer2, cookie)
25
    {peer1, node1} = start_peer(:openagents_ra_peer1, cookie)
26
    {peer2, node2} = start_peer(:openagents_ra_peer2, cookie)
27 27
    on_exit(fn -> for p <- [peer1, peer2], do: safe_stop(p) end)
28 28
29 29
    nodes = [node(), node1, node2]

@@ -73,8 +73,8 @@ defmodule OpenAgents.Cluster.RaClusterTest do

73 73
74 74
  test "a node joins an existing cluster via Ra.join and becomes a voting member" do
75 75
    cookie = :erlang.get_cookie()
76
    {peer1, node1} = start_peer(:sarah_ra_join1, cookie)
77
    {peer2, node2} = start_peer(:sarah_ra_join2, cookie)
76
    {peer1, node1} = start_peer(:openagents_ra_join1, cookie)
77
    {peer2, node2} = start_peer(:openagents_ra_join2, cookie)
78 78
    on_exit(fn -> for p <- [peer1, peer2], do: safe_stop(p) end)
79 79
80 80
    Ra.start_in(data_dir(node()))

@@ -103,8 +103,8 @@ defmodule OpenAgents.Cluster.RaClusterTest do

103 103
104 104
  test "a phantom member (local server down, still in config) restarts and rejoins" do
105 105
    cookie = :erlang.get_cookie()
106
    {peer1, node1} = start_peer(:sarah_ra_phantom1, cookie)
107
    {peer2, node2} = start_peer(:sarah_ra_phantom2, cookie)
106
    {peer1, node1} = start_peer(:openagents_ra_phantom1, cookie)
107
    {peer2, node2} = start_peer(:openagents_ra_phantom2, cookie)
108 108
    on_exit(fn -> for p <- [peer1, peer2], do: safe_stop(p) end)
109 109
110 110
    Ra.start_in(data_dir(node()))

@@ -115,11 +115,8 @@ defmodule OpenAgents.Cluster.RaClusterTest do

115 115
116 116
    # Stop peer2's local Raft server, leaving it in the cluster config but not
117 117
    # running here — the phantom-member state an ungraceful restart produces.
118
    # The Ra cluster atom was renamed `:sarah_sessions` -> `:openagents_sessions`
119
    # in `OpenAgents.Cluster.Ra`, but this line kept the old name. `:ra.stop_server`
120
    # answers `:ok` for a server id it does not know, so the stop silently did
121
    # nothing and peer2's real server stayed up. Ask the module for the id so the
122
    # test can never drift from the cluster name again.
118
    # Ask the module for the server ID so this test cannot drift from the
119
    # configured cluster name.
123 120
    :ok = :erpc.call(node2, :ra, :stop_server, [:default, Ra.server_id(node2)])
124 121
125 122
    assert eventually(fn -> :erpc.call(node2, Ra, :members, [node2]) == [] end),

@@ -139,8 +136,8 @@ defmodule OpenAgents.Cluster.RaClusterTest do

139 136
    cookie = :erlang.get_cookie()
140 137
    # standard_io control channels: the peers must survive losing their dist
141 138
    # connection to us — that loss IS the partition under test.
142
    {peer1, node1} = start_peer(:sarah_ra_part1, cookie, connection: :standard_io)
143
    {peer2, node2} = start_peer(:sarah_ra_part2, cookie, connection: :standard_io)
139
    {peer1, node1} = start_peer(:openagents_ra_part1, cookie, connection: :standard_io)
140
    {peer2, node2} = start_peer(:openagents_ra_part2, cookie, connection: :standard_io)
144 141
    on_exit(fn -> for p <- [peer1, peer2], do: safe_stop(p) end)
145 142
146 143
    Ra.start_in(data_dir(node()))

@@ -224,7 +221,7 @@ defmodule OpenAgents.Cluster.RaClusterTest do

224 221
  end
225 222
226 223
  defp data_dir(node) do
227
    base = Path.join(System.tmp_dir!(), "sarah_ra_test")
224
    base = Path.join(System.tmp_dir!(), "openagents_ra_test")
228 225
229 226
    short =
230 227
      node |> Atom.to_string() |> String.replace(~r/[^a-zA-Z0-9]/, "_")
test/openagents/coding_job_test.exs modified +9 -9

@@ -9,7 +9,7 @@ defmodule OpenAgents.CodingJobTest do

9 9
  action and is covered by the forge suite.
10 10
  """
11 11
12
  use OpenAgents.SarahDataCase, async: false
12
  use OpenAgents.DataCase, async: false
13 13
  import Ecto.Query
14 14
15 15
  alias OpenAgents.{Conversations, Work}

@@ -49,7 +49,7 @@ defmodule OpenAgents.CodingJobTest do

49 49
    Application.put_env(
50 50
      :openagents,
51 51
      :forge_self_push_url,
52
      "http://x:#{@operator_token}@127.0.0.1:#{port}/sarah.git"
52
      "http://x:#{@operator_token}@127.0.0.1:#{port}/openagents.com.git"
53 53
    )
54 54
55 55
    seed_repo!()

@@ -75,7 +75,7 @@ defmodule OpenAgents.CodingJobTest do

75 75
  end
76 76
77 77
  defp seed_repo! do
78
    path = Repos.ensure_repo!("sarah")
78
    path = Repos.ensure_repo!("openagents.com")
79 79
    {blob, 0} = plumb(path, ["hash-object", "-w", "--stdin"], "original content\n")
80 80
    {tree, 0} = plumb(path, ["mktree"], "100644 blob #{String.trim(blob)}\tnote.txt\n")
81 81

@@ -139,7 +139,7 @@ defmodule OpenAgents.CodingJobTest do

139 139
    # The report names the pushed commit — the operator promotes from it.
140 140
    assert job.report =~ "Coding report: pushed "
141 141
    assert [_, sha] = Regex.run(~r/pushed ([0-9a-f]{40})/, job.report)
142
    assert job.report =~ "sarah/job-#{job.id}"
142
    assert job.report =~ "openagents/job-#{job.id}"
143 143
144 144
    # Step receipts: all four tools succeeded, executor disclosed, and the
145 145
    # push step carries the commit SHA in its target receipt refs

@@ -150,18 +150,18 @@ defmodule OpenAgents.CodingJobTest do

150 150
151 151
    push_step = List.last(steps)
152 152
    # The middle segment is the forge repo the coding lane edits
153
    # (`OpenAgents.Tools.Repository.repo/0`) — still `sarah`, the same name
153
    # (`OpenAgents.Tools.Repository.repo/0`) — now `openagents.com`, the same repository
154 154
    # the push receipt below is looked up under.
155
    assert "forge-commit:sarah:#{sha}" in push_step.target_receipt_refs
155
    assert "forge-commit:openagents.com:#{sha}" in push_step.target_receipt_refs
156 156
157 157
    # The push is WAL-receipted on the forge, on the job's own branch.
158
    assert [push_receipt | _rest] = Forge.recent_pushes("sarah")
158
    assert [push_receipt | _rest] = Forge.recent_pushes("openagents.com")
159 159
    assert is_integer(push_receipt.wal_seq)
160
    branch_ref = "refs/heads/sarah/job-#{job.id}"
160
    branch_ref = "refs/heads/openagents/job-#{job.id}"
161 161
    assert %{"new" => ^sha} = Map.get(push_receipt.refs, branch_ref)
162 162
163 163
    # Only the job branch moved — main is untouched (promotion is a human).
164
    path = Repos.bare_path("sarah")
164
    path = Repos.bare_path("openagents.com")
165 165
    {main_sha, 0} = Repos.git(path, ["rev-parse", "refs/heads/main"])
166 166
    refute String.trim(main_sha) == sha
167 167
test/openagents/collective_generalizer_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.CollectiveGeneralizerTest do
2
  use OpenAgents.SarahDataCase, async: true
2
  use OpenAgents.DataCase, async: true
3 3
  alias OpenAgents.Collective
4 4
  alias OpenAgents.Collective.Generalizer
5 5
  alias OpenAgents.Conversations
test/openagents/collective_publication_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.CollectivePublicationTest do
2
  use OpenAgents.SarahDataCase, async: false
2
  use OpenAgents.DataCase, async: false
3 3
  alias OpenAgents.Collective
4 4
5 5
  alias OpenAgents.Collective.{
test/openagents/collective_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.CollectiveTest do
2
  use OpenAgents.SarahDataCase, async: true
2
  use OpenAgents.DataCase, async: true
3 3
  alias OpenAgents.Collective
4 4
  alias OpenAgents.Collective.{Candidate, ConsentReceipt}
5 5
  alias OpenAgents.Conversations
test/openagents/compensation_test.exs modified +14 -5

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.CompensationTest do
2
  use OpenAgents.SarahDataCase, async: false
2
  use OpenAgents.DataCase, async: false
3 3
  alias OpenAgents.{Compensation, Context.Composer, Conversations}
4 4
  alias OpenAgents.Compensation.{Event, Statement}
5 5
  alias OpenAgents.Providers.Request

@@ -34,7 +34,7 @@ defmodule OpenAgents.CompensationTest do

34 34
    assert event.classification == "eligible"
35 35
    assert event.technical_units == 100
36 36
    assert event.eligible_units == 100
37
    assert share.contribution_ref == "OpenAgentsInc/sarah"
37
    assert share.contribution_ref == "OpenAgentsInc/openagents.com"
38 38
    assert share.allocated_units == 100
39 39
40 40
    assert {:ok, %{event: same, shares: [same_share]}} = Compensation.account(step.id, policy)

@@ -207,7 +207,11 @@ defmodule OpenAgents.CompensationTest do

207 207
    register!(policy, artifact())
208 208
209 209
    assert {:ok, statement} =
210
             Compensation.reconcile("OpenAgentsInc/sarah", policy, operator("empty-statement"))
210
             Compensation.reconcile(
211
               "OpenAgentsInc/openagents.com",
212
               policy,
213
               operator("empty-statement")
214
             )
211 215
212 216
    assert_raise Postgrex.Error, fn ->
213 217
      statement |> Ecto.Changeset.change(net_units: 999) |> Repo.update!()

@@ -226,7 +230,12 @@ defmodule OpenAgents.CompensationTest do

226 230
             Compensation.register_module(
227 231
               policy,
228 232
               artifact,
229
               [%{"contribution_ref" => "OpenAgentsInc/sarah", "allocation_ppm" => 1_000_000}],
233
               [
234
                 %{
235
                   "contribution_ref" => "OpenAgentsInc/openagents.com",
236
                   "allocation_ppm" => 1_000_000
237
                 }
238
               ],
230 239
               operator("module-allocation")
231 240
             )
232 241
  end

@@ -274,7 +283,7 @@ defmodule OpenAgents.CompensationTest do

274 283
      "result" => %{"private" => private_result},
275 284
      "error" => nil,
276 285
      "target_receipt_refs" => ["message:opaque"],
277
      "attribution_refs" => ["OpenAgentsInc/sarah"],
286
      "attribution_refs" => ["OpenAgentsInc/openagents.com"],
278 287
      "started_at" => "2026-08-16T20:00:00Z",
279 288
      "completed_at" => "2026-08-16T20:00:01Z"
280 289
    }
test/openagents/computer_activity_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.ComputerActivityTest do
2
  use OpenAgents.SarahDataCase, async: false
2
  use OpenAgents.DataCase, async: false
3 3
  alias OpenAgents.{Accounts, Computer, ComputerActivity, Conversations, Machines}
4 4
  alias OpenAgents.Support.FakeController
5 5
test/openagents/conversations_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.ConversationsTest do
2
  use OpenAgents.SarahDataCase, async: true
2
  use OpenAgents.DataCase, async: true
3 3
  alias OpenAgents.Conversations
4 4
  alias OpenAgents.Conversations.{Message, Visitor}
5 5
test/openagents/data_rights/atif_export_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.DataRights.AtifExportTest do
2
  use OpenAgents.SarahDataCase, async: false
2
  use OpenAgents.DataCase, async: false
3 3
4 4
  alias OpenAgents.DataRights.AtifExport
5 5
  alias OpenAgents.Voice.ProviderEvent
test/openagents/deep_work_tool_loop_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.DeepWorkToolLoopTest do
2
  use OpenAgents.SarahDataCase
2
  use OpenAgents.DataCase
3 3
  import Ecto.Query
4 4
5 5
  alias OpenAgents.{Conversations, Turns, Work}
test/openagents/experience_memory_test.exs modified +2 -2

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.ExperienceMemoryTest do
2
  use OpenAgents.SarahDataCase, async: false
2
  use OpenAgents.DataCase, async: false
3 3
  alias OpenAgents.{Context.Composer, Conversations, ExperienceMemory}
4 4
  alias OpenAgents.ExperienceMemory.{Bank, BankItem, DeletionReceipt, EvidenceRef, Pattern}
5 5
  alias OpenAgents.Providers.Request

@@ -342,7 +342,7 @@ defmodule OpenAgents.ExperienceMemoryTest do

342 342
      "result" => %{"verified" => true},
343 343
      "error" => nil,
344 344
      "target_receipt_refs" => [target_ref],
345
      "attribution_refs" => ["OpenAgentsInc/sarah"],
345
      "attribution_refs" => ["OpenAgentsInc/openagents.com"],
346 346
      "started_at" => "2026-08-16T20:00:00Z",
347 347
      "completed_at" => "2026-08-16T20:00:01Z"
348 348
    }
test/openagents/forge/boot_converge_test.exs modified +1 -1

@@ -6,7 +6,7 @@ defmodule OpenAgents.Forge.BootConvergeTest do

6 6
  module — never a refusal to boot.
7 7
  """
8 8
9
  use OpenAgents.SarahDataCase, async: false
9
  use OpenAgents.DataCase, async: false
10 10
  alias OpenAgents.Forge.BootConverge
11 11
  alias OpenAgents.Forge.Repos
12 12
  alias OpenAgents.Forge.Target
test/openagents/forge/browse_test.exs modified +20 -19

@@ -6,7 +6,7 @@ defmodule OpenAgents.Forge.BrowseTest do

6 6
  shape gates that keep request data from ever becoming a git flag.
7 7
  """
8 8
9
  use OpenAgents.SarahDataCase, async: false
9
  use OpenAgents.DataCase, async: false
10 10
  alias OpenAgents.Forge.{Browse, Repos}
11 11
12 12
  @second_message """

@@ -38,7 +38,7 @@ defmodule OpenAgents.Forge.BrowseTest do

38 38
      File.rm_rf(base)
39 39
    end)
40 40
41
    seed_repo("sarah")
41
    seed_repo("openagents.com")
42 42
  end
43 43
44 44
  # Two chained commits in the bare repo via plumbing (no clone, no WAL):

@@ -47,7 +47,7 @@ defmodule OpenAgents.Forge.BrowseTest do

47 47
  defp seed_repo(repo) do
48 48
    path = Repos.ensure_repo!(repo)
49 49
50
    readme = write_blob(path, "# Sarah test repo\n\nFixture readme.\n")
50
    readme = write_blob(path, "# OpenAgents test repo\n\nFixture readme.\n")
51 51
    file_v1 = write_blob(path, "hello\n")
52 52
53 53
    tree_one =

@@ -114,26 +114,26 @@ defmodule OpenAgents.Forge.BrowseTest do

114 114
115 115
  describe "resolve_commit/2" do
116 116
    test "resolves a branch name, a full sha, and a short sha", %{second: second} do
117
      assert {:ok, ^second} = Browse.resolve_commit("sarah", "main")
118
      assert {:ok, ^second} = Browse.resolve_commit("sarah", second)
119
      assert {:ok, ^second} = Browse.resolve_commit("sarah", String.slice(second, 0, 8))
117
      assert {:ok, ^second} = Browse.resolve_commit("openagents.com", "main")
118
      assert {:ok, ^second} = Browse.resolve_commit("openagents.com", second)
119
      assert {:ok, ^second} = Browse.resolve_commit("openagents.com", String.slice(second, 0, 8))
120 120
    end
121 121
122 122
    test "an unknown ref or sha is :not_found" do
123
      assert {:error, :not_found} = Browse.resolve_commit("sarah", "no-such-branch")
124
      assert {:error, :not_found} = Browse.resolve_commit("sarah", "deadbeefdeadbeef")
123
      assert {:error, :not_found} = Browse.resolve_commit("openagents.com", "no-such-branch")
124
      assert {:error, :not_found} = Browse.resolve_commit("openagents.com", "deadbeefdeadbeef")
125 125
    end
126 126
127 127
    test "a malformed ref never reaches git" do
128
      assert {:error, :not_found} = Browse.resolve_commit("sarah", "-evil")
129
      assert {:error, :not_found} = Browse.resolve_commit("sarah", "a..b")
128
      assert {:error, :not_found} = Browse.resolve_commit("openagents.com", "-evil")
129
      assert {:error, :not_found} = Browse.resolve_commit("openagents.com", "a..b")
130 130
    end
131 131
  end
132 132
133 133
  describe "commit/2" do
134 134
    test "returns subject, author, parents, and parsed trailers — never an email",
135 135
         %{first: first, second: second} do
136
      assert {:ok, commit} = Browse.commit("sarah", String.slice(second, 0, 8))
136
      assert {:ok, commit} = Browse.commit("openagents.com", String.slice(second, 0, 8))
137 137
138 138
      assert commit.sha == second
139 139
      assert commit.subject == "Second commit"

@@ -154,14 +154,14 @@ defmodule OpenAgents.Forge.BrowseTest do

154 154
155 155
  test "changed_files/2 lists the second commit's additions and modifications",
156 156
       %{second: second} do
157
    assert {:ok, files} = Browse.changed_files("sarah", second)
157
    assert {:ok, files} = Browse.changed_files("openagents.com", second)
158 158
159 159
    assert %{status: "A", path: "docs/note.md"} in files
160 160
    assert %{status: "M", path: "file.txt"} in files
161 161
  end
162 162
163 163
  test "diff/2 returns the patch with an honest truncation flag", %{second: second} do
164
    assert {:ok, diff, false} = Browse.diff("sarah", second)
164
    assert {:ok, diff, false} = Browse.diff("openagents.com", second)
165 165
166 166
    assert diff =~ "docs/note.md"
167 167
    assert diff =~ "Body of the note."

@@ -169,7 +169,7 @@ defmodule OpenAgents.Forge.BrowseTest do

169 169
170 170
  describe "tree/3" do
171 171
    test "lists root entries with directories first" do
172
      assert {:ok, entries} = Browse.tree("sarah", "main")
172
      assert {:ok, entries} = Browse.tree("openagents.com", "main")
173 173
174 174
      assert Enum.map(entries, & &1.name) == ["docs", "README.md", "file.txt"]
175 175
      assert [%{kind: "tree", size: nil} | blobs] = entries

@@ -177,13 +177,14 @@ defmodule OpenAgents.Forge.BrowseTest do

177 177
    end
178 178
179 179
    test "lists a subdirectory" do
180
      assert {:ok, [%{name: "note.md", kind: "blob"}]} = Browse.tree("sarah", "main", "docs")
180
      assert {:ok, [%{name: "note.md", kind: "blob"}]} =
181
               Browse.tree("openagents.com", "main", "docs")
181 182
    end
182 183
  end
183 184
184 185
  describe "blob/3" do
185 186
    test "returns bounded content with size" do
186
      assert {:ok, blob} = Browse.blob("sarah", "main", "file.txt")
187
      assert {:ok, blob} = Browse.blob("openagents.com", "main", "file.txt")
187 188
188 189
      assert blob.content == "hello world\n"
189 190
      assert blob.size == byte_size("hello world\n")

@@ -192,7 +193,7 @@ defmodule OpenAgents.Forge.BrowseTest do

192 193
    end
193 194
194 195
    test "a missing path is :not_found" do
195
      assert {:error, :not_found} = Browse.blob("sarah", "main", "missing.txt")
196
      assert {:error, :not_found} = Browse.blob("openagents.com", "main", "missing.txt")
196 197
    end
197 198
198 199
    test "an unknown repo name is :not_found before any git runs" do

@@ -201,12 +202,12 @@ defmodule OpenAgents.Forge.BrowseTest do

201 202
  end
202 203
203 204
  test "readme/2 finds README.md at a ref" do
204
    assert {:ok, "README.md", blob} = Browse.readme("sarah", "main")
205
    assert {:ok, "README.md", blob} = Browse.readme("openagents.com", "main")
205 206
    assert blob.content =~ "Fixture readme."
206 207
  end
207 208
208 209
  test "log/3 returns both commits newest first", %{first: first, second: second} do
209
    assert {:ok, [newest, oldest]} = Browse.log("sarah", "main", 30)
210
    assert {:ok, [newest, oldest]} = Browse.log("openagents.com", "main", 30)
210 211
211 212
    assert newest.sha == second
212 213
    assert newest.subject == "Second commit"
test/openagents/forge/builder_test.exs modified +9 -9

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.Forge.BuilderTest do
2
  use OpenAgents.SarahDataCase, async: false
2
  use OpenAgents.DataCase, async: false
3 3
  alias OpenAgents.Forge.BuildExecutor
4 4
  alias OpenAgents.Forge.BuildExecutor.Sidecar
5 5
  alias OpenAgents.Forge.BuildReceipt

@@ -10,8 +10,8 @@ defmodule OpenAgents.Forge.BuilderTest do

10 10
11 11
  describe "pure Sidecar adapter pieces" do
12 12
    test "render_job serializes the two env-style lines the watcher sources" do
13
      assert Sidecar.render_job("abc123", "http://x:tok@127.0.0.1:8080/git/sarah.git") ==
14
               "SHA=abc123\nREPO_URL=http://x:tok@127.0.0.1:8080/git/sarah.git\n"
13
      assert Sidecar.render_job("abc123", "http://x:tok@127.0.0.1:8080/git/openagents.com.git") ==
14
               "SHA=abc123\nREPO_URL=http://x:tok@127.0.0.1:8080/git/openagents.com.git\n"
15 15
    end
16 16
17 17
    test "parse_result reads env-style lines, tolerating garbage" do

@@ -91,7 +91,7 @@ defmodule OpenAgents.Forge.BuilderTest do

91 91
      unless Process.whereis(Builder), do: start_supervised!({Builder, []})
92 92
93 93
      Phoenix.PubSub.subscribe(OpenAgents.PubSub, "forge:builds")
94
      %{sha: seeded_commit("sarah"), data_dir: Path.join(base, "data")}
94
      %{sha: seeded_commit("openagents.com"), data_dir: Path.join(base, "data")}
95 95
    end
96 96
97 97
    test "promotion builds, writes artifact tar + receipt, advances to built",

@@ -116,11 +116,11 @@ defmodule OpenAgents.Forge.BuilderTest do

116 116
        {:ok, %{beams: beams, warnings: "warn: something minor", tests: nil, duration_ms: 123}}
117 117
      )
118 118
119
      {:ok, target} = Targets.promote("sarah", sha, "test-operator")
119
      {:ok, target} = Targets.promote("openagents.com", sha, "test-operator")
120 120
121 121
      assert_receive {:forge_build_ready,
122 122
                      %{
123
                        repo: "sarah",
123
                        repo: "openagents.com",
124 124
                        sha: ^sha,
125 125
                        target_id: target_id,
126 126
                        artifact: artifact,

@@ -140,7 +140,7 @@ defmodule OpenAgents.Forge.BuilderTest do

140 140
      assert binary == beam.binary
141 141
142 142
      # Receipt row.
143
      receipt = Repo.get_by!(BuildReceipt, repo: "sarah", sha: sha)
143
      receipt = Repo.get_by!(BuildReceipt, repo: "openagents.com", sha: sha)
144 144
      assert receipt.target_id == target.id
145 145
      assert receipt.modules == [module]
146 146
      assert receipt.warnings == "warn: something minor"

@@ -161,7 +161,7 @@ defmodule OpenAgents.Forge.BuilderTest do

161 161
        {:error, "boom\n" <> String.duplicate("x", 20_000)}
162 162
      )
163 163
164
      {:ok, target} = Targets.promote("sarah", sha, "test-operator")
164
      {:ok, target} = Targets.promote("openagents.com", sha, "test-operator")
165 165
166 166
      failed = await_status(target, "failed")
167 167
      assert failed.status == "failed"

@@ -186,7 +186,7 @@ defmodule OpenAgents.Forge.BuilderTest do

186 186
        {:ok, %{beams: beams, warnings: "", tests: nil, duration_ms: 1}}
187 187
      )
188 188
189
      {:ok, target2} = Targets.promote("sarah", sha, "test-operator")
189
      {:ok, target2} = Targets.promote("openagents.com", sha, "test-operator")
190 190
      target2_id = target2.id
191 191
      assert_receive {:forge_build_ready, %{sha: ^sha, target_id: ^target2_id}}, 5_000
192 192
    end
test/openagents/forge/git_http_test.exs modified +1 -1

@@ -6,7 +6,7 @@ defmodule OpenAgents.Forge.GitHTTPTest do

6 6
  the roadmap, minus the fleet.
7 7
  """
8 8
9
  use OpenAgents.SarahDataCase, async: false
9
  use OpenAgents.DataCase, async: false
10 10
  alias OpenAgents.Forge
11 11
  alias OpenAgents.Forge.{Repos, WAL}
12 12
test/openagents/forge/hot_loader_test.exs modified +13 -12

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.Forge.HotLoaderTest do
2
  use OpenAgents.SarahDataCase, async: false
2
  use OpenAgents.DataCase, async: false
3 3
  @moduletag :capture_log
4 4
5 5
  alias OpenAgents.Forge.DeployReceipt

@@ -66,7 +66,7 @@ defmodule OpenAgents.Forge.HotLoaderTest do

66 66
    {:ok, target} =
67 67
      %Target{}
68 68
      |> Target.changeset(%{
69
        repo: "sarah",
69
        repo: "openagents.com",
70 70
        sha: sha,
71 71
        promoted_by: "test-op",
72 72
        status: status

@@ -102,7 +102,7 @@ defmodule OpenAgents.Forge.HotLoaderTest do

102 102
    Phoenix.PubSub.subscribe(OpenAgents.PubSub, @deploys_topic)
103 103
104 104
    broadcast_build_ready(loader, %{
105
      repo: "sarah",
105
      repo: "openagents.com",
106 106
      sha: sha,
107 107
      target_id: target.id,
108 108
      artifact: artifact,

@@ -116,13 +116,13 @@ defmodule OpenAgents.Forge.HotLoaderTest do

116 116
117 117
    receipt = deploy_receipt(sha)
118 118
    assert receipt.result == "live"
119
    assert receipt.repo == "sarah"
119
    assert receipt.repo == "openagents.com"
120 120
    assert receipt.target_id == target.id
121 121
    assert receipt.modules == [name]
122 122
    assert receipt.canary == "ok"
123 123
    assert receipt.nodes == ["#{Node.self()}=ok"]
124 124
125
    assert_receive {:forge_deploy, %{repo: "sarah", sha: ^sha, result: "live"}}
125
    assert_receive {:forge_deploy, %{repo: "openagents.com", sha: ^sha, result: "live"}}
126 126
  end
127 127
128 128
  test "allowlist refusal: off-allowlist module means needs_rolling_replace and no load", %{

@@ -137,7 +137,7 @@ defmodule OpenAgents.Forge.HotLoaderTest do

137 137
    Phoenix.PubSub.subscribe(OpenAgents.PubSub, @deploys_topic)
138 138
139 139
    broadcast_build_ready(loader, %{
140
      repo: "sarah",
140
      repo: "openagents.com",
141 141
      sha: sha,
142 142
      target_id: target.id,
143 143
      artifact: artifact,

@@ -155,7 +155,8 @@ defmodule OpenAgents.Forge.HotLoaderTest do

155 155
    offenders = Repo.get!(Target, target.id).details["modules"]
156 156
    assert offenders == ["OpenAgents.Turns.Whatever"]
157 157
158
    assert_receive {:forge_deploy, %{repo: "sarah", sha: ^sha, result: "needs_rolling_replace"}}
158
    assert_receive {:forge_deploy,
159
                    %{repo: "openagents.com", sha: ^sha, result: "needs_rolling_replace"}}
159 160
  end
160 161
161 162
  test "canary revert: a corrupt beam reverts the whole artifact", %{loader: loader} do

@@ -170,7 +171,7 @@ defmodule OpenAgents.Forge.HotLoaderTest do

170 171
    Phoenix.PubSub.subscribe(OpenAgents.PubSub, @deploys_topic)
171 172
172 173
    broadcast_build_ready(loader, %{
173
      repo: "sarah",
174
      repo: "openagents.com",
174 175
      sha: sha,
175 176
      target_id: target.id,
176 177
      artifact: artifact,

@@ -184,7 +185,7 @@ defmodule OpenAgents.Forge.HotLoaderTest do

184 185
    assert Repo.get!(Target, target.id).status == "reverted"
185 186
    assert deploy_receipt(sha).result == "reverted"
186 187
187
    assert_receive {:forge_deploy, %{repo: "sarah", sha: ^sha, result: "reverted"}}
188
    assert_receive {:forge_deploy, %{repo: "openagents.com", sha: ^sha, result: "reverted"}}
188 189
  end
189 190
190 191
  test "push_to_live_ms is measured from the matching push receipt", %{loader: loader} do

@@ -197,7 +198,7 @@ defmodule OpenAgents.Forge.HotLoaderTest do

197 198
    {:ok, _push} =
198 199
      %PushReceipt{}
199 200
      |> PushReceipt.changeset(%{
200
        repo: "sarah",
201
        repo: "openagents.com",
201 202
        wal_seq: System.unique_integer([:positive]),
202 203
        principal: "test-op",
203 204
        refs: %{"refs/heads/main" => %{"old" => String.duplicate("0", 40), "new" => sha}}

@@ -205,7 +206,7 @@ defmodule OpenAgents.Forge.HotLoaderTest do

205 206
      |> Repo.insert()
206 207
207 208
    broadcast_build_ready(loader, %{
208
      repo: "sarah",
209
      repo: "openagents.com",
209 210
      sha: sha,
210 211
      target_id: target.id,
211 212
      artifact: artifact,

@@ -226,7 +227,7 @@ defmodule OpenAgents.Forge.HotLoaderTest do

226 227
    artifact = tar_artifact([{name, binary}])
227 228
228 229
    broadcast_build_ready(loader, %{
229
      repo: "sarah",
230
      repo: "openagents.com",
230 231
      sha: sha,
231 232
      target_id: target.id,
232 233
      artifact: artifact,
test/openagents/forge/janitor_test.exs modified +7 -2

@@ -1,7 +1,7 @@

1 1
defmodule OpenAgents.Forge.JanitorTest do
2 2
  @moduledoc "Cache retention (#123): stale clones and non-live artifacts are pruned; truth never is."
3 3
4
  use OpenAgents.SarahDataCase, async: false
4
  use OpenAgents.DataCase, async: false
5 5
  alias OpenAgents.Forge.Janitor
6 6
  alias OpenAgents.Forge.Repos
7 7
  alias OpenAgents.Forge.Target

@@ -40,7 +40,12 @@ defmodule OpenAgents.Forge.JanitorTest do

40 40
    File.write!(Path.join(base, "data/beams/oldjunk.tar"), "junk")
41 41
42 42
    %Target{}
43
    |> Target.changeset(%{repo: "sarah", sha: live_sha, promoted_by: "op:t", status: "promoted"})
43
    |> Target.changeset(%{
44
      repo: "openagents.com",
45
      sha: live_sha,
46
      promoted_by: "op:t",
47
      status: "promoted"
48
    })
44 49
    |> Repo.insert!()
45 50
    |> Ecto.Changeset.change(%{
46 51
      status: "live",
test/openagents/forge/loop_v0_test.exs modified +10 -10

@@ -7,7 +7,7 @@ defmodule OpenAgents.Forge.LoopV0Test do

7 7
  rolling-replace verification; this is the loop itself.)
8 8
  """
9 9
10
  use OpenAgents.SarahDataCase, async: false
10
  use OpenAgents.DataCase, async: false
11 11
  alias OpenAgents.Forge.{FakeBuildExecutor, Targets}
12 12
13 13
  defmodule TestPipeline do

@@ -50,7 +50,7 @@ defmodule OpenAgents.Forge.LoopV0Test do

50 50
      File.rm_rf(base)
51 51
    end)
52 52
53
    %{base: base, url: "http://x:#{@operator_token}@127.0.0.1:#{port}/sarah.git"}
53
    %{base: base, url: "http://x:#{@operator_token}@127.0.0.1:#{port}/openagents.com.git"}
54 54
  end
55 55
56 56
  # Restoring a nil via put_env stores literal nil, which then shadows

@@ -113,21 +113,21 @@ defmodule OpenAgents.Forge.LoopV0Test do

113 113
114 114
    # 2. Promote (the operator approval).
115 115
    Phoenix.PubSub.subscribe(OpenAgents.PubSub, "forge:deploys")
116
    assert {:ok, target} = Targets.promote("sarah", sha, "operator:loop-test")
116
    assert {:ok, target} = Targets.promote("openagents.com", sha, "operator:loop-test")
117 117
118 118
    # 3–4. Build + hot-load run off the PubSub signals; wait for live.
119
    assert_receive {:forge_deploy, %{repo: "sarah", sha: ^sha, result: "live"}}, 10_000
119
    assert_receive {:forge_deploy, %{repo: "openagents.com", sha: ^sha, result: "live"}}, 10_000
120 120
121 121
    # The module is actually live in this runtime.
122 122
    module = Module.concat([module_name])
123 123
    assert module.revision() == "loop-v0"
124 124
125 125
    # Target walked the full lifecycle.
126
    assert Targets.current("sarah").status == "live"
127
    assert Targets.current("sarah").id == target.id
126
    assert Targets.current("openagents.com").status == "live"
127
    assert Targets.current("openagents.com").id == target.id
128 128
129 129
    # The loop time is first-class: measured from the push receipt.
130
    assert [deploy] = OpenAgents.Forge.recent_deploys("sarah")
130
    assert [deploy] = OpenAgents.Forge.recent_deploys("openagents.com")
131 131
    assert deploy.result == "live"
132 132
    assert is_integer(deploy.push_to_live_ms) and deploy.push_to_live_ms >= 0
133 133
    assert deploy.sha == sha

@@ -162,11 +162,11 @@ defmodule OpenAgents.Forge.LoopV0Test do

162 162
    sha = String.trim(sha)
163 163
164 164
    Phoenix.PubSub.subscribe(OpenAgents.PubSub, "forge:deploys")
165
    {:ok, target} = Targets.promote("sarah", sha, "operator:loop-test")
165
    {:ok, target} = Targets.promote("openagents.com", sha, "operator:loop-test")
166 166
167 167
    assert_receive {:forge_deploy, %{sha: ^sha, result: "needs_rolling_replace"}}, 10_000
168 168
    refute Code.ensure_loaded?(Module.concat([module_name]))
169
    assert Targets.current("sarah").id == target.id
170
    assert Targets.current("sarah").status == "needs_rolling_replace"
169
    assert Targets.current("openagents.com").id == target.id
170
    assert Targets.current("openagents.com").status == "needs_rolling_replace"
171 171
  end
172 172
end
test/openagents/forge/mirror_watch_test.exs modified +9 -6

@@ -5,7 +5,7 @@ defmodule OpenAgents.Forge.MirrorWatchTest do

5 5
  the unreachable-mirror posture (forge unaffected, nothing reported).
6 6
  """
7 7
8
  use OpenAgents.SarahDataCase, async: false
8
  use OpenAgents.DataCase, async: false
9 9
  import Ecto.Query
10 10
11 11
  alias OpenAgents.Forge.{MirrorWatch, Repos}

@@ -26,10 +26,10 @@ defmodule OpenAgents.Forge.MirrorWatchTest do

26 26
    # A local bare repo stands in for GitHub.
27 27
    mirror = Path.join(base, "github-mirror.git")
28 28
    {_, 0} = System.cmd("git", ["init", "--bare", "--quiet", mirror])
29
    Application.put_env(:openagents, :forge_mirror_urls, %{"sarah" => mirror})
29
    Application.put_env(:openagents, :forge_mirror_urls, %{"openagents.com" => mirror})
30 30
31 31
    # Seed the forge repo with one commit on main.
32
    path = Repos.ensure_repo!("sarah")
32
    path = Repos.ensure_repo!("openagents.com")
33 33
    seed_commit!(path, "one")
34 34
35 35
    on_exit(fn ->

@@ -105,12 +105,12 @@ defmodule OpenAgents.Forge.MirrorWatchTest do

105 105
  test "sustained lag records ONE degraded incident per episode" do
106 106
    # Make the mirror un-pushable but still readable: replace it with a
107 107
    # bare repo whose receive hook rejects everything.
108
    mirror = Application.fetch_env!(:openagents, :forge_mirror_urls)["sarah"]
108
    mirror = Application.fetch_env!(:openagents, :forge_mirror_urls)["openagents.com"]
109 109
    hook = Path.join(mirror, "hooks/pre-receive")
110 110
    File.write!(hook, "#!/bin/sh\nexit 1\n")
111 111
    File.chmod!(hook, 0o755)
112 112
    # Diverge the forge past the mirror.
113
    seed_commit!(Repos.bare_path("sarah"), "diverge")
113
    seed_commit!(Repos.bare_path("openagents.com"), "diverge")
114 114
115 115
    now = System.monotonic_time(:millisecond)
116 116
    state = MirrorWatch.check_all(fresh_state(), now)

@@ -132,7 +132,10 @@ defmodule OpenAgents.Forge.MirrorWatchTest do

132 132
    _state = MirrorWatch.check_all(fresh_state())
133 133
    assert MirrorWatch.state()["state"] == "off"
134 134
135
    Application.put_env(:openagents, :forge_mirror_urls, %{"sarah" => "/nonexistent/mirror.git"})
135
    Application.put_env(:openagents, :forge_mirror_urls, %{
136
      "openagents.com" => "/nonexistent/mirror.git"
137
    })
138
136 139
    _state = MirrorWatch.check_all(fresh_state())
137 140
    assert incident_count() == 0
138 141
  end
test/openagents/forge/receipt_replayer_test.exs modified +1 -1

@@ -6,7 +6,7 @@ defmodule OpenAgents.Forge.ReceiptReplayerTest do

6 6
  rows are never duplicated or rewritten; refs never live in Postgres.
7 7
  """
8 8
9
  use OpenAgents.SarahDataCase, async: false
9
  use OpenAgents.DataCase, async: false
10 10
  import Ecto.Query
11 11
12 12
  alias OpenAgents.Forge
test/openagents/forge/sarah_targets_test.exs → test/openagents/forge/target_lifecycle_test.exs renamed +2 -2

@@ -1,5 +1,5 @@

1
defmodule OpenAgents.Forge.SarahTargetsTest do
2
  use OpenAgents.SarahDataCase, async: false
1
defmodule OpenAgents.Forge.TargetLifecycleTest do
2
  use OpenAgents.DataCase, async: false
3 3
  alias OpenAgents.Forge.{Repos, Targets}
4 4
5 5
  setup do
test/openagents/forge/targets_test.exs modified +1 -1

@@ -1,7 +1,7 @@

1 1
defmodule OpenAgents.Forge.TargetsTest do
2 2
  @moduledoc """
3 3
  The two surfaces of `OpenAgents.Forge.Targets` that the end-to-end
4
  lifecycle test (`OpenAgents.Forge.SarahTargetsTest`, which drives a real
4
  lifecycle test (`OpenAgents.Forge.TargetLifecycleTest`, which drives a real
5 5
  bare repo) does not reach: the injectable `commit_store`, and the bounded
6 6
  `details` map.
7 7
test/openagents/forge/visibility_test.exs modified +21 -21

@@ -5,7 +5,7 @@ defmodule OpenAgents.Forge.VisibilityTest do

5 5
  each capability has an explicit minimum level.
6 6
  """
7 7
8
  use OpenAgents.SarahDataCase, async: false
8
  use OpenAgents.DataCase, async: false
9 9
  alias OpenAgents.Forge.Visibility
10 10
11 11
  defp override_visibility(map) do

@@ -29,11 +29,11 @@ defmodule OpenAgents.Forge.VisibilityTest do

29 29
  end
30 30
31 31
  test "the configured level is read from :forge_public_visibility" do
32
    # config.exs ships "sarah" at :l2 — it is a private repository.
33
    assert Visibility.level("sarah") == :l2
32
    # config.exs ships "openagents.com" at :l3 — it is a public repository.
33
    assert Visibility.level("openagents.com") == :l3
34 34
35
    override_visibility(%{"sarah" => :l1, "demo" => :l2})
36
    assert Visibility.level("sarah") == :l1
35
    override_visibility(%{"openagents.com" => :l1, "demo" => :l2})
36
    assert Visibility.level("openagents.com") == :l1
37 37
    assert Visibility.level("demo") == :l2
38 38
  end
39 39

@@ -75,41 +75,41 @@ defmodule OpenAgents.Forge.VisibilityTest do

75 75
76 76
  describe "published documents (a private repo publishes files, not history)" do
77 77
    test "published?/2 reads the operator-owned allowlist" do
78
      override_paths(%{"sarah" => ["docs/a.md", "CHANGELOG.md"]})
78
      override_paths(%{"openagents.com" => ["docs/a.md", "CHANGELOG.md"]})
79 79
80
      assert Visibility.published?("sarah", "docs/a.md")
81
      assert Visibility.published?("sarah", "CHANGELOG.md")
82
      refute Visibility.published?("sarah", "lib/secret.ex")
80
      assert Visibility.published?("openagents.com", "docs/a.md")
81
      assert Visibility.published?("openagents.com", "CHANGELOG.md")
82
      refute Visibility.published?("openagents.com", "lib/secret.ex")
83 83
      refute Visibility.published?("other", "docs/a.md")
84 84
    end
85 85
86 86
    test "allows_file?/4 serves a published path only at head below :l3" do
87
      override_visibility(%{"sarah" => :l2})
88
      override_paths(%{"sarah" => ["docs/a.md"]})
87
      override_visibility(%{"openagents.com" => :l2})
88
      override_paths(%{"openagents.com" => ["docs/a.md"]})
89 89
90
      assert Visibility.allows_file?("sarah", "docs/a.md", "headsha", "headsha")
90
      assert Visibility.allows_file?("openagents.com", "docs/a.md", "headsha", "headsha")
91 91
      # An older ref for the same published path is refused: publishing a
92 92
      # document must not publish its history.
93
      refute Visibility.allows_file?("sarah", "docs/a.md", "oldsha", "headsha")
93
      refute Visibility.allows_file?("openagents.com", "docs/a.md", "oldsha", "headsha")
94 94
      # A path that was never published is refused at any ref.
95
      refute Visibility.allows_file?("sarah", "lib/secret.ex", "headsha", "headsha")
95
      refute Visibility.allows_file?("openagents.com", "lib/secret.ex", "headsha", "headsha")
96 96
    end
97 97
98 98
    test "allows_file?/4 serves anything at :l3, at any ref" do
99
      override_visibility(%{"sarah" => :l3})
100
      override_paths(%{"sarah" => []})
99
      override_visibility(%{"openagents.com" => :l3})
100
      override_paths(%{"openagents.com" => []})
101 101
102
      assert Visibility.allows_file?("sarah", "lib/anything.ex", "oldsha", "headsha")
102
      assert Visibility.allows_file?("openagents.com", "lib/anything.ex", "oldsha", "headsha")
103 103
    end
104 104
105 105
    test "a dark repo publishes nothing even with an allowlist" do
106
      override_visibility(%{"sarah" => :l0})
107
      override_paths(%{"sarah" => ["docs/a.md"]})
106
      override_visibility(%{"openagents.com" => :l0})
107
      override_paths(%{"openagents.com" => ["docs/a.md"]})
108 108
109
      refute Visibility.allows?("sarah", :ledger)
109
      refute Visibility.allows?("openagents.com", :ledger)
110 110
      # allows_file?/4 still honors the explicit publication decision; the
111 111
      # surfaces gate on the level first, which is what makes :l0 dark.
112
      assert Visibility.published?("sarah", "docs/a.md")
112
      assert Visibility.published?("openagents.com", "docs/a.md")
113 113
    end
114 114
  end
115 115
test/openagents/forge/wal_test.exs modified +12 -7

@@ -2,13 +2,13 @@ defmodule OpenAgents.Forge.WALTest do

2 2
  use ExUnit.Case, async: false
3 3
  alias OpenAgents.Forge.WAL
4 4
5
  @repo "sarah"
5
  @repo "openagents.com"
6 6
7 7
  setup do
8 8
    tmp_dir =
9 9
      Path.join(
10 10
        System.tmp_dir!(),
11
        "sarah_forge_wal_test_#{System.unique_integer([:positive])}"
11
        "openagents_forge_wal_test_#{System.unique_integer([:positive])}"
12 12
      )
13 13
14 14
    File.mkdir_p!(tmp_dir)

@@ -115,7 +115,7 @@ defmodule OpenAgents.Forge.WALTest do

115 115
116 116
  describe "repo validation" do
117 117
    test "rejects invalid repo names on every dispatcher function" do
118
      for bad <- ["Sarah", "a/b", "", "-lead", "sarah.git", :openagents] do
118
      for bad <- ["Uppercase", "a/b", "", "-lead", "bad..git", :openagents] do
119 119
        assert {:error, :invalid_repo} = WAL.read_index(bad)
120 120
        assert {:error, :invalid_repo} = WAL.cas_index(bad, :none, WAL.new_index())
121 121
        assert {:error, :invalid_repo} = WAL.put_entry(bad, 0, "x")

@@ -228,11 +228,16 @@ defmodule OpenAgents.Forge.WALTest do

228 228
    end
229 229
230 230
    test "object naming helpers" do
231
      assert OpenAgents.Forge.WAL.Gcs.prefix("sarah") == "forge/wal/sarah/"
232
      assert OpenAgents.Forge.WAL.Gcs.index_object("sarah") == "forge/wal/sarah/index.json"
231
      assert OpenAgents.Forge.WAL.Gcs.prefix("openagents.com") == "forge/wal/openagents.com/"
233 232
234
      assert OpenAgents.Forge.WAL.Gcs.object_name("sarah", "entries/00000000-0123456789ab") ==
235
               "forge/wal/sarah/entries/00000000-0123456789ab"
233
      assert OpenAgents.Forge.WAL.Gcs.index_object("openagents.com") ==
234
               "forge/wal/openagents.com/index.json"
235
236
      assert OpenAgents.Forge.WAL.Gcs.object_name(
237
               "openagents.com",
238
               "entries/00000000-0123456789ab"
239
             ) ==
240
               "forge/wal/openagents.com/entries/00000000-0123456789ab"
236 241
    end
237 242
  end
238 243
end
test/openagents/github_oauth_test.exs modified +2 -2

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.GitHubOAuthTest do
2
  use OpenAgents.SarahDataCase, async: false
2
  use OpenAgents.DataCase, async: false
3 3
  alias OpenAgents.{GitHubOAuth, Repo}
4 4
  alias OpenAgents.Accounts.OAuthAttempt
5 5

@@ -44,7 +44,7 @@ defmodule OpenAgents.GitHubOAuthTest do

44 44
    Req.Test.expect(__MODULE__, fn conn ->
45 45
      assert conn.request_path == "/login/oauth/access_token"
46 46
      assert ["application/json"] = Plug.Conn.get_req_header(conn, "accept")
47
      assert ["OpenAgents-Sarah"] = Plug.Conn.get_req_header(conn, "user-agent")
47
      assert ["OpenAgents"] = Plug.Conn.get_req_header(conn, "user-agent")
48 48
      body = Req.Test.raw_body(conn)
49 49
      assert body =~ "client_id=test-github-client-id"
50 50
      assert body =~ "client_secret=test-github-client-secret"
test/openagents/github_test.exs modified +1 -1

@@ -22,7 +22,7 @@ defmodule OpenAgents.GitHubTest do

22 22
      assert ["Bearer gho_listing-token"] = Plug.Conn.get_req_header(conn, "authorization")
23 23
      assert ["application/vnd.github+json"] = Plug.Conn.get_req_header(conn, "accept")
24 24
      assert ["2022-11-28"] = Plug.Conn.get_req_header(conn, "x-github-api-version")
25
      assert ["OpenAgents-Sarah"] = Plug.Conn.get_req_header(conn, "user-agent")
25
      assert ["OpenAgents"] = Plug.Conn.get_req_header(conn, "user-agent")
26 26
27 27
      query = URI.decode_query(conn.query_string)
28 28
      assert query["per_page"] == "5"
test/openagents/graph_memory_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.GraphMemoryTest do
2
  use OpenAgents.SarahDataCase, async: false
2
  use OpenAgents.DataCase, async: false
3 3
  alias OpenAgents.{Conversations, ExperienceMemory, GraphMemory}
4 4
5 5
  alias OpenAgents.GraphMemory.{
test/openagents/incidents/fixer_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.Incidents.FixerTest do
2
  use OpenAgents.SarahDataCase
2
  use OpenAgents.DataCase
3 3
4 4
  alias OpenAgents.Incidents
5 5
  alias OpenAgents.Incidents.Fixer
test/openagents/incidents_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.IncidentsTest do
2
  use OpenAgents.SarahDataCase
2
  use OpenAgents.DataCase
3 3
  alias OpenAgents.Incidents
4 4
  alias OpenAgents.Incidents.Triage
5 5
  alias OpenAgents.{Accounts, Conversations, Repo}
test/openagents/inference_test.exs modified +2 -2

@@ -1,6 +1,6 @@

1 1
defmodule OpenAgents.InferenceTest do
2
  use OpenAgents.SarahDataCase, async: false
3
  import OpenAgentsWeb.SarahConnCase, only: [github_user: 1]
2
  use OpenAgents.DataCase, async: false
3
  import OpenAgentsWeb.ConnCase, only: [github_user: 1]
4 4
5 5
  alias OpenAgents.Inference
6 6
  alias OpenAgents.Inference.Grant
test/openagents/leaderboard_test.exs modified +1 -1

@@ -5,7 +5,7 @@ defmodule OpenAgents.LeaderboardTest do

5 5
  counted twice.
6 6
  """
7 7
8
  use OpenAgents.SarahDataCase, async: false
8
  use OpenAgents.DataCase, async: false
9 9
  alias OpenAgents.Accounts
10 10
  alias OpenAgents.Context.Composer
11 11
  alias OpenAgents.Conversations
test/openagents/machines/token_vault_test.exs added +32

@@ -0,0 +1,32 @@

1
defmodule OpenAgents.Machines.TokenVaultTest do
2
  use ExUnit.Case, async: true
3
4
  alias OpenAgents.Machines.TokenVault
5
6
  test "seals new tokens with the OpenAgents version" do
7
    assert {:ok, <<2, _rest::binary>> = sealed} = TokenVault.seal("smct_current")
8
    assert {:ok, "smct_current"} = TokenVault.open(sealed)
9
  end
10
11
  test "opens legacy Sarah version-1 tokens during migration" do
12
    token = "smct_legacy"
13
    nonce = :crypto.strong_rand_bytes(12)
14
    {:ok, key} = Base.decode64(Application.fetch_env!(:openagents, :github_token_encryption_key))
15
16
    {ciphertext, tag} =
17
      :crypto.crypto_one_time_aead(
18
        :aes_256_gcm,
19
        key,
20
        nonce,
21
        token,
22
        "sarah.machine_token.v1",
23
        true
24
      )
25
26
    assert {:ok, ^token} = TokenVault.open(<<1, nonce::binary, tag::binary, ciphertext::binary>>)
27
  end
28
29
  test "refuses unknown versions" do
30
    assert {:error, :token_unsealable} = TokenVault.open(<<3, 0::256>>)
31
  end
32
end
test/openagents/machines_capacity_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.MachinesCapacityTest do
2
  use OpenAgents.SarahDataCase, async: false
2
  use OpenAgents.DataCase, async: false
3 3
  alias OpenAgents.Machines
4 4
  alias OpenAgents.Machines.Machine
5 5
test/openagents/machines_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.MachinesTest do
2
  use OpenAgents.SarahDataCase, async: true
2
  use OpenAgents.DataCase, async: true
3 3
  alias OpenAgents.Machines
4 4
  alias OpenAgents.Machines.Pairing
5 5
test/openagents/memory/evaluation_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.Memory.EvaluationTest do
2
  use OpenAgents.SarahDataCase
2
  use OpenAgents.DataCase
3 3
4 4
  alias OpenAgents.Memory.Evaluation.{Corpus, ReleaseGate, Report, Runner}
5 5
test/openagents/memory/evidence_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.Memory.EvidenceTest do
2
  use OpenAgents.SarahDataCase
2
  use OpenAgents.DataCase
3 3
4 4
  alias OpenAgents.{Context.Composer, Conversations, Repo}
5 5
  alias OpenAgents.Conversations.Message
test/openagents/memory/lexical_recall_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.Memory.LexicalRecallTest do
2
  use OpenAgents.SarahDataCase
2
  use OpenAgents.DataCase
3 3
4 4
  alias OpenAgents.{Context.Composer, Conversations, Repo}
5 5
  alias OpenAgents.Conversations.Message
test/openagents/memory/policy_and_redaction_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.Memory.PolicyAndRedactionTest do
2
  use OpenAgents.SarahDataCase
2
  use OpenAgents.DataCase
3 3
4 4
  import ExUnit.CaptureLog
5 5
test/openagents/memory_portability_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.MemoryPortabilityTest do
2
  use OpenAgents.SarahDataCase, async: false
2
  use OpenAgents.DataCase, async: false
3 3
  import ExUnit.CaptureLog
4 4
5 5
  alias OpenAgents.{Conversations, ProfileMemory}
test/openagents/modules/lifecycle_test.exs modified +2 -2

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.Modules.LifecycleTest do
2
  use OpenAgents.SarahDataCase, async: true
2
  use OpenAgents.DataCase, async: true
3 3
4 4
  alias OpenAgents.Modules.{Discovery, Lifecycle, LifecycleReceipt, Metadata}
5 5
  alias OpenAgents.Tools.{Registry, Tool}

@@ -184,7 +184,7 @@ defmodule OpenAgents.Modules.LifecycleTest do

184 184
      required_authority: "conversation.read",
185 185
      executor: %{id: "sarah.test", disclosure: "Sarah lifecycle test"},
186 186
      maintainer: "OpenAgents",
187
      attribution: ["OpenAgentsInc/sarah"],
187
      attribution: ["OpenAgentsInc/openagents.com"],
188 188
      policy_facets: %{"privacy" => "test", "residency" => "test"},
189 189
      module_metadata:
190 190
        Metadata.first_party("conversation.read", "browser_conversation",
test/openagents/modules/registry_test.exs modified +1 -1

@@ -206,7 +206,7 @@ defmodule OpenAgents.Modules.RegistryTest do

206 206
      required_authority: "conversation.read",
207 207
      executor: %{id: "sarah.local", disclosure: "Sarah local module test"},
208 208
      maintainer: "OpenAgents",
209
      attribution: ["OpenAgentsInc/sarah"],
209
      attribution: ["OpenAgentsInc/openagents.com"],
210 210
      policy_facets: %{"privacy" => "browser_scoped", "residency" => "host"},
211 211
      module_metadata:
212 212
        Metadata.first_party("conversation.read", "browser_conversation",
test/openagents/network_status_test.exs modified +11 -6

@@ -1,11 +1,11 @@

1 1
defmodule OpenAgents.NetworkStatusTest do
2
  use OpenAgents.SarahDataCase
2
  use OpenAgents.DataCase
3 3
  alias OpenAgents.NetworkStatus
4 4
5 5
  test "the projection is bounded, content-free, and schema-versioned (STATUS-001)" do
6 6
    projection = NetworkStatus.projection(refresh: true)
7 7
8
    assert projection["schema"] == "sarah.network_status.v1"
8
    assert projection["schema"] == "openagents.network_status.v1"
9 9
    # Legacy /status compatibility keys survive in the superset.
10 10
    assert projection["status"] in ["ok", "degraded"]
11 11
    assert is_binary(projection["revision"])

@@ -75,7 +75,7 @@ defmodule OpenAgents.NetworkStatusTest do

75 75
      target =
76 76
        %Target{}
77 77
        |> Target.changeset(%{
78
          repo: "sarah",
78
          repo: "openagents.com",
79 79
          sha: sha,
80 80
          promoted_by: "operator:99887766",
81 81
          status: "promoted"

@@ -84,7 +84,7 @@ defmodule OpenAgents.NetworkStatusTest do

84 84
85 85
      %DeployReceipt{}
86 86
      |> DeployReceipt.changeset(%{
87
        repo: "sarah",
87
        repo: "openagents.com",
88 88
        sha: sha,
89 89
        target_id: target.id,
90 90
        modules: ["Elixir.OpenAgents.VerySecretModule"],

@@ -115,13 +115,18 @@ defmodule OpenAgents.NetworkStatusTest do

115 115
116 116
      target =
117 117
        %Target{}
118
        |> Target.changeset(%{repo: "sarah", sha: sha, promoted_by: "operator:1", status: "live"})
118
        |> Target.changeset(%{
119
          repo: "openagents.com",
120
          sha: sha,
121
          promoted_by: "operator:1",
122
          status: "live"
123
        })
119 124
        |> Repo.insert!()
120 125
121 126
      for {result, ms} <- [{"live", 30_000}, {"failed", nil}, {"live", 20_000}, {"live", 10_000}] do
122 127
        %DeployReceipt{}
123 128
        |> DeployReceipt.changeset(%{
124
          repo: "sarah",
129
          repo: "openagents.com",
125 130
          sha: sha,
126 131
          target_id: target.id,
127 132
          result: result,
test/openagents/observability_test.exs modified +2 -2

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.ObservabilityTest do
2
  use OpenAgents.SarahDataCase, async: false
2
  use OpenAgents.DataCase, async: false
3 3
  alias OpenAgents.Observability
4 4
  alias OpenAgents.Observability.{Readback, ReleaseGate}
5 5

@@ -51,7 +51,7 @@ defmodule OpenAgents.ObservabilityTest do

51 51
  test "read-back is aggregate-only and the zero-tolerance gate is explicit" do
52 52
    readback = Readback.snapshot()
53 53
54
    assert readback.schema == "sarah.observability.readback.v1"
54
    assert readback.schema == "openagents.observability.readback.v1"
55 55
56 56
    assert Map.keys(readback.planes) |> Enum.sort() ==
57 57
             ~w(collective evaluation memory module provider tool)
test/openagents/preferences_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.PreferencesTest do
2
  use OpenAgents.SarahDataCase, async: false
2
  use OpenAgents.DataCase, async: false
3 3
  alias OpenAgents.{Context.Composer, Conversations, Preferences}
4 4
  alias OpenAgents.Conversations.Message
5 5
  alias OpenAgents.Providers.Request
test/openagents/profile_memory_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.ProfileMemoryTest do
2
  use OpenAgents.SarahDataCase
2
  use OpenAgents.DataCase
3 3
  alias OpenAgents.{Conversations, ProfileMemory, Repo}
4 4
  alias OpenAgents.Conversations.{Message, Visitor}
5 5
  alias OpenAgents.ProfileMemory.Source
test/openagents/program_lifecycle_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.ProgramLifecycleTest do
2
  use OpenAgents.SarahDataCase, async: true
2
  use OpenAgents.DataCase, async: true
3 3
  alias OpenAgents.ProgramArtifacts.Reader
4 4
  alias OpenAgents.ProgramLifecycle
5 5
  alias OpenAgents.ProgramLifecycle.Event
test/openagents/semantic_recall_test.exs modified +1 -1

@@ -24,7 +24,7 @@ defmodule OpenAgents.Memory.SemanticFailingProvider do

24 24
end
25 25
26 26
defmodule OpenAgents.SemanticRecallTest do
27
  use OpenAgents.SarahDataCase, async: false
27
  use OpenAgents.DataCase, async: false
28 28
  alias OpenAgents.Conversations
29 29
  alias OpenAgents.Conversations.Message
30 30
test/openagents/shadow_programs_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.ShadowProgramsTest do
2
  use OpenAgents.SarahDataCase, async: true
2
  use OpenAgents.DataCase, async: true
3 3
  alias OpenAgents.{Conversations, ProgramArtifacts, ShadowPrograms}
4 4
  alias OpenAgents.Context.Composer
5 5
  alias OpenAgents.ProfileMemory.Record
test/openagents/tool_step_persistence_test.exs modified +2 -2

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.ToolStepPersistenceTest do
2
  use OpenAgents.SarahDataCase
2
  use OpenAgents.DataCase
3 3
  import Ecto.Query
4 4
5 5
  alias OpenAgents.{Context.Composer, Conversations}

@@ -318,7 +318,7 @@ defmodule OpenAgents.ToolStepPersistenceTest do

318 318
      "result" => result,
319 319
      "error" => nil,
320 320
      "target_receipt_refs" => ["message:1"],
321
      "attribution_refs" => ["OpenAgentsInc/sarah"],
321
      "attribution_refs" => ["OpenAgentsInc/openagents.com"],
322 322
      "started_at" => "2026-08-16T20:00:00Z",
323 323
      "completed_at" => "2026-08-16T20:00:01Z"
324 324
    }
test/openagents/tools/computer_tools_test.exs modified +7 -7

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.Tools.ComputerToolsTest do
2
  use OpenAgents.SarahDataCase
2
  use OpenAgents.DataCase
3 3
4 4
  alias OpenAgents.Conversations.Message
5 5
  alias OpenAgents.Incidents

@@ -828,7 +828,7 @@ defmodule OpenAgents.Tools.ComputerToolsTest do

828 828
                 "machine_id" => machine.id,
829 829
                 "agent_id" => "claude",
830 830
                 "prompt" => "first pass",
831
                 "cwd" => "/Users/test/work/sarah"
831
                 "cwd" => "/Users/test/work/openagents.com"
832 832
               }),
833 833
               context(scope)
834 834
             )

@@ -842,7 +842,7 @@ defmodule OpenAgents.Tools.ComputerToolsTest do

842 842
                 "machine_id" => machine.id,
843 843
                 "agent_id" => "claude",
844 844
                 "prompt" => "retry the same work",
845
                 "cwd" => "/Users/test/work/sarah"
845
                 "cwd" => "/Users/test/work/openagents.com"
846 846
               }),
847 847
               context(scope)
848 848
             )

@@ -877,7 +877,7 @@ defmodule OpenAgents.Tools.ComputerToolsTest do

877 877
    machine = probed_agent_machine(scope.user, "infer-box", ["claude"])
878 878
879 879
    connect(machine.id, fn {:agent, request_id, payload, caller} ->
880
      assert payload["cwd"] == "/Users/christopherdavid/work/sarah"
880
      assert payload["cwd"] == "/Users/test/work/openagents.com"
881 881
882 882
      FakeController.exit(caller, request_id, %{
883 883
        "status" => "completed",

@@ -895,13 +895,13 @@ defmodule OpenAgents.Tools.ComputerToolsTest do

895 895
               call_raw("computer_agent", %{
896 896
                 "agent_id" => "claude",
897 897
                 "prompt" =>
898
                   "Work only in /Users/christopherdavid/work/sarah. Do not touch openagents."
898
                   "Work only in /Users/test/work/openagents.com. Do not touch openagents."
899 899
               }),
900 900
               context(scope)
901 901
             )
902 902
903 903
    assert outcome["result"]["status"] == "started"
904
    assert outcome["result"]["cwd"] == "/Users/christopherdavid/work/sarah"
904
    assert outcome["result"]["cwd"] == "/Users/test/work/openagents.com"
905 905
    await_delegation(outcome)
906 906
  end
907 907

@@ -960,7 +960,7 @@ defmodule OpenAgents.Tools.ComputerToolsTest do

960 960
  end
961 961
962 962
  defp call("computer_agent", arguments) do
963
    call_raw("computer_agent", Map.put_new(arguments, "cwd", "/Users/test/work/sarah"))
963
    call_raw("computer_agent", Map.put_new(arguments, "cwd", "/Users/test/work/openagents.com"))
964 964
  end
965 965
966 966
  defp call(name, arguments), do: call_raw(name, arguments)
test/openagents/tools/conversation_recall_tools_test.exs modified +2 -2

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.Tools.ConversationRecallToolsTest do
2
  use OpenAgents.SarahDataCase
2
  use OpenAgents.DataCase
3 3
4 4
  alias OpenAgents.{Context.Composer, Conversations, Repo, Voice}
5 5
  alias OpenAgents.Conversations.Message

@@ -646,7 +646,7 @@ defmodule OpenAgents.Tools.ConversationRecallToolsTest do

646 646
      "result" => result,
647 647
      "error" => nil,
648 648
      "target_receipt_refs" => [],
649
      "attribution_refs" => ["OpenAgentsInc/sarah"],
649
      "attribution_refs" => ["OpenAgentsInc/openagents.com"],
650 650
      "started_at" => "2026-08-16T20:00:00Z",
651 651
      "completed_at" => "2026-08-16T20:00:01Z"
652 652
    }
test/openagents/tools/github_repo_tools_test.exs modified +5 -5

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.Tools.GitHubRepoToolsTest do
2
  use OpenAgents.SarahDataCase
2
  use OpenAgents.DataCase
3 3
4 4
  alias OpenAgents.Conversations.Message
5 5
  alias OpenAgents.Tools.{ExecutionContext, Registry, Runner}

@@ -61,14 +61,14 @@ defmodule OpenAgents.Tools.GitHubRepoToolsTest do

61 61
    scope = github_scope("repo-reader", "gho_stored-for-reading")
62 62
63 63
    Req.Test.expect(__MODULE__, fn conn ->
64
      assert conn.request_path == "/repos/repo-reader/sarah/contents/mix.exs"
64
      assert conn.request_path == "/repos/repo-reader/demo/contents/mix.exs"
65 65
66 66
      Req.Test.json(conn, %{
67 67
        "type" => "file",
68 68
        "path" => "mix.exs",
69 69
        "size" => 20,
70 70
        "encoding" => "base64",
71
        "content" => Base.encode64("defmodule Sarah do")
71
        "content" => Base.encode64("defmodule Demo do")
72 72
      })
73 73
    end)
74 74

@@ -76,7 +76,7 @@ defmodule OpenAgents.Tools.GitHubRepoToolsTest do

76 76
             Runner.run(
77 77
               snapshot,
78 78
               call("github_repo_read", %{
79
                 "repository" => "repo-reader/sarah",
79
                 "repository" => "repo-reader/demo",
80 80
                 "path" => "mix.exs",
81 81
                 "ref" => ""
82 82
               }),

@@ -85,7 +85,7 @@ defmodule OpenAgents.Tools.GitHubRepoToolsTest do

85 85
86 86
    assert outcome["status"] == "succeeded"
87 87
    assert outcome["result"]["type"] == "file"
88
    assert outcome["result"]["content"] == "defmodule Sarah do"
88
    assert outcome["result"]["content"] == "defmodule Demo do"
89 89
    refute inspect(outcome) =~ "gho_stored-for-reading"
90 90
  end
91 91
test/openagents/tools/incident_lookup_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.Tools.IncidentLookupTest do
2
  use OpenAgents.SarahDataCase
2
  use OpenAgents.DataCase
3 3
4 4
  alias OpenAgents.Incidents
5 5
  alias OpenAgents.Tools.{ExecutionContext, Registry, Runner}
test/openagents/tools/profile_memory_tools_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.Tools.ProfileMemoryToolsTest do
2
  use OpenAgents.SarahDataCase
2
  use OpenAgents.DataCase
3 3
4 4
  alias OpenAgents.Conversations.{Message, Visitor}
5 5
  alias OpenAgents.Memory.Consent
test/openagents/tools/registry_and_runner_test.exs modified +2 -2

@@ -111,7 +111,7 @@ defmodule OpenAgents.Tools.RegistryAndRunnerTest do

111 111
    assert outcome["module_ref"]["module_id"] == "sarah.tool.echo"
112 112
    assert outcome["executor_ref"]["disclosure"] == "Sarah's local test executor"
113 113
    assert outcome["target_receipt_refs"] == ["message:1"]
114
    assert outcome["attribution_refs"] == ["OpenAgentsInc/sarah"]
114
    assert outcome["attribution_refs"] == ["OpenAgentsInc/openagents.com"]
115 115
  end
116 116
117 117
  test "unknown tools, incompatible versions, and schema failures are bounded" do

@@ -302,7 +302,7 @@ defmodule OpenAgents.Tools.RegistryAndRunnerTest do

302 302
      required_authority: "conversation.read",
303 303
      executor: %{id: "sarah.local", disclosure: "Sarah's local test executor"},
304 304
      maintainer: "OpenAgents",
305
      attribution: ["OpenAgentsInc/sarah"],
305
      attribution: ["OpenAgentsInc/openagents.com"],
306 306
      policy_facets: %{"privacy" => "browser_scoped", "residency" => "host"},
307 307
      module_metadata:
308 308
        Metadata.first_party("conversation.read", "browser_conversation",
test/openagents/tools/repository_mutation_tools_test.exs modified +10 -10

@@ -6,7 +6,7 @@ defmodule OpenAgents.Tools.RepositoryMutationToolsTest do

6 6
  discipline — plus every refusal the invariant promises.
7 7
  """
8 8
9
  use OpenAgents.SarahDataCase, async: false
9
  use OpenAgents.DataCase, async: false
10 10
11 11
  alias OpenAgents.Forge
12 12
  alias OpenAgents.Forge.Repos

@@ -44,10 +44,10 @@ defmodule OpenAgents.Tools.RepositoryMutationToolsTest do

44 44
    Application.put_env(
45 45
      :openagents,
46 46
      :forge_self_push_url,
47
      "http://x:#{@operator_token}@127.0.0.1:#{port}/sarah.git"
47
      "http://x:#{@operator_token}@127.0.0.1:#{port}/openagents.com.git"
48 48
    )
49 49
50
    # Seed the forge's "sarah" repo with one commit so clones have a base.
50
    # Seed the forge's "openagents.com" repo with one commit so clones have a base.
51 51
    seed_repo!()
52 52
53 53
    on_exit(fn ->

@@ -72,7 +72,7 @@ defmodule OpenAgents.Tools.RepositoryMutationToolsTest do

72 72
  end
73 73
74 74
  defp seed_repo! do
75
    path = Repos.ensure_repo!("sarah")
75
    path = Repos.ensure_repo!("openagents.com")
76 76
77 77
    {blob, 0} = plumb(path, ["hash-object", "-w", "--stdin"], "original content\n")
78 78
    {tree, 0} = plumb(path, ["mktree"], "100644 blob #{String.trim(blob)}\tnote.txt\n")

@@ -222,23 +222,23 @@ defmodule OpenAgents.Tools.RepositoryMutationToolsTest do

222 222
    sha = pushed["result"]["sha"]
223 223
    assert sha =~ ~r/^[0-9a-f]{40}$/
224 224
    branch = pushed["result"]["branch"]
225
    assert branch == "sarah/job-11111111-2222-3333-4444-555555555555"
225
    assert branch == "openagents/job-11111111-2222-3333-4444-555555555555"
226 226
227 227
    # The commit SHA is in the outcome receipt refs (SELF-EDIT-001). The
228 228
    # middle segment is the forge repo the coding lane edits
229
    # (`OpenAgents.Tools.Repository.repo/0`), which is still `sarah` — the
230
    # same name this test uses for the branch, the push receipt, and the
229
    # (`OpenAgents.Tools.Repository.repo/0`), which is now `openagents.com` — the
230
    # same repository this test uses for the branch, the push receipt, and the
231 231
    # bare path below. Renaming the forge repo is a separate whole-repo
232 232
    # change (config `forge_repos`, visibility, public paths, git URL).
233
    assert "forge-commit:sarah:#{sha}" in pushed["target_receipt_refs"]
233
    assert "forge-commit:openagents.com:#{sha}" in pushed["target_receipt_refs"]
234 234
235 235
    # The push is receipted with a WAL sequence, and the ref exists on the
236 236
    # forge with exactly that sha.
237
    assert [receipt | _rest] = Forge.recent_pushes("sarah")
237
    assert [receipt | _rest] = Forge.recent_pushes("openagents.com")
238 238
    assert is_integer(receipt.wal_seq)
239 239
    assert %{"new" => ^sha} = Map.get(receipt.refs, "refs/heads/" <> branch)
240 240
241
    path = Repos.bare_path("sarah")
241
    path = Repos.bare_path("openagents.com")
242 242
    {out, 0} = Repos.git(path, ["rev-parse", "refs/heads/" <> branch])
243 243
    assert String.trim(out) == sha
244 244
test/openagents/tools/repository_tools_test.exs modified +5 -5

@@ -5,7 +5,7 @@ defmodule OpenAgents.Tools.RepositoryToolsTest do

5 5
  refusals that keep the family inside SELF-EDIT-001.
6 6
  """
7 7
8
  use OpenAgents.SarahDataCase, async: false
8
  use OpenAgents.DataCase, async: false
9 9
10 10
  alias OpenAgents.Tools.{
11 11
    CodeCheck,

@@ -22,13 +22,13 @@ defmodule OpenAgents.Tools.RepositoryToolsTest do

22 22
    File.mkdir_p!(Path.join(source, "lib"))
23 23
    File.write!(Path.join(source, "lib/sample.ex"), "defmodule RepoToolsFixtureSample do\nend\n")
24 24
    File.write!(Path.join(source, "README.md"), "hello forge sample\n")
25
    previous = Application.get_env(:openagents, :sarah_source_dir)
26
    Application.put_env(:openagents, :sarah_source_dir, source)
25
    previous = Application.get_env(:openagents, :source_repo_dir)
26
    Application.put_env(:openagents, :source_repo_dir, source)
27 27
28 28
    on_exit(fn ->
29 29
      if previous,
30
        do: Application.put_env(:openagents, :sarah_source_dir, previous),
31
        else: Application.delete_env(:openagents, :sarah_source_dir)
30
        do: Application.put_env(:openagents, :source_repo_dir, previous),
31
        else: Application.delete_env(:openagents, :source_repo_dir)
32 32
33 33
      File.rm_rf(source)
34 34
    end)
test/openagents/turn_memory_evidence_journeys_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.TurnMemoryEvidenceJourneysTest do
2
  use OpenAgents.SarahDataCase
2
  use OpenAgents.DataCase
3 3
  import Ecto.Query
4 4
5 5
  alias OpenAgents.{Conversations, Repo, Turns}
test/openagents/turn_provenance_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.TurnProvenanceTest do
2
  use OpenAgents.SarahDataCase
2
  use OpenAgents.DataCase
3 3
  alias OpenAgents.{Context.Composer, Conversations, Turns}
4 4
  alias OpenAgents.Conversations.{ProviderStep, TurnReceipt}
5 5
  alias OpenAgents.Provenance.Canonical
test/openagents/turn_provider_events_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.TurnProviderEventsTest do
2
  use OpenAgents.SarahDataCase
2
  use OpenAgents.DataCase
3 3
  alias OpenAgents.{Conversations, Turns}
4 4
  alias OpenAgents.Conversations.ProviderStep
5 5
  alias OpenAgents.Incidents.Incident
test/openagents/turn_tool_loop_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.TurnToolLoopTest do
2
  use OpenAgents.SarahDataCase
2
  use OpenAgents.DataCase
3 3
  import Ecto.Query
4 4
5 5
  alias OpenAgents.{Conversations, Turns}
test/openagents/voice/recordings_test.exs modified +19 -2

@@ -5,7 +5,7 @@ defmodule OpenAgents.Voice.RecordingsTest do

5 5
  things that keeps honest: ordering, fencing, ceilings, and sealing.
6 6
  """
7 7
8
  use OpenAgents.SarahDataCase, async: false
8
  use OpenAgents.DataCase, async: false
9 9
10 10
  alias OpenAgents.Conversations
11 11
  alias OpenAgents.Repo

@@ -299,6 +299,23 @@ defmodule OpenAgents.Voice.RecordingsTest do

299 299
      assert {:error, :chunk_unsealable} = RecordingVault.open(sealed, recording_id, 4)
300 300
    end
301 301
302
    test "legacy Sarah version-1 chunks remain readable during migration" do
303
      recording_id = Ecto.UUID.generate()
304
      sequence = 3
305
      nonce = :crypto.strong_rand_bytes(12)
306
307
      {:ok, key} =
308
        Base.decode64(Application.fetch_env!(:openagents, :voice_recording_encryption_key))
309
310
      aad = "sarah.voice_recording_chunk.v1:#{recording_id}:#{sequence}"
311
312
      {ciphertext, tag} =
313
        :crypto.crypto_one_time_aead(:aes_256_gcm, key, nonce, "legacy audio", aad, true)
314
315
      sealed = <<1, nonce::binary, tag::binary, ciphertext::binary>>
316
      assert {:ok, "legacy audio"} = RecordingVault.open(sealed, recording_id, sequence)
317
    end
318
302 319
    test "a wrong key fails closed" do
303 320
      recording_id = Ecto.UUID.generate()
304 321
      assert {:ok, sealed} = RecordingVault.seal("audio", recording_id, 1)

@@ -309,7 +326,7 @@ defmodule OpenAgents.Voice.RecordingsTest do

309 326
        Application.put_env(
310 327
          :openagents,
311 328
          :voice_recording_encryption_key,
312
          Base.encode64("sarah-test-voice-recording-OTHER")
329
          Base.encode64(String.duplicate("x", 32))
313 330
        )
314 331
315 332
        assert {:error, :chunk_unsealable} = RecordingVault.open(sealed, recording_id, 1)
test/openagents/voice/release_operations_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.Voice.ReleaseOperationsTest do
2
  use OpenAgents.SarahDataCase, async: false
2
  use OpenAgents.DataCase, async: false
3 3
4 4
  import ExUnit.CaptureLog
5 5
test/openagents/voice_sessions_test.exs modified +2 -2

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.VoiceSessionsTest do
2
  use OpenAgents.SarahDataCase, async: false
2
  use OpenAgents.DataCase, async: false
3 3
  alias OpenAgents.{Conversations, Voice, VoiceSessions}
4 4
  alias OpenAgents.Voice.ProviderEvent
5 5

@@ -398,7 +398,7 @@ defmodule OpenAgents.VoiceSessionsTest do

398 398
399 399
    assert_receive {:sideband_started, _sideband, _sideband_session}
400 400
401
    typed = "Check https://github.com/OpenAgentsInc/sarah please"
401
    typed = "Check https://github.com/OpenAgentsInc/openagents.com please"
402 402
    assert {:ok, message} = Conversations.create_voice_context_message(conversation, typed)
403 403
    assert message.status == "complete"
404 404
    assert message.role == "user"
test/openagents/voice_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.VoiceTest do
2
  use OpenAgents.SarahDataCase, async: false
2
  use OpenAgents.DataCase, async: false
3 3
  alias OpenAgents.{Conversations, Voice}
4 4
  alias OpenAgents.Tools.{ExecutionContext, Registry, Runner}
5 5
  alias OpenAgents.Voice.{Config, ProviderEvent, Session}
test/openagents/work/handoff_test.exs modified +2 -2

@@ -28,8 +28,8 @@ defmodule OpenAgents.Work.HandoffTest do

28 28
29 29
  test "a Horde singleton relocates to a survivor when its node dies, staying unique" do
30 30
    cookie = :erlang.get_cookie()
31
    {peer1, node1} = start_peer(:sarah_handoff_peer1, cookie)
32
    {peer2, node2} = start_peer(:sarah_handoff_peer2, cookie)
31
    {peer1, node1} = start_peer(:openagents_handoff_peer1, cookie)
32
    {peer2, node2} = start_peer(:openagents_handoff_peer2, cookie)
33 33
34 34
    on_exit(fn ->
35 35
      for p <- [peer1, peer2], do: safe_stop(p)
test/openagents/work_job_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgents.WorkJobTest do
2
  use OpenAgents.SarahDataCase
2
  use OpenAgents.DataCase
3 3
  alias OpenAgents.{Conversations, Work}
4 4
  alias OpenAgents.Conversations.Message
5 5
  alias OpenAgents.Work.{Job, JobServer}
test/openagents_web/account_chrome_test.exs modified +1 -1

@@ -7,7 +7,7 @@ defmodule OpenAgentsWeb.AccountChromeTest do

7 7
  the connection marker must actually be driven by the socket.
8 8
  """
9 9
10
  use OpenAgentsWeb.SarahConnCase
10
  use OpenAgentsWeb.ConnCase
11 11
  import Phoenix.LiveViewTest
12 12
13 13
  alias OpenAgents.Accounts
test/openagents_web/allowed_origins_test.exs modified +5 -5

@@ -4,18 +4,18 @@ defmodule OpenAgentsWeb.AllowedOriginsTest do

4 4
5 5
  test "includes the primary host and configured Cloud Run aliases" do
6 6
    assert AllowedOrigins.for_production(
7
             "sarah.example",
8
             "https://sarah-123.run.app, https://sarah.example"
9
           ) == ["https://sarah.example", "https://sarah-123.run.app"]
7
             "openagents.example",
8
             "https://openagents-123.run.app, https://openagents.example"
9
           ) == ["https://openagents.example", "https://openagents-123.run.app"]
10 10
  end
11 11
12 12
  test "rejects origins containing paths or an insecure scheme" do
13 13
    assert_raise ArgumentError, fn ->
14
      AllowedOrigins.for_production("sarah.example", "https://other.example/path")
14
      AllowedOrigins.for_production("openagents.example", "https://other.example/path")
15 15
    end
16 16
17 17
    assert_raise ArgumentError, fn ->
18
      AllowedOrigins.for_production("sarah.example", "http://other.example")
18
      AllowedOrigins.for_production("openagents.example", "http://other.example")
19 19
    end
20 20
  end
21 21
end
test/openagents_web/auth_controller_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgentsWeb.AuthControllerTest do
2
  use OpenAgentsWeb.SarahConnCase, async: false
2
  use OpenAgentsWeb.ConnCase, async: false
3 3
  import Phoenix.LiveViewTest
4 4
5 5
  alias OpenAgents.{Accounts, Conversations}
test/openagents_web/auth_gate_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgentsWeb.AuthGateTest do
2
  use OpenAgentsWeb.SarahConnCase, async: true
2
  use OpenAgentsWeb.ConnCase, async: true
3 3
  import Ecto.Query
4 4
5 5
  alias OpenAgents.Conversations.{Conversation, Visitor}
test/openagents_web/channels/computer_channel_test.exs modified +3 -3

@@ -1,8 +1,8 @@

1 1
defmodule OpenAgentsWeb.ComputerChannelTest do
2
  use OpenAgents.SarahDataCase, async: false
2
  use OpenAgents.DataCase, async: false
3 3
4 4
  import Phoenix.ChannelTest
5
  import OpenAgentsWeb.SarahConnCase, only: [github_user: 1]
5
  import OpenAgentsWeb.ConnCase, only: [github_user: 1]
6 6
7 7
  @endpoint OpenAgentsWeb.Endpoint
8 8

@@ -36,7 +36,7 @@ defmodule OpenAgentsWeb.ComputerChannelTest do

36 36
    assert {:error, %{"reason" => "machine_mismatch"}} =
37 37
             subscribe_and_join(socket, "computer:#{Ecto.UUID.generate()}", %{})
38 38
39
    assert {:ok, %{"protocol" => "sarah.computer.v1"}, _joined} =
39
    assert {:ok, %{"protocol" => "openagents.computer.v1"}, _joined} =
40 40
             subscribe_and_join(socket, "computer:#{machine.id}", %{})
41 41
  end
42 42
test/openagents_web/component_catalog_test.exs modified +1 -1

@@ -2,7 +2,7 @@ defmodule OpenAgentsWeb.ComponentCatalogTest do

2 2
  @moduledoc """
3 3
  Guards the catalog against silently falling behind the code.
4 4
5
  The catalog was originally hand-written and drifted: nineteen SarahUI
5
  The catalog was originally hand-written and drifted: nineteen UI
6 6
  components shipped without ever appearing on the components page. This test
7 7
  makes that failure mode loud — adding a public function component to a
8 8
  documented module fails the suite until it is catalogued or explicitly
test/openagents_web/controllers/atif_export_controller_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgentsWeb.AtifExportControllerTest do
2
  use OpenAgentsWeb.SarahConnCase, async: false
2
  use OpenAgentsWeb.ConnCase, async: false
3 3
  import Phoenix.LiveViewTest
4 4
5 5
  alias OpenAgents.Conversations.Visitor
test/openagents_web/controllers/computer_agent_jobs_controller_test.exs modified +2 -2

@@ -1,12 +1,12 @@

1 1
defmodule OpenAgentsWeb.ComputerAgentJobsControllerTest do
2
  use OpenAgentsWeb.SarahConnCase, async: false
2
  use OpenAgentsWeb.ConnCase, async: false
3 3
  import Phoenix.LiveViewTest
4 4
5 5
  alias OpenAgents.Machines
6 6
  alias OpenAgents.Support.FakeController
7 7
  alias OpenAgents.Work
8 8
9
  @root "/tmp/sarah-agent-api"
9
  @root "/tmp/openagents-agent-api"
10 10
11 11
  test "an API-started Codex job streams into ChatLive and posts its terminal report", %{
12 12
    conn: conn
test/openagents_web/controllers/computers_controller_test.exs modified +3 -3

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgentsWeb.ComputersControllerTest do
2
  use OpenAgentsWeb.SarahConnCase, async: false
2
  use OpenAgentsWeb.ConnCase, async: false
3 3
  alias OpenAgents.Computer
4 4
  alias OpenAgents.Machines
5 5
  alias OpenAgents.Machines.Pairing

@@ -10,7 +10,7 @@ defmodule OpenAgentsWeb.ComputersControllerTest do

10 10
    "tier" => "curated",
11 11
    "platform" => "linux-x64",
12 12
    "agent_version" => "0.4.0",
13
    "roots" => ["/tmp/sarah-api-canary"]
13
    "roots" => ["/tmp/openagents-api-canary"]
14 14
  }
15 15
16 16
  test "the API drives pairing, claim, presence, inventory, and revoke without exposing tokens",

@@ -70,7 +70,7 @@ defmodule OpenAgentsWeb.ComputersControllerTest do

70 70
      |> json_response(200)
71 71
72 72
    assert %{
73
             "schema" => "sarah.computers.v1",
73
             "schema" => "openagents.computers.v1",
74 74
             "pairing_enabled" => true,
75 75
             "computers" => [listed]
76 76
           } = inventory
test/openagents_web/controllers/controller_pairing_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgentsWeb.ControllerPairingTest do
2
  use OpenAgentsWeb.SarahConnCase, async: false
2
  use OpenAgentsWeb.ConnCase, async: false
3 3
  alias OpenAgents.Machines
4 4
5 5
  @create_params %{
test/openagents_web/controllers/data_controller_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgentsWeb.DataControllerTest do
2
  use OpenAgentsWeb.SarahConnCase, async: false
2
  use OpenAgentsWeb.ConnCase, async: false
3 3
  import Ecto.Query
4 4
  import Phoenix.LiveViewTest
5 5
test/openagents_web/controllers/health_controller_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgentsWeb.HealthControllerTest do
2
  use OpenAgentsWeb.SarahConnCase
2
  use OpenAgentsWeb.ConnCase
3 3
4 4
  test "reports healthy when PostgreSQL is reachable", %{conn: conn} do
5 5
    conn = get(conn, ~p"/status")
test/openagents_web/controllers/inference_proxy_controller_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgentsWeb.InferenceProxyControllerTest do
2
  use OpenAgentsWeb.SarahConnCase, async: false
2
  use OpenAgentsWeb.ConnCase, async: false
3 3
  alias OpenAgents.Inference
4 4
  alias OpenAgents.Inference.Grant
5 5
  alias OpenAgents.Machines
test/openagents_web/controllers/sarah_error_html_test.exs deleted -13

@@ -1,13 +0,0 @@

1
defmodule OpenAgentsWeb.SarahErrorHTMLTest do
2
  use OpenAgentsWeb.SarahConnCase, async: true
3
  # Bring render_to_string/4 for testing custom views
4
  import Phoenix.Template, only: [render_to_string: 4]
5
6
  test "renders 404.html" do
7
    assert render_to_string(OpenAgentsWeb.ErrorHTML, "404", "html", []) == "Not Found"
8
  end
9
10
  test "renders 500.html" do
11
    assert render_to_string(OpenAgentsWeb.ErrorHTML, "500", "html", []) == "Internal Server Error"
12
  end
13
end
test/openagents_web/controllers/sarah_error_json_test.exs deleted -12

@@ -1,12 +0,0 @@

1
defmodule OpenAgentsWeb.SarahErrorJSONTest do
2
  use OpenAgentsWeb.SarahConnCase, async: true
3
4
  test "renders 404" do
5
    assert OpenAgentsWeb.ErrorJSON.render("404.json", %{}) == %{errors: %{detail: "Not Found"}}
6
  end
7
8
  test "renders 500" do
9
    assert OpenAgentsWeb.ErrorJSON.render("500.json", %{}) ==
10
             %{errors: %{detail: "Internal Server Error"}}
11
  end
12
end
test/openagents_web/controllers/voice_call_controller_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgentsWeb.VoiceCallControllerTest do
2
  use OpenAgentsWeb.SarahConnCase, async: false
2
  use OpenAgentsWeb.ConnCase, async: false
3 3
4 4
  setup do
5 5
    previous_voice = Application.fetch_env!(:openagents, :voice)
test/openagents_web/controllers/voice_recording_controller_test.exs modified +1 -1

@@ -5,7 +5,7 @@ defmodule OpenAgentsWeb.VoiceRecordingControllerTest do

5 5
  container outside the allowlist, and another account's call.
6 6
  """
7 7
8
  use OpenAgentsWeb.SarahConnCase, async: false
8
  use OpenAgentsWeb.ConnCase, async: false
9 9
  alias OpenAgents.Conversations
10 10
  alias OpenAgents.Voice
11 11
  alias OpenAgents.Voice.Config
test/openagents_web/controllers/voice_telemetry_controller_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgentsWeb.VoiceTelemetryControllerTest do
2
  use OpenAgentsWeb.SarahConnCase, async: false
2
  use OpenAgentsWeb.ConnCase, async: false
3 3
  import Ecto.Query
4 4
5 5
  alias OpenAgents.{Conversations, Repo, Voice}
test/openagents_web/icon_affordances_test.exs modified +4 -17

@@ -8,7 +8,7 @@ defmodule OpenAgentsWeb.IconAffordancesTest do

8 8
  rather than reviewed by eye.
9 9
  """
10 10
11
  use OpenAgentsWeb.SarahConnCase
11
  use OpenAgentsWeb.ConnCase
12 12
  import Phoenix.LiveViewTest
13 13
14 14
  describe "icon-only controls" do

@@ -66,22 +66,9 @@ defmodule OpenAgentsWeb.IconAffordancesTest do

66 66
    test "no surface hand-writes an svg outside the vendored set" do
67 67
      # Every glyph must come from `priv/icons` through `icon/1`. A pasted
68 68
      # `<svg>` in a template is how a second, unmanaged icon set starts.
69
      #
70
      # Ported from Sarah, where this globbed `lib/sarah_web/**` and carried a
71
      # documented exemption for Sarah's landing template
72
      # (`lib/sarah_web/controllers/home_html/show.html.heex`), whose
73
      # `landing-grid` is a background pattern rather than an icon. That
74
      # exemption is dropped rather than repointed: the OpenAgents home is
75
      # `OpenAgentsWeb.HomeLive` ("The Agent Forge"), it renders no background
76
      # SVG, and Sarah's landing template is deliberately not part of this app.
77
      # If a background pattern is ever added here, re-add the exemption WITH
78
      # the staleness assertion that guarded it.
79
      #
80
      # The exempt files are the ones that IMPLEMENT the vendored set:
81
      # `icons.ex` holds it, and `icon/1` is defined in both `sarah_ui.ex` (which
82
      # matches the `ui.ex` suffix Sarah used) and `core_components.ex`. The
83
      # assertions below fail loudly if either stops being icon plumbing, so the
84
      # exemption cannot silently become a hole.
69
      # The exempt files implement the vendored set. `icons.ex` holds the
70
      # embedded SVG markup, while `ui.ex` and `core_components.ex` render it.
71
      # The assertions below fail if an exemption stops being icon plumbing.
85 72
      renderers = ["lib/openagents_web/components/core_components.ex"]
86 73
87 74
      for renderer <- renderers do
test/openagents_web/icons_test.exs modified +3 -3

@@ -2,7 +2,7 @@ defmodule OpenAgentsWeb.IconsTest do

2 2
  @moduledoc """
3 3
  Contract tests for the vendored icon set.
4 4
5
  The set is generated by `mix sarah.icons.vendor`, so these guard the
5
  The set is generated by `mix openagents.icons.vendor`, so these guard the
6 6
  conversion and the glyphs the product depends on: a bad re-vendor must fail
7 7
  the build rather than silently emptying an affordance.
8 8
  """

@@ -10,11 +10,11 @@ defmodule OpenAgentsWeb.IconsTest do

10 10
  use ExUnit.Case, async: true
11 11
  import Phoenix.Component
12 12
  import Phoenix.LiveViewTest
13
  import OpenAgentsWeb.SarahUI
13
  import OpenAgentsWeb.UI
14 14
15 15
  alias OpenAgentsWeb.Icons
16 16
17
  # Every glyph a Sarah surface renders. If a re-vendor drops one of these, the
17
  # Every glyph a OpenAgents surface renders. If a re-vendor drops one of these, the
18 18
  # control loses its affordance, so name them explicitly.
19 19
  @in_use ~w(
20 20
    arrow-up stop history memory-on-remember arrow-left chevron-down logout
test/openagents_web/live/admin_forge_live_test.exs modified +7 -7

@@ -6,7 +6,7 @@ defmodule OpenAgentsWeb.AdminForgeLiveTest do

6 6
  operator identity; only WAL-pushed SHAs are promotable.
7 7
  """
8 8
9
  use OpenAgentsWeb.SarahConnCase, async: false
9
  use OpenAgentsWeb.ConnCase, async: false
10 10
  import Phoenix.LiveViewTest
11 11
12 12
  alias OpenAgents.Forge.{Repos, Targets}

@@ -74,8 +74,8 @@ defmodule OpenAgentsWeb.AdminForgeLiveTest do

74 74
  end
75 75
76 76
  test "promote records the operator identity and broadcasts", %{conn: conn} do
77
    # "sarah" is the primary configured repo in test config.
78
    sha = seeded_commit("sarah")
77
    # "openagents.com" is the primary configured repo in test config.
78
    sha = seeded_commit("openagents.com")
79 79
    Phoenix.PubSub.subscribe(OpenAgents.PubSub, "forge:target")
80 80
81 81
    conn = log_in_admin_user(conn, "forge-promoter")

@@ -83,15 +83,15 @@ defmodule OpenAgentsWeb.AdminForgeLiveTest do

83 83
84 84
    render_hook(view, "promote", %{"sha" => sha})
85 85
86
    assert_receive {:forge_target, %{repo: "sarah", sha: ^sha}}
87
    target = Targets.current("sarah")
86
    assert_receive {:forge_target, %{repo: "openagents.com", sha: ^sha}}
87
    target = Targets.current("openagents.com")
88 88
    assert target.sha == sha
89 89
    assert target.promoted_by =~ "operator:"
90 90
    assert render(view) =~ String.slice(sha, 0, 12)
91 91
  end
92 92
93 93
  test "promoting a commit that is not in the forge is refused honestly", %{conn: conn} do
94
    Repos.ensure_repo!("sarah")
94
    Repos.ensure_repo!("openagents.com")
95 95
    conn = log_in_admin_user(conn, "forge-refuser")
96 96
    {:ok, view, _html} = live(conn, ~p"/admin/forge")
97 97

@@ -102,7 +102,7 @@ defmodule OpenAgentsWeb.AdminForgeLiveTest do

102 102
    # targets written by another test's async builder can leak past the
103 103
    # sandbox and land in the table, which made the global-emptiness form
104 104
    # flaky under the full suite.)
105
    refute Enum.any?(Targets.recent("sarah", 50), &(&1.sha == refused_sha))
105
    refute Enum.any?(Targets.recent("openagents.com", 50), &(&1.sha == refused_sha))
106 106
    assert render(view) =~ "only pushed commits are promotable"
107 107
  end
108 108
end
test/openagents_web/live/admin_live_test.exs modified +1 -1

@@ -5,7 +5,7 @@ defmodule OpenAgentsWeb.AdminLiveTest do

5 5
  what the page is allowed to show once it renders.
6 6
  """
7 7
8
  use OpenAgentsWeb.SarahConnCase, async: false
8
  use OpenAgentsWeb.ConnCase, async: false
9 9
  import Phoenix.LiveViewTest
10 10
11 11
  alias OpenAgents.Conversations
test/openagents_web/live/assignee_index_live_test.exs modified +4 -4

@@ -10,7 +10,7 @@ defmodule OpenAgentsWeb.AssigneeIndexLiveTest do

10 10
  end
11 11
12 12
  test "mounts with an honest empty state when no issue has an assignee", %{conn: conn} do
13
    {:ok, view, html} = live(conn, ~p"/OpenAgentsInc/sarah/assignees")
13
    {:ok, view, html} = live(conn, ~p"/OpenAgentsInc/openagents.com/assignees")
14 14
15 15
    assert html =~ "Assignees"
16 16
    assert has_element?(view, ~s{[role="status"]}, "No assignees have been assigned")

@@ -25,7 +25,7 @@ defmodule OpenAgentsWeb.AssigneeIndexLiveTest do

25 25
    # A closed issue still counts: the view lists `state: "all"`.
26 26
    {:ok, _} = Issues.update_issue(third, %{"state" => "closed"})
27 27
28
    {:ok, view, html} = live(conn, ~p"/OpenAgentsInc/sarah/assignees")
28
    {:ok, view, html} = live(conn, ~p"/OpenAgentsInc/openagents.com/assignees")
29 29
30 30
    assert has_element?(view, "#assignees")
31 31
    refute html =~ "No assignees have been assigned"

@@ -40,8 +40,8 @@ defmodule OpenAgentsWeb.AssigneeIndexLiveTest do

40 40
41 41
  test "an anonymous visitor is redirected away from the assignee list" do
42 42
    assert {:error, {:redirect, %{to: to}}} =
43
             live(build_conn(), ~p"/OpenAgentsInc/sarah/assignees")
43
             live(build_conn(), ~p"/OpenAgentsInc/openagents.com/assignees")
44 44
45
    refute to == "/OpenAgentsInc/sarah/assignees"
45
    refute to == "/OpenAgentsInc/openagents.com/assignees"
46 46
  end
47 47
end
test/openagents_web/live/changelog_live_test.exs modified +5 -5

@@ -8,7 +8,7 @@ defmodule OpenAgentsWeb.ChangelogLiveTest do

8 8
  repo.
9 9
  """
10 10
11
  use OpenAgentsWeb.SarahConnCase, async: false
11
  use OpenAgentsWeb.ConnCase, async: false
12 12
  import Phoenix.LiveViewTest
13 13
14 14
  alias OpenAgents.Changelog

@@ -19,7 +19,7 @@ defmodule OpenAgentsWeb.ChangelogLiveTest do

19 19
20 20
    {:ok, entry} =
21 21
      Changelog.record(%{
22
        repo: "sarah",
22
        repo: "openagents.com",
23 23
        sha: "abcdef1",
24 24
        summary: "Moved the mic button test entry",
25 25
        category: "ui",

@@ -43,7 +43,7 @@ defmodule OpenAgentsWeb.ChangelogLiveTest do

43 43
    {:ok, _view, html} = live(conn, ~p"/changelog")
44 44
45 45
    assert html =~ "abcdef1"
46
    assert html =~ "/OpenAgentsInc/sarah/commit/abcdef1"
46
    assert html =~ "/OpenAgentsInc/openagents.com/commit/abcdef1"
47 47
  end
48 48
49 49
  test "carries the shared command bar without account controls for a visitor", %{conn: conn} do

@@ -73,8 +73,8 @@ defmodule OpenAgentsWeb.ChangelogLiveTest do

73 73
    conn = get(conn, ~p"/api/changelog")
74 74
    payload = json_response(conn, 200)
75 75
76
    assert payload["schema"] == "sarah.changelog.v1"
77
    assert payload["repo"] == "sarah"
76
    assert payload["schema"] == "openagents.changelog.v1"
77
    assert payload["repo"] == "openagents.com"
78 78
79 79
    assert Enum.any?(
80 80
             payload["entries"],
test/openagents_web/live/chat_delegation_rail_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgentsWeb.ChatDelegationRailTest do
2
  use OpenAgentsWeb.SarahConnCase
2
  use OpenAgentsWeb.ConnCase
3 3
  import Phoenix.LiveViewTest
4 4
5 5
  alias OpenAgents.{Computer, Conversations, Machines}
test/openagents_web/live/chat_live_test.exs modified +3 -3

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgentsWeb.ChatLiveTest do
2
  use OpenAgentsWeb.SarahConnCase
2
  use OpenAgentsWeb.ConnCase
3 3
  import Phoenix.LiveViewTest
4 4
  import Ecto.Query
5 5

@@ -287,7 +287,7 @@ defmodule OpenAgentsWeb.ChatLiveTest do

287 287
             render(view) =~ ~s(data-server-status="listening")
288 288
           end)
289 289
290
    typed = "Read https://github.com/OpenAgentsInc/sarah while we talk."
290
    typed = "Read https://github.com/OpenAgentsInc/openagents.com while we talk."
291 291
292 292
    view
293 293
    |> form("#message-form", chat: %{message: typed})

@@ -301,7 +301,7 @@ defmodule OpenAgentsWeb.ChatLiveTest do

301 301
    assert eventually(fn ->
302 302
             rendered = render(view)
303 303
304
             rendered =~ "Read https://github.com/OpenAgentsInc/sarah" and
304
             rendered =~ "Read https://github.com/OpenAgentsInc/openagents.com" and
305 305
               rendered =~ ~s(data-server-status="listening")
306 306
           end)
307 307
test/openagents_web/live/chat_sidebar_sections_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgentsWeb.ChatSidebarSectionsTest do
2
  use OpenAgentsWeb.SarahConnCase
2
  use OpenAgentsWeb.ConnCase
3 3
  import Phoenix.LiveViewTest
4 4
5 5
  alias OpenAgents.{Conversations, Work}
test/openagents_web/live/code_live_test.exs modified +35 -29

@@ -7,7 +7,7 @@ defmodule OpenAgentsWeb.CodeLiveTest do

7 7
  missing path, and a flag-shaped ref 404 indistinguishably.
8 8
  """
9 9
10
  use OpenAgentsWeb.SarahConnCase, async: false
10
  use OpenAgentsWeb.ConnCase, async: false
11 11
  import Phoenix.LiveViewTest
12 12
13 13
  alias OpenAgents.Forge.{DeployReceipt, Repos}

@@ -37,8 +37,9 @@ defmodule OpenAgentsWeb.CodeLiveTest do

37 37
    # explicitly via `browsable/0`.
38 38
    previous_vis = Application.get_env(:openagents, :forge_public_visibility)
39 39
    previous_paths = Application.get_env(:openagents, :forge_public_paths)
40
    Application.put_env(:openagents, :forge_public_visibility, %{"sarah" => :l2})
41
    Application.put_env(:openagents, :forge_public_paths, %{"sarah" => ["docs/audit.md"]})
40
    Application.put_env(:openagents, :forge_public_visibility, %{"openagents.com" => :l2})
41
42
    Application.put_env(:openagents, :forge_public_paths, %{"openagents.com" => ["docs/audit.md"]})
42 43
43 44
    on_exit(fn ->
44 45
      if previous_data,

@@ -60,11 +61,11 @@ defmodule OpenAgentsWeb.CodeLiveTest do

60 61
      File.rm_rf(base)
61 62
    end)
62 63
63
    seed_repo("sarah")
64
    seed_repo("openagents.com")
64 65
  end
65 66
66 67
  defp browsable,
67
    do: Application.put_env(:openagents, :forge_public_visibility, %{"sarah" => :l3})
68
    do: Application.put_env(:openagents, :forge_public_visibility, %{"openagents.com" => :l3})
68 69
69 70
  # Two chained commits: the first seeds README.md + file.txt, the second
70 71
  # adds docs/audit.md (a markdown heading the tests can recognize) with the

@@ -72,7 +73,7 @@ defmodule OpenAgentsWeb.CodeLiveTest do

72 73
  defp seed_repo(repo) do
73 74
    path = Repos.ensure_repo!(repo)
74 75
75
    readme = write_blob(path, "# Sarah test repo\n\nFixture readme.\n")
76
    readme = write_blob(path, "# OpenAgents test repo\n\nFixture readme.\n")
76 77
    file = write_blob(path, "hello\n")
77 78
78 79
    tree_one =

@@ -140,7 +141,7 @@ defmodule OpenAgentsWeb.CodeLiveTest do

140 141
    {:ok, deploy} =
141 142
      %DeployReceipt{}
142 143
      |> DeployReceipt.changeset(%{
143
        repo: "sarah",
144
        repo: "openagents.com",
144 145
        sha: sha,
145 146
        target_id: Ecto.UUID.generate(),
146 147
        result: "live",

@@ -155,14 +156,16 @@ defmodule OpenAgentsWeb.CodeLiveTest do

155 156
156 157
  describe "/code/:repo" do
157 158
    test "a private repo below :l3 does not expose a browsable repo home", %{conn: conn} do
158
      assert_raise OpenAgentsWeb.PublicNotFoundError, fn -> live(conn, "/OpenAgentsInc/sarah") end
159
      assert_raise OpenAgentsWeb.PublicNotFoundError, fn ->
160
        live(conn, "/OpenAgentsInc/openagents.com")
161
      end
159 162
    end
160 163
161 164
    test "renders the repo home for a visitor with no session", %{conn: conn, short: short} do
162 165
      browsable()
163
      {:ok, _view, html} = live(conn, "/OpenAgentsInc/sarah")
166
      {:ok, _view, html} = live(conn, "/OpenAgentsInc/openagents.com")
164 167
165
      assert html =~ "<h1>OpenAgentsInc/sarah</h1>"
168
      assert html =~ "<h1>OpenAgentsInc/openagents.com</h1>"
166 169
      assert html =~ "Add the transparency audit fixture"
167 170
      assert html =~ "First commit"
168 171
      assert html =~ ~s(id="repo-refs")

@@ -174,7 +177,7 @@ defmodule OpenAgentsWeb.CodeLiveTest do

174 177
175 178
    test "publishes no node internals and no account controls anonymously", %{conn: conn} do
176 179
      browsable()
177
      {:ok, _view, html} = live(conn, "/OpenAgentsInc/sarah")
180
      {:ok, _view, html} = live(conn, "/OpenAgentsInc/openagents.com")
178 181
179 182
      refute html =~ to_string(node())
180 183
      refute html =~ ~s(id="account-menu-trigger")

@@ -183,12 +186,12 @@ defmodule OpenAgentsWeb.CodeLiveTest do

183 186
    test "the repo is reachable only under its owning account", %{conn: conn} do
184 187
      browsable()
185 188
      # The GitHub-identical URL works...
186
      {:ok, _view, _html} = live(conn, "/OpenAgentsInc/sarah")
189
      {:ok, _view, _html} = live(conn, "/OpenAgentsInc/openagents.com")
187 190
      # ...and no other account segment is even routed (no wildcard owner), so
188 191
      # a two-segment path that is not ours 404s without reaching a LiveView.
189
      assert conn |> get("/someoneelse/sarah") |> Map.fetch!(:status) == 404
192
      assert conn |> get("/someoneelse/demo") |> Map.fetch!(:status) == 404
190 193
      # The old /code/* shape is gone.
191
      assert conn |> get("/code/sarah") |> Map.fetch!(:status) == 404
194
      assert conn |> get("/code/demo") |> Map.fetch!(:status) == 404
192 195
    end
193 196
194 197
    test "a dark repo and an unknown repo 404 indistinguishably", %{conn: conn} do

@@ -199,7 +202,7 @@ defmodule OpenAgentsWeb.CodeLiveTest do

199 202
200 203
  describe "/code/:repo/blob/:ref/*path" do
201 204
    test "renders markdown as HTML, not source", %{conn: conn} do
202
      {:ok, _view, html} = live(conn, "/OpenAgentsInc/sarah/blob/main/docs/audit.md")
205
      {:ok, _view, html} = live(conn, "/OpenAgentsInc/openagents.com/blob/main/docs/audit.md")
203 206
204 207
      assert html =~ @audit_heading
205 208
      refute html =~ "# #{@audit_heading}"

@@ -207,7 +210,8 @@ defmodule OpenAgentsWeb.CodeLiveTest do

207 210
    end
208 211
209 212
    test "?plain=1 renders the raw markdown source", %{conn: conn} do
210
      {:ok, _view, html} = live(conn, "/OpenAgentsInc/sarah/blob/main/docs/audit.md?plain=1")
213
      {:ok, _view, html} =
214
        live(conn, "/OpenAgentsInc/openagents.com/blob/main/docs/audit.md?plain=1")
211 215
212 216
      assert html =~ "# #{@audit_heading}"
213 217
      assert html =~ ~s(class="code-source")

@@ -218,7 +222,7 @@ defmodule OpenAgentsWeb.CodeLiveTest do

218 222
      short: short
219 223
    } do
220 224
      browsable()
221
      {:ok, _view, html} = live(conn, "/OpenAgentsInc/sarah/blob/#{short}/docs/audit.md")
225
      {:ok, _view, html} = live(conn, "/OpenAgentsInc/openagents.com/blob/#{short}/docs/audit.md")
222 226
223 227
      assert html =~ @audit_heading
224 228
      refute html =~ "# #{@audit_heading}"

@@ -230,40 +234,42 @@ defmodule OpenAgentsWeb.CodeLiveTest do

230 234
      second: second
231 235
    } do
232 236
      # Head: served (it is on the allowlist).
233
      {:ok, _view, html} = live(conn, "/OpenAgentsInc/sarah/blob/main/docs/audit.md")
237
      {:ok, _view, html} = live(conn, "/OpenAgentsInc/openagents.com/blob/main/docs/audit.md")
234 238
      assert html =~ @audit_heading
235 239
236 240
      # The same path pinned to an older commit: 404. Publishing one document
237 241
      # must not publish every past revision of it.
238 242
      assert_raise OpenAgentsWeb.PublicNotFoundError, fn ->
239
        live(conn, "/OpenAgentsInc/sarah/blob/#{first}/docs/audit.md")
243
        live(conn, "/OpenAgentsInc/openagents.com/blob/#{first}/docs/audit.md")
240 244
      end
241 245
242 246
      # Pinning to the head sha itself is fine — it is the same content.
243
      {:ok, _view, html} = live(conn, "/OpenAgentsInc/sarah/blob/#{second}/docs/audit.md")
247
      {:ok, _view, html} =
248
        live(conn, "/OpenAgentsInc/openagents.com/blob/#{second}/docs/audit.md")
249
244 250
      assert html =~ @audit_heading
245 251
    end
246 252
247 253
    test "a path that is not on the published allowlist 404s below :l3", %{conn: conn} do
248 254
      assert_raise OpenAgentsWeb.PublicNotFoundError, fn ->
249
        live(conn, "/OpenAgentsInc/sarah/blob/main/README.md")
255
        live(conn, "/OpenAgentsInc/openagents.com/blob/main/README.md")
250 256
      end
251 257
252 258
      # …and is served once the repo is browsable.
253 259
      browsable()
254
      {:ok, _view, html} = live(conn, "/OpenAgentsInc/sarah/blob/main/README.md")
260
      {:ok, _view, html} = live(conn, "/OpenAgentsInc/openagents.com/blob/main/README.md")
255 261
      assert html =~ "Fixture readme."
256 262
    end
257 263
258 264
    test "a missing path 404s", %{conn: conn} do
259 265
      assert_raise OpenAgentsWeb.PublicNotFoundError, fn ->
260
        live(conn, "/OpenAgentsInc/sarah/blob/main/missing.txt")
266
        live(conn, "/OpenAgentsInc/openagents.com/blob/main/missing.txt")
261 267
      end
262 268
    end
263 269
264 270
    test "a flag-shaped ref 404s without reaching git", %{conn: conn} do
265 271
      assert_raise OpenAgentsWeb.PublicNotFoundError, fn ->
266
        live(conn, "/OpenAgentsInc/sarah/blob/-evil/file.txt")
272
        live(conn, "/OpenAgentsInc/openagents.com/blob/-evil/file.txt")
267 273
      end
268 274
    end
269 275
  end

@@ -273,7 +279,7 @@ defmodule OpenAgentsWeb.CodeLiveTest do

273 279
      conn: conn,
274 280
      short: short
275 281
    } do
276
      {:ok, _view, html} = live(conn, "/OpenAgentsInc/sarah/commit/#{short}")
282
      {:ok, _view, html} = live(conn, "/OpenAgentsInc/openagents.com/commit/#{short}")
277 283
278 284
      assert html =~ "Add the transparency audit fixture"
279 285
      assert html =~ "Claude-Session"

@@ -287,14 +293,14 @@ defmodule OpenAgentsWeb.CodeLiveTest do

287 293
288 294
    test "the diff is published once the repo is browsable", %{conn: conn, short: short} do
289 295
      browsable()
290
      {:ok, _view, html} = live(conn, "/OpenAgentsInc/sarah/commit/#{short}")
296
      {:ok, _view, html} = live(conn, "/OpenAgentsInc/openagents.com/commit/#{short}")
291 297
292 298
      assert html =~ "diff --git"
293 299
      assert html =~ @audit_heading
294 300
    end
295 301
296 302
    test "a commit with no receipts says so honestly", %{conn: conn, short: short} do
297
      {:ok, _view, html} = live(conn, "/OpenAgentsInc/sarah/commit/#{short}")
303
      {:ok, _view, html} = live(conn, "/OpenAgentsInc/openagents.com/commit/#{short}")
298 304
299 305
      assert html =~ "Not deployed through the forge lane"
300 306
      refute html =~ "push→live"

@@ -307,7 +313,7 @@ defmodule OpenAgentsWeb.CodeLiveTest do

307 313
    } do
308 314
      insert_deploy!(second)
309 315
310
      {:ok, view, html} = live(conn, "/OpenAgentsInc/sarah/commit/#{short}")
316
      {:ok, view, html} = live(conn, "/OpenAgentsInc/openagents.com/commit/#{short}")
311 317
312 318
      refute html =~ "Not deployed through the forge lane"
313 319

@@ -320,7 +326,7 @@ defmodule OpenAgentsWeb.CodeLiveTest do

320 326
321 327
    test "an unknown sha 404s", %{conn: conn} do
322 328
      assert_raise OpenAgentsWeb.PublicNotFoundError, fn ->
323
        live(conn, "/OpenAgentsInc/sarah/commit/deadbeefdead")
329
        live(conn, "/OpenAgentsInc/openagents.com/commit/deadbeefdead")
324 330
      end
325 331
    end
326 332
  end
test/openagents_web/live/computers_access_test.exs modified +2 -2

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgentsWeb.ComputersAccessTest do
2
  use OpenAgentsWeb.SarahConnCase, async: false
2
  use OpenAgentsWeb.ConnCase, async: false
3 3
  import Phoenix.LiveViewTest
4 4
5 5
  alias OpenAgents.Machines

@@ -27,7 +27,7 @@ defmodule OpenAgentsWeb.ComputersAccessTest do

27 27
               "tier" => "probe",
28 28
               "platform" => "linux-x64",
29 29
               "agent_version" => "0.4.0",
30
               "roots" => ["/home/sarah/work"]
30
               "roots" => ["/home/test/work"]
31 31
             })
32 32
33 33
    assert {:ok, machine} = Machines.approve_pairing(owner, pairing.code)
test/openagents_web/live/computers_live_test.exs modified +2 -2

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgentsWeb.ComputersLiveTest do
2
  use OpenAgentsWeb.SarahConnCase, async: false
2
  use OpenAgentsWeb.ConnCase, async: false
3 3
  import Phoenix.LiveViewTest
4 4
5 5
  alias OpenAgents.Computer

@@ -142,7 +142,7 @@ defmodule OpenAgentsWeb.ComputersLiveTest do

142 142
               "tier" => "curated",
143 143
               "platform" => "linux-x64",
144 144
               "agent_version" => "0.4.0",
145
               "roots" => ["/home/sarah/work"]
145
               "roots" => ["/home/test/work"]
146 146
             })
147 147
148 148
    pairing
test/openagents_web/live/computers_navigation_test.exs modified +1 -1

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgentsWeb.ComputersNavigationTest do
2
  use OpenAgentsWeb.SarahConnCase
2
  use OpenAgentsWeb.ConnCase
3 3
  import Phoenix.LiveViewTest
4 4
5 5
  test "the canonical Computers surface carries authenticated application chrome", %{conn: conn} do
test/openagents_web/live/computers_pairing_errors_test.exs modified +2 -2

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgentsWeb.ComputersPairingErrorsTest do
2
  use OpenAgentsWeb.SarahConnCase, async: false
2
  use OpenAgentsWeb.ConnCase, async: false
3 3
  import Phoenix.LiveViewTest
4 4
5 5
  alias OpenAgents.Machines

@@ -82,7 +82,7 @@ defmodule OpenAgentsWeb.ComputersPairingErrorsTest do

82 82
               "tier" => "curated",
83 83
               "platform" => "linux-x64",
84 84
               "agent_version" => "0.4.0",
85
               "roots" => ["/home/sarah/work"]
85
               "roots" => ["/home/test/work"]
86 86
             })
87 87
88 88
    pairing
test/openagents_web/live/issue_index_live_test.exs modified +27 -19

@@ -11,21 +11,24 @@ defmodule OpenAgentsWeb.IssueIndexLiveTest do

11 11
  end
12 12
13 13
  test "mounts with zeroed counts and an empty state", %{conn: conn} do
14
    {:ok, view, html} = live(conn, ~p"/OpenAgentsInc/sarah/issues")
14
    {:ok, view, html} = live(conn, ~p"/OpenAgentsInc/openagents.com/issues")
15 15
16 16
    assert html =~ "No open issues"
17 17
    assert html =~ "Issues will show up here once they are created."
18 18
    refute has_element?(view, "#issues")
19 19
20 20
    # The default filter is `open`, and it is the one marked current.
21
    assert has_element?(view, ~s{a[href="/OpenAgentsInc/sarah/issues?state=open"][aria-current]})
21
    assert has_element?(
22
             view,
23
             ~s{a[href="/OpenAgentsInc/openagents.com/issues?state=open"][aria-current]}
24
           )
22 25
23 26
    refute has_element?(
24 27
             view,
25
             ~s{a[href="/OpenAgentsInc/sarah/issues?state=closed"][aria-current]}
28
             ~s{a[href="/OpenAgentsInc/openagents.com/issues?state=closed"][aria-current]}
26 29
           )
27 30
28
    assert has_element?(view, ~s{a[href="/OpenAgentsInc/sarah/issues/new"]}, "New issue")
31
    assert has_element?(view, ~s{a[href="/OpenAgentsInc/openagents.com/issues/new"]}, "New issue")
29 32
  end
30 33
31 34
  test "lists open issues with their number, author, labels, and assignees", %{conn: conn} do

@@ -39,14 +42,14 @@ defmodule OpenAgentsWeb.IssueIndexLiveTest do

39 42
        "assignees" => ["grace"]
40 43
      })
41 44
42
    {:ok, view, html} = live(conn, ~p"/OpenAgentsInc/sarah/issues")
45
    {:ok, view, html} = live(conn, ~p"/OpenAgentsInc/openagents.com/issues")
43 46
44 47
    refute html =~ "No open issues"
45 48
    assert has_element?(view, "#issues")
46 49
47 50
    assert has_element?(
48 51
             view,
49
             ~s{a[href="/OpenAgentsInc/sarah/issues/#{issue.number}"]},
52
             ~s{a[href="/OpenAgentsInc/openagents.com/issues/#{issue.number}"]},
50 53
             "Streaming stalls"
51 54
           )
52 55

@@ -59,7 +62,7 @@ defmodule OpenAgentsWeb.IssueIndexLiveTest do

59 62
  test "an issue with no author falls back to anonymous", %{conn: conn} do
60 63
    {:ok, _} = Issues.create_issue(%{"title" => "Orphaned"})
61 64
62
    {:ok, _view, html} = live(conn, ~p"/OpenAgentsInc/sarah/issues")
65
    {:ok, _view, html} = live(conn, ~p"/OpenAgentsInc/openagents.com/issues")
63 66
64 67
    assert html =~ "Orphaned"
65 68
    assert html =~ "anonymous"

@@ -68,7 +71,7 @@ defmodule OpenAgentsWeb.IssueIndexLiveTest do

68 71
  test "the comment count only renders once an issue has comments", %{conn: conn} do
69 72
    {:ok, issue} = Issues.create_issue(%{"title" => "Chatty"})
70 73
71
    {:ok, _view, html} = live(conn, ~p"/OpenAgentsInc/sarah/issues")
74
    {:ok, _view, html} = live(conn, ~p"/OpenAgentsInc/openagents.com/issues")
72 75
    refute html =~ "hero-chat-bubble-left"
73 76
74 77
    {:ok, _} =

@@ -78,7 +81,7 @@ defmodule OpenAgentsWeb.IssueIndexLiveTest do

78 81
        user: %{"login" => "ada"}
79 82
      })
80 83
81
    {:ok, _view, html} = live(conn, ~p"/OpenAgentsInc/sarah/issues")
84
    {:ok, _view, html} = live(conn, ~p"/OpenAgentsInc/openagents.com/issues")
82 85
    assert html =~ "hero-chat-bubble-left"
83 86
  end
84 87

@@ -87,13 +90,13 @@ defmodule OpenAgentsWeb.IssueIndexLiveTest do

87 90
    {:ok, closed} = Issues.create_issue(%{"title" => "Closed one"})
88 91
    {:ok, _} = Issues.update_issue(closed, %{"state" => "closed"})
89 92
90
    {:ok, view, html} = live(conn, ~p"/OpenAgentsInc/sarah/issues")
93
    {:ok, view, html} = live(conn, ~p"/OpenAgentsInc/openagents.com/issues")
91 94
    assert html =~ "Open one"
92 95
    refute html =~ "Closed one"
93 96
94 97
    html =
95 98
      view
96
      |> element(~s{a[href="/OpenAgentsInc/sarah/issues?state=closed"]})
99
      |> element(~s{a[href="/OpenAgentsInc/openagents.com/issues?state=closed"]})
97 100
      |> render_click()
98 101
99 102
    assert html =~ "Closed one"

@@ -101,16 +104,19 @@ defmodule OpenAgentsWeb.IssueIndexLiveTest do

101 104
102 105
    assert has_element?(
103 106
             view,
104
             ~s{a[href="/OpenAgentsInc/sarah/issues?state=closed"][aria-current]}
107
             ~s{a[href="/OpenAgentsInc/openagents.com/issues?state=closed"][aria-current]}
105 108
           )
106 109
107
    refute has_element?(view, ~s{a[href="/OpenAgentsInc/sarah/issues?state=open"][aria-current]})
110
    refute has_element?(
111
             view,
112
             ~s{a[href="/OpenAgentsInc/openagents.com/issues?state=open"][aria-current]}
113
           )
108 114
  end
109 115
110 116
  test "the closed filter has its own empty-state wording", %{conn: conn} do
111 117
    {:ok, _open} = Issues.create_issue(%{"title" => "Open one"})
112 118
113
    {:ok, _view, html} = live(conn, ~p"/OpenAgentsInc/sarah/issues?state=closed")
119
    {:ok, _view, html} = live(conn, ~p"/OpenAgentsInc/openagents.com/issues?state=closed")
114 120
115 121
    assert html =~ "No closed issues"
116 122
    refute html =~ "No open issues"

@@ -123,24 +129,26 @@ defmodule OpenAgentsWeb.IssueIndexLiveTest do

123 129
    {:ok, _} = Issues.update_issue(c, %{"state" => "closed"})
124 130
125 131
    for state <- ["open", "closed"] do
126
      {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/sarah/issues?state=#{state}")
132
      {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/openagents.com/issues?state=#{state}")
127 133
128 134
      assert has_element?(
129 135
               view,
130
               ~s{a[href="/OpenAgentsInc/sarah/issues?state=open"]},
136
               ~s{a[href="/OpenAgentsInc/openagents.com/issues?state=open"]},
131 137
               "2 Open"
132 138
             )
133 139
134 140
      assert has_element?(
135 141
               view,
136
               ~s{a[href="/OpenAgentsInc/sarah/issues?state=closed"]},
142
               ~s{a[href="/OpenAgentsInc/openagents.com/issues?state=closed"]},
137 143
               "1 Closed"
138 144
             )
139 145
    end
140 146
  end
141 147
142 148
  test "an anonymous visitor is redirected away from the issue list" do
143
    assert {:error, {:redirect, %{to: to}}} = live(build_conn(), ~p"/OpenAgentsInc/sarah/issues")
144
    refute to == "/OpenAgentsInc/sarah/issues"
149
    assert {:error, {:redirect, %{to: to}}} =
150
             live(build_conn(), ~p"/OpenAgentsInc/openagents.com/issues")
151
152
    refute to == "/OpenAgentsInc/openagents.com/issues"
145 153
  end
146 154
end
test/openagents_web/live/issue_new_live_test.exs modified +9 -9

@@ -12,20 +12,20 @@ defmodule OpenAgentsWeb.IssueNewLiveTest do

12 12
  end
13 13
14 14
  test "mounts with an empty form and a cancel link back to the list", %{conn: conn} do
15
    {:ok, view, html} = live(conn, ~p"/OpenAgentsInc/sarah/issues/new")
15
    {:ok, view, html} = live(conn, ~p"/OpenAgentsInc/openagents.com/issues/new")
16 16
17 17
    assert html =~ "New issue"
18 18
    assert has_element?(view, "#new-issue-form")
19 19
    assert has_element?(view, "#issue_title")
20 20
    assert has_element?(view, "#issue_body")
21
    assert has_element?(view, ~s{a[href="/OpenAgentsInc/sarah/issues"]}, "Cancel")
21
    assert has_element?(view, ~s{a[href="/OpenAgentsInc/openagents.com/issues"]}, "Cancel")
22 22
  end
23 23
24 24
  test "the milestone and label selects offer the seeded records", %{conn: conn} do
25 25
    milestone = milestone_fixture(%{title: "v1.0", due_on: nil})
26 26
    label_fixture(%{name: "bug", color: "d73a4a"})
27 27
28
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/sarah/issues/new")
28
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/openagents.com/issues/new")
29 29
30 30
    assert has_element?(view, ~s{#issue_milestone option[value="#{milestone.number}"]}, "v1.0")
31 31
    assert has_element?(view, ~s{#issue_labels option[value="bug"]}, "bug")

@@ -33,14 +33,14 @@ defmodule OpenAgentsWeb.IssueNewLiveTest do

33 33
  end
34 34
35 35
  test "with no milestones or labels the selects render only the prompt", %{conn: conn} do
36
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/sarah/issues/new")
36
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/openagents.com/issues/new")
37 37
38 38
    assert has_element?(view, "#issue_milestone")
39 39
    refute has_element?(view, ~s{#issue_labels option})
40 40
  end
41 41
42 42
  test "submitting a title creates the issue and navigates to it", %{conn: conn} do
43
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/sarah/issues/new")
43
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/openagents.com/issues/new")
44 44
45 45
    result =
46 46
      view

@@ -52,7 +52,7 @@ defmodule OpenAgentsWeb.IssueNewLiveTest do

52 52
    assert issue.body == "Please"
53 53
54 54
    {:ok, _show, html} =
55
      follow_redirect(result, conn, ~p"/OpenAgentsInc/sarah/issues/#{issue.number}")
55
      follow_redirect(result, conn, ~p"/OpenAgentsInc/openagents.com/issues/#{issue.number}")
56 56
57 57
    assert html =~ "Issue created"
58 58
    assert html =~ "Add a runbook"

@@ -62,7 +62,7 @@ defmodule OpenAgentsWeb.IssueNewLiveTest do

62 62
    milestone = milestone_fixture(%{title: "v1.0", due_on: nil})
63 63
    label_fixture(%{name: "bug", color: "d73a4a"})
64 64
65
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/sarah/issues/new")
65
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/openagents.com/issues/new")
66 66
67 67
    assert {:error, {:live_redirect, _}} =
68 68
             view

@@ -85,7 +85,7 @@ defmodule OpenAgentsWeb.IssueNewLiveTest do

85 85
  test "an empty label and milestone selection leaves the issue bare", %{conn: conn} do
86 86
    label_fixture(%{name: "bug", color: "d73a4a"})
87 87
88
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/sarah/issues/new")
88
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/openagents.com/issues/new")
89 89
90 90
    assert {:error, {:live_redirect, _}} =
91 91
             view

@@ -100,7 +100,7 @@ defmodule OpenAgentsWeb.IssueNewLiveTest do

100 100
  end
101 101
102 102
  test "a blank title re-renders the form with an error and creates nothing", %{conn: conn} do
103
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/sarah/issues/new")
103
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/openagents.com/issues/new")
104 104
105 105
    html =
106 106
      view
test/openagents_web/live/issue_show_live_test.exs modified +4 -4

@@ -16,7 +16,7 @@ defmodule OpenAgentsWeb.IssueShowLiveTest do

16 16
    issue
17 17
  end
18 18
19
  defp path(issue), do: ~p"/OpenAgentsInc/sarah/issues/#{issue.number}"
19
  defp path(issue), do: ~p"/OpenAgentsInc/openagents.com/issues/#{issue.number}"
20 20
21 21
  test "mounts and renders the title, number, state, and body", %{conn: conn} do
22 22
    issue =

@@ -65,12 +65,12 @@ defmodule OpenAgentsWeb.IssueShowLiveTest do

65 65
    assert html =~ "bug"
66 66
    assert html =~ "Assignees"
67 67
    assert has_element?(view, ~s{[title="grace"]})
68
    assert has_element?(view, ~s{a[href="/OpenAgentsInc/sarah/milestones"]}, "v1.0")
68
    assert has_element?(view, ~s{a[href="/OpenAgentsInc/openagents.com/milestones"]}, "v1.0")
69 69
  end
70 70
71 71
  test "a missing issue number raises rather than rendering an empty page", %{conn: conn} do
72 72
    assert_raise Ecto.NoResultsError, fn ->
73
      live(conn, ~p"/OpenAgentsInc/sarah/issues/9999")
73
      live(conn, ~p"/OpenAgentsInc/openagents.com/issues/9999")
74 74
    end
75 75
  end
76 76

@@ -206,6 +206,6 @@ defmodule OpenAgentsWeb.IssueShowLiveTest do

206 206
    issue = issue!(%{"title" => "Private"})
207 207
208 208
    assert {:error, {:redirect, %{to: to}}} = live(build_conn(), path(issue))
209
    refute to == "/OpenAgentsInc/sarah/issues/#{issue.number}"
209
    refute to == "/OpenAgentsInc/openagents.com/issues/#{issue.number}"
210 210
  end
211 211
end
test/openagents_web/live/label_index_live_test.exs modified +5 -5

@@ -11,7 +11,7 @@ defmodule OpenAgentsWeb.LabelIndexLiveTest do

11 11
  end
12 12
13 13
  test "mounts with the create form and an empty state", %{conn: conn} do
14
    {:ok, view, html} = live(conn, ~p"/OpenAgentsInc/sarah/labels")
14
    {:ok, view, html} = live(conn, ~p"/OpenAgentsInc/openagents.com/labels")
15 15
16 16
    assert html =~ "Labels"
17 17
    assert has_element?(view, "#new-label-form")

@@ -23,7 +23,7 @@ defmodule OpenAgentsWeb.LabelIndexLiveTest do

23 23
    label_fixture(%{name: "bug", color: "d73a4a", description: "Something is broken"})
24 24
    label_fixture(%{name: "docs", color: "0075ca", description: nil})
25 25
26
    {:ok, view, html} = live(conn, ~p"/OpenAgentsInc/sarah/labels")
26
    {:ok, view, html} = live(conn, ~p"/OpenAgentsInc/openagents.com/labels")
27 27
28 28
    refute html =~ "No labels yet"
29 29
    assert has_element?(view, "#labels")

@@ -35,7 +35,7 @@ defmodule OpenAgentsWeb.LabelIndexLiveTest do

35 35
  end
36 36
37 37
  test "submitting the form creates a label and re-renders the table", %{conn: conn} do
38
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/sarah/labels")
38
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/openagents.com/labels")
39 39
40 40
    html =
41 41
      view

@@ -48,7 +48,7 @@ defmodule OpenAgentsWeb.LabelIndexLiveTest do

48 48
  end
49 49
50 50
  test "a label missing its required color re-renders the form with an error", %{conn: conn} do
51
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/sarah/labels")
51
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/openagents.com/labels")
52 52
53 53
    html =
54 54
      view

@@ -63,7 +63,7 @@ defmodule OpenAgentsWeb.LabelIndexLiveTest do

63 63
  test "deleting a label removes it and returns the empty state", %{conn: conn} do
64 64
    label = label_fixture(%{name: "wontfix", color: "ffffff"})
65 65
66
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/sarah/labels")
66
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/openagents.com/labels")
67 67
    assert has_element?(view, "#labels td", "wontfix")
68 68
69 69
    html =
test/openagents_web/live/leaderboard_live_test.exs modified +1 -1

@@ -5,7 +5,7 @@ defmodule OpenAgentsWeb.LeaderboardLiveTest do

5 5
  carry anything past the published projection.
6 6
  """
7 7
8
  use OpenAgentsWeb.SarahConnCase, async: false
8
  use OpenAgentsWeb.ConnCase, async: false
9 9
  import Phoenix.LiveViewTest
10 10
11 11
  alias OpenAgents.Accounts
test/openagents_web/live/milestone_index_live_test.exs modified +8 -8

@@ -12,7 +12,7 @@ defmodule OpenAgentsWeb.MilestoneIndexLiveTest do

12 12
  end
13 13
14 14
  test "mounts with the create form and an empty state", %{conn: conn} do
15
    {:ok, view, html} = live(conn, ~p"/OpenAgentsInc/sarah/milestones")
15
    {:ok, view, html} = live(conn, ~p"/OpenAgentsInc/openagents.com/milestones")
16 16
17 17
    assert html =~ "Milestones"
18 18
    assert has_element?(view, "#new-milestone-form")

@@ -22,7 +22,7 @@ defmodule OpenAgentsWeb.MilestoneIndexLiveTest do

22 22
  test "lists seeded milestones with their state and due date", %{conn: conn} do
23 23
    milestone_fixture(%{title: "v1.0", state: "open", due_on: "2026-12-31", description: "Ship"})
24 24
25
    {:ok, _view, html} = live(conn, ~p"/OpenAgentsInc/sarah/milestones")
25
    {:ok, _view, html} = live(conn, ~p"/OpenAgentsInc/openagents.com/milestones")
26 26
27 27
    refute html =~ "No milestones yet"
28 28
    assert html =~ "v1.0"

@@ -40,7 +40,7 @@ defmodule OpenAgentsWeb.MilestoneIndexLiveTest do

40 40
    {:ok, done_issue} = Issues.set_milestone(done_issue, milestone.number)
41 41
    {:ok, _} = Issues.update_issue(done_issue, %{"state" => "closed"})
42 42
43
    {:ok, view, html} = live(conn, ~p"/OpenAgentsInc/sarah/milestones")
43
    {:ok, view, html} = live(conn, ~p"/OpenAgentsInc/openagents.com/milestones")
44 44
45 45
    assert has_element?(
46 46
             view,

@@ -56,7 +56,7 @@ defmodule OpenAgentsWeb.MilestoneIndexLiveTest do

56 56
  test "a milestone with no issues reports zero progress", %{conn: conn} do
57 57
    milestone_fixture(%{title: "Empty", due_on: nil})
58 58
59
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/sarah/milestones")
59
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/openagents.com/milestones")
60 60
61 61
    assert has_element?(
62 62
             view,

@@ -65,7 +65,7 @@ defmodule OpenAgentsWeb.MilestoneIndexLiveTest do

65 65
  end
66 66
67 67
  test "submitting the form creates a milestone", %{conn: conn} do
68
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/sarah/milestones")
68
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/openagents.com/milestones")
69 69
70 70
    html =
71 71
      view

@@ -80,7 +80,7 @@ defmodule OpenAgentsWeb.MilestoneIndexLiveTest do

80 80
  end
81 81
82 82
  test "a milestone with no title re-renders the form with an error", %{conn: conn} do
83
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/sarah/milestones")
83
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/openagents.com/milestones")
84 84
85 85
    html =
86 86
      view

@@ -94,7 +94,7 @@ defmodule OpenAgentsWeb.MilestoneIndexLiveTest do

94 94
  test "closing a milestone flips its state and hides the close button", %{conn: conn} do
95 95
    milestone = milestone_fixture(%{title: "Closeable", state: "open", due_on: nil})
96 96
97
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/sarah/milestones")
97
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/openagents.com/milestones")
98 98
    assert has_element?(view, ~s{button[phx-click="close"][phx-value-id="#{milestone.id}"]})
99 99
100 100
    html =

@@ -110,7 +110,7 @@ defmodule OpenAgentsWeb.MilestoneIndexLiveTest do

110 110
  test "deleting a milestone returns the empty state", %{conn: conn} do
111 111
    milestone = milestone_fixture(%{title: "Deletable", due_on: nil})
112 112
113
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/sarah/milestones")
113
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/openagents.com/milestones")
114 114
115 115
    html =
116 116
      view
test/openagents_web/live/network_status_live_test.exs modified +6 -6

@@ -1,5 +1,5 @@

1 1
defmodule OpenAgentsWeb.NetworkStatusLiveTest do
2
  use OpenAgentsWeb.SarahConnCase
2
  use OpenAgentsWeb.ConnCase
3 3
  import Phoenix.LiveViewTest
4 4
5 5
  test "renders publicly for an anonymous browser visitor", %{conn: conn} do

@@ -42,7 +42,7 @@ defmodule OpenAgentsWeb.NetworkStatusLiveTest do

42 42
       %{conn: conn} do
43 43
    response = conn |> get(~p"/api/status") |> json_response(200)
44 44
45
    assert response["schema"] == "sarah.network_status.v1"
45
    assert response["schema"] == "openagents.network_status.v1"
46 46
    assert response["status"] in ["ok", "degraded"]
47 47
    assert is_binary(response["revision"])
48 48
    assert is_list(response["nodes"])

@@ -59,7 +59,7 @@ defmodule OpenAgentsWeb.NetworkStatusLiveTest do

59 59
  defp seed_target(status, opts \\ []) do
60 60
    %Target{}
61 61
    |> Target.changeset(%{
62
      repo: "sarah",
62
      repo: "openagents.com",
63 63
      sha: Keyword.get(opts, :sha, String.duplicate("c", 40)),
64 64
      promoted_by: Keyword.get(opts, :promoted_by, "operator:31337"),
65 65
      status: status

@@ -119,7 +119,7 @@ defmodule OpenAgentsWeb.NetworkStatusLiveTest do

119 119
        OpenAgents.PubSub,
120 120
        "forge:target",
121 121
        {:forge_target_status,
122
         %{repo: "sarah", sha: target.sha, target_id: target.id, status: "building"}}
122
         %{repo: "openagents.com", sha: target.sha, target_id: target.id, status: "building"}}
123 123
      )
124 124
125 125
      html = render(view)

@@ -128,7 +128,7 @@ defmodule OpenAgentsWeb.NetworkStatusLiveTest do

128 128
      Phoenix.PubSub.broadcast(
129 129
        OpenAgents.PubSub,
130 130
        "forge:deploys",
131
        {:forge_deploy, %{repo: "sarah", sha: target.sha, result: "live"}}
131
        {:forge_deploy, %{repo: "openagents.com", sha: target.sha, result: "live"}}
132 132
      )
133 133
134 134
      assert render(view) =~ "hot deploy #{String.duplicate("c", 12)}: live"

@@ -139,7 +139,7 @@ defmodule OpenAgentsWeb.NetworkStatusLiveTest do

139 139
140 140
      %DeployReceipt{}
141 141
      |> DeployReceipt.changeset(%{
142
        repo: "sarah",
142
        repo: "openagents.com",
143 143
        sha: target.sha,
144 144
        target_id: target.id,
145 145
        modules: ["Elixir.OpenAgents.HiddenModuleName"],
test/openagents_web/live/project_index_live_test.exs modified +8 -8

@@ -11,7 +11,7 @@ defmodule OpenAgentsWeb.ProjectIndexLiveTest do

11 11
  end
12 12
13 13
  test "mounts with the create form and an empty state", %{conn: conn} do
14
    {:ok, view, html} = live(conn, ~p"/OpenAgentsInc/sarah/projects")
14
    {:ok, view, html} = live(conn, ~p"/OpenAgentsInc/openagents.com/projects")
15 15
16 16
    assert html =~ "Projects"
17 17
    assert has_element?(view, "#new-project-form")

@@ -21,20 +21,20 @@ defmodule OpenAgentsWeb.ProjectIndexLiveTest do

21 21
  test "lists projects owned by the URL owner and links to each board", %{conn: conn} do
22 22
    project = project_fixture(%{title: "Roadmap", owner: "OpenAgentsInc", state: "open"})
23 23
24
    {:ok, view, html} = live(conn, ~p"/OpenAgentsInc/sarah/projects")
24
    {:ok, view, html} = live(conn, ~p"/OpenAgentsInc/openagents.com/projects")
25 25
26 26
    refute html =~ "No projects yet"
27 27
    assert html =~ "Roadmap"
28 28
29 29
    assert has_element?(
30 30
             view,
31
             ~s{a[href="/OpenAgentsInc/sarah/projects/#{project.number}"]},
31
             ~s{a[href="/OpenAgentsInc/openagents.com/projects/#{project.number}"]},
32 32
             "Roadmap"
33 33
           )
34 34
35 35
    assert has_element?(
36 36
             view,
37
             ~s{a[href="/OpenAgentsInc/sarah/projects/#{project.number}"]},
37
             ~s{a[href="/OpenAgentsInc/openagents.com/projects/#{project.number}"]},
38 38
             "View"
39 39
           )
40 40
  end

@@ -42,14 +42,14 @@ defmodule OpenAgentsWeb.ProjectIndexLiveTest do

42 42
  test "a project owned by someone else is filtered out", %{conn: conn} do
43 43
    project_fixture(%{title: "Someone elses", owner: "other-org"})
44 44
45
    {:ok, _view, html} = live(conn, ~p"/OpenAgentsInc/sarah/projects")
45
    {:ok, _view, html} = live(conn, ~p"/OpenAgentsInc/openagents.com/projects")
46 46
47 47
    refute html =~ "Someone elses"
48 48
    assert html =~ "No projects yet"
49 49
  end
50 50
51 51
  test "submitting the form creates a project owned by the URL owner", %{conn: conn} do
52
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/sarah/projects")
52
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/openagents.com/projects")
53 53
54 54
    html =
55 55
      view

@@ -66,7 +66,7 @@ defmodule OpenAgentsWeb.ProjectIndexLiveTest do

66 66
  end
67 67
68 68
  test "a project with no title re-renders the form with an error", %{conn: conn} do
69
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/sarah/projects")
69
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/openagents.com/projects")
70 70
71 71
    html =
72 72
      view

@@ -81,7 +81,7 @@ defmodule OpenAgentsWeb.ProjectIndexLiveTest do

81 81
  test "deleting a project returns the empty state", %{conn: conn} do
82 82
    project = project_fixture(%{title: "Doomed", owner: "OpenAgentsInc"})
83 83
84
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/sarah/projects")
84
    {:ok, view, _html} = live(conn, ~p"/OpenAgentsInc/openagents.com/projects")
85 85
86 86
    html =
87 87
      view
test/openagents_web/live/project_show_live_test.exs modified +13 -8

@@ -15,7 +15,7 @@ defmodule OpenAgentsWeb.ProjectShowLiveTest do

15 15
16 16
  defp project!, do: project_fixture(%{title: "Roadmap", owner: "OpenAgentsInc"})
17 17
18
  defp path(project), do: ~p"/OpenAgentsInc/sarah/projects/#{project.number}"
18
  defp path(project), do: ~p"/OpenAgentsInc/openagents.com/projects/#{project.number}"
19 19
20 20
  test "mounts and renders every board column plus the add form", %{conn: conn} do
21 21
    project = project!()

@@ -27,7 +27,12 @@ defmodule OpenAgentsWeb.ProjectShowLiveTest do

27 27
    assert html =~ "In Progress"
28 28
    assert html =~ "Done"
29 29
    assert has_element?(view, "#new-project-item-form")
30
    assert has_element?(view, ~s{a[href="/OpenAgentsInc/sarah/projects"]}, "Back to projects")
30
31
    assert has_element?(
32
             view,
33
             ~s{a[href="/OpenAgentsInc/openagents.com/projects"]},
34
             "Back to projects"
35
           )
31 36
  end
32 37
33 38
  test "an empty board renders the columns with no cards", %{conn: conn} do

@@ -35,7 +40,7 @@ defmodule OpenAgentsWeb.ProjectShowLiveTest do

35 40
36 41
    {:ok, view, _html} = live(conn, path(project))
37 42
38
    refute has_element?(view, ~s{a[href^="/OpenAgentsInc/sarah/issues/"]})
43
    refute has_element?(view, ~s{a[href^="/OpenAgentsInc/openagents.com/issues/"]})
39 44
    # With no issues in the repo the issue select carries only its prompt.
40 45
    refute has_element?(view, ~s{#item_issue_number option:not([value=""])})
41 46
  end

@@ -83,7 +88,7 @@ defmodule OpenAgentsWeb.ProjectShowLiveTest do

83 88
84 89
    assert has_element?(
85 90
             view,
86
             ~s{a[href="/OpenAgentsInc/sarah/issues/#{issue.number}"]},
91
             ~s{a[href="/OpenAgentsInc/openagents.com/issues/#{issue.number}"]},
87 92
             "Fix the parser"
88 93
           )
89 94
  end

@@ -121,7 +126,7 @@ defmodule OpenAgentsWeb.ProjectShowLiveTest do

121 126
122 127
    assert has_element?(
123 128
             view,
124
             ~s{a[href="/OpenAgentsInc/sarah/issues/#{issue.number}"]},
129
             ~s{a[href="/OpenAgentsInc/openagents.com/issues/#{issue.number}"]},
125 130
             "Ship the runbook"
126 131
           )
127 132

@@ -148,19 +153,19 @@ defmodule OpenAgentsWeb.ProjectShowLiveTest do

148 153
    # Cards are grouped by column; the "Done" column is the third section.
149 154
    assert has_element?(
150 155
             view,
151
             ~s{section:nth-of-type(3) a[href="/OpenAgentsInc/sarah/issues/#{issue.number}"]},
156
             ~s{section:nth-of-type(3) a[href="/OpenAgentsInc/openagents.com/issues/#{issue.number}"]},
152 157
             "Fixture-placed"
153 158
           )
154 159
155 160
    refute has_element?(
156 161
             view,
157
             ~s{section:nth-of-type(1) a[href="/OpenAgentsInc/sarah/issues/#{issue.number}"]}
162
             ~s{section:nth-of-type(1) a[href="/OpenAgentsInc/openagents.com/issues/#{issue.number}"]}
158 163
           )
159 164
  end
160 165
161 166
  test "a missing project number raises rather than rendering an empty board", %{conn: conn} do
162 167
    assert_raise Ecto.NoResultsError, fn ->
163
      live(conn, ~p"/OpenAgentsInc/sarah/projects/9999")
168
      live(conn, ~p"/OpenAgentsInc/openagents.com/projects/9999")
164 169
    end
165 170
  end
166 171
end
test/openagents_web/tool_activity_test.exs modified +2 -2

@@ -34,12 +34,12 @@ defmodule OpenAgentsWeb.ToolActivityTest do

34 34
    test "github_repo_read is read repository/path" do
35 35
      step =
36 36
        activity("github_repo_read", %{
37
          "repository" => "OpenAgentsInc/sarah",
37
          "repository" => "OpenAgentsInc/openagents.com",
38 38
          "path" => "DESIGN.md",
39 39
          "ref" => "main"
40 40
        })
41 41
42
      assert ToolActivity.title(step) == "read OpenAgentsInc/sarah/DESIGN.md"
42
      assert ToolActivity.title(step) == "read OpenAgentsInc/openagents.com/DESIGN.md"
43 43
    end
44 44
45 45
    test "conversation_search is search with the quoted query" do
test/openagents_web/ui_test.exs modified +7 -7

@@ -1,6 +1,6 @@

1 1
defmodule OpenAgentsWeb.UITest do
2 2
  @moduledoc """
3
  Contract tests for Sarah's interface primitives.
3
  Contract tests for OpenAgents interface primitives.
4 4
5 5
  These assert the rules `DESIGN.md` and `INVARIANTS.md` place on the component
6 6
  library itself, so a surface cannot inherit a violation from a primitive.

@@ -10,7 +10,7 @@ defmodule OpenAgentsWeb.UITest do

10 10
  import ExUnit.CaptureIO
11 11
  import Phoenix.Component
12 12
  import Phoenix.LiveViewTest
13
  import OpenAgentsWeb.SarahUI
13
  import OpenAgentsWeb.UI
14 14
15 15
  describe "button/1" do
16 16
    test "defaults to a primary control and omits unset variant attributes" do

@@ -96,7 +96,7 @@ defmodule OpenAgentsWeb.UITest do

96 96
          Code.compile_string("""
97 97
          defmodule OpenAgentsWeb.UITest.IconSize do
98 98
            use Phoenix.Component
99
            import OpenAgentsWeb.SarahUI
99
            import OpenAgentsWeb.UI
100 100
101 101
            def render(assigns), do: ~H"<.button size={:icon}>X</.button>"
102 102
          end

@@ -113,7 +113,7 @@ defmodule OpenAgentsWeb.UITest do

113 113
          Code.compile_string("""
114 114
          defmodule OpenAgentsWeb.UITest.BogusVariant do
115 115
            use Phoenix.Component
116
            import OpenAgentsWeb.SarahUI
116
            import OpenAgentsWeb.UI
117 117
118 118
            def render(assigns), do: ~H"<.button variant={:bogus}>X</.button>"
119 119
          end

@@ -347,7 +347,7 @@ defmodule OpenAgentsWeb.UITest do

347 347
      # and recall content must never reach the browser. The component's
348 348
      # signature is the enforcement point.
349 349
      declared =
350
        OpenAgentsWeb.SarahUI.__components__()
350
        OpenAgentsWeb.UI.__components__()
351 351
        |> Map.fetch!(:item)
352 352
        |> Map.fetch!(:attrs)
353 353
        |> Enum.map(& &1.name)

@@ -441,7 +441,7 @@ defmodule OpenAgentsWeb.UITest do

441 441
442 442
    test "requires a name, because a page of recordings is a page of identical players" do
443 443
      declared =
444
        OpenAgentsWeb.SarahUI.__components__()
444
        OpenAgentsWeb.UI.__components__()
445 445
        |> Map.fetch!(:audio_player)
446 446
        |> Map.fetch!(:attrs)
447 447

@@ -465,7 +465,7 @@ defmodule OpenAgentsWeb.UITest do

465 465
  describe "status_indicator/1" do
466 466
    test "requires a label so color never carries state alone" do
467 467
      declared =
468
        OpenAgentsWeb.SarahUI.__components__()
468
        OpenAgentsWeb.UI.__components__()
469 469
        |> Map.fetch!(:status_indicator)
470 470
        |> Map.fetch!(:attrs)
471 471
test/support/sarah_channel_case.ex → test/support/channel_case.ex renamed +4 -4

@@ -1,6 +1,6 @@

1
defmodule OpenAgentsWeb.SarahChannelCase do
1
defmodule OpenAgentsWeb.ChannelCase do
2 2
  @moduledoc """
3
  Sarah-specific channel case used by the lifted Sarah channel tests.
3
  Shared channel case for authenticated OpenAgents channel tests.
4 4
  """
5 5
6 6
  use ExUnit.CaseTemplate

@@ -12,8 +12,8 @@ defmodule OpenAgentsWeb.SarahChannelCase do

12 12
      use OpenAgentsWeb, :verified_routes
13 13
14 14
      import Phoenix.ChannelTest
15
      import OpenAgentsWeb.SarahConnCase, only: [github_user: 1]
16
      import OpenAgentsWeb.SarahChannelCase
15
      import OpenAgentsWeb.ConnCase, only: [github_user: 1]
16
      import OpenAgentsWeb.ChannelCase
17 17
    end
18 18
  end
19 19
test/support/sarah_conn_case.ex deleted -66

@@ -1,66 +0,0 @@

1
defmodule OpenAgentsWeb.SarahConnCase do
2
  @moduledoc """
3
  Sarah-specific connection case used by the lifted Sarah web tests.
4
  """
5
6
  use ExUnit.CaseTemplate
7
8
  using do
9
    quote do
10
      @endpoint OpenAgentsWeb.Endpoint
11
12
      use OpenAgentsWeb, :verified_routes
13
14
      import Plug.Conn
15
      import Phoenix.ConnTest
16
      import OpenAgentsWeb.SarahConnCase
17
    end
18
  end
19
20
  setup tags do
21
    OpenAgents.DataCase.setup_sandbox(tags)
22
    {:ok, conn: Phoenix.ConnTest.build_conn()}
23
  end
24
25
  def github_user(key) when is_binary(key) do
26
    digest = :crypto.hash(:sha256, key)
27
    github_id = digest |> binary_part(0, 7) |> :binary.decode_unsigned()
28
    login_suffix = digest |> Base.encode16(case: :lower) |> binary_part(0, 12)
29
30
    {:ok, user} =
31
      OpenAgents.Accounts.upsert_github_user(%{
32
        github_id: github_id,
33
        github_login: "test-#{login_suffix}",
34
        github_avatar_url: "https://avatars.githubusercontent.com/u/#{github_id}?v=4"
35
      })
36
37
    user
38
  end
39
40
  def log_in_github_user(conn, key) when is_binary(key) do
41
    user = github_user(key)
42
    Plug.Test.init_test_session(conn, %{"user_id" => user.id})
43
  end
44
45
  def log_in_admin_user(conn, key) when is_binary(key) do
46
    user = github_user(key)
47
    grant_operator(user)
48
    Plug.Test.init_test_session(conn, %{"user_id" => user.id})
49
  end
50
51
  def grant_operator(%OpenAgents.Accounts.User{github_id: github_id}) do
52
    original = Application.get_env(:openagents, :admin_github_ids, [])
53
    Application.put_env(:openagents, :admin_github_ids, [github_id | original])
54
55
    ExUnit.Callbacks.on_exit(fn ->
56
      Application.put_env(:openagents, :admin_github_ids, original)
57
    end)
58
59
    :ok
60
  end
61
62
  def revoke_operator do
63
    Application.put_env(:openagents, :admin_github_ids, [])
64
    :ok
65
  end
66
end
test/support/sarah_data_case.ex deleted -23

@@ -1,23 +0,0 @@

1
defmodule OpenAgents.SarahDataCase do
2
  @moduledoc """
3
  Sarah-specific data case used by the lifted Sarah test suite.
4
  """
5
6
  use ExUnit.CaseTemplate
7
8
  using do
9
    quote do
10
      alias OpenAgents.Repo
11
12
      import Ecto
13
      import Ecto.Changeset
14
      import Ecto.Query
15
      import OpenAgents.DataCase
16
    end
17
  end
18
19
  setup tags do
20
    OpenAgents.DataCase.setup_sandbox(tags)
21
    :ok
22
  end
23
end
test/support/sarah_factory.ex deleted -5

@@ -1,5 +0,0 @@

1
defmodule OpenAgents.SarahFactory do
2
  @moduledoc """
3
  Stub factory used by the lifted Sarah test suite.
4
  """
5
end
test/support/sarah_fixtures.ex deleted -5

@@ -1,5 +0,0 @@

1
defmodule OpenAgents.SarahFixtures do
2
  @moduledoc """
3
  Stub fixtures used by the lifted Sarah test suite.
4
  """
5
end
test/support/tools/test_recall.ex modified +1 -1

@@ -36,7 +36,7 @@ defmodule OpenAgents.Tools.TestRecall do

36 36
      required_authority: "conversation.read",
37 37
      executor: %{id: "sarah.local.recall", disclosure: "Sarah local conversation recall"},
38 38
      maintainer: "OpenAgents",
39
      attribution: ["OpenAgentsInc/sarah"],
39
      attribution: ["OpenAgentsInc/openagents.com"],
40 40
      policy_facets: %{"privacy" => "browser_scoped"},
41 41
      module_metadata:
42 42
        Metadata.first_party("conversation.read", "browser_conversation",

This page updates live while a promote is in flight · changelog