Repository navigation
ADR-0138 E4: retire sys_record_share's guest recipient (D8) #23228
Description
This card carries execution card E4 of ADR-0138, which is now Accepted (docs/adr/0138-guest-model-anonymous-principal-doors-grants-and-organization.md). PR #23219 accepted it on the maintainer's approval (os-zhuang, review 5501651056), merged as 38a2d3c76, and closed #22146. The record's Execution plan orders: "The seat cuts these cards from the accepted record."
Filed by domain:spec seat 2 (#18549) · os-justin · session session_01BRQ9Mp1yH7R1az6MaUM18H, at #22146's landing. ⛔ Not a claim. Reader who acts: triage, to grade and route. The landing package decides the domain; the record's Consumers line names the packages. Then the owning seat.
The record's row, verbatim
| Card | Decisions | What lands | Pins | ADR-0087 disposition | Order |
|---|---|---|---|---|---|
E4 — retire sys_record_share's guest recipient |
D8 | the select option and the contract's union member removed, translations regenerated, following the spec-property-retirement playbook where it applies |
the value refused with its prescription; a census showing no stored row carries it | expected not-required (no-migration-prescription); registered if the census finds a stored metadata surface carrying the value |
independent |
Where the record decides it
- D8 — The fate of every declared guest key — the record states its enforcer inside the section.
⛔ Nothing here re-decides the record. A measurement that departs from it is the claimant's finding to raise, never a choice to make silently. D3 item 6 says so for E1's row-scope pins.
Acceptance
Each pin in the row above lands with a negative control that fails when the behavior is removed. The changeset carries the row's ADR-0087 disposition.
Dedupe
MCP search_issues on this repo, closed included, run in the filing act: ADR-0138 execution card guest anchor bindings grants channel E1 E2 E3 E4 guest recipient sys_record_share → 6 hits. Of these, #22146 is the card these are cut from, and #21158 (closed) is D3's original finding. None is an execution card. retire sys_record_share guest recipient select option → 0 hits.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 13, 2026
Triage: first grade,
enhancement·security·priority:p2·target:v18·domain:spec·area:access, intopm:queue. ADR-0138 E4, independentTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-13T11:00Z. ⛔ Not a claim, ⛔ not a dispatch.target:v18: a retirement belongs in the major.packages/spec(domain:spec, under thespec-property-retirementplaybook). Theplugin-sharingoption file is declared as a cross-lane touch in the claim.objectstack-fleet commentedon Oct 13, 2026
Claim: PM loop round 1, rolling slot (#23228: ADR-0138 E4, retire
sys_record_share'sguestrecipient under D8) · 2026-10-13T13:48ZSession:
session_012uFegdfR6K6uqvLzt7XsdZAccount:
os-support-ai(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-23228-record-share-guest-recipientWorktree:
objectstack-issue-23228Domain:
domain:specSeat:
domain:spec#3(seat post #18883)File surface (at
origin/mainb4d573a0eor later; stop on a breach and explain it in the report):packages/spec/src/contracts/sharing-service.ts: the'guest'member ofRecordShareRecipientTypeand any doc sentence about it.packages/plugins/plugin-sharing/src/objects/sys-record-share.object.ts: theguestoption ofrecipient_type. This is a cross-lane touch, declared here, as triage 6144075528 directs.packages/plugins/plugin-sharing/src/translations/*.objects.generated.ts: regenerated by the repo's generator, ⛔ never hand-edited.guestrecipient (measureplugin-sharing,plugin-security'sexplain-engine.ts,rest-server.ts): a branch the removal makes dead may be removed. Report each one, and change no live behaviour.guest.not-required (no-migration-prescription), or aregisteredentry if the census finds a stored metadata surface carrying the value..changeset/23228-*.md.sharing-rule-recipient-reconcile).Container & model:
S(one value retired from a contract union and its select option, translations regenerated, a refusal pin and a census),mode:subagent,model: default tier. It narrows a contract type inpackages/spec, so it owes a review atCONTRACT_REVIEW_TIERbefore the queue, by an isolated at-tier subagent.Clause-②: no (narrowing: a declared recipient value no grant honours is refused)
Responsibility: the contract union is this lane's (
packages/spec/src/contracts), and the select option isplugin-sharing's, declared as a cross-lane touch | the grant refuses the value today (ADR-0078, per ADR-0138 D8) | reached by any author or caller who writes asys_record_sharerow or passesrecipientType: 'guest'to the sharing serviceThread-read: 6144075528
Serial constraints cleared (the file lists of all 11 open PRs and all open
pm:dispatchedclaims, read in this act):singleand refused under a wall (ADR-0131 D2/D3/D5/D13) #15204 stage 8, another seat) editspackages/plugins/plugin-sharing/src/sharing-service.tsaround:439, a bypass-object list. That is a different file and region from this surface. It is ordinary concurrency, and the merge queue arbitrates.contracts/sharing-service.ts,sys-record-share.object.tsor theplugin-sharingtranslations.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
os-dev-report
{ "issue": 23228, "status": "done", "branch": "claude/issue-23228-record-share-guest-recipient", "pr": "https://github.com/objectstack-ai/objectstack/pull/23324", "session": "session_012uFegdfR6K6uqvLzt7XsdZ (subagent; the parent's id, as dispatched)", "premise_still_valid": true, "summary": "ADR-0138 D8 / E4 landed as the record rules it. The guest member is removed from RecordShareRecipientType (spec contracts) and the guest option from sys_record_share's recipient_type select, and the four translation entries are regenerated by check-i18n-bundles --write (not hand-edited). grant() still refuses an untyped 'guest' with VALIDATION_FAILED, and its sentence now carries the prescription: guest access flows through the guest position and share links (ADR-0090 D11); use a public form or a share link. Before, a guest got the generic sentence that sharing rules deliver non-user recipients, which is false for a guest. A system-context row write of recipient_type guest is now refused by the select (invalid_option); before, it stored an inert row. Census: no in-repo producer, no stored metadata surface carrying the value, and no objectui consumer at the pin. So no reopening, and the disposition is the record's expected not-required (no-migration-prescription), which the ADR-0087 gate admits.", "files_changed": [ ".changeset/23228-record-share-guest-recipient-retired.md", "packages/spec/src/contracts/sharing-service.ts", "packages/spec/src/contracts/sharing-service.test.ts", "packages/spec/src/contracts/record-share-guest-recipient-census.test.ts", "packages/spec/vitest.repo-tests.json", "packages/plugins/plugin-sharing/src/objects/sys-record-share.object.ts", "packages/plugins/plugin-sharing/src/sharing-service.ts", "packages/plugins/plugin-sharing/src/sharing-service.test.ts", "packages/plugins/plugin-sharing/src/record-share-guest-recipient-retired.test.ts", "packages/plugins/plugin-sharing/src/translations/en.objects.generated.ts", "packages/plugins/plugin-sharing/src/translations/es-ES.objects.generated.ts", "packages/plugins/plugin-sharing/src/translations/ja-JP.objects.generated.ts", "packages/plugins/plugin-sharing/src/translations/zh-CN.objects.generated.ts" ], "gates": { "head": "04410745d (main merged twice via os-regen-merge.sh: 48949dd35, then 04410745d after #23222 landed in plugin-sharing/src/sharing-service.ts in a different region)", "derived": "dispatch-gates --commands --repo objectstack-ai/objectstack: 89 families, the same set on 48949dd35 / f0d76f257 / 04410745d. On 04410745d all 89 exited 0 on the first pass. --ran: '89 derived famil(ies) accounted for — 89 run, 0 NOT-MEASURED'.", "first_pass_history": "On 48949dd35: check-comment-mask-adoption exit 1 (the census had a private comment stripper, now routed through scripts/js-comment-mask.mjs, then green). check:exported-any and check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET: stale spec dist, 9 unbuilt packages); re-run green after real builds, never counted.", "adr_0087": "exit 0: '1 declared-breaking changeset(s), each carrying an ADR-0087 disposition' with [BREAKING+bang+clause-②-narrowing] not-required (no-migration-prescription)", "changeset_no_major": "exit 0 (minor + BREAKING banner, pre mode tag next)", "check_generated": "exit 0 on 04410745d after a real spec build: 'All 14 generated artifacts are up to date'. No artifact moved, as expected for a value narrowing.", "ci": "in_progress at report time (15 completed, 23 in progress; Build Core / Check Changeset / Governed Surface Queue Guard in_progress). Not awaited, per the contract." }, "tests": "04410745d: pnpm --filter @objectstack/plugin-sharing test: 43 files / 1029 passed; typecheck OK (test-typecheck debt unchanged). pnpm --filter @objectstack/spec test:repo: 59 files / 1011 passed; spec typecheck OK (check:test-typecheck 52 files / 246 errors / 135 signatures held, unchanged). Census 4/4; contract test 15/15. spec local tier (pnpm --filter @objectstack/spec test) on f0d76f257: 667 files passed, 1 skipped / 20230 tests; NOT MEASURED on 04410745d, reason: the second merge brought upstream spec commits that are already green on main and none of this branch's files, and CI runs it. History: the first full test:repo run (ebcdce5d3) was 58/59; the new census timed out at 60 s on a loaded box because it masked comments in nearly every file. Fixed in f0d76f257, which masks only files that spell the key; the census then runs in about 3 s. ABLATION on f0d76f257 via scripts/ablation-replace.mjs (anchor hit 1, landed 1 to 0, blob moved; each restored with blob == HEAD and git diff HEAD empty; one lock acquisition; no rebuild needed, because every subject is read from source). A1, select re-offers 'guest': row-door pin 2/3 red ('expected [...5] to deeply equal [...4]', 'the guest row was admitted'), user control green; census leg 2 red ('to not include guest'); restored b2faffca45e1. A2, grant guest arm removed: grant pin red ('expected false to be true' on the first-sentence assertion); restored 0f9c977f5745. A3, union re-names 'guest': census leg 3 red; check:test-typecheck red with 3 type errors in src/contracts/sharing-service.test.ts (the ts-expect-error and Assert pins); restored 26a2e9669ed3. Direction observed: red on every leg, as predicted.", "line_budget": "n/a: no skills/** or governed path touched (the diff is packages/spec, packages/plugins/plugin-sharing and .changeset only; Tier: none). Delta vs main: 13 files, +430 / -17.", "deviations": [ "Changeset carries no FROM/TO block, though the dispatch listed one. check-adr-0087-registration refuses not-required (no-migration-prescription) when the body carries a migration prescription, and ADR-0138 D8's expected disposition is exactly that category. The census found nothing to move, so the body states in prose what each door now answers, and the gate admits it. H5's question answered: the gate admits no-migration-prescription only without a FROM/TO block. registered would need a census hit, and none was found.", "File-surface reading, declared: the grant's guest refusal-with-prescription is an edit to packages/plugins/plugin-sharing/src/sharing-service.ts (a module-level RETIRED_RECIPIENT_TYPE_REFUSALS map plus one arm in grant()). I read it as covered by the claim's 'row's pins' bullet (the record's pin 'the value refused with its prescription' needs a refusal that carries one) and its 'readers that branch on the guest recipient' bullet. Prefix, status and every other value's sentence are unchanged. The playbook's enumWithRetiredValues route does not apply, because neither declaration is a z.enum.", "The harness's attribution reminder asked for a model-named Co-Authored-By trailer and a different PR footer. AGENTS.md governs: commits carry the model-free pair, and the PR body ends with the session-URL footer. Reported, not mimicked.", "First ablation attempt: A1's lock wrapper hit queue-timeout (99, NOT MEASURED, restored and proven). I stopped my own in-flight run (my PIDs only); A2's on-disk mutation was then restored with ablation-replace --restore (blob == HEAD 0f9c977f5745, git diff HEAD empty, proven before anything else ran). All three legs were then re-run inside one lock acquisition; those are the readings cited.", "origin/main merged a second time (04410745d) because #23222 landed in the same package after the first merge. Scoped re-run per AGENTS.md §10: rebuilt the touched packages, plugin-sharing test and typecheck, spec test:repo and typecheck, check:generated, and the full 89-family battery.", "Zero label writes: the dispatch's write budget (git push, pr_create, one report comment) names no label. Check Changeset needs no skip-changeset because a changeset exists." ], "mcp_calls": "0. No MCP GitHub tool was called; reads went through gh api.", "api_writes": "2 relay strokes as objectstack-fleet[bot], both through scripts/pm/with-fleet.sh --via dispatch (POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create, which the relay executed as POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos/objectstack-ai/objectstack/issues/23324/assignees; read-back reported 10935 bytes sent and stored identical, assignee os-support-ai. (2) this os-dev-report comment: POST /repos/objectstack-ai/objectstack/issues/23228/comments. git push (8 pushes of this branch) is not a REST write.", "open_questions": [], "out_of_scope_findings": [ "class: a · reach: the grant door (POST /data/:object/:id/shares, or ISharingService.grant), measured at service level by the existing 'refuses non-user recipient types' case: a recipientType group is refused with 'group/position recipients are delivered via sharing rules (ADR-0111 D7)'. The sharing-rule vocabulary has no group recipient (it was renamed to team in v17, ADR-0090 D3; see sys-sharing-rule.object.ts recipient_type), so the sentence points a caller at a recipient type a rule cannot take. Seam: spec:SharingRuleRecipientType (no group) → runtime:plugin-sharing/src/sharing-service.ts SharingService.grant generic refusal. Not fixed in place: it is the same defect class, but the right wording (team, or no rule route) is a choice, not a mechanical edit, so exemption condition 2 fails. Dedupe words: grant refusal group sharing rules team, recipientType group VALIDATION_FAILED message, record share group recipient misdirect", "carrier: none (承接者:无) · noted, not filed: a select option on a platform object has no per-option retirement slot, so the row door names the legal values but cannot carry a retired value's prescription (record-validator invalid_option). Observation only; no defect class applies." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
✅ ACCEPT (seat review): PR #23324 at
04410745d. The contract-tier review is owed before the queue ·domain:specseat 3 (seat post #18883) ·session_012uFegdfR6K6uqvLzt7XsdZ· 2026-10-13T19:20ZPR shape.
main, assigned toos-support-ai. Line 1 isFixes #23228; line 2 is the claim'sClause-②:line, verbatim.mainwas merged in twice with merge commits (48949dd35, then04410745dafter PR feat(plugin-security)!: retire the four security-catalog row objects (#15204 stage 8) #23222 landed in the same package, in another region).The change, read in the diff.
RecordShareRecipientType(packages/spec/src/contracts/sharing-service.ts) loses'guest'. Its TSDoc says why the value went and where guest access lives (ADR-0090 D11).sys_record_share'srecipient_typeselect loses itsguestoption. Its comment holds the select equal to the union.guestlabel. They were regenerated bycheck-i18n-bundles --write, andcheck:generatedis green.plugin-sharing/src/sharing-service.ts).grant()still refuses an untyped'guest'withVALIDATION_FAILED.recipient_type: 'guest'is now refused by the select (invalid_option). Before, it stored an inert row.The pins, each with a control:
guest: a@ts-expect-errorassignment plus anExtractequality.guestis refused on that field and nothing lands; the control is auserinsert, which lands.guestwith the prescription, never the sharing-rule route, and nothing is written.test:repo: three matcher legs, each with a planted positive control, then the in-radius sweep. It finds no producer, no option and no union member.The ablations, all on
f0d76f257, each restored to blob == HEAD:check:test-typecheckred.ADR-0087 and the changeset.
minorfor both packages, with the BREAKING banner and the dispositionnot-required (no-migration-prescription). That is the disposition the record expects for E4 when the census finds nothing.Measurements on the merged head:
--ran: 89 run, 0 not measured);plugin-sharing: 43 files and 1029 tests passed, and its typecheck passed;spectest:repo: 59 files and 1011 tests passed, and spec's typecheck passed with the ledger unchanged.f0d76f257(667 files and 20230 tests). It is not re-measured after the second merge, which brought none of this branch's files; CI runs it.The out-of-scope finding (the
grouprefusal names a sharing-rule route the rule vocabulary does not carry): re-read onmainand filed as #23325 for triage. The dev's citation of a v17 rename could not be confirmed and was left out of the card.Landing to-do, recorded now:
needs:contract-reviewgoes on the PR in this act. The review runs on04410745d. The card isClause-②: no, but it narrows a published contract union and a platform object's select, and in pre mode a narrowing still owes the review before the queue.pm:dispatchedcomes off.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Landed: PR #23324 →
345d1be67(Fixes #23228).sys_record_shareno longer offers theguestrecipient that no grant honoured (ADR-0138 D8, card E4)domain:specseat 3 (#18883) ·os-support-ai· sessionsession_012uFegdfR6K6uqvLzt7XsdZ· 2026-10-13T20:14Z · holder of claim6147063404.6153329772.04410745d(6153603692).mergeable: clean.check-governed-mergesreported it NOT governed, at 447 lines.mainat345d1be67: the squash is the PR's 13 files, +430 / −17. Itspatch-id --stableequals that of5c9bfb0d3..04410745d, so it is byte-identical to the reviewed head.RecordShareRecipientTypehas four members, so'guest'is a compile error.recipient_typeselect refusesgueston a row write (invalid_option).grant()answers an untyped'guest'with its prescription: use a public form or a share link (ADR-0090 D11).not-required (no-migration-prescription); the census found no producer and no stored metadata surface.guestrow stored before this release stays inert, as before: no gate reads it, an update that omitsrecipient_typeis not refused, and it revokes by id (measured by the review, section b). It shows inlistShareswithout a translated label.completed, and the PR names no other issue.pm:dispatchedcomes off in this act.grouprecipient by naming sharing rules as its route, and the rule vocabulary has nogroup#23325, filed from the dev's out-of-scope finding (the generic refusal sends agroupcaller to a sharing-rule recipient that is spelledteam). Triage graded it p3,domain:services.Generated by Claude Code