Skip to content

feat(people): add organizations and employment records - #593

Open
salmonumbrella wants to merge 1 commit into
kenn-io:mainfrom
salmonumbrella:organizations-employment-v0193-publication
Open

feat(people): add organizations and employment records#593
salmonumbrella wants to merge 1 commit into
kenn-io:mainfrom
salmonumbrella:organizations-employment-v0193-publication

Conversation

@salmonumbrella

Copy link
Copy Markdown
Contributor

What changed

  • Add first-class organizations with versioned profiles, typed custom attributes, lifecycle and merge operations, and reviewable duplicate suggestions.
  • Add current and historical employment records with primary-employment constraints and read-time person and vCard projections.
  • Keep SQLite and PostgreSQL behavior aligned, and expose the model through daemon APIs, generated clients, and organization and employment CLI commands.

Why

People can have several current or historical roles, but copied company and title fields cannot preserve that history or represent one organization consistently across people. First-class records make employment changes queryable without losing source evidence.

Usage

msgvault organization create "Example Corp" --domain example.com
msgvault employment add --person 42 --organization 7 --title "Engineer" --start 2024-01 --primary

Refs #534

@roborev-ci

roborev-ci Bot commented Aug 9, 2026

Copy link
Copy Markdown

roborev: Combined Review (538ed05)

The change is generally sound, but three medium-severity API and profile-reconciliation gaps should be addressed.

Medium

  • Missing organization attribute clear endpointinternal/api/organizations.go:281
    Organization attributes can be listed and replaced, but no route invokes SupersedeOrganizationAttributeValueContext. Clients therefore cannot clear an attribute without replacing it, and cannot remove values whose definitions are inactive. Add a DELETE/clear endpoint analogous to the person-attribute endpoint, including ordinal, expected-value, and dry-run handling, and expose it through the generated client and CLI.

  • Profile reconciliation can discard metadata changes or violate uniquenessinternal/store/organization_profile.go:455
    Reconciliation treats rows as unchanged based only on their normalized business key, silently dropping changes to writable metadata such as pref, ordinal, source fields, and vCard identity. Changing a value while preserving its vCard property identity can also insert the replacement before superseding the old row, violating the active property-identity uniqueness constraint. Compare all writable fields, explicitly match durable vCard identities, and supersede changed or removed rows before inserting replacements.

  • Organization profile request limit is too small for supported mediainternal/api/organizations.go:491
    Profile PUT uses the generic 1 MiB decoder even though inline organization media supports up to 8 MiB. Base64 payloads larger than roughly 750 KiB are rejected before reaching the store-level limit. Use a dedicated decoder sized for an 8 MiB base64 payload, similar to the person-profile decoder, and return the appropriate oversized-request response.


Reviewers: 2 done | Synthesis: codex, 16s | Total: 11m42s

@salmonumbrella
salmonumbrella force-pushed the organizations-employment-v0193-publication branch from 538ed05 to 7c31adf Compare August 11, 2026 21:37
@roborev-ci

roborev-ci Bot commented Aug 11, 2026

Copy link
Copy Markdown

roborev: Combined Review (7c31adf)

Medium-severity lifecycle and media round-trip gaps remain; no Critical or High findings were reported.

Medium

  • internal/store/organization_attributes.go:179 — Organization attributes can reference people, but person deletion checks only person_attribute_values. Deleting a person referenced by a current organization attribute leaves a dangling value_record_id.

    • Fix: Include current organization_attribute_values references in DeletePersonContext and add a matching record-reference index.
  • internal/store/organizations.go:382 — Deleting a merge survivor with no employments reaches the database delete, but losing organizations still reference it through merged_into_id ... ON DELETE RESTRICT. This produces an internal database error instead of a defined lifecycle conflict.

    • Fix: Detect inbound merged redirects and return a typed conflict, or explicitly repoint/remove those redirects during deletion.
  • internal/api/organizations.go:143 — Inline organization media can be uploaded, but responses expose only metadata, with no content-read endpoint or stable existing-media reference accepted by replacement requests. Stored blob-only media therefore cannot be downloaded or preserved through a GET/modify/PUT round trip.

    • Fix: Add organization-media content retrieval analogous to person media and allow replacements to retain an existing media row without re-uploading its bytes.

Reviewers: 2 done | Synthesis: codex, 12s | Total: 11m4s

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant