Terminology inventory — the ten decisions, inspected

What each of the ten approved N1 distinctions actually means in propflowai today: file and symbol, the sense the code really carries, whether the proposed target name is free, how each occurrence classifies, and which board row owns it.

Inspected commit 94172a0fad7ba2170f60345521455d98ffcd2d4c (propflowai origin/main, 2026-09-16 16:37:56 −0500) · board and D10 read at propflow-docs f4e676628c9489f750f631de51f886275d9c73c9 · deliverable of board row p00-terminology-inventory

This page is EVIDENCE for a board row, not an architecture document. The source of truth for this inventory is the context dropdown on p00-terminology-inventory on the Implementation board; this page renders the same inspection so the row has a landed ref to point at. It decides nothing, supersedes nothing, and adds no vocabulary. The vocabulary itself is R13 · Terminology and rename sequence, decided by Gera on 2026-09-16. Per N2, the inventory lives in the row's dropdown or in linked implementation-PR evidence — not in a competing architecture document — and this page is the second of those, deliberately.

0 · The instrument, and the controls that make its zeros mean something

One call shape for every collision query, run at the pinned sha over the protect-list scope:

git grep -I -o -P "\b<name>\b" HEAD -- src agents lambda scripts config tests evals e2e ':(exclude)scripts/operator-rename' | wc -l

Occurrences (-o), never lines — p00-the-sweep-cannot-converge-alone lost a whole correction to that unit.

ControlKindResult
operatorDomainsknown-present, and its protect-list.json baseline is 47occ=47, files=17 — matches the baseline exactly
isPlatformStaffknown-present, baseline 47occ=66 — above baseline; check-protected.mjs fails only on a DROP, so growth is expected, not instrument failure
zzq_no_such_sym_9f3aknown-absent — a nonce that CANNOT become realocc=0
QQ_absent_control_never_realknown-absent — nonceocc=0

Every occ=0 on this page came out of the same invocation as operatorDomains=47. A zero here is a zero from an instrument that was demonstrably finding real hits in the same breath.

1 · How each occurrence was classified, and who owns it

Applied mechanically, not by feel. A candidate is persisted / public / durable if ANY of: a string literal inside a type union; a key or value in evals/ or a fixture JSON; a route segment under src/app; an env var or CLI flag; a Temporal registration (workflow type, task queue, signal/query/update name, search attribute, workflow-id component); or a DynamoDB key builder (FOO#). Frozen = inside protect-list.json frozenPaths.globs, or any value a model reads. Retained history = quoted receipts, anchors, row ids, URLs. Prose/UI = user-visible strings and docs. Otherwise internal-only.

Owner by lookup, so no judgement is left implicit: persisted/public → p00-terminology-contracts · frozen → p00-prompt-text-frozen · workflow registration → p9-terminology-workflows · prose/UI → p7-terminology-surfaces · internal D7/D8/D9 → p3-terminology-resources · internal D2/D3/D5/D6 → p5-terminology-access · internal residue → p00-terminology-internal.

2 · The headline

Seven of the ten distinctions resolve to retain-with-a-meaning-map or to prose, because this codebase has already reached most of the preferred words: organizationId is the isolation key (16,929 occurrences / 2,278 files, backing ORG#<id>/PROFILE); platform_admin / org_admin are already the two D2 senses, split by ADR-0019 §2.4, and ROLE_LABELS already renders them “Platform Admin” / “Organization Admin”; servedProperties already carries D8's preferred label. The remaining ambiguity lives overwhelmingly in persisted spellings that N1 itself permits to retain.

3 · The ten distinctions

D1 · company / firm / customer / operator → Organization (+ Operator for the property relationship)

Already inventoried; do not redo it. Nine merged/shipped rows plus an executable inventory in scripts/operator-rename/protect-list.json (six groups, per-identifier baselines) and ratchet.mjs.

D2 · admin / org admin / system admin → Platform administrator / Organization administrator

The one identifier that creates the ambiguity N1 exists to remove: src/lib/platform/auth/permissions.ts:60 isAdminRole(role) returns role === 'platform_admin' || role === 'org_admin' — true for both. Same shape as isOperatorInitiatedisStaffOrSystemInitiated in p00-last-person-sense-identifiers: a short name asserting a narrower domain than the predicate has. Consumers: src=62, scripts=1; agents / lambda / config / tests / evals / e2e all 0 — 63 occurrences across 28 files. Targets isOrgOrPlatformAdminRole and isAdminTierRole are both free (0).

retain the UserRole literals, ROLE_LABELS, the Admin nav label (nav-items.tsx:136,564 — N1 says keep it), and the /admin and /settings/admin route segments.

D3 · owner → Property owner / Credential holder / Task owner

The two ownerIds in src/lib/data/types.ts mean opposite things and both are persisted, so neither may be renamed: :3900 Property.ownerId is the credential holder (“a Better Auth userId for PMS credentials rather than a landlord identity”, the type's own words at :3478) and keys PMSCRED#<ownerId>/APPFOLIO; :7309 AppfolioWoOwner.ownerId is the actual property owner as AppFolio reports him — a foreign id we do not name.

The third sense, Task owner, has no code occupant — checked, not assumed: assigneeId 0, assignedToId 0, taskOwnerId 0, assignedStaffId 0, with operatorDomains=47 as the present control in the same call. Assignment is spelled assigned* throughout: assignedVendorCompanyId (:6685), assignedPmPersonId (:8012), assignedUsers (:7373), assignedUserId (:17749). Near-misses: ownerName (6 occurrences, 2 scripts, credential-holder sense again) and assignedTo (5 occurrences, a PO field name inside a drift-test regex). Disposition: prose only — and a D3 sweep on owner must not convert assignment fields into owner-language.

D4 · technical tenant isolation / TenantContext → Organization isolation / Organization context

#DeclarationMeaningShareDisposition
1agents/clara/lib/agent/clara-unified.ts:759renter facts for the prompt — firstName, unitNumber, leaseStart/End2 files in agents/retain
2src/lib/domain/identity/resolve-tenant-context.ts:46resolved renter bundle for tool handlers — {tenant, unit, property, conversation}retain
3src/lib/domain/portfolio/context/mint.ts:200“The wall as a type”organizationId, roster, reach, path, mapVersion, origin; one of five branded contexts beside AdminContext, ComplianceContext, LifeSafetyContext, ReportsContext85 occ / 17 files — the dominant senserenameOrganizationContext (0)

So N1 D4 is right after all, and the danger is the opposite of what I first wrote: the rename is real, it targets sense (3) only, and a sense-blind sweep on the token would rename all three and destroy the rental sense N1 explicitly retains. p00-token-count-cannot-prove-sense, exactly — 127 is a token count spanning three meanings.

What still stands: all five tenantId declarations in types.ts are the renter, none the customer boundary (:7976 is additionally a Temporal workflow-id component and says so); organizationId is already the isolation key; and lambda/agent-runtime/vendors/appfolio/multiTenantConfig.ts is a filename in the technical sense with a build-time rewrite attached (build.ts:25), consumers src=3 lambda=7 scripts=1.

D5 · role / permission / entitlement → Staff role / Access grant / Module entitlement

The three-way split already maps onto three separate code paths, which is why it is cheap: staff rolecanAccess(role, entity, action) (permissions.ts:194), pure role, no boundary input; access grantreadPropertyAssignment (property-assignment.ts:117) and canCallerViewProperty (visible-property.ts:103), which never consult the role matrix; module entitlementisModuleEnabled (permissions.ts:541), which takes no role at all.

retain everything, with three meaning maps worth writing down:

D6 · scope → Authorized scope / Selected scope / Effective query scope

The best-placed distinction in the tree: the three senses already have three types in src/lib/domain/scope/selection.tsProposal (:217, “what somebody ASKED for”, never trusted) = selected scope; Grants/OrgGrant (:188/:133, server-minted) = authorized scope; Selection from deriveSelection(grants, proposal) (:267/:404) = effective query scope.

retain the public spellings — header x-pf-selection and cookie propflow-selection (relay-constants.ts:23,40), URL params org=/prop=, and the cache-key literals sel2, sel2:staff, sel2:none, sel2:chain, noprovider, where a spelling change silently invalidates every cached body. Note a fourth sense: PersonRoleScope (types.ts:16749) is what a role row is bound to.

rename the local effectiveScope in 18 files → effectiveQueryScope (target 0). Consumers: src=56, every other directory 0; never exported and never imported. One stale mention to fix in a comment (not an assertion) at property-scope-surface-registry.drift.test.ts:657.

defer getUserPropertyScope (590 occ / 252 files) and getOptionalCallerOrgScope (82 / 33): free targets, but grants.ts:5-9 documents the chain they belong to as a stopgap slated for replacement.

Adjacent, not owned here: there is no single place where authorized ∩ selected happens — two authorized chains coexist and routes intersect both. A behaviour question for p5-terminology-access / Phase 10, not a rename.

D7 · calendar → Connected calendar / Organization schedule view; availability source / booking destination

ChannelAttachment (dynamo/channel-attachment.ts:249, keys ORG#…/ATTACH#calendar#primary#<startedAt> and PERSON#…) is the connected-calendar record — retain. building-calendar.ts:164 is the organization schedule view and is already spelled “building”, never “org”. org-calendar.ts:101 orgCalendarPresence is the opposite of what its name suggests — a provider endpoint, not a view — but :4-10 records its demotion to a fallback rung on 2026-09-16, so renaming it is low value (consumers src=26 scripts=2). Two spellings exist for one vendor across two stores (ChannelProvider 'microsoft' vs CalendarIntegration.provider 'outlook'), both persisted, both retained with a meaning map. resolveTourCalendarAvailability (sync-tour.ts:916) and syncTourToCalendarWithNode (:947) already name the two roles separately; only the destination is persisted (Tour.calendarNode, types.ts:11642).

D8 · custody / attachment / coverage → Controlling organization / Attached to / Serves properties

The real D8 triple already exists and is already correct, in the phone domain: the AddressHead custody ConditionCheck and declareLineCoverage in src/lib/data/dynamo/phone-address.ts, whose own test says custody and coverage are separate. servedProperties already carries the preferred label — but it is frozen, not internal: servedPropertiesCreateGuidance (agents/clara/lib/agent/capabilities/handyman.ts:95) returns literal prompt sentences a model reads.

D9 · capability / enabled / live / mode → Provider capability / Module entitlement / Automation mode / Phone action switch

The most ambiguous of the ten, and the approved four-way split does not cover it.

“capability” carries six senses. N1 names one: provider feature set — PMSCapability (src/lib/domain/pms/types.ts:19) and CalendarProviderCapabilities (calendar/provider/types.ts:177). The others: comms-channel affordance (channel-capabilities.ts:13); a pointer at an attached resource (portfolio/scope/resolve-capability.ts:71, whose header says a capability “is a POINTER at a thing that exists — a mailbox, a calendar, a credential, a phone line” and is explicitly not a setting); PMS write-back wiring (types.ts:3587 Property.pmsSource, docblock opening “CAPABILITY — this property is wired for PMS write-back”); and user permission (settings-write-requires-edit-capability.test.ts). See U6.

“live” carries five senses: a temporal in-force interval (registry/is-live.ts:1); current-not-historical (LiveSettingRow, ATTACH# vs HIST#); the stage value; the per-company send gate Organization.claraLive (:13719, 202 occurrences, strict === true); and a sync cursor (liveSinceIds).

phoneAction and phone_action are 0 in my own sweep, with operatorDomains=47 as the present control in the same call — the spelling N1's “Phone action switch” implies does not exist. The real switches are Property.voiceCallbackEnabled (:4099, fail-closed on exactly true), env VOICE_CALLBACK, and renewalPolicy.voiceCallEnabled (:4733).

Named *Mode, different axis — recorded so a sweep does not take them: Property.operatingMode, VendorJobReferenceMode, LeasingIntakeMode, emailShadowMode (a boolean spelled “mode”, DEAD 2026-09-14), Tour.mode, MessageSafetyCheck.mode, BackfillRunMode, GateMode, PolicyGateMode, ChannelsMode. Not modes despite “auto”: aiAutoApproveThreshold, renewalAutoStartDaysBeforeLeaseEnd. There is no off/suggest/auto triple in this tree ('suggest' = 0; present control 'observe' resolves).

D10 · Phase Dock / ladder / board / plan → Implementation board

D10 does not exist in propflowai: Phase Dock 0, phase-dock 0, Implementation board 0 — same call shape, same controls.

In propflow-docs at the inspected sha: Phase Dock 49 across 4 artifacts, Implementation board already adopted 8 times. retain phase-dock as an id/anchor, and specifically phase-dock-rotation, a live system id in agentflow/SYSTEMS.md cited by bin/refresh-trackers:563.

4 · N3 — the durable-name audit comes back clean

All 14 task queues enumerated (propflow-leasing, propflow-renewal, propflow-tour, propflow-collections-chase, propflow-maintenance-comms, propflow-permutation-harness, the eval/test variants): zero carry any of the ten ambiguous terms. Of 40+ signal/query/update names, exactly four carry one — getTenantConfirmationReviewSnapshot, tenantConfirmationApproved, tenantConfirmationRejected, tenantIntent — and all four are the rental sense, which N1 retains. Workflow ids embed tenantId (turnover-tenant_…-2026-07-15, with a safety gate at prove-turnover-e2e-assertions.test.ts:528 asserting the id contains it), so that stays too.

5 · A gap found while classifying: the frozen class does not cover nine of the ten

protect-list.json frozenPaths watches countWord: "operator" across five evals/ globs, and freeze-fingerprint.json pins 47 files, every one under evals/.

At this sha 104 files under agents/ and src/ mention prompt-building symbols (promptBlock / SYSTEM_PROMPT / systemPrompt) — and that 104 is a ceiling, not a count of model-read files: the same match hits harness code that merely names a variable systemPrompt and never reaches a model, which is exactly what protect-list's boundary rule puts out. A token count cannot prove sense, so the number carries nothing alone. The finding rests on the confirmed instance: servedPropertiesCreateGuidance returns literal prompt sentences (“You service exactly ONE property: …”), carries D8's preferred label, and sits outside every frozen glob.

protect-list's own stated boundary rule is “the question is not which directories, it is WHETHER A MODEL READS IT.” Measured against that rule the guard is D1-specific and directory-bound. Any D2–D10 sweep touching agents/ prompt text would move eval baselines with nothing firing. Owner: p00-prompt-text-frozen, alongside p00-frozen-scope-too-broad, which recorded the opposite error on the same instrument.

6 · The bounded subset downstream rows may consume first — TI-S1

Membership rule, applied mechanically: internal-only ∧ target count 0 in the same call as a live present control ∧ not frozen ∧ not a Temporal registration ∧ not persisted ∧ a single owner.

#CandidateTargetConsumer categoriesOwner
1permissions.ts:60 isAdminRoleisOrgOrPlatformAdminRole / isAdminTierRole0 / 0src=62, scripts=1; all others 0 (63 occ / 28 files). Exported, so every importer moves with itp5-terminology-access
2local effectiveScope, 18 fileseffectiveQueryScope0src=56; all others 0. Never exported or imported — a pure per-file local renamep5-terminology-access

Also excluded, with the reason, because the inventory owes the judgement and not just the test: getUserPropertyScope, getOptionalCallerOrgScope and orgCalendarPresence all pass every mechanical criterion but belong to chains their own docblocks record as stopgaps or as demoted — renaming code scheduled for replacement is waste. p00-terminology-contracts needs no subset and can start immediately on the retain-list, which is the bulk of the work.

7 · Unknowns — explicit, with the question, not guessed

8 · Adjacent behaviour risks — routed, not adopted

Neither is a naming question and neither is owned by this row.

9 · What this page does not claim

PropFlow Docs