Skip to content

feat(people)!: manage Auth-backed people before and after their first login - #99

Open
BIA3IA wants to merge 15 commits into
mainfrom
feat/people-single-source
Open

BIA3IA wants to merge 15 commits into
mainfrom
feat/people-single-source

Conversation

@BIA3IA

@BIA3IA BIA3IA commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

People reads admin and team roles from PoliNetwork Auth and membership from the Microsoft 365 Soci group. Each operation reads these sources through an in-memory cache (Auth: 15 seconds; Soci: 5 minutes), keeping history and People-specific fields in the register.

  • Admin status comes from the role bound to admin in Auth. People no longer reads Telegram group administrators from the bot, tracks group join requests, or records group removals in census answers.
  • The Capo Admin area lists active admins who have the caller as Capo of reference. Auth role revocation removes them from that view; personal email and member details remain protected.
  • Linked records send role changes to Auth through the existing idempotent outbox. Until Auth confirms a command, the record retains the roles Auth holds. Reads fail after an hour without Auth; shorter outages use the last answer.
  • Soci membership and employeeId determine member status and number. Recent admissions retain the existing listing grace period.

Supersedes #95 and the People bot integration in #98. Based directly on #97; no bot group-admin feature is required. Coordinate with PoliNetworkOrg/admin#136, stacked on admin#132.

Validation: bun run check, bun run typecheck, bun run test, bun run build, and bun run build:npm passed. 323 tests passed and 2 were skipped when rerun with a disposable PostgreSQL instance; both database integration suites passed. Regression coverage checks Auth role revocation, Capo scoping, contact redaction and census answers without Telegram groups.

BREAKING CHANGE: Admin and Capo responses omit Telegram group assignments and join requests. Census answers no longer take keepGroups; capo.approve and capo.decline are removed. Deploy the matching dashboard change together. Deploy Auth #73 and its role-date follow-up first, bind the People roles, and grant idp:people:roles to the backend client for role commands.

People before first login

New manual People creation requires only first and last name, with optional contact details, initial teams and admin status. The person and roles are created in Auth, even before login. Census details are optional on profile editing and are not required for creation; census submission/import validation remains strict.

Auth supplies verified-email/Telegram matches, name-only hints and conflict proposals to Linking. A Master Admin approves the link in Auth before the actual account receives the preliminary person's roles. Stable registration IDs preserve the People record/history across approval and restarts; declared Telegram contacts never cause automatic linking. Existing historical records/imports are not automatically migrated.

Requires the expanded Auth#75 (on Auth#73), including migration 0024, deployed first. No new dependency on the Telegram bot or legacy anonymous access is introduced.

Validation for this addition: check, typecheck, full unit suite, server and npm package builds passed; 320 tests passed and 10 were skipped without PostgreSQL. Five new backend tests cover minimal Auth creation, idempotent retry, approval, stable recovery and refusal of local production fallback or contact-based auto-claim.

Recent links and undoing identity links

Adds people.linking.recent (people:read) for current links made in the last 30 days, with method/date and actor category. Unlinking a preliminary identity delegates to Auth and requires Master Admin; normal record unlinking requires Auth-confirmed revocation of bound roles. The record/history remain and the detached account is excluded from future proposals. Linking/unlinking advances the role revision to reject in-flight stale directory responses.

Validation: check, typecheck, full suite including disposable PostgreSQL, server build and npm package build passed: 361 tests passed, 2 skipped. Requires the new unlink operation in Auth #73.

BIA3IA and others added 7 commits October 26, 2026 20:32
…y fields

Phase 6 is deployed and every caller uses IdP tokens, so the leftovers
kept for rollback and for older clients go:

- LEGACY_ANONYMOUS and the anonymous path: a request without a token is
  always refused, handlers always get the token's actor, and policy()
  loses its tokenOnly option. The e-signing development user that only
  worked on the anonymous path goes too.
- Grant compatibility fields: key, source and grantedBy (always
  idp:<id>, "idp" and null).
- tg_audit_log.admin_id (migration 0029). Rows that still have only
  admin_id get it copied into actor_tg_id first, so no author is lost.
- adminId and performerId in the socket events to the bot, which reads
  only actor and performer.

A router test now checks that every procedure refuses a call without
a token.
…g them

People needs who administers each Telegram group (admins' groups, the Capo Admin area, the census), and Telegram is the only source (RFC v3 decision 2). The backend now asks the bot over the socket (getGroupAdmins) for the groups it stores; the bot answers from its cache of Telegram, and nothing is stored here.

A group the bot has not read yet, or every group while the bot does not answer within 3 seconds, stays unknown (null), so People keeps what it last knew instead of reading "no admins".
The People register only filled through the dashboard and census
imports, so Overview, Admins, Teams and the member register stayed empty
while the same people held roles in PoliNetwork Auth and were in the
Microsoft 365 Soci group.

- Every five minutes the sweep reads the roles bound to People in the
  IdP (GET /api/internal/people-roles) and the Soci group. Role holders
  get linked records with their admin and team entries; Soci members get
  a membership numbered by employeeId. Entries People added on its own
  are left alone, and leaving the Soci group ends nothing.
- Dashboard changes to a linked record's admin or team role, census
  answers and imports give or take back the bound role through
  POST /api/internal/people-roles for the acting user; the IdP decides.
  Direct actions fail on a refusal, census and imports note it.
- Records known only from these sources have no campus, course or year
  yet: the contract's profile.campus is now nullable.
- The Design team is now called Design & Marketing.
- Imported memberships skip automatic fee reminders that year.

Needs the IdP change that adds the endpoint, and the idp:people:roles
scope on the backend client for writes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A failed POST to PoliNetwork Auth (timeout, reset, DNS) is now a
  RoleChangeError: census answers, imports and linking note it instead
  of failing, and direct actions get PRECONDITION_FAILED.
- Ending a role takes back only the bound roles given directly, using
  the IdP directory's new assigned list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Role changes on linked records become one durable command per person,
saved in the People transaction and sent to Auth after it commits, so the
People lock is never held across a network call. Until Auth confirms, the
record keeps its previous roles: a requested or refused role never grants
access. A refusal fails direct dashboard actions and flags the record for
review otherwise; no answer keeps the command for the sweep, which resends
it with the same ID.

- Linking a record ends the roles recorded before it until Auth confirms
  them, keeping their history, instead of leaving refused roles active.
- Auth owns every bound concept on linked records, so a Lead it refused
  can't survive a sync, and a sync that read Auth before a command was
  applied no longer overwrites it.
- Other role edits, unlinking and merging wait while a command is pending,
  and a pending Lead appointment counts as the team's Lead.
- Linked records take their account's Microsoft 365 ID, so a later
  PoliNetwork address doesn't create a second record on import, and
  identities matching several records are flagged instead of guessed.
- Refreshing a linked record's identities no longer rewrites how it was
  linked.

BREAKING CHANGE: needs PoliNetwork Auth with the command-based
POST /api/internal/people-roles; deploy Auth first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a linked record

A 401 from PoliNetwork Auth now refreshes the token and sends the same command once more; a second 401 is a refusal. Merging an unlinked record into a linked one diffs against the roles Auth confirmed and sends the command. README: the member import only covers new memberships.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…tion

People kept a copy of PoliNetwork Auth's roles and of the Microsoft 365 Soci group, refreshed every five minutes, and worked from it in between. Now each fact has one source and People reads it first on every operation:

- Roles: PoliNetwork Auth, cached in memory for 15 seconds. An admin or team entry is active exactly while its role is held, starting when Auth gave it when Auth says. If Auth hasn't answered for an hour, People refuses to work rather than decide who leads or administers from an old answer.
- Members and their number: the Microsoft 365 Soci group and employeeId, cached for 5 minutes. A register member no longer in the group becomes former, unless admitted in the last two days; a number changed in Microsoft 365 is taken over.
- The register keeps only history and People's own data. Role commands stay as an outbox for Auth.

The five-minute sync is gone; syncSources() reads both sources now, without the cache.
@coderabbitai

coderabbitai Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 57053db8-b244-422b-95bc-fc55e37b0c2f
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Remove bot group-admin reads, join requests and group-removal tracking. Scope the Capo area to assigned active Auth admins.

BREAKING CHANGE: People responses omit Telegram group assignments and join requests; census input no longer accepts keepGroups and capo.approve/capo.decline are removed.
@BIA3IA BIA3IA changed the title feat(people): read roles and members from their sources, without copying them feat(people)!: read admin roles from Auth and members from Microsoft 365 Oct 26, 2026
@BIA3IA
BIA3IA changed the base branch from feat/telegram-group-admins-sync to chore/remove-legacy-compat October 26, 2026 21:57
… links

Route new identities, contact changes and preliminary role assignments through Auth. Reconcile the stable registration ID after Master approval without duplicating the People record or trusting declared contacts. Keep census validation separate from minimal creation.
@BIA3IA BIA3IA changed the title feat(people)!: read admin roles from Auth and members from Microsoft 365 feat(people)!: manage Auth-backed people before and after their first login Oct 27, 2026
…urce

# Conflicts:
#	tests/people-router.test.ts
- My area answers `awaiting-approval` while a preliminary Auth person is
  proposed for the account. The record is shown only for a verified email or
  Telegram match; asking to be added is refused meanwhile.
- Provisional records are matched through Auth's proposals only, so a verified
  match no longer adds a duplicate name-only review.
- Accept Auth's `telegram-username` proposal reason.
@BIA3IA
BIA3IA changed the base branch from chore/remove-legacy-compat to main October 28, 2026 00:10
…me policy

- `identity.ts` is the only place that decides whether an account matches a
  record: Microsoft 365 ID, verified email or Telegram ID, then Telegram
  username, then name; ambiguous weak evidence matches nobody.
- `linking.ts` decides what a match leads to and holds the shared state
  changes: "Is this you?", reviews, automatic links and preliminary persons.
- Accounts come only from Auth's People directory, which now reports the
  verified email. Email matching works against real Auth for the first time;
  `esign-users` and the snapshot's Telegram IDs are no longer read for People.
- A record that carries roles is linked after HR review, not self-confirmed.
- A role holder is linked automatically only by a Microsoft 365 identity; a
  Telegram ID match becomes an `identity-match` review.
- Removes `candidate()`, the proposal loop and the matching filter in
  `syncDirectory`.

BREAKING CHANGE: People needs Auth's People directory with `verifiedEmail`
and no longer reads `proposals`; adds the `identity-match` review kind.
- Declining or dismissing a single-record match excludes that record for the
  account, so the review no longer opens again on the next pass.
- Matching looks records up by key, and sync indexes records by account and
  registration: a pass over every Auth account no longer scans the register.
- A record holding another Microsoft 365 account is a conflict for HR, and a
  role holder is auto-linked by address only when it is the one Microsoft 365
  gave the record.
- A preliminary person is never linked locally; stale preliminary reviews
  close when their record or account is gone; the reconcile marker is stored
  with the register; an unknown account rereads Auth at most every 2 seconds;
  empty names match nothing.
…a button

Every People email (invitation, census, fee reminders, rejected transfer,
signed receipt, HR questions) now renders one Italian template: logo,
greeting by first name, paragraphs, an optional button with the link
also as text, and a no-reply footer. The outbox keeps the button as an
optional action, so queued mail stays valid.
@toto04

toto04 commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

Please don't merge this. It's superseded by the People roadmap PoliNetworkOrg/admin#146.

Three parts of this PR conflict with the decisions there:

What to reuse:

  • src/people/roles.ts: reading the role directory, the outbox to Auth, and revision handling;
  • its tests;
  • the cache and outage behaviour.

These move into #101. The identity matching (identity.ts, linking.ts) isn't reused.

The order of work: the backend cleanup (#100) comes first, then #101 on top of the reworked Auth API (PoliNetworkOrg/auth#78). I'd close this PR once the new ones are open.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants