Files
multica/server/cmd
9a30c51dac MUL-6286: feat(properties): actor / multi_actor custom property types (members) (#7075)
* feat(properties): add actor / multi_actor custom property types (MUL-6286)

Custom issue properties gain their first reference type: `actor` (single) and
`multi_actor` (multiple), holding workspace members and agents.

Value shape is a prefixed string — "member:<user_id>" / "agent:<agent_id>",
multi being an array of them. Keeping it a plain string rather than an object
means the existing @> containment filter, the jsonb_path_ops GIN index and the
client value schema all keep working unchanged; an object-shaped value would
have been silently dropped by the client preprocess that guards against
unknown value shapes.

Scope decisions:

- Squads are out. The assignee pair accepts member/agent/squad, but a squad is
  a routing target rather than a person. The stored form carries the kind, so
  widening later needs no migration and no new type.
- Referencing an agent is NOT an assignment: nothing is enqueued, routed or
  notified. The picker therefore does not gate agents on runtime binding or
  invoke permission the way the assignee picker does — it gates on visibility,
  matching the server.
- Agent identity still needs protecting, so writes and facet reads both run
  through accessibleAgentIDs: a private agent never becomes referenceable or
  discoverable by id.
- multi_actor dedupes but preserves insertion order instead of canonicalizing
  like multi_select. There is no config order to sort against, and sorting by
  id would reshuffle the avatar row on every edit. @> containment is
  order-insensitive, so filtering is unaffected.
- Actor properties are filterable (including "= me", with the signed-in member
  sorted first) but not sortable or groupable — the same position multi_select
  already holds.

Both types ship together because a property's type is immutable: shipping
single-only would have forced anyone wanting multi to recreate the property
and re-enter its values.

Co-authored-by: multica-agent <[email protected]>

* refactor(properties): narrow actor properties to members only (MUL-6286)

Scope change after review: `actor` / `multi_actor` hold workspace members
only. Agents are out — a passive reference had to answer agent-visibility
questions (private / non-allow-listed agents must not become discoverable by
id) and carry a "referencing is not assigning" caveat everywhere, all for a
use case nobody has asked for. The original request was for members.

The envelope is unchanged on purpose, so this stays a one-line widening later
rather than a migration: the types are still named `actor` / `multi_actor`,
the stored value is still the kind-prefixed "member:<user_id>", and the
migration still allows both type names. Adding "agent" back means appending to
`actorPropertyKinds` — existing definitions would gain the kind in place, with
no new property type and no value rewrite.

What this removes:

- The agent branch of `resolveActorRefs`, including the accessibleAgentIDs
  visibility gate and the archived-agent check.
- `filterAccessibleActorFacetValues` entirely. Facet keys are now all
  `member:<uuid>`, and workspace membership is already visible to every
  member, so there is nothing left to gate.
- The agent section of the actor picker, and agents from the filter dropdown's
  candidate list.
- The "reference, not an assignment" framing from the skill and product docs —
  that distinction only existed because agents were allowed, and keeping it
  would now confuse rather than clarify.

`actorPropertyKinds` carries the note that any future kind which is not
universally visible to every member must restore the visibility gate on both
the write path and the facet read path.

Tests updated to match: agent refs are now rejected at the kind check, and the
private-agent visibility test is replaced by TestIssueActorPropertyFacets,
which covers the facet path that backs the "= me" filter.

Co-authored-by: multica-agent <[email protected]>

* fix(properties): address actor-property review findings (MUL-6286)

Four defects from review, plus one pre-existing bug found next to them.

1. Editing a multi_actor value silently deleted kinds the client did not
   recognise. The picker rebuilt the array from `actorRefsFromValue`, which
   filters unknown kinds — so once the backend widens actorPropertyKinds, an
   installed desktop build would drop every agent reference the moment the
   user toggled a member. The edit path now round-trips the RAW string list
   via the new `actorRefValuesFromValue`, and the toggle is extracted to
   `toggleActorRefValue` so the guarantee is directly testable.

2. Actor ids were stored exactly as received. `uuid.Parse` also accepts
   uppercase, braces and the urn: form, and every consumer (member directory
   lookup, "= me" filter, @> containment) compares reference strings exactly —
   so `member:<UPPERCASE-UUID>` passed validation and then rendered as Unknown
   and matched no filter. `parseActorRef` now stores the canonical form.

3. The CLI, docs and skill all promised member email as a way to address a
   value, but `resolveAssignee` only matched id and name. Email is now an
   exact-match alias ranked with id matches (it is as unambiguous as an id),
   so the documented behaviour is real. This also makes `--assignee` accept an
   email, which is a strict improvement.

4. An active actor-property filter rendered as a bare count. The chips bar
   loads each directory lazily and gated the member directory on
   assignee/creator filters only, then looked names up in `config.options`,
   which actor properties do not have. It now detects actor-typed filters,
   loads the directory, and renders names plus an avatar stack.

Found while fixing 4: the chips bar keyed its actor-name map by the
membership row id (`m.id`) while every actor filter value carries `user_id`,
so member names never resolved in the assignee and creator chips either. One
token, same expression, fixed here rather than left next to the new code.

Also drops two comments still describing the member/agent scope.

Co-authored-by: multica-agent <[email protected]>

* fix(properties): stop a single actor value overwriting an unreadable ref (MUL-6286)

Round-two review finding. The previous fix only protected `multi_actor`:
its toggle round-trips raw strings, so a reference kind this build cannot
parse survives an edit. Single `actor` had no such protection.

Once the backend widens actorPropertyKinds, an older client reading
`agent:<id>` in a single actor property would filter it out, render the field
as empty, and then — when the user "filled in the empty field" — replace it
outright. The user sees themselves setting a blank field; what actually
happens is a value they were never shown gets deleted.

The editor now degrades to read-only when a single actor value holds a
reference it cannot parse, and the display says so instead of showing the
empty-state label. Clear is still offered, so a genuinely stale value can be
removed deliberately. `multi_actor` stays editable — its toggle is already
non-destructive.

The read-only decision moved into an exported `isCustomPropertyReadOnly`, so
all three reasons it can fire (archived, unknown type, unreadable actor ref)
are covered by tests rather than being inline in the component.

Also closes the two non-blocking gaps from the same review:

- The "email outranks a name substring match" CLI test asserted a ranking its
  fixture could not actually exercise — no candidate's name contained the
  email. Added an agent named "Mailer for [email protected]", so the test now
  fails if email stops being ranked with id matches.
- Extracted `buildChipActorNames` and asserted a member resolves by `user_id`
  and NOT by the membership row id — the pre-existing bug fixed in the last
  commit now has a test that would catch its return.

Co-authored-by: multica-agent <[email protected]>

* chore(migrations): renumber actor-types migration to 341 (MUL-6286)

332 was taken by 332_issue_status on main while this PR was open; the
migration lint test caught the collision in CI.

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Bohan-J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-08-17 19:30:03 +08:00
..