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.
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.
| Control | Kind | Result |
|---|---|---|
operatorDomains | known-present, and its protect-list.json baseline is 47 | occ=47, files=17 — matches the baseline exactly |
isPlatformStaff | known-present, baseline 47 | occ=66 — above baseline; check-protected.mjs fails only on a DROP, so growth is expected, not instrument failure |
zzq_no_such_sym_9f3a | known-absent — a nonce that CANNOT become real | occ=0 |
QQ_absent_control_never_real | known-absent — nonce | occ=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.
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.
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.
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.
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 isOperatorInitiated → isStaffOrSystemInitiated 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.
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.
| # | Declaration | Meaning | Share | Disposition |
|---|---|---|---|---|
| 1 | agents/clara/lib/agent/clara-unified.ts:759 | renter facts for the prompt — firstName, unitNumber, leaseStart/End | 2 files in agents/ | retain |
| 2 | src/lib/domain/identity/resolve-tenant-context.ts:46 | resolved renter bundle for tool handlers — {tenant, unit, property, conversation} | — | retain |
| 3 | src/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, ReportsContext | 85 occ / 17 files — the dominant sense | rename → OrganizationContext (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.
The three-way split already maps onto three separate code paths, which is why it is cheap: staff role → canAccess(role, entity, action) (permissions.ts:194), pure role, no boundary input; access grant → readPropertyAssignment (property-assignment.ts:117) and canCallerViewProperty (visible-property.ts:103), which never consult the role matrix; module entitlement → isModuleEnabled (permissions.ts:541), which takes no role at all.
retain everything, with three meaning maps worth writing down:
UserRole (types.ts:13812) and the persisted PersonRoleType (:16718), which spells it pm, not property_manager — plus a third grouping, STAFF_TIERS (src/lib/domain/staff/inbound-resolution.ts:45), that defines “staff” for Clara and is not either list minus anything.User.propertyAssignment: 'all'|'subset' vs InvitePropertyGrant: 'org_only'|'listed'|'all' — and must not be unified.memberRoleLabel (ProspectsClient.tsx:398) is household membership, not staff role. A sweep on role would hit it.The best-placed distinction in the tree: the three senses already have three types in src/lib/domain/scope/selection.ts — Proposal (: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.
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).
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.
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 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.
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.
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.
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.
| # | Candidate | → | Target | Consumer categories | Owner |
|---|---|---|---|---|---|
| 1 | permissions.ts:60 isAdminRole | isOrgOrPlatformAdminRole / isAdminTierRole | 0 / 0 | src=62, scripts=1; all others 0 (63 occ / 28 files). Exported, so every importer moves with it | p5-terminology-access |
| 2 | local effectiveScope, 18 files | effectiveQueryScope | 0 | src=56; all others 0. Never exported or imported — a pure per-file local rename | p5-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.
busySource / freeBusySource, both 0) and keep “availability source” as prose for the leasing field, or (b) migrate the persisted leasing field so the calendar concept can take the word? The one place the approved vocabulary collides with a persisted value — the exact intersection p00-persisted-values-are-not-renames and p00-rename-into-an-occupied-name were both opened for.isAdminRole is safe either way and waits on nothing, but it makes a conflation visible without deciding it. Does the intent — org-side leadership passing these gates — hold at all 63 sites, or did one or two inherit the predicate by convenience?enabledModules keys are persisted DDB map keys and moduleEntitlement is free (0). Recorded disposition is retain — the readability win does not pay for migrating keys on every ORG#/PROFILE row — but the ruling belongs to p00-terminology-contracts.multiTenantConfig.ts is a filename with a build-time rewrite (build.ts:25 warns the vendored-module path rewrites it and that it was not deleted), majority lambda/. In scope for p00-terminology-internal, or does the bundler coupling make it an infra change?Property.capabilityStage ('shadow', persisted, shipped) vs registry capability_stage ('observe', unwritten) — which vocabulary wins, and does 'shadow' become 'observe' or the reverse? A value change here is a persisted-value supersession in the shape p00-persisted-values-are-not-renames established, not a rename.MyCalendarCard.tsx:217 tells the customer “Clara stops booking tours on this calendar and stops reading it for your availability” — one sentence, both roles, one calendar. N1 wants them named separately, but the product genuinely uses one calendar for both today (tour-calendar-pool.ts:32-37: with several connected agents the pool returns all “but the tour chokepoint uses only the FIRST”). Splitting the copy ahead of the behaviour would make the UI describe a product we do not ship.Neither is a naming question and neither is owned by this row.
calendar-slot-reservation.ts:152 mailboxResourceId dedupes capacity by lower-cased mailbox address, and slot-hold.ts:81 resolveCalendarResourceId has only two rungs (property, org) where the tour pool has three — so a tour booked on a leasing agent's own calendar holds against a different partition. Route to p3-terminology-resources.org-calendar.ts:47-58 records that Property.leasingCalendar is used as a proxy for “does this building have a calendar” on paths that never reach the chokepoint — exactly the “an organization schedule view does not prove a provider calendar exists” failure mode, already present and already documented as unfixed.context dropdown is; this page renders it so the row has a landed ref.git grep or a file read at one sha, and every zero was produced in the same invocation as a known-present control. Two counting errors of my own are recorded on this page rather than silently corrected — the -w residue unit in D1, and the truncated grep in D4.