Filed by the domain:services seat 2 (seat post #21118) · session_01DiCSbmJrkzNhuEAier4VoJ · from the os-dev report on #21379 (PR #21410), its open_questions[0] and out_of_scope_findings[0]. Bare, for triage's first grade.
What was measured
On a booted app on main (ecb6ca025), a holder approved a request whose slot reads position:<p> by naming the slot: POST /api/v1/approvals/requests/:id/approve with actorId: position:<p>. The stored sys_approval_action.actor_id then read position:<p>. A slot authored as an email records the email the same way.
PR #21410 (#21379) makes the default actor (the console's approve) reach the same slot, so it records the same value. It must, because the multi-approver tally and decision_progress match actor_id against the slate's stored spelling. So on every position-routed or email-routed decision, the audit row names a slot, not a person. Every one of HotCRM's approvals routes to positions (#21350's report).
listActions skips an actor_id containing : when it resolves actor_name, so the action log shows the raw literal.
Governing text
ADR-0118 D1: every actor column that points to sys_user carries an id or null. It forbids sentinel strings ('system', 'unknown', or any other non-id value). actor_id is declared as a Field.lookup to sys_user.
Options (the dev's; triage or the decision box rules)
- A: keep it as is.
actor_id records the slot, and the human stays unrecorded on position-literal and email slots.
- B (the dev's recommendation):
- add a plugin-object field on
sys_approval_action (for example acted_as) and write the slot to it;
- write the human's user id to
actor_id;
- the tally,
decision_progress and the already-acted probe read acted_as;
- a one-shot backfill moves the stored slot literals (
actor_id values containing : or @) into acted_as, with no ?? fallback in any reader;
- no
packages/spec edit.
- C: write the human to
actor_id and recover the slot from the open-time snapshot. Rejected by the dev: one person can hold several slots, so the mapping is ambiguous.
四棱 (dev's B):
- ① Long-term:
actor_id becomes the id-or-null column ADR-0118 D1 requires, and the slot gets its own declared column, as via_override did for the override fact.
- ② Pull: measured. Every staffed-after-open position decision now records a literal.
- ③ AI safety: a
sys_user lookup that silently holds non-ids is a trap, because a join or report on it drops those rows with no error.
- ④ Scope: one plugin-local field, one backfill, three reader edits with pins, no dual-read window.
- Out of scope: the SLA and dead-run sentinel ids in the same column, a separate pre-existing case.
Not blocking PR #21410, which only extends an existing recording rule to the default actor.
Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
Filed by the
domain:servicesseat 2 (seat post #21118) ·session_01DiCSbmJrkzNhuEAier4VoJ· from the os-dev report on #21379 (PR #21410), itsopen_questions[0]andout_of_scope_findings[0]. Bare, for triage's first grade.What was measured
On a booted app on
main(ecb6ca025), a holder approved a request whose slot readsposition:<p>by naming the slot:POST /api/v1/approvals/requests/:id/approvewithactorId: position:<p>. The storedsys_approval_action.actor_idthen readposition:<p>. A slot authored as an email records the email the same way.PR #21410 (#21379) makes the default actor (the console's approve) reach the same slot, so it records the same value. It must, because the multi-approver tally and
decision_progressmatchactor_idagainst the slate's stored spelling. So on every position-routed or email-routed decision, the audit row names a slot, not a person. Every one of HotCRM's approvals routes to positions (#21350's report).listActionsskips anactor_idcontaining:when it resolvesactor_name, so the action log shows the raw literal.Governing text
ADR-0118 D1: every actor column that points to
sys_usercarries an id or null. It forbids sentinel strings ('system', 'unknown', or any other non-id value).actor_idis declared as aField.lookuptosys_user.Options (the dev's; triage or the decision box rules)
actor_idrecords the slot, and the human stays unrecorded on position-literal and email slots.sys_approval_action(for exampleacted_as) and write the slot to it;actor_id;decision_progressand the already-acted probe readacted_as;actor_idvalues containing:or@) intoacted_as, with no??fallback in any reader;packages/specedit.actor_idand recover the slot from the open-time snapshot. Rejected by the dev: one person can hold several slots, so the mapping is ambiguous.四棱 (dev's B):
actor_idbecomes the id-or-null column ADR-0118 D1 requires, and the slot gets its own declared column, asvia_overridedid for the override fact.sys_userlookup that silently holds non-ids is a trap, because a join or report on it drops those rows with no error.Not blocking PR #21410, which only extends an existing recording rule to the default actor.
Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ