@@ -17,33 +17,34 @@ export const entry: SemanticMigration = {
1717 + 'SINGLE assignment (`roles: core.positions`), so the two keys were verbatim identical '
1818 + 'on every dispatch — a second spelling of the vocabulary ADR-0090 D3 reserves and bans, '
1919 + 'published straight into author-written code. The maintainer ruled it closed IMMEDIATELY '
20- + '(2026-08-06 14:49Z, #6011): no deprecation window, no dual-emit, the alias simply gone '
21- + 'in 17 (PR #6048) . '
20+ + '(2026-08-06 14:49Z, on the finding that this alias had no closing date): no deprecation '
21+ + 'window, no dual-emit, the alias simply gone in 17 . '
2222 + '⚠️ Do not read this entry across to its sibling `action-session-roles-to-positions`: '
2323 + '`action-session-roles-to-positions` governs `ctx.session`, a DIFFERENT object reached '
24- + 'through the same `ctx`, and that one KEEPS its one-window dual-emit (#5613) . Same word, '
24+ + 'through the same `ctx`, and that one KEEPS its one-window dual-emit. Same word, '
2525 + 'same dispatch, two faces, two schedules — `ctx.user.roles` is absent in 17 while '
2626 + '`ctx.session.roles` still answers for the length of its window. '
2727 + 'What makes this entry different in KIND from both session-side siblings: `ctx.user` has '
2828 + 'no spec schema and never had one. It is a runtime TS interface, so unlike '
29- + '`HookContext.session.roles` (tombstoned on a deliberately non-strict `HookContextSchema`, '
30- + '#5050) and unlike `ActionSessionSchema` (declared contract-first at #5697 precisely so '
31- + 'its key could be renamed), there is no schema key here to tombstone and no '
29+ + '`HookContext.session.roles` (tombstoned on a deliberately non-strict `HookContextSchema` '
30+ + 'once it had no producer and no consumer left) and unlike `ActionSessionSchema` (declared '
31+ + 'contract-first, as it stood, as the first stage of the session rename, precisely so its '
32+ + 'key could be renamed), there is no schema key here to tombstone and no '
3233 + '`retiredKey()` prescription that could reach anybody — nothing ever ran an `ActorUser` '
3334 + 'through a `.parse()`, so a prescription there would have no one to reach. The enforced '
3435 + 'channel is tsc, and it reports at the READ site inside the author\'s own body; for an '
3536 + 'untyped or sandboxed body there is no enforced channel at all, which is exactly why '
3637 + 'this ledger entry has to exist — `spec-changes.json` and the generated upgrade guide '
37- + 'are the ONLY way such a reader learns of the rename. It is the `findStream` (#4484 ) / '
38- + '`IStorageService.list` (#5540 ) disposition — a TS/API contract, no stored source, no '
38+ + 'are the ONLY way such a reader learns of the rename. It is the `findStream` (no caller ) / '
39+ + '`IStorageService.list` (no consumer ) disposition — a TS/API contract, no stored source, no '
3940 + 'tombstone, tsc at the call site — applied to a surface that lives one layer further '
4041 + 'out than either: those two are at least DECLARED in `packages/spec/src/contracts`, '
4142 + 'this one only in `packages/runtime`. '
4243 + 'Why it is a D3 semantic TODO and not a D2 conversion, on the same two independent '
4344 + 'grounds as its session sibling: FIRST, there is no source to convert — an `ActorUser` '
4445 + 'is constructed per dispatch and never persisted, so no `sys_metadata` row, example or '
45- + 'template can carry the key (the `openApi31` (#4579) / `activationEvents` (#4657) / '
46- + '`hook-context-session-roles-retired` (#5050) shape). SECOND, the only place the key is '
46+ + 'template can carry the key (the `openApi31` / `activationEvents` / '
47+ + '`hook-context-session-roles-retired` shape). SECOND, the only place the key is '
4748 + 'ever SPELLED is inside an action body or an AI route handler: author-written JS/TS, or '
4849 + 'a sandboxed script. A declarative transform cannot safely rewrite an identifier inside '
4950 + 'free-form code — the same reason the ADR-0090 wave delegated `current_user.roles` to '
@@ -53,12 +54,12 @@ export const entry: SemanticMigration = {
5354 + 'here because the ledger is where an upgrading consumer meets it: the declaration\'s own '
5455 + 'comment claimed the alias was "kept for the REST/AI shapes", and that claim was '
5556 + 'DISPROVEN face by face against `origin/main` — repo-wide `user.roles` was 4 hits, all '
56- + 'of them in the pins PR #6048 flipped; the four `ActorUser` construction sites build '
57+ + 'of them in the pins the removal flipped; the four `ActorUser` construction sites build '
5758 + 'server-side envelopes that never enter a response body; objectui\'s `.roles` reads '
5859 + 'belong to two unrelated producers (the better-auth session, and the '
5960 + '`/auth/me/permissions` payload). The `cloud` repo was NOT reachable in that session and '
6061 + 'is the one consumer face left unverified — this entry, and the changeset\'s FROM/TO '
61- + 'prescription, are its disposition. ADR-0090 D3 / ADR-0049 / ADR-0087, #6011 (PR #6048) .' ,
62+ + 'prescription, are its disposition. ADR-0090 D3 / ADR-0049 / ADR-0087.' ,
6263 acceptanceCriteria :
6364 'No action body reads `ctx.user.roles` and no AI route handler reads `req.user.roles`; '
6465 + 'every such read is `.positions` and observes the SAME array — the value was '
0 commit comments