CatalyzeUp
sondely/migration

Framework Copy Manifest

Every file Sondely takes from TourCockpit (Ideaplaces/ideaplaces-tour-platform), what happens to it, which of its imports are cut, in which phase it is copied and where it lands in Sondely. After the copy the two products diverge; a TourCockpit fix reaches Sondely only when someone carries it across on purpose, by the procedure below.

As built on 7 November 2026. The copy lives in its own repository, CatalyzeUp/sondely-platform, whose root is the Next.js app, rather than a server/ folder in CatalyzeUp/sondely; read every server/ path on this page as the repository root. The base is TourCockpit commit 74e9a2a53 (74e9a2a53956731c46f707790e9ffae96fb9fafa, 7 November 2026, the head of develop that day), 230 commits after the f952d0eea this page was read at. framework-base.json at the repository root records the base and the copied files (on 8 November 2026: 84 copied and 19 written for Sondely, while about 120 files carry a copy header, the assistant and MCP files among them); it, not these tables, is meant to be the record of what was copied.

The imports in every table were read from the files at f952d0eea (git show f952d0eea:<path>), not inferred from names. The verdicts stand; line counts and imports may differ at 74e9a2a53. The plan this manifest serves is the migration overview; how the assistant pieces are rewritten for surveys is in Agent Layer.

How to read the tables

Verdict Meaning
Copy as is Copied byte for byte apart from the header and the global renames below. It has no TourCockpit domain imports, or only imports that are themselves copied
Copy and strip The mechanism is copied, the tour content is removed or rewritten. The "Cut" column names the imports and the content that go
Later phase Useful to Sondely, but not until a capability exists that it serves. Not copied now; the phase or condition is named
Leave behind Tour domain, or a TourCockpit feature Sondely has no counterpart for. Never copied. Any import of it from a copied file is cut

Target: same means the same path at the root of sondely-platform (src/lib/api/idempotency.ts lands at src/lib/api/idempotency.ts). The tables write it with the server/ prefix of the first plan. A different name is a rename, recorded in the file's header so the source is never lost. Keeping paths identical is deliberate: it is what makes a later port a plain diff.

Phases are the ones in the plan:

Code Phase What it needs from this manifest
P2 Scaffold and infrastructure The API plumbing, tenant layer, auth core, logger, test harness and CI. Serves no traffic
P3 Parity Sign-in doors, sends, the write handler list as route groups are ported
P4 Cutover End to end and deploy workflows, the migration rehearsal
P4b Screens into Next Component and usability harness, UI parity scans, cookie session
P5 API an agent can read Asked marks, batch, read-back, refusal classes and their tests
P6 MCP MCP server, OAuth, the write path (propose, dry run, commit)
P7 In-app chat The assistant turn, drawer events, nightly matrix
P8a Respondents by WhatsApp text (first half of the plan's phase 8) The WhatsApp door, voice note transcription
P8b Respondents by voice (second half of phase 8) Call transport, recording, call simulation

Global renames applied to every copied file

TourCockpit Sondely Why
Company, companyId, company in identifiers Organization, organizationId, organization Organizations are Sondely's tenant (Data Model)
UserCompany OrganizationMember Users are global and belong to organizations through memberships
UserRole ADMIN, MANAGER, OPERATOR MemberRole: Sondely's roles (owner, admin, respondent) Roles are decided in Auth
Faro (persona name) Sona, held in src/lib/assistant/config.ts (ASSISTANT_NAME) TourCockpit already keeps the persona in one file (Digby's rename rule, in the file's own comment). Named from Chip's goal of 7 November 2026
Postgres GUC app.tenant_id Unchanged It is a name inside migrations and db.ts, with no product meaning; keeping it keeps the RLS policy text (98 uses of current_setting('app.tenant_id') at the base) portable
Cookie tp_session sondely_session, set in auth/config.ts SESSION_COOKIE is the one place it is defined

The renames are mechanical and applied in the strip commit, never in the copy commit (see the procedure), so git diff <copy commit> -- <file> always shows exactly what Sondely changed.

The copy procedure

1. Record the base once

A single file, framework-base.json at the repository root, is the record of where the framework came from. As built (abridged):

{
  "source": "https://github.com/Ideaplaces/ideaplaces-tour-platform",
  "commit": "74e9a2a53956731c46f707790e9ffae96fb9fafa",
  "copiedOn": "2026-11-07",
  "renames": { "Company": "Organization", "companyId": "organizationId", "UserCompany": "OrganizationMember", "UserRole": "MemberRole", "tp_session": "sondely_session" },
  "files": {
    "src/lib/api/idempotency.ts": { "from": "src/lib/api/idempotency.ts", "verdict": "copy-and-strip", "ported": [] },
    "src/lib/api/dispatch.ts": { "written": "sondely" }
  }
}

files holds one entry per copied file, written by the copy step, and marks the files written for Sondely with "written": "sondely". It is the machine-readable twin of this page.

2. Copy from the commit object, never from a working tree

TP=/path/to/ideaplaces-tour-platform
BASE=74e9a2a53956731c46f707790e9ffae96fb9fafa
git -C "$TP" show "$BASE:src/lib/api/idempotency.ts" > src/lib/api/idempotency.ts

git show <commit>:<path> reads the file as it was at the base, whatever branch or local change the TourCockpit checkout carries. A copy from a working tree would silently take someone's uncommitted edit.

3. Header on every copied file

The first lines of every copied file, before any import:

/**
 * @framework-source ideaplaces-tour-platform src/lib/api/errors.ts @ 74e9a2a53956731c46f707790e9ffae96fb9fafa
 * @framework-verdict copy-and-strip: the tour error codes and their messages removed; the envelope and the classing kept
 * @framework-ported (none)
 */
Tag Content
@framework-source Repository, source path at the base, commit (the full sha, as built)
@framework-verdict copy-as-is or copy-and-strip: followed by one sentence naming what was cut
@framework-ported (none), or one line per fix carried across later: TourCockpit sha, date, one sentence

JSON files cannot hold a comment: their header lives in framework-base.json only. A YAML workflow or a Markdown agent file takes the same three tags as # or HTML comment lines.

A unit test, tests/unit/framework-headers.test.ts, reads framework-base.json and fails when a listed file lacks the header, when the header's source path disagrees with the JSON, or when a file under src/lib/{api,tenant,auth,mcp,assistant} carries no header and is not marked "written": "sondely" in the JSON. It does not need TourCockpit at test time. On 8 November 2026 the test walks src/lib/{api,tenant,auth} only, and the files copied for Sona (src/lib/assistant, src/lib/mcp) carry their headers but have no entry in framework-base.json: a gap between this rule and the repository, to close.

4. Two commits per group: copy, then strip

  1. Copy commit: the files exactly as at the base, plus the header. It does not compile, and that is expected; it never lands alone on develop (the two commits travel in one pull request).
  2. Strip commit: the global renames, the cuts named in the tables, the tour content removed. This commit is the Sondely divergence and the one a reviewer reads.

Groups follow the tables below (API, tenant, auth, logger, then the harness, then CI), so each pull request stays one concern.

5. Carrying a TourCockpit fix across later

The two products diverge, and that is the design: nothing syncs automatically. A fix is ported when someone decides it matters for Sondely.

  1. Find what changed upstream for a copied file:
    git -C "$TP" log --oneline 74e9a2a53..origin/develop -- src/lib/api/idempotency.ts
    
  2. Read the fix as a diff against the base, not against Sondely: git -C "$TP" show <fix sha> -- <path>.
  3. Reproduce it first: a failing test in Sondely that shows the defect, per the engineering standard. If the defect does not exist in Sondely (the strip removed the code it lived in), stop: nothing to port.
  4. Apply by hand or with git apply on the Sondely file. Because the path and the copy commit are kept, git diff <copy commit> -- <file> shows what Sondely changed, and the upstream hunk usually lands cleanly next to it.
  5. Append to the header and the JSON: @framework-ported <sha> 2026-12-01 idempotency: a replay answers the stored content type, and the same line in files[...].ported.
  6. Never move the base. commit in framework-base.json stays 74e9a2a53 for ever; a port is recorded per file. Moving the base would claim every file matches a newer TourCockpit, which is false.

A drift report (a script listing, for every entry in framework-base.json, the upstream commits since the base and whether each is already in ported) is a reasonable later convenience. It is inferred, not built.

src/lib/api (32 files)

File Lines Verdict Phase TourCockpit imports and content to cut Target
asked.ts 114 Copy as is P5 None (imports zod only) same
auth.ts 259 Copy and strip P2 tourOperatorId operator scope on the bound context; role names via @/lib/auth/roles; @/lib/tenant/read-only member check reduced to the hook same
batch-match.ts 73 Copy as is P2 None. Needed early: strict-request.ts and read-back.ts import it same
batch-refs.ts 73 Copy as is P5 None same
batch.ts 85 Copy as is P5 @/lib/validation batch schema, rewritten in Sondely's validation module same
came-by.ts 8 Leave behind @/lib/reference/came-by: how a booking reached the office
commission.ts 15 Leave behind @/lib/auth/commission-visibility
delivery.ts 258 Copy and strip P3 @/lib/board/send-doors (DoorRefusal type, redefined locally), @/lib/board/send-intent (SENDING_STALE_MS, copied as a constant). Kept: a dry run sends nothing, the Idempotency-Key is the send's record of intent. Serves survey invitations and reminders same
door.ts 42 Leave behind @/lib/reference/came-by. Its import from request-transaction.ts is cut
errors.ts 189 Copy and strip P2 @/lib/services/errors (tour ServiceErrorCode list) and @/lib/i18n/service-errors (their messages). Kept: { "error": { code, message, details } } and the Zod flattening same
examples.ts 989 Copy and strip P5 No imports; every example is a tour route. The types and readers stay, the content is written for survey routes same
guide-pay-door.ts 19 Leave behind @/lib/auth/guide-pay-visibility, guide pay private feature
idempotency.ts 243 Copy as is P2 None (imports ./errors, @/lib/db) same
invitation-read-back.ts 16 Copy and strip P5 @/lib/people/invitation-answer; catalog keys rewritten for organization invitations same
mail-api.ts 44 Leave behind @/lib/services/mail, @/lib/private-features (operator mail)
names.ts 62 Copy and strip P5 @/lib/reference/came-by; one reader per tour kind replaced by survey, question group, question, member same
no-route.ts 32 Copy and strip P2 @/lib/seo/public-routes (the public site list) same
openapi.ts 1605 Copy and strip P2 @/lib/private-features; every tour registry entry and its @/lib/validation schemas. Kept: RouteDef, the registry mechanics, RBAC per route, the document builder same
pagination.ts 38 Copy as is P2 None (the page schema it reads from @/lib/validation is copied with it) same
read-back.ts 468 Copy and strip P5 @/lib/assistant/endpoint-map (read through an interface until P6), @/lib/i18n/recipient, the per kind wording for tours same
refs.ts 176 Copy and strip P2 RefKind (tour, departure, booking, hotel, zone, vehicle, driver, operator, language, childSeat, rep, cameBy) and REF_KINDS rewritten for survey kinds same
request-transaction.ts 328 Copy and strip P2 @/lib/outbox/drain (after-commit sends; returns if Sondely adopts the outbox, see Business Logic), ./door. Kept: one serializable transaction per write, the tenant GUC, the audit binding, ?dryRun=true same
rest-description.ts 31 Copy as is P2 None same
standalone-writes.ts 44 Copy and strip P3 The board send routes in the set; Sondely's send and export routes take their place same
strict-request.ts 62 Copy as is P2 None same
strict-schema.ts 223 Copy as is P6 None. Only the model's write tools read it same
tower-door.ts 20 Leave behind @/lib/services/whatsapp-tower (office WhatsApp inbox)
tower-json.ts 25 Leave behind @/lib/whatsapp/tower-write
ui-parity.ts 298 Copy and strip P4b No imports; the tour screen to route table is rewritten as screens move same
ui-read-parity.ts 243 Copy and strip P4b @/lib/assistant/screens; tour screen list same
unknown-fields.ts 303 Copy as is P2 None same
write-handlers.ts 199 Copy and strip P5 All 90 @/app/api/v1/... tour route imports. The file becomes the list of Sondely's write routes same

Summary: 10 copy as is, 15 copy and strip, 7 leave behind.

src/lib/tenant (7 files)

File Lines Verdict Phase TourCockpit imports and content to cut Target
audit-extension.ts 168 Copy as is P2 None same
audit-payload.ts 184 Copy and strip P2 The captured model list. @/lib/audit/diff is copied with it (93 lines, no imports) same
context.ts 66 Copy and strip P2 Rename only: companyId to organizationId, the role type same
extension.ts 183 Copy and strip P2 TENANT_MODELS: the closed list of tenant models, rewritten for Sondely's tables. User is tenant owned in TourCockpit and global in Sondely, so it is not on the list same
read-only-send.ts 87 Leave behind Phone number matching for read-only members' sends
read-only.ts 182 Copy and strip P2 Read-only memberships have no Sondely counterpart. The memberMayWrite hook the extension calls is kept and answers yes; inferred use: the cutover write freeze (Cutover Runbook) same
request-scope.ts 91 Copy as is P2 None same

src/lib/logger.ts

File Lines Verdict Phase Cut Target
logger.ts 53 Copy as is P2 None (no imports). One JSON object per line on stdout, which Azure Container Apps hands to Log Analytics and the C3 log monitor reads by level same

src/lib/auth (34 files)

File Lines Verdict Phase TourCockpit imports and content to cut Target
brand-host.ts 133 Later phase P4b White label by <slug>.tourcockpit.com. Becomes custom domains per organization once screens are in Next (an open item in the plan) same
card-token.ts 44 Leave behind Invitation card render tokens
commission-visibility.ts 34 Leave behind Commission gate
company.ts 149 Copy and strip P2 @/lib/company/kind; the session's active company becomes the active organization server/src/lib/auth/organization.ts
config.ts 65 Copy and strip P2 tp_session and TourCockpit's window constants; the secrets' names same
email.tsx 754 Leave behind 15 @/emails/* templates, @/lib/activation/*, @/lib/follow-through/registry, @/lib/help/*, @/lib/onboarding/*, @/lib/services/activation-seats. Sondely's emails are ported from Python (Business Logic)
google-invite.ts 33 Copy and strip P3 Invitation lookup through magic-link.ts kinds same
google-return.ts 17 Copy as is P3 None same
google.ts 142 Copy as is P3 None same
guide-pay-visibility.ts 32 Leave behind @/lib/private-features, guide pay
history-visibility.ts 24 Leave behind @/lib/audit/visibility, commission and guide pay gates
index.ts 666 Copy and strip P2 @/lib/cross-company/fan-out, @/lib/mail/pilot, @/lib/services/onboarding, @/lib/services/view-as, @/lib/staff, @/lib/whatsapp/tower-pilot, ./view-as. Kept: getCurrentUser, runAsUser, AuthUser (without tourOperatorId) same
invite-reminders.ts 53 Leave behind TourCockpit's reminder cadence. Sondely keeps its own cadence until a product decision changes it
joined.ts 33 Leave behind First-join stamp with no Sondely counterpart
magic-link.ts 320 Copy and strip P3 ./email, @/lib/email/send, @/lib/people/contact, @/lib/people/greeting-name, @/lib/i18n/recipient. TokenKind gains the one-time sign-in code; the storage and single use stay same
next-path.ts 66 Copy and strip P4b The operator-request path; Sondely's landing paths same
operator-invite-limit.ts 55 Leave behind Operator invites
operator-request-token.ts 115 Leave behind Operator request links
password-policy.ts 205 Copy as is P2 None. Applies to passwords set after cutover only same
password.ts 116 Copy as is P2 None. A bcrypt verify for hashes migrated from Python is written new beside it and replaced by argon2 on first success same
profile-locale.ts 56 Copy and strip P4b @/lib/i18n/latest-choice; locale cookie names same
rate-limit.ts 30 Copy as is P2 None. In memory, per replica: Sondely runs more than one replica (the plan's reminders note), so a shared store replaces it before public endpoints rely on it (inferred) same
read-only-switch.ts 38 Leave behind Read-only members
report-token.ts 46 Leave behind Weekly WhatsApp report links
reserved-domains.ts 30 Copy as is P3 None same
roles.ts 91 Copy and strip P2 ADMIN, MANAGER, OPERATOR predicates rewritten for Sondely's roles; the "one place" rule kept same
session-cookie.ts 107 Copy and strip P4b ./brand-host until custom domains. Cutover keeps the bearer contract; the cookie arrives with the screens (Auth) same
session.ts 265 Copy and strip P2 @/lib/people/contact, tourOperatorId on the membership. activeCompanyId becomes activeOrganizationId same
share-token.ts 45 Leave behind Board share tokens
sign-in.ts 138 Copy and strip P3 ./company renamed; the LOGIN audit row and the one-function-for-every-door rule kept same
signed-payload.ts 94 Copy as is P2 None same
switch-company.ts 50 Copy and strip P4b ./brand-host; switches organization server/src/lib/auth/switch-organization.ts
tokens.ts 11 Copy as is P2 None same
view-as.ts 22 Leave behind Manager view as operator

Summary: 8 copy as is, 12 copy and strip, 1 later phase, 13 leave behind.

src/lib/mcp (12 files) and the routes that serve it

All MCP files are copied in P6. What each one becomes is in Agent Layer.

File Lines Verdict Phase TourCockpit imports and content to cut Target
deps.ts 427 Copy and strip P6 @/lib/board/board-screenshot, @/lib/changelog/reader, @/lib/help/registry, @/lib/kpi, @/lib/assistant/changelog-search, @/lib/assistant/help-corpus, @/lib/assistant/transcribe (until P8a). Kept: the API over loopback as the caller same
identity.ts 152 Copy and strip P6 @/lib/assistant/open-features (private features) same
instructions.ts 147 Copy and strip P6 @/lib/kpi/definitions, @/lib/assistant/capabilities; rules come from the rewritten assistant/rules.ts same
oauth/clients.ts 158 Copy as is P6 None same
oauth/metadata.ts 58 Copy as is P6 None same
oauth/request.ts 85 Copy as is P6 None same
oauth/seal.ts 64 Copy as is P6 None same
server.ts 720 Copy and strip P6 @/lib/services/daily-board, @/lib/kpi/dimensions, @/lib/kpi/window, ./ui/board-view, the propose_change replacement notice same
tools.ts 510 Copy and strip P6 @/lib/board/file-ask, @/lib/help/registry, @/lib/kpi/question, @/lib/assistant/changelog-search, @/lib/assistant/deliver-file, @/lib/assistant/help-* same
ui/board-view.ts and ui/board.generated.html 3037 Leave behind The daily board as an MCP Apps component
ui/views.ts 93 Later phase after P7 No imports. An interactive results view in Claude or ChatGPT is a later product choice same

Route files outside src/lib that come with P6: src/app/mcp/route.ts (cut @/lib/assistant/asker-on-crew), src/app/oauth/{authorize,register,revoke,token}/route.ts, src/app/oauth/consent/{page.tsx,actions.ts}, src/app/.well-known/oauth-authorization-server/[[...issuer]]/route.ts, src/app/.well-known/oauth-protected-resource/[[...resource]]/route.ts. Same target paths under server/.

src/lib/assistant (115 files)

Phases P6 to P8b; the survey rewrite of each piece is described in Agent Layer. Files renamed because they carry the persona or the word company are marked in Target.

The write path and its tools

File Lines Verdict Phase TourCockpit imports and content to cut Target
actions.ts 1208 Copy and strip P6 @/components/profile/notification-copy, @/lib/board/booking-link, @/lib/board/language-coverage, @/lib/board/recipients, @/lib/private-features, @/lib/whatsapp/tower-access, @/lib/whatsapp/tower-visibility. Kept: validateWrite same
call-api.ts 651 Copy and strip P6 @/lib/company/clock (the organization's time zone instead). Kept: GET only, registered routes only, RBAC per route same
capabilities/types.ts 68 Copy as is P6 None (write-tools.ts imports it) same
capabilities/index.ts 21 Copy and strip P7 The tour catalogue; survey capabilities are written new same
capabilities/find-departures.ts 289 Leave behind @/lib/board/guest-fit, @/lib/names/tour-words
capabilities/pickup-place.ts 127 Leave behind @/lib/pickup/answer
capabilities/price-for.ts 158 Leave behind @/lib/services/prices
capabilities/resolve.ts 105 Leave behind Tour, hotel and zone resolution
capabilities/who-changed.ts 145 Later phase after P7 @/lib/audit/relations, @/lib/board/booking-name. "Who changed this survey" over the Audit table same
endpoint-map.ts 373 Copy and strip P6 @/lib/private-features; reads @/lib/api/examples content same
look-ahead.ts 572 Copy and strip P6 @/lib/names/language-words, @/lib/names/tour-words, @/lib/pickup/answer, @/lib/rules. Kept: names matched to ids for ref fields. @/lib/names/match (350 lines, no imports) is copied as is with it same
named-ids.ts 149 Copy and strip P6 Tour id fields (driverId, tourOperatorId) same
plan-readings.ts 96 Leave behind @/lib/money, @/lib/company/opening-hours, @/lib/pay/rate-words. Its import from proposal-actions.ts is cut
proposal-actions.ts 1158 Copy and strip P6 ./change-confirmation, ./plan-readings, ./rep-row, @/lib/money, @/lib/i18n/service-errors (tour messages). Kept: propose, dry run, commit once with an idempotency key same
proposal-card.ts 147 Copy and strip P6 @/lib/board/rep-choice, @/lib/reference/child-seat, @/lib/services/daily-board. The card shape is rewritten for a survey draft same
proposal-form.ts 90 Later phase after P4b @/lib/board/booking-form, @/lib/services/{child-seats,pickup-time-options,reference,reps,schedule}. Edit on the card opens the builder once the builder is in Next same
refused-before.ts 78 Copy as is P6 None same
rep-row.ts 24 Leave behind @/lib/rules/rep
write-tool-names.ts 99 Copy and strip P6 The tool name list (create_booking, add_departure and the rest) same
write-tools.ts 634 Copy and strip P6 Tour wording in tool descriptions; the generator from the registry is kept unchanged same
writes-together.ts 144 Copy as is P7 None same
written.ts 57 Copy as is P6 None same

Refusals

File Lines Verdict Phase TourCockpit imports and content to cut Target
refusal-legibility.ts 268 Copy and strip P5 @/lib/whatsapp/note-cap-words; every row of LEGIBILITY_BY_CODE, LEGIBILITY_BY_INVALID_REASON, LEGIBILITY_BY_DOOR_CODE rewritten for Sondely codes. Kept: the four classes and the typing that refuses an unclassed code same
refusal-fixes.ts 151 Copy and strip P5 Tour fixes (the smallest free vehicle and the rest) same
write-refusal.ts 37 Copy and strip P7 @/lib/i18n/common-words, @/lib/validation same
answer-guards.ts 252 Copy and strip P7 The tour assertions it checks (guides, departures); the guard mechanism kept same
promise-guard.ts 54 Copy as is P7 None same

The turn, the prompt and the drawer

File Lines Verdict Phase TourCockpit imports and content to cut Target
access.ts 47 Copy as is P7 None same
agent.ts 2022 Copy and strip P7 @/lib/board/file-ask, @/lib/help/registry, @/lib/kpi/{dimensions,question,window}, @/lib/services/company, ./changelog-search, ./find-change, ./help-*, ./tool-set-store, ./capabilities tour entries, ./asker-on-crew. The largest strip in the copy same
audience.ts 16 Copy and strip P6 @/lib/help/registry. Office or desk becomes builder or respondent same
azure.ts 76 Copy as is P7 None same
changes-made.ts 123 Copy as is P7 None same
config.ts 135 Copy and strip P6 @/lib/company-features, @/lib/staff; the persona name same
conversation-kind.ts 115 Copy as is P7 None same
conversation-since.ts 21 Copy as is P7 None same
delivered.ts 187 Copy as is P7 None. Who a send reached, line per person: fits invitation sends same
door.ts 88 Copy as is P7 None same
earlier-reads.ts 183 Copy as is P7 None same
events.ts 105 Copy as is P7 None same
faro-rules.ts 473 Copy and strip P6 @/lib/help/registry; every rule text rewritten (the structure, one export per principle, is kept) server/src/lib/assistant/rules.ts
faro-view.ts 12 Copy and strip P7 View names server/src/lib/assistant/history-view.ts
language.ts 34 Copy as is P7 None same
memory.ts 92 Copy as is P7 None same
model-calls.ts 181 Copy as is P7 None same
names-in-query.ts 70 Copy and strip P7 Tour filters (vehicleId, tourOperatorId) same
profile.ts 178 Copy as is P7 None same
prompt.ts 299 Copy and strip P7 Tour sections of the prompt same
rate-limit.ts 28 Copy as is P7 None same
retold.ts 78 Copy as is P7 None (turn.ts and the assistant service import it) same
role-scope.ts 67 Copy and strip P7 ./tool-set-store (until it is needed), ./capabilities same
screens.ts 82 Copy and strip P7 @/lib/i18n/nav-label; the screen list grows as screens move same
service-tier.ts 18 Copy as is P7 None same
starters.ts 146 Copy and strip P7 Tour starter questions same
step-watch.ts 54 Copy as is P7 None same
stub.ts 176 Copy and strip P7 Tour fixtures. The no-model mode every CI test runs on same
tool-bytes.ts 40 Copy as is P7 None same
transcript.ts 303 Copy as is P7 None same
turn-context.ts 77 Copy as is P7 None same
turn-trace.ts 206 Copy as is P7 None same
turn.ts 597 Copy and strip P7 @/lib/board/board-screenshot, @/lib/changelog/reader, @/lib/company/clock, ./call-company, ./change-confirmation, ./changelog-search, ./deliver-file, ./open-features same
where-now.ts 143 Copy as is P7 None same
widgets.ts 121 Copy and strip P7 Board widgets; results widgets take their place same
working-notes.ts 119 Copy as is P7 None same

Organizations (TourCockpit's companies)

File Lines Verdict Phase TourCockpit imports and content to cut Target
company-binding.ts 207 Copy and strip P6 Sandbox naming. One connection, every organization, one at a time server/src/lib/assistant/org-binding.ts
company-list.ts 104 Copy and strip P6 Rename only server/src/lib/assistant/org-list.ts
company-names.ts 202 Copy and strip P7 The tour names fed to the voice model's hearing server/src/lib/assistant/org-names.ts
company-switch.ts 175 Copy and strip P7 @/lib/auth/switch-company (renamed) server/src/lib/assistant/org-switch.ts
asker-on-crew.ts 41 Leave behind @/lib/whatsapp/number-key, the crew roster
open-features.ts 32 Leave behind @/lib/private-features, @/lib/whatsapp/tower-*, @/lib/auth/guide-pay-visibility. Cut from mcp/identity.ts and turn.ts

Help, changelog, tool index, mail

File Lines Verdict Phase Reason Target
help-chunks.ts, help-corpus.ts, help-index.ts, help-index.generated.json 327 Later phase when Sondely has a help center @/lib/help/* content same
changelog-search.ts 128 Leave behind @/lib/changelog/*; Sondely has no What's new
find-change.ts, tool-index.ts, tool-index.generated.json, tool-set-store.ts 682 Later phase when the write tools outgrow the prompt budget TourCockpit has 87 write tools and picks a subset per turn; Sondely's set is small enough to send whole (inferred from the route count in API Routes) same
deliver-file.ts 106 Later phase with exports POST /api/daily-board/send-to-me; becomes the CSV or PDF export to the person same
mail/act.ts, mail/agent.ts, mail/prompt.ts, mail/reader.ts, mail/record.ts 756 Leave behind Operator booking mail. The pattern (a model with no tools extracts, code acts) may suit importing a survey from a document; inferred
change-confirmation.ts 101 Leave behind @/lib/company/recipient-guard, @/lib/services/whatsapp, @/lib/whatsapp/senders: the office's written confirmation of a change made on a call
whatsapp-pass.ts 100 Leave behind @/lib/whatsapp/needs-office, @/lib/whatsapp/office-promise: a desk handing over to the office

WhatsApp and voice

File Lines Verdict Phase TourCockpit imports and content to cut Target
whatsapp.ts 846 Copy and strip P8a ./whatsapp-pass, @/lib/whatsapp/{door-closed,faro-intro}, @/lib/company/recipient-guard, @/lib/company-features, @/lib/services/whatsapp-notes, @/lib/board/board-screenshot. Kept: number as credential, one conversation per channel, unknown number gets a log line and no reply. @/lib/whatsapp/{wire,senders,number-key} and @/lib/services/{whatsapp,whatsapp-media} come with it, stripped of the office inbox same
call-limits.ts 78 Copy as is P8a None same
transcribe.ts 192 Copy as is P8a None. Respondents answer by voice note same
whatsapp-call.ts 670 Copy and strip P8b @/lib/kpi, @/lib/board/board-screenshot, ./company-switch, @/lib/auth/brand-host same
voice.ts 410 Copy and strip P8b The call instructions; ask_faro becomes the survey ask tool same
voice-live.ts 232 Copy as is P8b None same
voice-ask-client.ts 51 Copy and strip P8b @/lib/board/board-screenshot same
voice-character.ts 34 Copy and strip P8b Faro's character text same
call-controller.ts 776 Copy and strip P8b The greeting and nudge wording it imports; the transport-free rules kept same
call-delivery.ts 197 Copy and strip P8b Board list reading rules; question reading rules instead same
call-nudge.ts 62 Copy and strip P8b Nudge words (catalog keys) same
opening.ts 128 Copy and strip P8b The office greeting same
call-audio.ts 90 Copy as is P8b None same
call-audio-response.ts 67 Copy as is P8b None (@/lib/http/range copied with it) same
call-id.ts 14 Copy as is P8b None same
call-mix.ts 205 Copy as is P8b None same
call-recorder.ts 108 Copy as is P8b None same
call-relay.ts 453 Copy as is P8b None same
live-calls.ts 86 Copy as is P8b None same
recording-check.ts 162 Copy as is P8b None same
recording-sound.ts 73 Copy as is P8b None same
replay.ts 215 Copy as is P8b None same
reply-recovery.ts 248 Copy as is P8b None same
said-since-ask.ts 83 Copy as is P8b None same
until-quiet.ts 112 Copy as is P8b None same
web-call.ts 201 Later phase after P8b A call from the survey page; WhatsApp calls come first same
call-audio-split.ts 83 Leave behind A call that changes company mid-call
call-company.ts 109 Leave behind Same
call-part-intervals.ts 93 Leave behind Same

Summary for the assistant directory, counted by file including the two generated JSON indexes: 44 copy as is, 40 copy and strip, 12 later phase, 19 leave behind. By lines, 19,100 of its 22,900 lines are copied in some form (5,300 as is, 13,800 copied and stripped), 2,200 are left behind and 1,550 wait for a later phase. The plan's estimate that about a third of the assistant code is reusable is about what survives: the copy and strip files keep their mechanism and lose their tour wording, and how many of their 13,800 lines remain is known only after the strip.

Services and support files outside the listed directories

The copied directories import these. Each comes with the phase of its first importer.

File Lines Verdict Phase Cut Target
src/lib/db.ts 446 Copy and strip P2 None beyond renames. Two clients: db on the constrained app role with the tenant extension and RLS, dbAdmin on the owner role for pre-auth reads and seeding. Holds the write_gate lock same
src/lib/prisma.ts 3 Copy as is P2 None same
src/lib/perf/prisma-timing.ts, request-timing.ts 160 Copy as is P2 None same
src/lib/audit/diff.ts 93 Copy as is P2 None same
src/lib/services/errors.ts 514 Copy and strip P2 @/lib/board/slots; the 65 tour codes of ServiceErrorParamMap. The err() helper, ServiceError and ServiceErrorParamMap typing kept same
src/lib/i18n/service-errors.ts 511 Copy and strip P2 Tour messages; see Translation same
src/lib/validation/index.ts 2014 Leave behind, except the pieces copied P2 Tour schemas. The page schema, the batch schema and the shared helpers are written into Sondely's validation module
src/lib/names/match.ts 350 Copy as is P6 None same
src/lib/http/range.ts 24 Copy as is P8b None same
src/lib/services/assistant.ts, assistant-proposals.ts, assistant-memory.ts, assistant-profile.ts 1269 Copy and strip P6 and P7 ./company in the profile service same
src/lib/services/assistant-recordings.ts 253 Copy as is P8b None same
src/lib/services/mcp-connections.ts 606 Copy and strip P6 @/lib/company-features, @/lib/private-features, @/lib/people/contact same
src/lib/errorReporter.ts, src/lib/deployment.ts 409 Copy as is P4b None. The browser error forwarder, with the screens same

Prisma models that come along

From prisma/schema.prisma at the base. Every model with a company becomes organization scoped and joins TENANT_MODELS, so the tenant isolation and coverage tests cover it. Sondely's own models are in Data Model.

TourCockpit model Sondely model Phase Change
IdempotencyKey IdempotencyKey P2 companyId to organizationId; unique on (organization, key) kept
Audit and enum AuditSource Audit P2 role takes Sondely's role enum; actingAsOperatorId cut; actorKind, apiKeyId, requestId, revertsRequestId kept
WriteGate WriteGate P2 Unchanged: an empty table the serializable writes lock
Session Session P2 activeCompanyId to activeOrganizationId. Replaces the Redis sessions
MagicLinkToken and enum TokenKind VerificationToken P3 Kinds: set password, reset, plus the one-time sign-in code and email verification. Hash only, single use, expiry per kind kept
ApiKey ApiKey P2 Bound to one organization; role becomes a scope (read, draft, full); tourOperatorId cut
UserCompany OrganizationMember P2 tourOperatorId, boardExportPrefs, readOnly, sellsDirect cut
OutboundSend Decided in Business Logic P3 Only if invitations and reminders move to an outbox
McpGrant, McpConnection Same names P6 Company to organization
AssistantConversation, AssistantMessage, AssistantProposal Same names P6 channel on the conversation gains the respondent channels; costUsd on the message is filled from the first day
AssistantCallRecording, AssistantLiveCall Same names P8b Company to organization

Row-level security comes with the models: 45 TourCockpit migrations enable RLS, with policies on current_setting('app.tenant_id'). Sondely's first migration writes the same policy for every organization-scoped table rather than copying the 45 migrations, whose tables are tour tables.

Test harness

File Lines Verdict Phase Cut Target
vitest.config.ts 32 Copy as is P2 None server/vitest.config.ts
vitest.integration.config.ts 102 Copy and strip P2 The real-time-first sequencer for call files stays dormant until P8b; test database names server/ same
vitest.component.config.ts 35 Copy as is P4b None server/ same
vitest.coverage.ts 42 Copy and strip P2 The help and page content excludes server/ same
coverage-floors.json, scripts/coverage-merge.ts, scripts/coverage-changed.ts 440 Copy and strip P2 Layer paths; the floors are Sondely's call (TourCockpit: 85 for the repository) server/ same
playwright.config.ts 242 Copy and strip P4 _fake-google, Faro desk people, port 7961 (Sondely takes its own from the port registry) server/ same
playwright.call-sim.config.ts 56 Copy and strip P8b Ports 7975 and 7976 server/ same
.eslintrc.json 107 Copy and strip P2 The react/jsx-no-literals allowed strings (GUIA, CHOFER, PAX and the rest) server/ same
tsconfig.json, tsconfig.scripts.json 38 Copy as is P2 None server/ same
tests/stubs/server-only.ts 3 Copy as is P2 None same
tests/component/setup.ts, tests/component/server-only-stub.ts 102 Copy as is P4b None same
tests/integration/global-setup.ts 127 Copy and strip P2 tourplatform_test template name same
tests/integration/per-worker-db.ts 61 Copy and strip P2 Database names same
tests/integration/setup.ts 41 Copy and strip P2 The truncate list same
tests/integration/_parts.ts 24 Copy and strip P2 The calls part until P8b same
tests/integration/helpers.ts 364 Copy and strip P2 @/lib/reference/kalitravel, @/lib/notifications/register; TestTenant becomes an organization with an owner, an admin and a respondent same
tests/integration/api-helpers.ts 84 Copy and strip P2 Operator keys; mints org-bound scoped keys same
tests/integration/audit-helpers.ts 31 Copy as is P2 None same
tests/integration/tenant-isolation.test.ts 91 Copy and strip P2 @/lib/services/{bookings,daily-board,users}. Rewritten: a member of organization A cannot read, edit, publish or delete a known survey, session or response id of B, and gets not found same
tests/integration/tenant-coverage.test.ts 130 Copy as is P2 None. Fails when a model with organizationId is missing from TENANT_MODELS same
tests/integration/audit-isolation.test.ts 133 Copy and strip P2 @/lib/services/bookings same
tests/integration/dual-membership-isolation.test.ts 240 Copy and strip P2 One user in two organizations, which is Sondely's normal case same
tests/integration/global-email-uniqueness.test.ts 118 Copy and strip P3 @/lib/auth/email mock same
tests/unit/no-raw-tenant-sql.test.ts 55 Copy as is P2 The two reviewed exceptions are re-listed same
tests/unit/refusal-legibility.test.ts 198 Copy and strip P5 Tour codes in fixtures same
tests/unit/refusal-fixes.test.ts 115 Copy and strip P5 Tour fixes same
tests/unit/refusal-sites.test.ts, refusal-sites.json 170 Copy and strip P5 The site list starts empty and is regenerated same
tests/fixtures/refusal-probes.ts, service-error-samples.ts 179 Copy and strip P5 Tour samples; typed against the new code list, so they do not compile until every code is sampled same
tests/integration/refusal-probes.test.ts 48 Copy and strip P5 @/app/api/v1/{bookings,reference/drivers,tours}/route same
tests/integration/rule-matrix.test.ts 256 Copy and strip P5 Booking rules; becomes the survey validation matrix server/tests/integration/survey-matrix.test.ts
tests/unit/faro-rules-parity.test.ts 260 Copy and strip P7 @/lib/kpi, @/lib/kpi/definitions server/tests/unit/rules-parity.test.ts
tests/unit/mcp-parity.test.ts 139 Copy as is P7 None same
tests/unit/ui-api-parity.test.ts, ui-api-parity.scan.ts, ui-read-parity.test.ts, ui-read-parity.scan.ts 515 Copy and strip P4b Tour screen expectations same
tests/e2e/global-setup.ts, prepare-db.ts 110 Copy and strip P4 Operational table list same
tests/e2e/seed-e2e-users.ts 617 Copy and strip P4 src/lib/reference/kalitravel, src/lib/help/onboarding same
tests/e2e/faro-matrix.ts, assistant-matrix.spec.ts, faro-personas.ts, faro-jobs.ts, faro-legibility.ts, legibility-rubric.ts, assistant-legibility.spec.ts, tests/fixtures/legibility-calibration.ts, tests/unit/faro-matrix-grade.test.ts, tests/unit/legibility-rubric.test.ts 2791 Copy and strip P7 Booking cells, personas and jobs; the grader and the judge calibration are kept faro- becomes survey- in names
tests/call-sim/meta.ts 51 Copy as is P8a None. Signs a webhook the way Meta does, so WhatsApp text tests go through the real route same
tests/call-sim/caller.ts, fake-voice-live.ts, opus.ts, caller-wav.ts, heard-tone.ts 798 Copy as is P8b None same
tests/integration/_whatsapp-call-simulated.ts 1827 Copy and strip P8b @/app/api/v1/batch/route, @/lib/services/bookings, @/lib/whatsapp/classify, ./_tower-gate, booking scenarios same
tests/call-sim-e2e/web-call.spec.ts 214 Later phase with web-call.ts same
tests/visual/* (usability capture, contrast, rubric) 1449 Copy and strip P4b Tour routes and rubric wording same

CI workflows and scripts

Workflows live at the repository root (.github/workflows/), where GitHub reads them, and replace Sondely's current ci.yml, deploy.yml and deploy-prod.yml as each phase lands. TourCockpit runs on the IdeaPlaces nous runner pool; Sondely's jobs move to CatalyzeUp's own self-hosted runner (labels self-hosted, catalyzeup-linux, as used by catalyzeup-docs; that it can carry Postgres and Playwright jobs is inferred). Ideaplaces/discord-ci-status is used from another organization's repository, which needs the action to be readable from CatalyzeUp (inferred, check before relying on it).

File Lines Verdict Phase Cut Target
.github/workflows/ci.yml 928 Copy and strip P2, growing Jobs scope and films (help films), e2e-webkit until P4b, integration-calls until P8b. Kept from P2: static (typecheck, lint), unit, integration, integration-migrations, verify (coverage merge, changed lines, Discord). e2e from P4, component and usability from P4b. migration-check and migration-rehearsal from P4, once there is a production database to copy .github/workflows/ci.yml
.github/workflows/deploy-dev.yml, deploy.yml 469 Copy and strip P4 The red-night gate until P7; the drain job until P8b; image and app names same
.github/workflows/drain-revisions.yml 50 Copy as is P8b None. Keeps a deploy from cutting a live call same
.github/workflows/faro-matrix.yml 340 Copy and strip P7 Booking matrix; skipped on nights with no change, as TourCockpit does .github/workflows/survey-matrix.yml
.github/workflows/voice-scenarios.yml 132 Copy and strip P8b Faro scenarios same
.github/workflows/webkit-nightly.yml 110 Copy as is P4b None. Respondents are on phones, Safari included same
.github/workflows/mail-eval.yml 100 Leave behind Operator mail golden set
scripts/ci/no-ai-attribution.mjs, parallel-checks.sh, npm-install.sh Copy as is P2 None server/scripts/ci/
scripts/ci/migration-rehearsal.sh, e2e-build.ts, wait-for-e2e-build.mjs Copy and strip P4 Database and app names server/scripts/ci/
scripts/ci/faro-matrix-changed.sh, nightly-gate.sh Copy and strip P7 The paths that count as an assistant change server/scripts/ci/
scripts/ci/drain-revisions.mjs Copy as is P8b None server/scripts/ci/
scripts/ci/clip-bump-only.sh Leave behind Help clips

Repository agents

TourCockpit's agents live in .claude/agents/. Each copy is renamed, its paths and spec links pointed at Sondely and these docs, and its incident history kept only where the rule still applies.

TourCockpit agent Verdict Phase Sondely agent
tour-platform-legibility.md Copy and strip P5 sondely-legibility.md: reads every API contract change the way an outside agent meets it
tour-platform-ui.md Copy and strip P4b sondely-ui.md: desktop and phone render parity
tour-platform-components.md Copy and strip P4b sondely-components.md: each UI element built once
tour-platform-translator.md, tour-platform-translate.md Copy and strip P4b sondely-translator.md, sondely-translate.md (Translation)
tour-platform-calls.md, tour-platform-voice-architect.md Copy and strip P8b sondely-calls.md, sondely-voice-architect.md
tour-platform-help.md, tour-platform-help-judge.md, tour-platform-week.md Leave behind Sondely has no help center or What's new yet
tour-platform-insights.md, tour-platform-insights-toolsmith.md Later phase after P7 Once Sondely has insight tools over results

What this manifest does not cover

  • The translation catalogs and scripts (i18n/, scripts/i18n/): Translation.
  • The route handlers under src/app/api/v1/: none is copied. Sondely's routes are written new on the copied plumbing (API Routes).
  • UI components under src/components/: chosen screen by screen in Frontend Screens.
  • Infrastructure (Postgres server, Key Vault, container apps): Infrastructure.