Skip to content

feat(#1169): give award recipients a person record from their first award - #1209

Merged
RonildoBraga merged 13 commits into
mainfrom
claude/1169-award-person-records
Oct 21, 2026
Merged

RonildoBraga merged 13 commits into
mainfrom
claude/1169-award-person-records

Conversation

@RonildoBraga

@RonildoBraga RonildoBraga commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Refs #1169

Backend half of #1169: award recipients become person records, the owner's option A from #1165. The web half is #1216, stacked on this PR. Both wait for the owner's OK on the journey page before merging.

What changed

Person records. Recording an award for a new recipient now creates their person record in the same transaction. That is a member, plus particulars whose source is the award.

  • They stay off the register of members and Shareholders until shares are issued, because those lists come from register positions.
  • Later awards, allotments, grants and transfers name that member with the retained particulars.
  • A new-member request under the recipient's ID is refused (member_reference_conflict), and different particulars are refused ("must match the retained particulars"). That closes the gap in the issue comments for all four flows.

Database guards (shared/0014_award_person_records, re-installing from the SQL files):

  • Particulars guard: a new award branch. It accepts an award source only from the company's award to a new recipient, under register_award_record, by the recording user, dated today.
  • Member inserts during an award: a new trigger requires that award.
  • Commit check: a deferred check refuses an award to a new recipient that commits without its person record.
  • tokens_member_identity: a person's identity holds while they have an award, so member access keeps working. The old award-copy fallback is gone.
  • tokens_mirror_award_identities_valid: copied first holdings are compared with the person's current particulars.
  • Digests: grant and allotment digests omit an empty source_award_id, and the particulars snapshot adds it only when set, so existing digests and snapshots don't change. The Python transfer snapshot matches.
  • Backfill: each existing award recipient without a member gets one person record from their earliest award.

Retention:

  • A person with an award keeps their particulars: the purge skips them, and the SQL delete guard refuses.
  • Their particulars can be changed like a current member's (_beyond_retention).
  • Imports and particulars changes clear source_award when they replace the particulars.

API (snapshot and generated types updated):

  • Awards add recipient_name and recipient_residential_address, the person's current particulars. The award keeps its own copy.
  • The members list adds on_register. It shows award-only people only to readers of the company's awards, as award_recipients did. award_recipients stays in the response, always empty, and on_register is optional in the schema, so the mobile app and its fixtures keep compiling and working until Mobile: the remaining UX standards and register work #1201.
  • Held particulars add source_award.
  • Particulars requests now work for an award recipient before any shares. The refusal wording no longer says "an award is an entitlement".

Docs: business/issuing-shares.md, register.md, member-records.md, engineering/records.md and backend.md describe person records.

Not in this PR:

Tests kept, removed and consolidated

Backend: one case added (cap 150). Before main's class rights case this was 25 → 26; with it, the merged branch runs 27. The new case is test_an_award_records_one_person_whose_particulars_every_later_flow_keeps. It checks:

  • the person record and that it is not on the register;
  • an award recorded with its person record skipped is refused at commit (the API's 503 database error), leaving no award and no member;
  • the members list;
  • refusals for awards, allotments, grants and transfers that would make a second identity or change the particulars;
  • a second award to the same person;
  • a particulars change shown on both awards.

Adapted to person records, with no cases removed:

  • Member access: the award recipient requests particulars before any shares.
  • Mirror: a second award can no longer change an identity. The identity change now comes through a particulars change, at the end of the case.
  • Scoped access: a foreign company can't reuse a recipient's ID (400, without leaking the name). The employee issue case checks there's no position, not that no member exists.
  • Fixture: linked_grant_payload defaults to an existing member.

Client suites: unchanged at dashboard 20/20, shared 10/10 and mobile 10/10. The shared award fixture gains the two new fields.

Checks

  • Backend checks (scripts/test-backend.py through the local wrapper; throwaway postgres:16-alpine; venv with the CI library pins): audit, lint, system, migration consistency, fresh migrate, check_rls_roles, check_rls_catalogue, and the ordinary suite (27 tests with main's class rights case, OK). The schema phases need Python 3.13.15. Instead I exported the schema with the CI library pins against my migrated database: it differed from the snapshot only by the three fields above, and I updated the snapshot and generated types from it. CI's backend job is the record.
  • Local database (my own POSTGRES_DB, migrated from current main, holding an award-only recipient recorded with the old code): the migration gave them a person record from their award. The members list showed them with onRegister: false and an empty awardRecipients. An invitation to them was recorded under the new identity rule.
  • Shared dev database (read-only check): the migration will create one person record, for one recipient with two identical awards. No reserved ID is shared across companies, and no pending new-member proposal uses an award recipient's ID.
  • Type checks: dashboard, shared and mobile tsc pass. Client suites pass (20/10/10).
  • make preflight: results on the PR.

Mobile

No mobile changes. For #1201: the import screens read awardRecipients (now empty), and the transfer screen still has an awardOnly new-member path, which can't trigger now. Both can go once mobile reads onRegister, and the API field can then be removed.

🤖 Generated with Claude Code

…ward

Recording an award for a new recipient now creates their person record in
the same transaction: a member and particulars whose source is the award.
They stay off the register of members and Shareholders until shares are
issued. Later awards, allotments, grants and transfers name that member
with the retained particulars, so none of them can send different
particulars under an award recipient's ID; a new-member request under it
is refused.

- Model: particulars gain `source_award`, one of the one-source options.
- Database guards (shared/0014): the particulars guard accepts an award
  source only from the company's award to a new recipient; a member insert
  during an award needs that award; a deferred check refuses an award
  that commits without its person record; member identity holds while the
  person has an award; copied first holdings compare with current
  particulars; grant and allotment digests omit an empty award source.
  Existing award recipients without a member get a person record from
  their earliest award.
- Retention: particulars of a person with an award are kept, and their
  particulars can be changed, like a current member's.
- API: awards add `recipient_name` and `recipient_residential_address`
  (current particulars); the members list adds `on_register` and shows
  award-only people to award readers only; `award_recipients` stays empty
  for the mobile app until #1201.
- Particulars requests work for award recipients before any shares.
- Docs describe person records; the backend suite adds one case (26).

Refs #1169

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 20, 2026 21:49

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-20T21:54:03.954884Z 170429e PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

RonildoBraga and others added 2 commits October 21, 2026 08:58
The award case records an award with the person record skipped. The
deferred check refuses it at commit, so neither the award nor a member is
written.

Refs #1169

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@RonildoBraga

Copy link
Copy Markdown
Collaborator Author

Checks at d9e643e3 (the commit-check test fix fece1c9b, merged with main at bb1064be)

  • make preflight: every step before backend-test passes, including lint, typecheck, API types, builds, dashboard 20/20, shared 10/10, mobile 10/10 and smoke 9/9. backend-test stops at the local Python 3.13.15 pin, as expected.
  • Backend checks through the local wrapper (throwaway postgres:16-alpine, CI library pins): audit, lint, system, migration consistency, fresh migrate, check_rls_roles, check_rls_catalogue, and the ordinary suite, 26 tests OK. They ran on 0fd98ded, which differs from d9e643e3 only in mobile/jest.config.js.
  • The schema regenerated from the merged tree with the CI library pins equals the committed snapshot.

The changed test case now expects 503: the deferred check refuses at commit, and the API reports that as its generic database error. No award or member is left behind.

RonildoBraga and others added 2 commits October 21, 2026 09:47
The members list hid every member with no register position unless the
reader could see their award. That also hid members created by a wallet
link before their first shares, which the chain grant's documentary link
flow requires. Only award-only people are now hidden from readers who
cannot see the company's awards, and on_register is false only for them.

Two member-list assertions that still checked the always-empty
award_recipients now check the members list itself. The person-record
case checks that a member without shares or an award stays listed. The
docs give the backend case count and the award refusal wording.

Refs #1169

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@RonildoBraga

Copy link
Copy Markdown
Collaborator Author

Independent review: BLOCK at d9e643e

PR #1209, "feat(#1169): give award recipients a person record from their first award". Reviewed the exact head
d9e643e38b8c3d6f279aecc5c60e34c8a2fd2b06, diff bb1064be..d9e643e3, in a detached review worktree. Nothing
was edited, committed, pushed or posted.

One blocking finding: the new members-list filter drops more people than award-only recipients. It breaks the
non-paid chain grant to a new member on web and mobile. Everything else checked out, including the database
guards, the backfill, digest stability and the privacy rule.

Depth of read

  • Read in full: every backend file in the diff. That covers the model and both migrations, the installer and
    backfill, all four SQL diffs, the services, serializers, the five test files and fixtures, the schema snapshot,
    the generated types, the shared fixture and the five docs.
  • Read in full, unchanged context: the current particulars guard and how its sources branch, the award guard
    file head (tokens_guard_employee_award, grant match and readiness), and the mirror import validation and
    award identity check. Also: import apply and newer, particulars change prepare and apply, transfer _state and
    apply, grant _state, the particulars request, the purge and its task alias, and the award and company
    querysets. Also: the members view scoping, the test-env role switching (shared/db), the wallet link _link,
    and the other guard installers, to confirm which function copy is installed last.
  • Read in full, clients: peopleFrom, isRegisterImportMembers and rowsOf, useCompanyIssueInstructions
    (guardIssue), the dashboard and mobile CompanyIssueFlow, and the mobile screens that read awardRecipients
    or awardOnly.
  • Skimmed: the rest of openapi.json, docs outside the changed sections, and other client pages that use
    usePeople.
  • Not read: the author's uncommitted web-half changes (not in this PR), and main's newer commits fix(#1179): show the server's reason for refused requests, never raw codes #1203 and
    feat(#1181): load more notifications and company publications #1210 beyond their file lists (client only, no overlap with this PR).

Commands and results

  • Backend checks. I ran the provided wrapper's exact copy from a clean tools directory. Every phase passed:
    audit, lint, system, migration consistency, fresh migrate, check_rls_roles and check_rls_catalogue. The
    ordinary suite ran 26 tests, OK. The schema phases were skipped for the Python 3.13.15 pin, and CI covers
    them. Evidence is in /tmp/ledova-backend-checks/run-5zhqc96v.
  • Two wrapper failures (environment, not the PR). The first two attempts failed with ModuleNotFoundError
    before any database existed. The cause is scratchpad/inspect.py, a file from the author's session. It shadows
    the stdlib inspect because run_backend_checks.py runs from the scratchpad directory. Running identical copies
    from review-1209/tools/ worked.
  • CI at d9e643e: all checks pass. That covers Backend verification (schema phases included), Source and
    client checks, the Django verdict, PR type, GitGuardian and runner selection.
  • Main has moved to d15867be (fix(#1179): show the server's reason for refused requests, never raw codes #1203, feat(#1181): load more notifications and company publications #1210). Those commits change client files only and don't overlap this
    PR, so there's no migration-number clash. The branch still needs updating, and CI must pass on the updated head
    before merge.
  • Mutations. Each ran through a targeted runner against a throwaway postgres:16-alpine on port 55495, and
    each was reverted. Details are in the next section.
  • Experiments. I wrote a temporary module with 7 cases, copied it in, ran it and removed it. All 7/7 pass at
    head
    . Under M2 one fails (E4), and under M5 two fail (E6, E7).
  • Source-scanning modules. tokens/tests holds only chain_smoke.py. It is a manual test that needs a local
    chain, so I couldn't run it, and it's unrelated to this PR. The ordinary suite covers every shared/tests
    module.
  • Final state. The worktree is clean at d9e643e, the experiment file is removed, every scratch database
    reported cleanup complete, and no review container is left.

Mutations (each reverted; worktree clean after each)

Id Mutation Result
M1 tokens_check_award_person_record never raises (deferred check off) Caught. The new case fails: 201 != 503.
M3 record_award skips the award-sourced particulars Caught. Both award cases fail with 503 at recording (the deferred check refuses).
M9 tokens_member_identity without the award clause Caught. The member access case fails: 404 on the particulars request.
M7 Python particulars_snapshot without source_award Caught. Two transfer cases fail with 409 (the Python and SQL snapshots differ).
M2 Award branch of the particulars guard accepts any award source Not caught. The whole suite passes, 26/26. My E4 catches it: backdated particulars are accepted, 201.
M5 Members list shows every company member (privacy filter removed) Not caught. The whole suite passes, 26/26. My E6 catches it (award-only person shown to a non-award reader), and so does E7.

Verified

  • Guard patterns. The new SQL functions follow the existing guard patterns: SECURITY INVOKER,
    search_path = pg_catalog, public, pg_temp, and __OPERATOR__/__MIGRATE__ replaced by the installer like the
    other installers. tokens_guard_award_member mirrors the allotment, grant and transfer member guards: it acts
    only under its own operation.
  • Award branch of the particulars guard. It accepts INSERT only. The award must be a new-member award for the
    same member and company, with the same name and address, recorded by the same user under
    register_award_record for that company, and the particulars must be dated today. The migrate role is allowed
    only with no company operation and with the award's creation date. E4 confirms these are refused with 409 and
    leave no member: a backdated date, a changed name, and another member's award. A direct UPDATE of award-sourced
    particulars is refused with the guard message.
  • Deferred check. It refuses an award to a new recipient that commits without its person record (M1). The 503
    is the API's generic database error at commit, and no award or member remains.
  • Deletes and retention. The DELETE refusal doesn't block legitimate flows: imports and particulars changes
    UPDATE through update_or_create and clear source_award. The purge excludes award holders, and the purge
    task runs on the operator alias, which can read awards. E5 shows a former holder with an award survives a purge
    at a future clock with no error.
  • Identity rule. tokens_member_identity keeps the 7-year rule and adds "has an award in this company". It
    matches the purge exclusion and the SQL delete refusal.
  • Digests and snapshots. Only the two current whole-row digests (allotment and grant, in
    database_allotment_guards.sql) serialise a particulars row. Both strip a null source_award_id, so existing
    digests are byte-identical. The particulars snapshot adds source_award only when it is set, and the Python
    transfer snapshot matches it (M7 caught; E1 applies a transfer whose to_particulars carries sourceAward). The
    copies in database_award_guards.sql:269 and database_guards.sql:6241 are older and superseded; shared/0010
    and shared/0014 install the allotment copy last.
  • Migrations.
    • _function takes the last definition in a file, and every changed definition is the last one in its file.
    • SQL-language bodies reach the new column only through to_jsonb(held), so the initial installer works on a
      fresh migrate before tokens/0012 adds it.
    • shared/0014 runs after tokens/0012 and is idempotent: E3 runs it twice and both triggers exist.
    • The backfill gives each recipient one person record from their earliest new-member award. It matches the
      company, uses the award's creation date (UTC), skips people who already have particulars, and the identity
      resolves (E3).
  • Imports. A copied import holding an award recipient and dated before the award applies, and the newer
    award-sourced particulars are kept (E2).
  • Privacy. Award-only people are listed only to readers of the company's awards (E6). A foreign company can't
    reuse a recipient's ID and doesn't see the name (scoped test).
  • The issue's gap is closed. Awards, allotments, grants and transfers all refuse a new-member request under a
    recipient's ID, and refuse different particulars (new case).
  • API compatibility. award_recipients stays and is always empty. on_register is optional. The client
    validator (rowsOf) ignores the extra key, and mobile compiles (CI).
  • Tests. The backend count goes from 25 to 26, within the cap of 150. The new case is meaningful: M1 and M3
    are both caught by it.

Findings

  1. BLOCKING, high: chain grants to a new member stop on web and mobile.
    backend/tokens/services/register_transfers.py:107 filters with
    if member.pk in registered or member.pk in recipients. That hides every member with no RegisterPosition
    who isn't a readable award recipient, not only award-only people. Before this PR the list held every company
    member (bb1064be, no filter).

    Members created by a wallet link before any register entry have no position. register_openings._link
    (:427) calls create_member and nothing else; a position appears only once the mint is recorded. That is
    exactly the default path of a non-paid chain grant: "Create a new member through the documentary link"
    (dashboard/.../detail/CompanyIssueFlow.tsx:36, useCompanyIssueFlow.ts:60). The flow is mounted on the
    dashboard Share class page and on mobile TokenDetailScreen.

    useCompanyIssueInstructions.guardIssue (packages/shared/src/hooks/useCompanyIssueInstructions.ts:355)
    requires the member in members with walletless: false. It runs before preparing
    (useCompanyIssueFlow.ts:123, mobile CompanyIssueFlow.tsx:379) and before each decision (newEffectGuard).

    Scenario: a company links a new investor's nominated wallet, then prepares the grant. The client now throws
    "Refresh genuine proof, finite approval and the exact linked member before this new grant decision". Pending
    grants to positionless members can't be approved or applied either.

    E7 confirms a positionless member without an award is left out at head; it was listed before. No suite covers
    this flow, so nothing failed. The PR also states that the unchanged mobile app keeps working, and this
    contradicts that for chain grants.

    Suggested fix: hide only award-only people from non-award readers and keep everyone else as before. For
    example, awarded = set(RegisterAward.objects.filter(company_id=token.company_id).values_list("member", flat=True))
    with the condition member.pk in registered or member.pk not in awarded or member.pk in recipients.

  2. Non-blocking, medium: existing clients lose the awardOnly safeguards until the web half (web) and Mobile: the remaining UX standards and register work #1201
    (mobile).
    peopleFrom now marks award-only people as ordinary members (awardOnly: false). So the chain-grant
    member pickers (CompanyIssueFlow.tsx:261 on web, :299 on mobile, both !person.awardOnly) offer award
    recipients. A documentary wallet link to one makes their award's walletless issue path refuse
    (member_wallet_linked). The "Award recipient without shares" labels also disappear. on_register: false
    can't tell award-only people from positionless linked members, so the web half (and Mobile: the remaining UX standards and register work #1201) needs an explicit
    award flag rather than onRegister. Worth noting on Mobile: the remaining UX standards and register work #1201.

  3. Non-blocking, low: two guard paths are untested by the suite. The award branch's refusal conditions (M2) and
    the members-list award privacy filter (M5) both pass the whole suite. E4 and E6 show both behave correctly at
    head. This is acceptable under the minimum-test policy. If a case is spent, the privacy filter is the one to
    cover.

  4. Non-blocking, low: the delivered backend case count is stale. docs/engineering/testing.md:31 still says
    25 delivered backend cases; it is now 26. fix(#1171): deliver publications to walletless members #1189 updated this table when it added a case.

  5. Non-blocking, low: one refusal code is misattributed. docs/engineering/records.md:484 says a new-member
    request under the recipient's ID is refused with member_reference_conflict. For awards, the refusal is the
    validation message "The new recipient UUID already identifies a member."; the code applies to allotments,
    grants and transfers.

  6. Non-blocking, low: database states to check before migrating. Any pending new-member grant, allotment or
    transfer under an award recipient's reserved ID can't be applied after the backfill, because the member now
    exists; it would need rejecting and re-preparing. A recipient whose reserved ID already belongs to another
    company's member gets no person record, so their member access identity becomes null. The author checked the
    dev database has neither. Check any other database before migrating.

  7. Non-blocking, info: one extra query per award. RegisterAwardSerializer.to_representation adds one
    particulars query per award, next to the existing per-award quantities query. Fine at current scale.

@RonildoBraga

Copy link
Copy Markdown
Collaborator Author

Checks at 71ff9daf: the fix for the review's BLOCK (93cc777b), merged with main at 3e24dff4.

The fix:

  • register_members hides only award-only people, and only from readers who can't see the company's awards. Members with no shares and no award, such as one created by a wallet link for a chain grant, are listed again.
  • on_register is false only for award-only people. It is documented in the schema.

Tests:

  • The person-record case now also checks that a member with no shares and no award stays listed, with onRegister true.
  • Two assertions that compared the now always-empty awardRecipients now check the members list itself: the copied recipient is listed with 100 shares, and the foreign recipient's name stays out.
  • The backend count stays 26 (cap 150); testing.md now records 26.

Docs:

  • records.md gives the award refusal wording.
  • backend.md describes the members flag.

Checks:

  • make preflight: every step before backend-test passes, including dashboard 20/20, shared 10/10, mobile 10/10 and smoke 9/9. backend-test stops at the local Python 3.13.15 pin.
  • Backend checks through the local wrapper pass: audit, lint, system, migration consistency, fresh migrate, check_rls_roles, check_rls_catalogue, and the ordinary suite, 26 tests OK.
  • The schema exported with the CI library pins differs from the old snapshot only by the new field description. The snapshot is updated; the generated types are unchanged.

The reviewer's delta review of this head is running.

RonildoBraga and others added 2 commits October 21, 2026 10:04
The person-record case now also lists members as a register reader who
can't read the company's awards (issuer identity required, owner
unverified). The award recipient is absent and every listed row reads as
on the register. The flag's description says it is false only for an
award recipient who has never been on the company's register.

Refs #1169

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@RonildoBraga

Copy link
Copy Markdown
Collaborator Author

Independent review delta: PASS at 71ff9da

PR #1209, delta review of the fix for the earlier BLOCK (record.md, at d9e643e). I reviewed local head
71ff9dafbe905cfc47d2e043c703740e49161a42 in my detached review worktree. Nothing was edited, committed, pushed
or posted.

The blocking finding is fixed, with the suggested change. One expectation from the request doesn't hold: removing
the members-list privacy filter (my M5, D2 below) still passes the whole suite. The new assertion catches the
over-hiding regression, not the privacy direction. This is non-blocking, as before.

Depth of read

Commands and results

  • Merge tree. 71ff9daf^{tree} = git merge-tree --write-tree 93cc777b 3e24dff4 =
    51eb57baf6d7ad89c4f609d725c10ddf370ca9ff.
  • Branch's own changes. git diff 3e24dff4 71ff9daf is byte-identical to git diff bb1064be 93cc777b
    (31 files, +533/−150). That is exactly the d9e643e diff plus 93cc777.
  • Main's files. Every file main changed since bb1064b has the same blob at 71ff9da as at 3e24dff, and no
    file overlaps the PR. origin/main is still 3e24dff, which 71ff9da contains.
  • Backend checks. The provided scratchpad/review-backend-checks.sh now runs from the scratchpad; the
    shadowing inspect.py is gone. Every phase passed: audit, lint, system, migration consistency, fresh migrate,
    check_rls_roles and check_rls_catalogue. The ordinary suite ran 26 tests, OK. Schema phases were skipped
    for the Python pin. Evidence: /tmp/ledova-backend-checks/run-9yow3mqg.
  • Schema snapshot. I exported with the CI library pins against a migrated scratch database, the same way the
    author did (ignoring only the Python patch-version drift). scripts/check-api-schema.py reports "The complete
    generated OpenAPI document matches its committed snapshot." The generated types strip descriptions
    (removeComments: true), so api.ts correctly needs no change.
  • CI. The PR's remote head was still d9e643e when I reviewed, so CI on 71ff9da hasn't run. Merge only after
    CI is green on the pushed head.
  • Mutations and experiments. All ran on port 55495, and each was reverted. Every scratch database reported
    cleanup complete, the worktree is clean at 71ff9da, and no review container is left.

Mutations (each reverted; worktree clean after each)

Each ran against test_employee_awards, test_member_access and test_scoped_access; D2 ran against the full
suite.

Id Mutation of register_members Caught by
D1 Old filter registered or in recipients (the BLOCK regression) test_employee_awards…every_later_flow_keeps: the plain member is not listed.
D3 on_register = registered (old flag) test_employee_awards…every_later_flow_keeps: the plain member reads onRegister false.
D4 on_register = not awarded test_member_access…requests_and_revocation: the copied recipient with 100 shares reads onRegister false.
D5 Hide award-only people from everyone (drop or in recipients) All three modules: employee awards and member access fail with StopIteration (recipient missing); scoped access fails because the owner's selection list is empty.
D2 Filter removed, every member shown to every register reader (my M5) Not caught. The full suite passes, 26/26. My X1 catches it: the award-only person shows to a non-award reader, with onRegister false.

Verified

  • The fix is the suggested change. awarded holds every award member of the company. The list keeps a member
    when they are registered, not awarded, or in recipients. on_register is registered or not awarded,
    false only for award-only people.
  • No award flag leaks. Award-only people are the only rows with on_register false, and they're hidden from
    readers who can't see the company's awards. Such a reader therefore sees onRegister true on every row. X1 shows
    it with issuer KYC required and the owner unverified: no award-only person, no award name, plain and registered
    members listed, every row true.
  • The chain-grant guard is satisfied again. X2 inserts a wallet-linked member with no position or award. It is
    listed with walletless false, onRegister true and currentShares "0", which is what
    useCompanyIssueInstructions.guardIssue needs. Under D1 it disappears.
  • Former holders with an award. X3 takes a former holder with an award (shares issued, then transferred away).
    They read onRegister true with currentShares "0". They are on the register as a former member, which is
    correct.
  • Test changes. The person-record case adds a plain member and checks it is listed with onRegister true; that
    catches D1 and D3. The member-access check of the always-empty awardRecipients becomes a check of the copied
    recipient (100 shares, onRegister true); that catches D4. The scoped-access change swaps one always-empty
    check for an assertNotIn of the foreign award name; it is weak but harmless. The count stays at 26, within the
    cap of 150.
  • Docs. testing.md now says 26 delivered backend cases. records.md gives each refusal correctly. The
    backend.md members-flag text matches the code, except for the wording point in finding 2.

Findings

Resolved from record.md: blocking finding 1 (chain grants to a new member), finding 4 (the stale case count) and
finding 5 (the refusal-code wording).

  1. Non-blocking, low: the privacy direction is still untested (D2 / M5). The new assertion catches the
    over-hiding regression, not a leak of award-only people to readers who can't see awards. My temporary X1 shows
    the filter works at head. To cover it without a new case, the person-record case could list members once as a
    register reader who can't read awards, using X1's approach: issuer KYC required and the owner unverified.
    Optional under the minimum-test policy.
  2. Non-blocking, low: help text and docs wording.
    backend/tokens/serializers/register_transfer.py:146-148 (help text, also in the schema) says onRegister is
    "False only for an award recipient who holds no shares in the company". docs/engineering/backend.md:225 says
    "award recipients with no shares in the company". Both describe X3's former holder, who holds no shares yet reads
    true. More precise: "an award recipient who has never been on the company's register".
  3. Carried over, non-blocking. Existing clients still treat award-only people as ordinary members, so the
    awardOnly safeguards are off until the web half and Mobile: the remaining UX standards and register work #1201. onRegister === false is now an exact award-only
    flag those clients can use; worth noting on Mobile: the remaining UX standards and register work #1201.
  4. Carried over, non-blocking. The particulars guard's award-branch refusals are still untested by the suite
    (M2).
  5. Carried over, non-blocking. Check other databases before migrating, for pending new-member proposals under a
    recipient's reserved ID and for reserved IDs shared across companies.
  6. Carried over, non-blocking. One extra particulars query per award in the award serializer.

@RonildoBraga

Copy link
Copy Markdown
Collaborator Author

Independent review delta: PASS at 49de9c6

PR #1209, second delta review, at pushed head 49de9c69fac4aa143f750eed5f133257dbd791ce (also the PR's remote
head). I reviewed it in my detached review worktree. Nothing was edited, committed, pushed or posted.

My M5 mutation (privacy filter removed) now fails the suite, as expected. The merge of main at 9fdfc88 is clean,
and the branch's own changed lines are unchanged.

Depth of read

  • Read in full: 49de9c6, which touches the person-record case in test_employee_awards.py, the help text,
    the schema description and backend.md.
  • Checked mechanically: merge 4d2eddf, by comparing trees, per-file diffs and changed-line sets.
  • Not read: Record company-supplied class rights #1215's prose beyond confirming its lines carry over.
  • Unchanged since 71ff9da: the rest of the PR (see delta.md).

Commands and results

  • Merge 4d2eddf (origin/main 9fdfc88).
    • 4d2eddfe^{tree} equals git merge-tree --write-tree 71ff9daf 9fdfc884
      (780d4fe0089eaf237e5b4b973ef9094c8f3d421b). merge-tree exits 0, so there are no conflicts.
    • Record company-supplied class rights #1215 changes only docs/business/issuing-shares.md, which the PR also edits in a different section. The
      PR's changed lines in that file are identical before and after the merge, and so are Record company-supplied class rights #1215's.
    • Every other PR file has a byte-identical diff: 9fdfc884..4d2eddfe equals 3e24dff4..71ff9daf, apart from
      the hunk positions in issuing-shares.md.
  • Backend checks. The provided wrapper passed every phase: audit, lint, system, migration consistency, fresh
    migrate, check_rls_roles and check_rls_catalogue. The ordinary suite ran 26 tests, OK. Schema phases were
    skipped for the Python pin. Evidence: /tmp/ledova-backend-checks/run-bje3xewq.
  • Schema. I exported with the CI library pins (same method as before). It matches the committed snapshot,
    including the new description. Generated types drop descriptions, so api.ts is correctly unchanged.
  • CI on 49de9c6: all green. Backend verification, Source and client checks, Django verdict, PR type,
    GitGuardian and runner selection all pass.
  • Upcoming last delta (read-only preview). git merge-tree --write-tree 49de9c69 6c89fc1e exits 0, so main's
    feat(#1181): page the company eligibility queue and filter it by outcome #1213 merges without conflicts. It overlaps only in backend/schema/openapi.json and
    packages/shared/src/generated/api.ts, in different regions. CI on the merged head will confirm the snapshot and
    generated types.
  • Mutations. All ran on port 55495 against the four modules that read the members list (test_employee_awards,
    test_member_access, test_register_mirror, test_scoped_access; 10 cases). Each was reverted, every scratch
    database reported cleanup complete, and the worktree is clean at 49de9c6 with no review container left.

Mutations (each reverted; worktree clean after each)

Id Mutation of register_members Result
D2 (my M5) Filter removed, every member shown to every register reader Caught. The person-record case fails: the award recipient's ID appears in the unverified reader's list.
D1 Old filter registered or in recipients (the original BLOCK) Caught by the same case: the plain member is not listed.
D6 Show readers who can't see awards no members at all Not caught. The four modules pass 10/10, because the new assertions also pass on an empty list (see finding 1).

Verified

  • The new block works. It sits at the end of the person-record case, while the recipient is still award-only.
    It turns on issuer KYC (use_migrate), unverifies the owner (use_operator), and lists members again. It
    asserts the recipient's ID is absent and that every row reads onRegister true. This is my X1 approach, and it
    catches D2.
  • No leakage between tests. The operator setting isn't cached in memory, and APITransactionTestCase flushes
    the database after each case. The full suite passes 26/26. The case count is unchanged at 26, within the cap of
    150.
  • Wording now matches behaviour. The help text and docs/engineering/backend.md say "an award recipient who
    has never been on the company's register". That matches X3 from the first delta: a former holder with an award
    reads onRegister true.

Findings

  1. Non-blocking, low: the new assertions can pass on an empty list. assertNotIn and
    all(row["onRegister"] …) both hold when unawarded["members"] is empty, so a regression hiding everyone from
    these readers passes (D6). A one-line fix closes it: assert that the plain member created earlier in the case
    (awaiting) or self.member is in unawarded["members"]. Optional.
  2. Carried over, non-blocking. Existing clients still lack their awardOnly safeguards until the web half and
    Mobile: the remaining UX standards and register work #1201; onRegister === false is the exact award-only flag they can use. The award-branch refusals in the
    particulars guard are still untested (M2). Check other databases before migrating. The award serializer still
    makes one particulars query per award.

@RonildoBraga

Copy link
Copy Markdown
Collaborator Author

The owner approved the journey page on 21 October 2026, relayed from the parent session: "the journey page is ok." The decision is on #1169.

This PR can merge once main is merged in, an independent review passes at its merging head, and CI is green on a branch up to date with main.

RonildoBraga and others added 2 commits October 21, 2026 17:45
…ds see

The privacy assertions held on an empty members list too. The case now
also requires the class's shareholder in that reader's list.

Refs #1169

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@RonildoBraga

Copy link
Copy Markdown
Collaborator Author

Checks at the merge candidate 5a4f03cf: main at 2a05de45 merged in (99a99c7d), plus the review's empty-list tightening (5a4f03cf).

  • Merge: main merged without conflicts. No migrations came from main. The schema exported with the CI library pins from the merged code equals the committed snapshot as JSON, and check-api-types reports the generated types match.
  • Test: the privacy step also requires the class's shareholder in the list that a reader without award access sees, so it can't pass on an empty list. The backend count stays at 26.
  • Backend checks through the local wrapper all pass: audit, lint, system, migration consistency, fresh migrate, check_rls_roles, check_rls_catalogue, and the ordinary suite with 26 tests OK.
  • make preflight (base 2a05de45): every step before backend-test passes, including dashboard 20/20, shared 10/10, mobile 10/10 and smoke 9/9. backend-test stops at the local Python 3.13.15 pin.

Owner's OK on the journey page, 21 October 2026, given over Remote Control in the parent session and relayed from there (recorded on #1169): "the journey page is ok. Dont start the mobile, post everything in the git hub issues and handover everything to another agent to continue."

@RonildoBraga

Copy link
Copy Markdown
Collaborator Author

Independent review delta: PASS at 5a4f03c

PR #1209, final delta review, at merging head 5a4f03cf444755e5d0e7b1a00a0ef481dd92a96c. It became the PR's remote
head while I was reviewing. I reviewed it in my detached review worktree. Nothing was edited, committed, pushed or
posted.

The merge of main at 2a05de4 is clean, the branch's own changed lines are unchanged apart from the one-line test
change, and the empty-list gap is closed.

The BLOCK I recorded at d9e643e (finding 1 in record.md, posted on the PR) was resolved by 93cc777, as stated
in my PASS at 71ff9da. The fix's lines are byte-identical at 5a4f03c, so it stays resolved. No uncleared BLOCK
remains from my reviews.

Depth of read

  • Read in full: 5a4f03c (the one-line test change) and main's change to share_token.py.
  • Checked mechanically: merge 99a99c7, by comparing trees, per-file diffs and changed-line sets.
  • Not read: main's 27 commits beyond confirming their backend file list and their lines in the five files they
    share with the PR. Their integration is covered by the full suite and the schema export below.

Commands and results

  • Merge 99a99c7 (origin/main 2a05de4, 27 commits).
    • 99a99c7d^{tree} equals git merge-tree --write-tree 49de9c69 2a05de45
      (0d5461c2c3c2c6df9c9133d0befbf31dd4714103). merge-tree exits 0, so it auto-merged with no conflicts.
    • No migrations came from main. Main's backend files are backend/schema/openapi.json, tokens/views/ capital_increase.py, tokens/views/share_token.py, users/filters.py and users/views/company_eligibility.py.
  • The branch's own changed lines.
    • Note on the comparison base: bb1064be..49de9c69 now also contains main commits merged earlier. The PR's own
      diff at 49de9c6 is 9fdfc884..49de9c69, which is what I compared against 2a05de45..5a4f03cf.
    • Both have the same 31 files: +544/−151 and +545/−151. The extra line is the test change.
    • The 25 files main didn't touch have byte-identical diffs, except test_employee_awards.py, whose only change
      is the new line.
    • The five files both sides touched (openapi.json, generated/api.ts, issuing-shares.md,
      member-records.md, register.md): the PR's changed lines are identical before and after (openapi.json 20,
      register.md 14), and main's changed lines carry over intact (16, 1, 46, 6 and 13 lines).
  • share_token.py. Main only adds a tiebreaker to the issuances ordering (-completed_at, -uuid). The
    register/members action is unchanged: register_members is still in operator_actions (line 80) and
    register_reads (line 111), and the action at lines 241–244 still returns
    RegisterMembersSerializer(register_members(self.get_object(), request.user)).
  • Backend checks. The provided wrapper passed every phase: audit, lint, system, migration consistency, fresh
    migrate, check_rls_roles and check_rls_catalogue. The ordinary suite ran 26 tests, OK. Schema phases were
    skipped for the Python pin. Evidence: /tmp/ledova-backend-checks/run-d2lfs6xf.
  • Schema. I exported the merged code with the CI library pins. It matches the committed snapshot: "The complete
    generated OpenAPI document matches its committed snapshot." I did not run check-api-types, which needs the node
    toolchain my worktree lacks. The coordinator reports it matches, and CI runs it.
  • Mutations. All ran on port 55495 against the four modules that read the members list (10 cases). Each was
    reverted, every scratch database reported cleanup complete, and the worktree is clean at 5a4f03c with no review
    container left.
  • CI on 5a4f03c. Pending when I finished: Backend verification and Source and client checks. GitGuardian, PR
    type and runner selection pass.
  • Main moved again. It is now 7ba6fed2 (docs(#1181): defer the person picker's server search #1245, docs(#1181): defer the person picker's server search). That
    commit is docs only, with no backend files and no overlap with the PR. git merge-tree --write-tree 5a4f03cf origin/main exits 0. Under the merge rule, the branch needs main merged in and green CI again before merging.

Mutations (each reverted; worktree clean after each)

Id Mutation of register_members Result
D2 (my M5) Filter removed, every member shown to every register reader Caught. The award recipient's ID appears for the reader without award access.
D6 Show readers without award access no members Caught (new). str(self.member.pk) is "not found in []".
D1 Old filter registered or in recipients (the original BLOCK) Caught. The plain member is not listed.

Verified

  • The new line closes the empty-list gap. It asserts that the reader without award access sees the
    shareholder: assertIn(str(self.member.pk), [row["member"] for row in unawarded["members"]]). The case count is
    unchanged at 26, within the cap of 150.
  • Fix and tests unchanged. register_members, on_register, the help text and the docs are byte-identical to
    what I passed at 49de9c6.
  • Owner gate recorded. Award recipients as person records #1169 records the owner's approval of the journey page on 21 October 2026 (06:45 UTC
    comment), with feat(#1169): give award recipients a person record from their first award #1209 to merge first after main is merged in, an independent review at the merging head, and
    green CI.

Findings

No new findings. These carry over, all non-blocking and unchanged:

@RonildoBraga

Copy link
Copy Markdown
Collaborator Author

Independent review delta: PASS at 969cf2f

PR #1209, last delta review, at local merging head 969cf2f78c0a46f2ccd9c8983f6bea5e0294c473. I reviewed it in my
detached review worktree. Nothing was edited, committed, pushed or posted.

The head is 5a4f03c (my PASS) merged with origin/main at 7ba6fed. The merge brings in only main's
docs/decisions.md change, and the PR's own changed lines are unchanged.

Depth of read

Commands and results

  • Parents. 969cf2f's parents are 5a4f03c and 7ba6fed (docs(#1181): defer the person picker's server search #1245, docs(#1181): defer the person picker's server search).
  • Merge tree. 969cf2f7^{tree} equals git merge-tree --write-tree 5a4f03cf 7ba6fed2
    (2462ff70909424bf8697ad61955875ed4f45aceb). merge-tree printed only the tree, so there are no conflicts.
  • Head vs 5a4f03c. git diff 5a4f03cf 969cf2f7 is byte-identical to main's change,
    git diff 2a05de45 7ba6fed2: docs/decisions.md, 7 insertions and 1 deletion.
  • The PR's own changed lines. The PR's diff against main, 7ba6fed2..969cf2f7, is byte-identical to
    2a05de45..5a4f03cf, the diff I passed. The PR doesn't touch docs/decisions.md.
  • Up to date with main. origin/main is still 7ba6fed, so 969cf2f is up to date.
  • Backend rerun. Not needed, because no code, schema, migration or test changed since 5a4f03c. My full checks
    at 5a4f03c passed every phase, 26 tests OK.
  • CI. It finished green on 5a4f03c: Backend verification, Source and client checks, Django verdict, PR type,
    GitGuardian and runner selection. 969cf2f wasn't pushed yet (the PR's remote head was still 5a4f03c), so its
    CI is pending the push. Merge after it's green.
  • State. The worktree is clean at 969cf2f, and no review container or scratch database is left.

Mutations

None run for this delta, because there is no code change. The mutation results at 5a4f03c (D1, D2 and D6 all
caught) apply unchanged.

Verified

  • The merge is clean and contains exactly main's docs/decisions.md change.
  • The PR's own diff is identical to the one I passed at 5a4f03c.
  • My earlier BLOCK at d9e643e stays resolved (fixed by 93cc777; see delta.md and delta3.md).

Findings

No new findings. The non-blocking notes carry over from delta3.md:

Main's class rights added tokens 0012, so the award person-record
migration becomes 0013 after it, and the shared guard installer depends
on 0013. The backend case count is 27 with both features.

Refs #1169

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@RonildoBraga

Copy link
Copy Markdown
Collaborator Author

Independent review delta: PASS at 29de1a7

PR #1209, delta review at merging head 29de1a795cba95fdf958f77ec2ceba0a29161da9. It became the PR's remote head
while I was reviewing. I reviewed it in my detached review worktree. Nothing was edited, committed, pushed or
posted.

The merge of main's class rights (b8dd835) is clean, and 29de1a7 only reorders our migration after it. The PR's
own lines are otherwise unchanged. Class rights doesn't interact with anything the PR changed.

Depth of read

  • Read in full:
    • 29de1a7.
    • Main's class-rights backend: database_class_rights.sql and .py, tokens/0012_class_rights_revisions, the
      model, the service, the share_token.py changes, the company_pack.py change, the policies.py entry, and
      its additions to test_scoped_access.py.
  • Skimmed: test_class_rights.py, the test_register_outputs.py additions, and class rights' client and docs
    changes beyond the five files it shares with the PR.

Commands and results

  • Merge 4feca0f. Its tree equals git merge-tree --write-tree 969cf2f7 b8dd835b
    (8681aeaed4c3b3a80fd0fa9eb625969b68d9700d). merge-tree printed only the tree, so there are no textual
    conflicts.

  • 29de1a7 contains exactly three changes:

    • the rename of tokens/0012_award_person_records.py to 0013_award_person_records.py, with its one dependency
      line changed from 0011_required_signing_challenge_wallet to 0012_class_rights_revisions;
    • shared/0014_award_person_records depending on ("tokens", "0013_award_person_records");
    • testing.md backend delivered cases going from 26 to 27. Main is 26 including its class-rights case, the PR
      adds one, and the suite runs 27.

    No reference to the old migration name remains.

  • The PR's own lines. Comparing b8dd835b..29de1a79 with 7ba6fed2..969cf2f7 (the diff I passed), both have
    31 files and +545/−151.

    • Every file main didn't touch has a byte-identical diff. The only exception is the migration, which differs
      only by the rename and the dependency line.
    • In the seven files both sides touched (openapi.json, test_scoped_access.py, issuing-shares.md,
      member-records.md, records.md, testing.md, api.ts), the PR's changed lines are identical, apart from
      testing.md's count. Main's lines carry over intact: 286, 20, 43, 2, 16 and 113 lines.
  • Migration graph. Each app has one leaf, with no cycle. A fresh migrate applies shared.0013_member_name_guards,
    then tokens.0012_class_rights_revisions, then tokens.0013_award_person_records, then
    shared.0014_award_person_records.

  • Backend checks. The provided wrapper passed every phase: audit, lint, system, migration consistency, fresh
    migrate, check_rls_roles and check_rls_catalogue. The ordinary suite ran 27 tests, OK, including the
    class-rights case and our person-record case. Schema phases were skipped for the Python pin. Evidence:
    /tmp/ledova-backend-checks/run-rwrzgv3i.

  • Schema. I exported the merged code with the CI library pins. It matches the committed snapshot.

  • Main is still b8dd835.

  • CI on 29de1a7. Pending when I finished: Backend verification and Source and client checks. GitGuardian, PR
    type and runner selection pass.

  • State. The worktree is clean at 29de1a7, every scratch database reported cleanup complete, and no review
    container is left.

Interaction with class rights

  • The functions shared/0014 reinstalls aren't touched. Those are the particulars guard and snapshot, the
    allotment and grant digests, tokens_member_identity, tokens_mirror_award_identities_valid,
    tokens_guard_award_member and tokens_check_award_person_record. Class rights changes none of their source
    files (the allotment, award, member and mirror guard SQL files) and doesn't mention them.
  • Class rights installs only its own SQL file. Its installer adds three functions and three triggers: two on
    its own table, and tokens_class_rights_company BEFORE UPDATE OF company_id on tokens_sharetoken. It
    reinstalls no shared function, so neither installer can overwrite the other's versions in either order.
  • No contact with members, particulars, identity or the members list. Class rights adds no member or particulars
    writes, and its guard runs only under its own class_rights_record operation. register_members is unchanged:
    still in operator_actions (line 92) and register_reads (line 126), and the action at lines 298–300 is the
    same. The new class_rights action sits beside it, and company_pack.py adds only a class_rights.json file.
  • The merged scoped-access case stays coherent. Main's class-rights assertions sit in the same case as the PR's
    members-list edits, but on separate endpoints. The case passes.

Mutations

None run for this delta. register_members and the person-record case are byte-identical to 5a4f03c, where D1,
D2 and D6 were all caught. The merged suite runs that case (27 OK).

Findings

  1. Non-blocking, low: any local database that applied the old migration name needs repairing or recreating.
    That includes the author's working database. Its django_migrations table holds
    tokens.0012_award_person_records and shared.0014_award_person_records. Under the new graph, shared 0014
    depends on the unapplied tokens 0013, so migrate would refuse with an inconsistent history. Shared and live
    databases never ran the unmerged PR, so they follow the normal path. Recreating the local database fits the
    fresh-migration policy.
  2. Carried over, non-blocking, unchanged since delta3.md:

@RonildoBraga

Copy link
Copy Markdown
Collaborator Author

Independent review delta: PASS at 1f25780

PR #1209, delta review at merging head 1f25780a9840b543af72be67f1335c2099c8cd84. It became the PR's remote head
while I was reviewing. I reviewed it in my detached review worktree. Nothing was edited, committed, pushed or
posted.

The head is 29de1a7 (my PASS) merged with origin/main at 37c7b4a (#1252). The merge brings in exactly #1252's
change, and the PR's own lines are unchanged.

Depth of read

  • Read in full: the backend part of fix(#1247): refuse a date of birth in the future at sign-up #1252, a validate_date_of_birth validator on UserProfileSerializer
    that refuses a future date of birth at sign-up.
  • Skimmed: its two client files in packages/shared (sign-up wording and hook), which are outside the PR.
  • Unchanged since my PASS at 29de1a7: everything else (see delta5.md).

Commands and results

  • Parents. 1f25780's parents are 29de1a7 and 37c7b4a. 37c7b4a's parent is b8dd835.
  • Merge tree. 1f25780a^{tree} equals git merge-tree --write-tree 29de1a79 37c7b4a2
    (937cc8e3387a60349116d0b1585bda44ddc1997f). merge-tree printed only the tree, so there are no conflicts.
  • Head vs 29de1a7. git diff 29de1a79 1f25780a is byte-identical to fix(#1247): refuse a date of birth in the future at sign-up #1252's change,
    git diff b8dd835b 37c7b4a2. That is 3 files, +15/−2: backend/users/serializers/user_profile.py,
    packages/shared/src/constants/business/signup.ts and packages/shared/src/hooks/useSignupUserProfile.ts.
    None is a PR file, and fix(#1247): refuse a date of birth in the future at sign-up #1252 adds no migrations.
  • The PR's own lines. The PR's diff against main, 37c7b4a2..1f25780a, is byte-identical to
    b8dd835b..29de1a79, the diff I passed.
  • Backend checks. I ran them anyway, because fix(#1247): refuse a date of birth in the future at sign-up #1252 changes a backend file. The provided wrapper passed every
    phase: audit, lint, system, migration consistency, fresh migrate, check_rls_roles and check_rls_catalogue.
    The ordinary suite ran 27 tests, OK. Evidence: /tmp/ledova-backend-checks/run-amigvc1y.
  • Schema. I didn't rerun the export, because neither side changed the schema snapshot and fix(#1247): refuse a date of birth in the future at sign-up #1252 doesn't touch
    it. My export at 29de1a7 matched.
  • Main is still 37c7b4a, so 1f25780 is up to date.
  • CI on 1f25780. Pending when I finished: Backend verification and Source and client checks. GitGuardian, PR
    type and runner selection pass. Merge after the pending checks are green.
  • State. The worktree is clean at 1f25780, the scratch database was cleaned up, and no review container is left.

Mutations

None run for this delta, because none of the PR's code changed. The results at 5a4f03c (D1, D2 and D6 caught)
still apply.

Verified

Findings

No new findings. The non-blocking notes from delta5.md carry over:

@RonildoBraga
RonildoBraga merged commit dde71a0 into main Oct 21, 2026
6 checks passed
@RonildoBraga

Copy link
Copy Markdown
Collaborator Author

Merge record: squash-merged as dde71a05 with gh pr merge --squash --match-head-commit 1f25780a.

  • Reviewed head: 1f25780a. The independent review chain:
    • BLOCK at d9e643e3, the members-list over-hiding, fixed in 93cc777b;
    • PASS at 71ff9daf;
    • delta PASS at 49de9c69, 5a4f03cf, 969cf2f7, 29de1a79 and 1f25780a. The last three cover main merges, including Record company-supplied class rights #1215's class rights and the migration renumbered to tokens/0013_award_person_records.
    • No BLOCK is left uncleared.
  • CI: green at 1f25780a, on a branch up to date with main at 37c7b4a2.
  • Local checks at 1f25780a: make preflight passes up to the local Python pin. The backend wrapper passes every phase, including a fresh migrate, with 27 tests OK.
  • Squash check: the squash tree 937cc8e3 equals the reviewed tree, and the commit's only parent is the prior main 37c7b4a2.
  • Owner's OK on the journey page, 21 October 2026, given over Remote Control in the parent session and relayed from there (recorded on Award recipients as person records #1169): "the journey page is ok. Dont start the mobile, post everything in the git hub issues and handover everything to another agent to continue."
  • Standing instruction (21 October 2026): "always merge when CI is green, no need to wait for my approval".

Note for local databases: a database that applied this PR's earlier migration name, tokens.0012_award_person_records, has an inconsistent history now that the migration is 0013 after class rights. Recreate it. Shared and live databases never ran the unmerged PR.

#1216, the web half, follows next.

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.

2 participants