Skip to content

Client Identity & Data Sharing

This page captures the re-shape of the V2 client model confirmed by Stefan (2026-05-02). It supersedes the previous "duplicate Player rows per stringer + single share button" model. It is a requirements-level document — the conceptual model and the user-visible behaviour. The schema for Person / ClientProfile is owned by Theo (Solution Architect); this page references it conceptually only.

Cross-cuts: V2 scope, data model (refers here for identity + sharing), use cases, glossary. Architectural counterpart: ADR-0004 (Theo) — the schema-level shape of Person, ClientProfile, order_shares, person_stringer_share, the chokepoint predicate, and the serialization-layer redaction. Tracked in racket-book#76 (this requirements rewrite) and racket-book#77 (Theo's parallel ADR + architecture rewrite).

Why we re-shaped

The 2026-04-27 baseline stored the same human as two Player rows (one per stringer) and crossed the boundary only via the explicit M18 Share player snapshot. That worked while the platform was conceptually one stringer (Stefan). With multiple stringers and a future client portal (V3 — see V3 vision), the consequences of duplication were unacceptable:

  • A client with two stringers had no platform-level identity, so the V3 portal could not present a unified history.
  • "Sharing" was a stringer-initiated, snapshot-only act. Clients had no agency over their own data.
  • The single-button snapshot conflated three distinct sharing intents (pick existing jobs, share ongoing relationship, client-driven shares) into one mechanism.

The re-shape is two changes:

  1. Identity is platform-level. One Person per real human, looked up by verified email. Each stringer keeps a private ClientProfile referencing the same Person.
  2. Sharing is an explicit, typed grant. Three sharing rules, each with its own user-facing semantics, granularity, and redaction policy.

Identity

Conceptual model

Concept Owns Scope
Person Platform One row per human. The identity primitive.
ClientProfile Stringer One per (Person, Stringer) pair. Carries the stringer's private notes, locale preference for that client, notification overrides, and any other stringer-only data about that person.
Order Stringer Unchanged. Belongs to a Stringer + a ClientProfile (which resolves to a Person).

A stringer never operates on Person directly — they always work through the ClientProfile they own. The Person is the join point that makes cross-stringer sharing and (V3) client-portal access possible.

Schema-level shape: owned by Theo. ADR-0004 + docs/architecture/data-model.md are the authority. This page is the requirements contract those documents must satisfy.

Identity matching when adding a client

This is load-bearing for privacy: a false positive leaks history.

  • Strict verified-email match only. When a stringer adds a new client, the system attempts a match against existing Person rows on verified email and nothing else. Name, phone, postal address, birthdate — none of these are used for auto-matching. Ever.
  • Pre-login clients (no email, or email not yet verified): the system creates a draft Person owned by the inviting stringer. No platform identity is asserted.
  • Claim flow: when a draft Person is later sent a magic-link (V3 portal onboarding) and the recipient verifies the email, the draft is either:
  • Promoted to a full Person (if no existing Person with that verified email exists), or
  • Merged into the existing Person (if one with that verified email exists). Merge consolidates ClientProfile rows from all involved stringers under the surviving Person.
  • No silent merging. A merge is triggered only by the client themselves verifying the email. Stringers cannot merge two Person rows on a hunch; admins can only merge with the verified-email rule satisfied.
  • Merge does not auto-share (decided 2026-05-02; was OQ-4). A successful Person merge consolidates identity only. Existing ClientProfile rows from different stringers under the surviving Person remain private to their respective owning stringers. Cross-stringer visibility of any job still requires an explicit Rule 1, Rule 2, or Rule 3 grant. Rationale: identity unification is a privacy-neutral act (it just says "these two records refer to the same human"); adding cross-stringer visibility would silently expand who-sees-what, which violates the explicit-grant principle of the sharing model.

Self-Player slot stays unchanged. Stringers still have a is_stringer_self slot (the locked decision from 2026-04-27 — see V2 scope). The self-Player is conceptually a ClientProfile whose Person is the stringer themselves.

Data sharing — the three rules

All three rules grant read-only access. The receiving stringer can never edit or delete a shared job. Revocation is server-side instant (already-printed receipts and already-exported XLSX/JSON cannot be recalled — see out of scope below).

Rule 1 — Stringer-initiated job share

Granter: Stringer A. Grantee: Stringer B. Granularity: per-job, past only.

As Stringer A, I want to share specific past string jobs I performed for client X with Stringer B, so that Stringer B has the technical context (racket, strings, tensions, DT) to take over stringing for client X.

Acceptance criteria:

  • Stringer A can pick one or more existing (past) jobs they performed and grant Stringer B read-only visibility on each.
  • Stringer A cannot grant visibility on jobs they did not perform.
  • Stringer A cannot grant visibility on future jobs via this rule. (Future-inclusive sharing is Rule 3 only, and only the client can grant it.)
  • The system provides a bulk-UI convenience: "share all existing past jobs for this client with Stringer B". This is sugar over the per-job primitive — internally each job becomes its own grant row, and revocation is per-job.
  • The grant is created without the client's consent (this is the stringer's own work product). The redaction policy below is what protects the client's identity.
  • Stringer B sees the job(s) under their "Shared with me" inbox (see hidden requirements).
  • Stringer A and Stringer B can independently revoke at any time (per-job or all of A→B). Revocation is immediate.
  • Every grant create + every grant revoke + every read of a shared job is audit-logged (see audit).

Redaction (Rule 1 — see matrix): pricing and client PII redacted except first name. Racket, strings, tension, DT, and technical job fields visible. Comments are blanket-redacted in V2 (consistent with ADR-0004) because granular sanitization is impractical at grant time.

Deferred to V3 (was OQ-2; decided 2026-05-02): a per-job "include / exclude comments in this share" toggle for Rule 1 is explicitly out of scope for V2. V2 keeps the blanket comments-redact. Revisit in V3 if real demand emerges.

Rule 1 grantee already has a ClientProfile for the same Person (decided 2026-05-02; was OQ-5): in V2 the share appears as a standalone entry in the receiving stringer's "Shared with me" inbox and is not auto-merged into their existing ClientProfile view of that client. Auto-link is a V3 polish item — keeping V2 standalone makes the redaction boundary unambiguous (the receiving stringer can never confuse "what I see because I have my own relationship with this client" with "what I see because Stringer A shared a job"). V3 may collapse the two views once the surface is mature.

Rule 2 — Client-initiated job share (per-job)

Granter: Client (the Person, via the V3 portal — but see V2 timing note). Grantee: A specific Stringer. Granularity: per-job, past only.

As a client, I want to share specific past stringing jobs done for me with a chosen stringer (one I have a relationship with, or am evaluating), so that that stringer can see exactly what setup I've been on.

Acceptance criteria:

  • The client browses their own merged history (see hidden requirements) and selects one or more existing past jobs.
  • The client picks a target stringer from the list of platform stringers.
  • The client cannot share future jobs via this rule. (Rule 3 is the only future-inclusive mechanism.)
  • A bulk-UI convenience exists: "share all my existing past jobs with Stringer X". Internally per-job grants, revocable individually.
  • Inviting a non-platform stringer to receive a share is out of scope (see out of scope).
  • The grant is created without the originating stringer's consent — this is the client's own data; the originating stringer is informed via notification but cannot block.
  • The receiving stringer sees the job(s) in their "Shared with me" inbox.
  • The client and the receiving stringer can independently revoke at any time. Revocation is immediate.
  • Every grant create + revoke + access is audit-logged.

Redaction (Rule 2 — see matrix): full client identity is exposed to the receiving stringer (the client is consenting to be visible — otherwise the share is pointless). Pricing is visible (decided 2026-05-02 — it is the client's own data; the client is the granter).

Rule 3 — Client global preference (future-inclusive)

Granter: Client. Grantee: A specific Stringer. Granularity: all of the client's jobs, past + future, across all current and future stringers.

As a client, I want to toggle "share ALL my stringing jobs (past and future) with Stringer X", so that Stringer X always has my full history visible — even jobs Stringer X did not perform and even jobs from stringers I haven't met yet.

This is the only future-inclusive sharing mechanism in the model. It is the most powerful grant the platform offers.

Acceptance criteria:

  • The client toggles a per-stringer "share everything (past + future) with this stringer" preference.
  • Once on, Stringer X sees all of the client's existing jobs (regardless of which stringer performed them) and automatically sees any new jobs created later — including jobs performed by stringers added to the client's network after the toggle was set. Example: client toggles for Stringer X today; six months later starts using Stringer Z. Stringer X automatically sees Z's jobs from the moment they are recorded.
  • The toggle is per (Client, Stringer) pair. The client can have rule-3 grants active to multiple stringers simultaneously.
  • The client can revoke at any time. The grantee stringer can also decline / revoke.
  • Revocation is immediate: from the next request onwards, the grantee no longer sees jobs covered by this grant. Already-cached client artifacts (printed receipts, exported XLSX/JSON) are not recallable.
  • The originating stringer of any auto-included future job is notified when their job becomes visible to a rule-3 grantee. (See hidden requirements — notifications.)
  • Every toggle on, toggle off, and access of a job covered by this grant is audit-logged.

Redaction (Rule 3 — see matrix): identical to Rule 2 — full client identity is exposed.

V2 vs V3 timing

Rules 2 and 3 require the client to act, which requires a client UI — i.e. the V3 client-facing magic-link portal (V3 vision — C1). They are therefore conceptually defined in V2 (so the data model and authorization layer accommodate them) but not surfaced to clients until V3 ships the portal.

Rule 1 is fully usable in V2 — it operates entirely on the stringer surface.

The Person / ClientProfile split must land in V2: it is the prerequisite for the V3 portal, and a post-hoc duplicate-merge is materially harder than getting it right at migration time.

Visibility / redaction matrix

Field on a shared job Rule 1 (stringer→stringer) Rules 2 & 3 (client-granted)
Client first name Visible Visible
Client last name, email, phone, address, any other PII Redacted Visible
Racket make / model / version / head size / pattern / serial-or-instance ID Visible Visible
Main + cross string mfr / model / gauge Visible Visible
Main + cross tension Visible Visible
Main + cross color Visible Visible
BYO flags (per side) Visible Visible
Method Visible Visible
Dynamic Tension after Visible Visible
Comments (free text) Redacted (may contain PII the granter can't sanitize en masse) Visible
Lifecycle dates (Ordered / Strung / Returned / Paid) Visible (Strung is the technically useful one) Visible
Labor (CHF) Redacted Visible (decided 2026-05-02)
String prices, strings subtotal, total Redacted Visible (decided 2026-05-02)

Rationale:

  • Rule 1 redaction — the receiving stringer needs the technical setup to take over future stringing; they do not need the client's identity beyond a first name (for in-conversation reference) and they should not see what another stringer charged. Comments are redacted because granular sanitization is impractical on every grant; in V2 the bulk redact is the safe default.
  • Rules 2/3 — full identity — the client granted the share; identity exposure is the entire point.
  • Rules 2/3 — pricingdecided 2026-05-02: visible. It is the client's own data and the client is the granter; client agency takes precedence over the originating stringer's competitive concerns. (Was OQ-1 in the original draft of this page.)

Edit + revoke semantics

  • Edit: a stringer can edit only their own jobs. A shared job is read-only for any non-owner viewer in every rule.
  • Revoke — Rule 1: Stringer A or Stringer B can revoke. Per-job or bulk. Immediate effect.
  • Revoke — Rule 2: the client or the receiving stringer can revoke. Per-job or bulk. Immediate effect.
  • Revoke — Rule 3: the client or the receiving stringer can revoke the global preference. Immediate effect; covers all past + future jobs from the next request onwards.
  • Already-emitted artifacts: revocation does not recall printed receipts, emailed receipts already in inboxes, or already-downloaded XLSX/JSON exports (see out of scope). The audit log makes this transparent — every access of a shared job is logged with timestamp.

Audit

Every grant create, every grant revoke, and every read of a shared job is audit-logged. The log entry contains, at minimum:

  • Actor (stringer or client Person ID).
  • Timestamp.
  • Grant ID + rule type (1 / 2 / 3).
  • Target row(s) — the job ID (or "all jobs" for rule-3 reads).
  • Action (grant_create / grant_revoke / access).

The log is immutable (append-only). FADP-relevant: this is the record we rely on to demonstrate, on request, who saw what about a client's data.

Surface: at minimum, admin-visible (for Stefan, today). Per-client audit transparency in the V3 portal is a candidate follow-up — see hidden requirements.

User stories — by role

Stringer (granter or grantee)

  • (Granter, Rule 1) As a stringer, I can select a client and a subset of past jobs I performed for them, and grant another stringer read-only access — so handover is one click instead of an ad-hoc PDF email.
  • (Grantee, Rules 1 & 2 & 3) As a stringer, I have a "Shared with me" inbox listing all jobs I currently have read-only access to, by source (which other stringer / which client granted) and by client.
  • (Grantee, all rules) When I open a shared job, the redaction matrix is applied automatically; I cannot see redacted fields.
  • (Either side, Rule 1) As a stringer, I can list and revoke my own grants from a sharing-management screen.
  • (Notifications) As a stringer, I receive an in-app notification when a client (Rules 2 / 3) shares a job with me, when a client revokes a grant, and when one of my own jobs becomes visible to a third party because of a client's rule-3 toggle.

Client (V3 portal)

  • (Granter, Rules 2 & 3) As a client, I can browse my full merged history (across all stringers I've used) and select past jobs to share with a stringer (Rule 2), or toggle a "share everything past + future" preference per stringer (Rule 3).
  • (Granter, all rules) As a client, I can revoke any grant or the global preference at any time from a sharing-management screen.
  • (Granter, Rule 3) As a client, I see clearly which stringers currently have my "share everything" preference on.
  • (Notifications) As a client, I receive a notification when a stringer (Rule 1) shares a job concerning me with another stringer, including which fields the other stringer can see (transparency on the redaction policy).

Admin (Stefan)

  • As admin, I can read the audit log: every grant create, revoke, and shared-data access on the platform.
  • As admin, I can perform a Person merge only when both source rows have the same verified email. The merge consolidates Person identity but does not create cross-stringer share grants — every existing ClientProfile remains private to its owning stringer, and any cross-stringer visibility still requires a Rule 1 / 2 / 3 grant.

Hidden requirements

Surfaces that must exist for the three sharing rules to be usable:

  1. "Shared with me" inbox view for receiving stringers — a single list of all jobs currently shared with them, by source. Filterable by client and by source stringer.
  2. Sharing-management screen — stringer side — list active grants the stringer has issued (Rule 1) or received (any rule); per-row revoke.
  3. Sharing-management screen — client side (V3) — list active grants the client has issued (Rules 2 & 3) and global preferences active per stringer; per-row revoke.
  4. Per-client merged history view for the client themselves (V3) — all jobs across all stringers, in one chronological list. This is the surface the client uses to grant Rule 2 / Rule 3.
  5. Notifications on grant create + revoke. Channel rules (decided 2026-05-02; was OQ-3):
  6. In-app is mandatory for every grant/revoke event. It is the always-available baseline channel and cannot be disabled per user.
  7. Email is opt-in per recipient via Person.notification_prefs (the same preference store used for receipt-emailing decisions). Default value to be set by Mira/Theo as part of preference-defaults work; the requirement here is only that email is gated on an explicit preference, not assumed-on.
  8. Other channels (SMS, push, etc.) follow the cost/feasibility exploration in V3 vision — B2 and are not in scope for this rewrite.

Events to notify on: - Stringer notified when they receive a grant (any rule). - Stringer notified when a client revokes a grant they had received. - Stringer (originating) notified when one of their jobs becomes visible to a third stringer because of a client's Rule 3 toggle. - Client notified when a stringer shares (Rule 1) a job concerning them. - Client notified on revocation actions on grants they care about.

UX surfacing of notifications is Mira's; this page only specifies the channel rules and the events that must trigger a notification. 6. Audit log surface — admin-visible at minimum (V2). Per-client audit transparency in the V3 portal is a follow-up candidate. 7. UX design for items 1–5 is owned by Mira (UX/UI Designer) — flagged as a follow-up requirement; not in scope for this requirements rewrite.

Out of scope

These were explicitly considered and ruled out for the rewrite:

  • Inviting non-platform stringers to receive a share. Rules 1 and 2 grantees must be existing platform stringers. Inviting a stringer to the platform via a share link is not in scope.
  • Recall of already-emitted artifacts after revocation. Printed receipts, emailed receipts already delivered, and downloaded XLSX/JSON exports cannot be recalled. The audit log records access; FADP-grade compliance is achieved through the access log, not artifact retrieval.
  • Auto-matching Person on anything other than verified email. No name / phone / address / fuzzy heuristics. Ever.
  • Multi-client history merging by stringers. Only the client themselves can trigger a merge (by verifying their email).

Open questions

All five open questions logged in the original draft of this page (OQ-1 through OQ-5) were closed on 2026-05-02 with the decisions inlined into the relevant sections above. No open questions remain on this page. See the change log entry CC-2026-05-02-2 for the closure record.

Cross-references

  • Architecture — ADR-0004 (Theo) is the authoritative schema and chokepoint design. Companion architecture chapter rewrites in docs/architecture/data-model.md and docs/architecture/auth-and-tenancy.md. ADR-0004 names the three-grant tables order_shares (per-job, polymorphic granter — Rules 1 & 2) and person_stringer_share (per-(person, stringer), future-inclusive — Rule 3). The visibility/redaction matrix in this page corresponds 1:1 to ADR-0004's serialization-layer schemas.
  • Coherence with ADR-0004: Iris reviewed ADR-0004 against this requirements page. Same identity model. Same three-rule taxonomy. Same identity-matching rule (verified-email only). Same revocation semantics. The five open questions originally logged on this page were closed on 2026-05-02 with the proposed defaults (see change log entry CC-2026-05-02-2). The closures align with ADR-0004's existing posture (ADR-0004 already locks comments-redact under Rule 1; the pricing-visible decision under Rules 2/3 is a serialization-layer detail consistent with ADR-0004's "client granted, full identity visible" stance). The Person-merge-does-not-auto-share decision and the Rule-1-standalone-in-V2 decision are requirements-level clarifications that do not require schema changes in ADR-0004. Iris flagged no contradictions at the time of the closure commit; if review surfaces any drift, coordinate with Theo before editing ADR-0004.
  • UX: Mira will design the three sharing-management screens, the "shared with me" inbox, the per-client merged history view, and the redaction surfacing. Tracked as a follow-up requirement (see hidden requirements item 7).
  • Migration: the existing V1 and V2-baseline data has no Person concept yet. The migration step from "duplicate Players per stringer" to "Person + ClientProfile" is detailed in ADR-0004's "Migration impact" section. For V1 specifically: Stefan is one Stringer, every V1 client becomes one Person + one ClientProfile under Stefan, no share rows are seeded. This requirements page is the contract that migration must satisfy.
  • Issue tracking: racket-book#76 (this requirements rewrite, owned by Iris); racket-book#77 (ADR-0004 + architecture rewrite, owned by Theo).