Skip to content

AL-1998: [Phase 9] Manage one stable Ministry Assignment and its separate participant memberships (P27-S01) #1998

Open

Description

@cobmojo

AL-1998: [Phase 9] Manage one stable Ministry Assignment and its separate participant memberships

Approved implementation ticket. Plan key: P27-S01. Labels: type:feature, complexity:hard. Blocking prerequisites are tracked as native "blocked by" links on this issue (currently #570, #606, #1947 and #2250), not as a label. Publication does not authorize implementation before its documented gates are satisfied.

Parent

#604

Phase 9 owns this enabling source; Phase 27 consumes it through the explicit blockers below. #1992 is a related consumer specification, not a second native parent.

Source of truth

Documentation PR: #1993. Exact specification commit: b5a65b3f2da880fcbfa4b3cae189d216d06542be. Inspected implementation baseline: 71b81fae2ce1f9b615bcfee8606cd2c978a70e71. Publishing this contract does not establish implementation, merge, qualification or activation.

Stories: US27-P02. Primary acceptance ownership: Already accepted Phase 9/ADR0136 enabling contract; source-owned criteria below. No invented Phase 27 acceptance ID..

Applicable ADRs:

What to build

Implement the Phase9-owned Ministry Assignment identity/current eligibility and versioned lifecycle plus zero-to-many prospective Party Participant Memberships. Build a narrow accessible CRM People/association context to create/select the identified ministry and add/end/correct exact participant intervals. The record owns exactly one Tenant and Legal Entity; a Party is same Tenant without invented Party Legal Entity ownership. Identity survives participant/spouse/team/login changes. Membership is append-only-corrected with exact non-overlapping half-open intervals, trusted attribution/current-head command, scope-complete uniqueness/idempotency and deletion restriction. Preserve owner-certified source correction/lifecycle evidence for consumers. Expose narrow authorized current identity, lifecycle/eligibility and permitted participants, not raw membership rows. Include migration census with explicit unresolved legacy profile mappings, no guessed spouse/team association. Deliver browser/API/persistence behavior with real current-authority and interval concurrency proof.

Context and contracts

ADR0136 and the dated Phase22 D19 amendment explicitly place this source in Phase9. Existing #607 owns Party-to-Party relationship edges; #1285 owns Page contributors/display and explicitly lists Ministry Assignment as an external Phase9 prerequisite. Exact phrase issue search found no implementation producer. This is a missing already-decided producer, not a new P27 entity or a Ministry Project/Support Assignment merge.

Current authority, tenant/field/history-safe read and write behavior, atomic source history/receipt/required intent, truthful lost-response recovery and applicable two-session concurrency proof accompany this slice. Base UI/base-maia/Zinc, keyboard/screen-reader/mobile/zoom behavior and slow/failing network recovery accompany every reached screen. These are in-slice delivery obligations under REQ27-Q01–Q14, not deferred to final qualification. Terminal Q tickets prove composition and release evidence.

Scope and non-goals

  • No Public Ministry Page, Page contributor/display grant, Support Assignment or binding writer, finance or notification access, invitation/shared login, new authorization engine or person-owned fund.
  • No automatic participant propagation, current-work assignment, donor enrollment, source history union or destructive merge/split.
  • No requirement to finish all Phase9; no arbitrary new membership roles/lifecycle states beyond accepted source contract.

Acceptance criteria

  • A ministry with zero current participants exists and is selectable under current permitted exact Tenant/Legal Entity scope without a Page, gift, email, support binding or login.
  • One Party may participate in several ministries, several Parties may participate in one ministry, and changing spouse/team/leadership/login names does not replace ministry identity.
  • Prospective add/end/correct records exact half-open membership interval and immutable source evidence; overlapping concurrent membership intervals in one exact scope cannot both commit, adjacent intervals behave correctly.
  • Current principal/field/history/purpose authority applies before list/count/detail/result and at acceptance; real cross-Tenant/entity, caller-supplied actor/scope, raw grants/RLS/RPC and privileged-service attacks cannot add forbidden facts or disclose restricted participants.
  • Membership alone creates no responsibility, task, notification, Page display/edit, Support Workspace/supporter identity or finance grant. Former member with independent current grant and member without grant receive the correct independent policy outcome.
  • Retirement or owner-certified identity correction preserves source identity/history and exposes current eligibility without retargeting existing released or accepted downstream facts; actual source changes can be consumed prospectively.
  • Lost response/retry/ABA/revocation race leaves one intended mutation and original actor/time; before-commit failures leave no partial source/history/receipt.
  • Evidence-based migration and rollback preserve restrictions and history; ambiguous profile-to-many-ministry cases remain unresolved, no inferred membership or money/attention effect.
  • Keyboard/screen-reader/single-pointer/320px/400% zoom/RTL/international-name workflows allow staff to distinguish Associated with ministry from all independently granted rights; restricted alias never falls back to legal name/location.

Primary source cases are reproduced below so the implementer can distinguish one slice's first-demonstration ownership from later composed regression proof. An AC whose multi-step scenario crosses a later producer must have that producer as a genuine blocker or its primary acceptance transferred explicitly; a mocked internal owner does not satisfy it.

Testing and verification

Ship the behavior with substantive TDD, actual authenticated Mission Control browser → API → disposable Supabase persistence evidence, effective-role grant/RLS/RPC negatives and two independently controlled PostgreSQL sessions for each named race; also fault before/after commit, lost-response reconciliation, same-key changed-meaning refusal, current-authority replay disclosure and keyboard/screen-reader/320 CSS px/400% zoom/single-pointer proof. Existing mocked task/CRM and demo-auth suites are regression seams only. Record the exact new focused test files and runnable commands in the implementation PR; no invented test:p27 command is claimed here. Add concurrent half-open interval and retirement/current-reference fixtures. Use actual non-owner database roles and independently configured current authorization; service-role setup alone is not proof.

Verified existing commands (future implementation checks, not a claim that this new behavior is already tested):

  • bunx vitest run tests/unit/packages/api/admin/mission-control-tasks.test.ts tests/unit/packages/api/crm-auth.test.ts
  • bun run verify:supabase-migrations
  • bun run verify:data-boundary
  • bun run test:e2e:strict
  • bun run test:a11y:strict
  • bun run ci:preflight

Commands verified in current develop package manifest/test paths but NOT executed for this research task.; verify:supabase-migrations uses disposable localhost PostgreSQL compatibility bootstrap, not alone a real Supabase auth/Data API/RLS gate.; test:e2e:strict still needs genuinely authenticated non-demo disposable backend configuration; default Playwright environments include auth bypass.; Unit tests are in-memory mocks; SQL migration test is static inspection, neither qualifies database authorization or atomicity.

Completion evidence must link the implementation commit and exact focused test commands/results to the listed story/REQ/AC IDs; show before/after user or source outcomes, real-role denial evidence and each relevant race/recovery result. Extend existing stable seams using red-green-refactor; no production test endpoint, alternate task engine or copied in-memory owner substitutes.

Operational controls

Honor reached source controls; this slice introduces no additional monitor threshold. Final qualification does not postpone source repair or safe failure behavior.

Blocked by

Non-issue prerequisites

Implementation constraints

  • Use current Bun/Turborepo/Next.js, shared Base UI/base-maia and approved TanStack data boundaries; no new component library or feature-local Supabase.
  • Source state, attributable history, effect receipt and required local work/owned exception commit together; external owners remain independent. Current authority governs result/replay disclosure.
  • Server derives actor and scope. Qualify old and resulting row, all changed fields, grants/RLS USING and WITH CHECK semantics, views/RPC/service/import/repair paths. Assignment, membership and stage grant nothing.
  • No automatic donor outreach, lifecycle transition, supporter assent, finance mutation, public publication, missionary quota or compulsory encounter logging.
  • Security, atomicity, source ownership, accessible UX, migrations/old-writer fencing and the applicable Q01–Q06/Q09–Q11/Q13/Q14 proof travel inside this behavior; terminal release qualification is not a substitute.
  • Document merge gate: use root documentation PR exact immutable commit before implementation; approval of specification does not approve this ticket breakdown. Parent AL-1992 — Phase 27 Donor Development & Portfolio Management specification #1992 remains unchanged.

Read AGENTS.md, the nearest scoped instructions, testing rules, document authority and OpenSpec rules. The explicitly invoked to-tickets skill governs this ticket; do not invoke implementation as part of ticket publication. For implementation, inspect current manifests and installed framework guidance rather than freezing this baseline's versions into new code.

Fresh-context scope: assigned_acceptance_text_tokens_approx: 0; estimated_total_input_tokens: 11500; basis: Exact currently assigned full-primary AC text chars/4 plus 11,500 for requirement paragraphs, scoped instructions, focused actual source implementations and tests. Shared producer obligations are linked explicitly; do not load every predecessor volume. This is an estimate, not a tokenizer measurement.. Read focused source and tests for this behavior; retained historical review records are provenance, not mandatory bulk context. If a consumed source is missing, report its exact contract and keep the affected behavior gated; do not quietly expand the ticket to implement a whole earlier phase.

Delivery boundary

This ticket proves its behavior and mapped acceptance on its qualified inputs. It does not close the parent, mark unrelated requirements complete, satisfy another owner's unresolved decision, activate an unqualified cohort, or authorize merging the documentation PR. E7/C-01 remain explicit where reached. All retained implementation task boxes stay unchecked during planning.

Activity

  1. changed the title [-]P27-S01: [Phase 9] Manage one stable Ministry Assignment and its separate participant memberships[/-] [+]AL-1998: [Phase 9] Manage one stable Ministry Assignment and its separate participant memberships (P27-S01)[/+] on Oct 11, 2026
  2. II-ricky-bobby-II commented on Oct 24, 2026

    @II-ricky-bobby-II
    Contributor

    Final spec r1 (Oct 24, 2026)

    Status: pending Ready, prep only. Building starts only after Blake marks the issue Ready. This revision changes no labels and adds no Ready.

    Scope: this spec covers the Phase 9 database source for one stable Ministry Assignment and its separate Party Participant Memberships (ADR-0136, Phase 9 dated D19 amendment). As filed, the issue spans five things: source tables, write commands, the server API with Phase 12 PDP integration, the People & access UI, and a legacy census. That is well over the size caps (§10). OQ1 proposes a split. By default, #1998 keeps S1: the source schema, the append-only history, the guarded command and narrow read functions, the read-only legacy census, and real-database race and grant proof. The browser and API surfaces move to follow-ups that stay gated on the Phase 12 runtime.

    §0 What I checked (Oct 24, 2026, about 4:35 PM PT)

    • Base: develop 6ac594d9 (Oct 24, 2:34 PM PT), after a full fetch of 41 refs. The newest migration is 20261024120000. There are 83 versioned files, plus 5 rollback_* scripts with no version.
    • Issue state:
      • Open; labels complexity:hard, type:feature. The body still says status:blocked, but that label isn't applied.
      • No comments, so no earlier Scope spec exists. This is r1.
    • Governing documents, all on develop:
      • PR docs(AL-1992): publish Phase 27 specification and implementation handoff #1993 merged Oct 11 (8:11 AM PT), so the body's "unmerged" non-issue prerequisite is satisfied. Spec commit b5a65b3f is in history.
      • docs/adr/0136-organization-owned-ministry-assignments-and-separated-support-access.md §Decision, "Database enforcement" paragraph: complete Tenant + Legal Entity scope, composite FKs, non-overlapping half-open intervals, scope-complete uniqueness and idempotency, deletion restriction, forced coarse Tenant RLS, no anon/authenticated grants on raw relations, and definer functions outside exposed schemas with a fixed search_path and PUBLIC EXECUTE revoked.
      • docs/prds/sitestacker-parity/phase-09-full-crm-depth-relationship-graph.md:1299-1341: the D19 amendment. It describes a membership as "prospective, effective-dated, append-only-corrected", and says the Ministry Assignment is distinct from Party, household, relationship edge, Ministry Project, Support Assignment and Page.
    • Catalog (scratch PG17, all develop migrations applied):
      • There is no parties, persons, households or legal_entities table, and no column named party_id or legal_entity_id anywhere.
      • missionaries has no person_id column. Its columns are id, tenant_id, profile_id, bio, …, birth_date.
      • Extensions: plpgsql, pgcrypto, uuid-ossp. No btree_gist. No migration uses EXCLUDE or tstzrange yet.
      • authz has memberships plus current_tenant_id() (JWT app_metadata.tenant_id), current_user_id(), has_membership, has_staff_membership and is_super_admin(). No PDP, epoch, grant table or principal exists on develop.
    • Name collision: public.support_assignments (20260515025814_support_hub_core_modules.sql:448) is the Support Hub conversation assignment. It is not Phase 21's Support Assignment (ADR-0108). S1 references neither.
    • Existing CRM code:
      • packages/api/src/crm/auth/access.ts:22-62: requireCrmAccess is a role bridge (staff/admin/super_admin). The issue says this "is not a substitute" for Phase 12.
      • packages/api/src/admin/crm/relationships/service.ts:51-61 reads donors/missionaries/profiles through the RelationshipsClient.
      • apps/admin/app/(app)/crm/detail-drawer.tsx (694 lines) is the CRM People surface that S3 would extend.
    • Upstream specs this builds on:

    §1 Intended behavior

    A Ministry Assignment is an organization-owned row with exactly one Tenant and one Legal Entity. Its identity never changes. Its name and lifecycle change only by appending a version, under a compare-and-swap on the head version.

    A membership links one same-Tenant person Party to one assignment for one half-open interval [valid_from, valid_to). For one (Tenant, assignment, Party):

    • in-effect intervals never overlap;
    • adjacent intervals are allowed.

    Ending or correcting a membership appends a successor row and marks the old one superseded. Nothing is deleted.

    Every command:

    • is idempotent per (tenant_id, idempotency_key);
    • returns the original result for the same key and the same request hash;
    • refuses the same key with a different request hash;
    • commits its source row, history and receipt in one transaction, or nothing.
    Threat Control in S1
    Browser or anon reads raw rows Explicit REVOKE from PUBLIC/anon/authenticated/service_role; no grants; RLS ENABLE+FORCE
    Cross-tenant write or read through guarded functions TO tenant_guard policies keyed on tenant_guard.current_tenant_id(); composite (tenant_id, …) FKs; tenant_id echo
    Party from another tenant, a tombstoned Party, or a non-person Party Composite FK (tenant_id, party_id) → parties; command refuses merged_into_party_id IS NOT NULL or party_kind <> 'person'
    Legal Entity mismatch between assignment and membership Composite FK (tenant_id, legal_entity_id, ministry_assignment_id) → assignment's UNIQUE (tenant_id, legal_entity_id, id)
    Concurrent overlapping adds EXCLUDE USING gist over in-effect rows (needs btree_gist, OQ3)
    Lost response, retry, ABA Receipt table; head-version CAS (versions only increase)
    Delete or rewrite of history Triggers reject DELETE on all four tables, and reject UPDATE except the one-time supersede and head bump; FKs are ON DELETE RESTRICT
    Membership treated as a grant No column, function or policy derives access from membership; a done criterion checks this

    Non-goals: Public Ministry Page, Support Binding Version, Phase 21 Support Assignment, Page contributors or display, notifications, finance, invitations, the PDP itself, Realtime, views, requireCrmAccess changes, inferred memberships, and writes to missionaries.person_id.

    §2 In (S1)

    1. One migration, 20261024185000_ministry_assignments_source.sql (provisional version, §11), containing:
      • CREATE EXTENSION IF NOT EXISTS btree_gist WITH SCHEMA extensions (OQ3).
      • public.ministry_assignments:
        • columns: tenant_id, id, legal_entity_id, head_version int NOT NULL DEFAULT 1, created_by_user_id, created_at;
        • PK (tenant_id, id) and UNIQUE (tenant_id, legal_entity_id, id);
        • FK (tenant_id, legal_entity_id) → legal_entities ON DELETE RESTRICT.
      • public.ministry_assignment_versions (append-only):
        • columns: display_name, lifecycle_status CHECK ('active','retired'), change_kind CHECK ('create','revise','retire','owner_certified_correction'), reason, actor_user_id, recorded_at;
        • PK (tenant_id, ministry_assignment_id, version);
        • composite FK to the assignment.
      • public.ministry_assignment_memberships:
        • columns: party_id, valid_from, valid_to (NULL = open), change_kind CHECK ('add','end','correct'), supersedes_membership_id, superseded_by_membership_id, reason, actor_user_id, recorded_at;
        • CHECK valid_to IS NULL OR valid_to > valid_from;
        • EXCLUDE USING gist (tenant_id WITH =, ministry_assignment_id WITH =, party_id WITH =, tstzrange(valid_from, valid_to, '[)') WITH &&) WHERE (superseded_by_membership_id IS NULL).
      • public.ministry_assignment_command_receipts: PK (tenant_id, idempotency_key), plus command, request_hash, result jsonb, actor_user_id and recorded_at.
      • Named constraints throughout. The three guard triggers.
      • Guarded functions in schema tenant_guard, following [Phase 4] #505 slice 1/4: withTenant tenant guard (non-owner, non-BYPASSRLS role, fail-closed) #2250's pattern (owner tenant_guard, SET search_path = '', every name schema-qualified, REVOKE EXECUTE … FROM PUBLIC, anon, authenticated, GRANT EXECUTE … TO service_role, each sets the GUC from p_tenant_id and echoes tenant_id):
        • ministry_assignment_create
        • ministry_assignment_revise (CAS on p_expected_version; also covers retire and correction)
        • ministry_membership_add
        • ministry_membership_end
        • ministry_membership_correct
        • ministry_assignment_current: the narrow read. It returns id, legal entity, current name, lifecycle, eligible = (lifecycle_status = 'active'), head version, and the party ids currently in effect. It never returns raw membership rows.
      • Command rules:
        • add and end are prospective (valid_from/valid_to ≥ statement_timestamp());
        • only correct may change a past interval, and it needs a non-empty reason (OQ5);
        • adds are refused on a retired assignment (OQ6).
      • Tables: ENABLE + FORCE RLS. One policy per table: "<table> tenant guard isolation" FOR ALL TO tenant_guard USING/WITH CHECK (tenant_id = tenant_guard.current_tenant_id()).
      • Grants: explicit REVOKE ALL … FROM PUBLIC, anon, authenticated, service_role. Then tenant_guard gets only SELECT, INSERT on all four tables, plus UPDATE (superseded_by_membership_id) on memberships and UPDATE (head_version) on assignments.
      • [Phase 4] #505 slice 2/4: ENABLE + FORCE RLS and at least one policy on every public tenant table #2251's fail-closed owner-bypass precondition, repeated for the four new tables.
    2. Read-only census scripts/verify/ministry-assignment-census.mjs:
      • It reads missionaries (tenant_id, id, profile_id), never * and never person_id.
      • Per tenant, it emits JSON counts: missionaries rows, rows with no Ministry Assignment mapping (today: all of them), and ambiguity classes (one profile on several rows; no Party yet).
      • It writes nothing, creates no assignment or membership, and infers nothing from names, households or URLs.
    3. docs/features/crm-ministry-assignments/README.md: the model, the commands, "membership grants nothing", and how to run the census.

    §3 Out (S1)

    §4 Done criteria

    (A) Acceptance, real stack (tests/unit/acceptance/issue-1998/ministry-assignments.realstack.acceptance.test.ts; two independent pg clients; fixtures are 2 tenants, 2 legal entities, and persons, a household and a tombstoned Party):

    • a1. An assignment with zero members is created and ministry_assignment_current returns it as eligible. It needs no Page, gift, email, binding or login.
    • a2. Many-to-many: one Party belongs to 2 assignments, and 2 Parties belong to 1 assignment. A revise (rename) keeps id.
    • a3. Intervals:
      • (i) [t1,t2) then [t2,t3) both commit (adjacent).
      • (ii) Two sessions add overlapping intervals for the same key concurrently. Exactly one commits; the other gets 23P01.
      • (iii) end and correct append rows; the predecessor's superseded_by is set; row count only grows.
    • a4. Scope attacks, each refused:
      • a cross-tenant party_id;
      • a cross-legal-entity membership;
      • a mismatched p_tenant_id vs. a row id;
      • household or tombstoned Party;
      • anon/authenticated SELECT/INSERT/EXECUTE (42501);
      • service_role direct table SELECT (42501);
      • a GUC set for tenant A reading tenant B through the guard role (0 rows).
    • a5. Idempotency: the same key with the same hash returns the original ids, actor and time with no new rows. The same key with a different hash is refused. A fault injected after the first insert leaves 0 rows in all four tables.
    • a6. CAS: two sessions revise from the same expected_version. One wins; the other is refused; there is no lost update. An ABA attempt with a stale version is refused.
    • a7. Retire: eligible=false; history and memberships are kept; a new add is refused; end and correct still work.
    • a8. Grants nothing: catalog query. No policy, function or view outside the six functions references ministry_assignment_memberships, and no authz object reads it.

    (B) Unit and static (tests/unit/scripts/ministry-assignment-census.test.ts and tests/unit/supabase/ministry-assignments-migration.test.ts):

    • b1. The census selects only the three named columns. It emits unresolved counts and makes no write calls (spy).
    • b2. The migration has the REVOKE list before any GRANT, FORCE on all four tables, PUBLIC EXECUTE revoked on all six functions, search_path = '', and no is_super_admin, authz.current_tenant_id or person_id tokens.

    (S) Security

    (Handback) The PR body lists the test files, the commands and their results, the s1 result, the census output on the seeded DB, and Closes #1998 only if OQ1 is approved. Otherwise it uses Part of #1998.

    §5 Mapping (issue AC → S1 criteria)

    Issue AC S1 Deferred
    Zero-participant selectable ministry a1 UI selection (S3)
    Many-to-many, identity survives a2 —
    Half-open intervals, concurrency, evidence a3, a5 —
    Current authority before list/detail; attacks a4 (DB layer) PDP, field and purpose authority (S2)
    Membership grants nothing a8 Independent-grant outcomes (S2)
    Retirement / owner-certified correction a6, a7 Downstream consumption (#2004, #1285)
    Lost response / ABA / revocation race a5, a6 Revocation race (S2, needs epoch)
    Migration census, no inference b1, census —
    Keyboard / screen reader / 320 px / 400% — S3

    §6 Declared edits

    Path Change Owner
    supabase/migrations/20261024185000_ministry_assignments_source.sql new Builder
    scripts/verify/ministry-assignment-census.mjs new Builder
    docs/features/crm-ministry-assignments/README.md new Builder
    tests/unit/acceptance/issue-1998/ministry-assignments.realstack.acceptance.test.ts new Acceptance
    tests/unit/scripts/ministry-assignment-census.test.ts new Acceptance
    tests/unit/supabase/ministry-assignments-migration.test.ts new Acceptance
    s1 live-name check (no file) read-only Builder or Acceptance

    §7 Pins

    §8 CI

    No new step and no workflow edit.

    §9 Data, grants and missionaries.person_id

    §10 Size

    • Whole issue as filed: about 1,750 code+docs (migration ~420, API ~500, UI ~600, census ~140, docs ~90) and about 1,500 tests. That's over both caps, so OQ1 proposes a split.
    • S1: migration ~420, census ~140, docs ~70 ≈ ~630. Tests: real stack ~520, unit/static ~200 ≈ ~720. Within caps.

    §11 Dependencies and migration

    §12 OQs (defaults in bold)

    1. Split. Default: AL-1998: [Phase 9] Manage one stable Ministry Assignment and its separate participant memberships (P27-S01) #1998 = S1 (this spec). Scope files S2 (API + PDP) and S3 (UI + a11y) as follow-ups after Samson approves, each blocked by S1 and the Phase 12 components. Alternative: keep one issue over the caps.
    2. Native links. Default: Samson adds "blocked by [Phase 7] Party spine: parties supertype + persons anchor + households + household_members + org subtype + org_contacts + donors.party_id #570, [Phase 7] Canonical Legal Entity and immutable issuer-profile runtime foundation (Backlog draft) #1947, [Phase 4] #505 slice 1/4: withTenant tenant guard (non-owner, non-BYPASSRLS role, fail-closed) #2250" to AL-1998: [Phase 9] Manage one stable Ministry Assignment and its separate participant memberships (P27-S01) #1998. Scope adds nothing until told.
    3. btree_gist. Default: enable it in extensions and use EXCLUDE. That's race-proof without app locking. Alternative: an advisory-lock-per-key trigger, which is weaker and harder to prove.
    4. Participant kind. Default: person Parties only (ADR-0136: every spouse or teammate keeps a separate Party). Alternative: allow org.
    5. Past intervals. Default: add/end are prospective only; correct may change past intervals with a required reason.
    6. Retired assignments. Default: refuse new adds; allow end and correct.
    7. [Phase 12] #668 slice (c): positive tenant-RLS catalog rules, livePolicies baseline, identity exemptions, cross-tenant-edge gate #2294 ordering. Default: if [Phase 12] #668 slice (c): positive tenant-RLS catalog rules, livePolicies baseline, identity exemptions, cross-tenant-edge gate #2294 lands first, S1's guard policies must pass its rules; if S1 lands first, [Phase 12] #668 slice (c): positive tenant-RLS catalog rules, livePolicies baseline, identity exemptions, cross-tenant-edge gate #2294 inventories them.
    8. Version. Default: reserve 20261024185000 provisionally; re-pick at build if any prerequisite sorts later.
    9. Attribution. Default: actor_user_id uuid (auth.users id) until [Phase 12] Principal foundation: four principal classes + active-assignment primitive + fire-time re-resolution + profiles.role inert-for-authz #669 principals exist. S2 maps principal → user.
    10. Containment. Default: S1 ships only after Blake's P0 containment decision on production donors/profiles, with no special handling. S1 adds no anon surface.

    §13 Deploy

    Assume production is behind. Production is missing all 58 develop versions from 20260515025814 to 20261024120000 (25 applied, newest 20260513173938), and the anon P0 on donors/profiles is under Blake's containment decision. Dev is 55 behind, with four 20261001* collisions and an extra 20260519233845. develop's catalog is live nowhere hosted.

    Samson relays to Blake at merge. The factory runs nothing hosted.

    §14 Flags

  3. II-ricky-bobby-II commented on Oct 24, 2026

    @II-ricky-bobby-II
    Contributor

    Spec r1 approved (Samson), pending Ready (prep only), all defaults (OQ1–OQ10). OQ1 (split ruling, under Blake's standing grant of Oct 24, 2026 to split any issue into slices of about 1k lines): #1998 keeps S1, the database layer: 4 tables and 6 functions, writes only through #2250's tenant-guard functions, anon, authenticated and service_role revoked, RLS enabled and forced, no #2253 allowlist growth. That's about 630 lines of code and docs plus about 720 of tests, inside the budget. Scope files S2 (API) and S3 (UI) as not-Ready follow-ups. OQ2: add native blocked-by links to #570, #1947 and #2250. It can't build until #570, #606, #1947 and #2250 are on develop. OQ3: btree_gist with EXCLUDE. Adding the extension is a new dependency, so I'll tell Security Watch when the build adds it. OQ4: only person Parties can be members. OQ5: only a correction can backdate, and it needs a reason. OQ6: a retired assignment refuses new members. OQ7: whichever of #2294 and S1 lands second does the update. OQ8: migration 20261024185000 is provisional and reserved, and Builder re-checks it's unique before handback. OQ9: the auth.users id is the actor until Phase 12 principals exist. OQ10: production ordering follows Blake's containment decision and the backlog-first rule.

  4. II-ricky-bobby-II commented on Oct 24, 2026

    @II-ricky-bobby-II
    Contributor

    Split recorded (Scope, Oct 24, 2026). Samson approved the split under OQ1 of r1 (#1998 (comment)), using Blake's standing grant of Oct 24, 2026 to split issues into slices of about 1k lines.

    Labels unchanged; no Ready.

  5. II-ricky-bobby-II commented on Oct 26, 2026

    @II-ricky-bobby-II
    Contributor

    Pins addendum (Scope, Oct 26, 2026, 8:20 AM PT), pending Samson approval:

    1. Migration renumbered: 20261024185000_ministry_assignments_source.sql becomes 20261025124500_ministry_assignments_source.sql. The old version sorts below develop's newest (20261025120000, develop 88eefbd9).
    2. Order: it sorts after every slice it consumes: [Phase 7] #570 slice a/2: party spine tables, party fill for existing donors, donors.party_id, census v4 and docs #2271 20261025124000 and [Phase 7] #570 slice b/2: tenant-guarded party accessors (tenant_guard grants, guarded functions, packages/api/src/party) #2272 20261025124100 ([Phase 7] Party spine: parties supertype + persons anchor + households + household_members + org subtype + org_contacts + donors.party_id #570's parties), [Phase 9] #606 slice a/3: parties lifecycle_status, tags, same-kind merge tombstone; spine verification; seed regeneration #2278 20261025124200 and [Phase 9] #606 slice b/3: drop donors.type, organization, spouse; replace donors_assign_party; census v5; minimal excision #2279 20261025124300 ([Phase 9] Extend the party spine: lifecycle_status, tags, merge tombstone; regenerate seeds #606's tombstone), and [Phase 7 #1947a] #1947 follow-up: legal_entities root, idempotent one-default setup and exact Tenant+Entity check #2403 20261025124400 (#1947a).
    3. Legal Entity pin: the legal_entities (tenant_id, id) FK target now comes from [Phase 7 #1947a] #1947 follow-up: legal_entities root, idempotent one-default setup and exact Tenant+Entity check #2403 (#1947a, spec r1 [Phase 7 #1947a] #1947 follow-up: legal_entities root, idempotent one-default setup and exact Tenant+Entity check #2403 (comment)). The shape is unchanged.
    4. Native link added: AL-1998: [Phase 9] Manage one stable Ministry Assignment and its separate participant memberships (P27-S01) #1998 is blocked by [Phase 7 #1947a] #1947 follow-up: legal_entities root, idempotent one-default setup and exact Tenant+Entity check #2403. Read back: blocked by [Phase 9] Extend the party spine: lifecycle_status, tags, merge tombstone; regenerate seeds #606, [Phase 7] Party spine: parties supertype + persons anchor + households + household_members + org subtype + org_contacts + donors.party_id #570, [Phase 7] Canonical Legal Entity and immutable issuer-profile runtime foundation (Backlog draft) #1947, [Phase 4] #505 slice 1/4: withTenant tenant guard (non-owner, non-BYPASSRLS role, fail-closed) #2250 and [Phase 7 #1947a] #1947 follow-up: legal_entities root, idempotent one-default setup and exact Tenant+Entity check #2403. The existing links are unchanged, because the body and the r1 approval (OQ2) name [Phase 7] Party spine: parties supertype + persons anchor + households + household_members + org subtype + org_contacts + donors.party_id #570 and [Phase 9] Extend the party spine: lifecycle_status, tags, merge tombstone; regenerate seeds #606 as whole-issue prerequisites.
    5. Downstream: S2 [Phase 9] #1998 slice S2: Ministry Assignment server API through withTenant and the Phase 12 PDP #2315 (20261024212000) and AL-2069 — [P28-W01] Enter and switch the exact authorized workspace context #2069 (20261024215000) must sort after this version and after [#670 (a)] Governance epoch and governance_version: the per-tenant counter #2287's. Each needs its own renumber addendum, which is not posted here.
    6. Version is reserved; Samson may renumber at handback to keep merge order.
  6. II-ricky-bobby-II commented on Oct 26, 2026

    @II-ricky-bobby-II
    Contributor

    Pins addendum (Scope, Oct 26, 2026, 8:24 AM PT), approved by Samson under the standing authorization:

    1. Dependencies narrowed: in approval 6390959381 OQ2, "[Phase 7] Party spine: parties supertype + persons anchor + households + household_members + org subtype + org_contacts + donors.party_id #570, [Phase 9] Extend the party spine: lifecycle_status, tags, merge tombstone; regenerate seeds #606, [Phase 7] Canonical Legal Entity and immutable issuer-profile runtime foundation (Backlog draft) #1947 and [Phase 4] #505 slice 1/4: withTenant tenant guard (non-owner, non-BYPASSRLS role, fail-closed) #2250" now reads [Phase 7] #570 slice a/2: party spine tables, party fill for existing donors, donors.party_id, census v4 and docs #2271 (from [Phase 7] Party spine: parties supertype + persons anchor + households + household_members + org subtype + org_contacts + donors.party_id #570: parties (tenant_id, id)), [Phase 9] #606 slice a/3: parties lifecycle_status, tags, same-kind merge tombstone; spine verification; seed regeneration #2278 (from [Phase 9] Extend the party spine: lifecycle_status, tags, merge tombstone; regenerate seeds #606: merged_into_party_id tombstone), [Phase 7 #1947a] #1947 follow-up: legal_entities root, idempotent one-default setup and exact Tenant+Entity check #2403 (from [Phase 7] Canonical Legal Entity and immutable issuer-profile runtime foundation (Backlog draft) #1947: legal_entities (tenant_id, id)) and [Phase 4] #505 slice 1/4: withTenant tenant guard (non-owner, non-BYPASSRLS role, fail-closed) #2250 (closed). S1 can't build until [Phase 7] #570 slice a/2: party spine tables, party fill for existing donors, donors.party_id, census v4 and docs #2271, [Phase 9] #606 slice a/3: parties lifecycle_status, tags, same-kind merge tombstone; spine verification; seed regeneration #2278 and [Phase 7 #1947a] #1947 follow-up: legal_entities root, idempotent one-default setup and exact Tenant+Entity check #2403 are on develop.
    2. Body wording: the body's "currently [Phase 7] Party spine: parties supertype + persons anchor + households + household_members + org subtype + org_contacts + donors.party_id #570, [Phase 9] Extend the party spine: lifecycle_status, tags, merge tombstone; regenerate seeds #606, [Phase 7] Canonical Legal Entity and immutable issuer-profile runtime foundation (Backlog draft) #1947 and [Phase 4] #505 slice 1/4: withTenant tenant guard (non-owner, non-BYPASSRLS role, fail-closed) #2250" now reads "currently [Phase 7] #570 slice a/2: party spine tables, party fill for existing donors, donors.party_id, census v4 and docs #2271, [Phase 9] #606 slice a/3: parties lifecycle_status, tags, same-kind merge tombstone; spine verification; seed regeneration #2278, [Phase 7 #1947a] #1947 follow-up: legal_entities root, idempotent one-default setup and exact Tenant+Entity check #2403 and [Phase 4] #505 slice 1/4: withTenant tenant guard (non-owner, non-BYPASSRLS role, fail-closed) #2250". The body itself is not edited.
    3. Native links: they now match item 1. [Phase 7] #570 slice a/2: party spine tables, party fill for existing donors, donors.party_id, census v4 and docs #2271 and [Phase 9] #606 slice a/3: parties lifecycle_status, tags, same-kind merge tombstone; spine verification; seed regeneration #2278 were added, and [Phase 7] Party spine: parties supertype + persons anchor + households + household_members + org subtype + org_contacts + donors.party_id #570, [Phase 9] Extend the party spine: lifecycle_status, tags, merge tombstone; regenerate seeds #606 and [Phase 7] Canonical Legal Entity and immutable issuer-profile runtime foundation (Backlog draft) #1947 were removed. [Phase 7] Canonical Legal Entity and immutable issuer-profile runtime foundation (Backlog draft) #1947 is the parent of [Phase 7 #1947a] #1947 follow-up: legal_entities root, idempotent one-default setup and exact Tenant+Entity check #2403 and is no longer in the dependency list. Read back: blocked by [Phase 4] #505 slice 1/4: withTenant tenant guard (non-owner, non-BYPASSRLS role, fail-closed) #2250, [Phase 7] #570 slice a/2: party spine tables, party fill for existing donors, donors.party_id, census v4 and docs #2271, [Phase 9] #606 slice a/3: parties lifecycle_status, tags, same-kind merge tombstone; spine verification; seed regeneration #2278 and [Phase 7 #1947a] #1947 follow-up: legal_entities root, idempotent one-default setup and exact Tenant+Entity check #2403.
    4. The migration stays 20261025124500 (pins addendum AL-1998: [Phase 9] Manage one stable Ministry Assignment and its separate participant memberships (P27-S01) #1998 (comment)). Version is reserved; Samson may renumber at handback to keep merge order.
    5. This is AL-1998: [Phase 9] Manage one stable Ministry Assignment and its separate participant memberships (P27-S01) #1998's 2nd addendum since approval 6390959381. Any further change needs a consolidated revision (r2).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions