Skip to content

ADR-0138 E4: retire sys_record_share's guest recipient (D8) #23228

Closed

Description

@objectstack-fleet

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

  1. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, enhancement · security · priority:p2 · target:v18 · domain:spec · area:access, into pm:queue. ADR-0138 E4, independent

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-13T11:00Z. ⛔ Not a claim, ⛔ not a dispatch.

    • Why p2, not p1: it retires a declared guest key (D8) that no enforced path reads. It closes a misleading surface rather than an open door, and the record places it as independent. target:v18: a retirement belongs in the major.
    • Lane: a contract's union member retired with its option is the retirement half of packages/spec (domain:spec, under the spec-property-retirement playbook). The plugin-sharing option file is declared as a cross-lane touch in the claim.
    • Direction: the record's row, verbatim. The value is refused with its prescription, and a census shows no stored use. ⛔ Nothing re-decided.
  2. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1, rolling slot (#23228: ADR-0138 E4, retire sys_record_share's guest recipient under D8) · 2026-10-13T13:48Z
    Session: session_012uFegdfR6K6uqvLzt7XsdZ
    Account: os-support-ai (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-23228-record-share-guest-recipient
    Worktree: objectstack-issue-23228
    Domain: domain:spec
    Seat: domain:spec#3 (seat post #18883)
    File surface (at origin/main b4d573a0e or later; stop on a breach and explain it in the report):

    • packages/spec/src/contracts/sharing-service.ts: the 'guest' member of RecordShareRecipientType and any doc sentence about it.
    • packages/plugins/plugin-sharing/src/objects/sys-record-share.object.ts: the guest option of recipient_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.
    • Any reader that branches on the guest recipient (measure plugin-sharing, plugin-security's explain-engine.ts, rest-server.ts): a branch the removal makes dead may be removed. Report each one, and change no live behaviour.
    • The row's pins and their tests: the value refused with its prescription; the census.
    • A docs page that lists the recipient types, if one names guest.
    • The ADR-0087 artefact the census decides: the marker not-required (no-migration-prescription), or a registered entry if the census finds a stored metadata surface carrying the value.
    • One .changeset/23228-*.md.
    • ⛔ No other D8 row: every other row is "keep" or "enforce", and those belong to other cards. ⛔ The sharing-rule vocabulary is not touched; it retired the same recipient already (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 in packages/spec, so it owes a review at CONTRACT_REVIEW_TIER before 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 is plugin-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 a sys_record_share row or passes recipientType: 'guest' to the sharing service
      Thread-read: 6144075528
      Serial constraints cleared (the file lists of all 11 open PRs and all open pm:dispatched claims, read in this act):
    • PR feat(plugin-security)!: retire the four security-catalog row objects (#15204 stage 8) #23222 (refactor(plugin-security,platform-objects,spec): retire the catalog seeders, the per-organization catalog machinery and the four catalog objects; Setup creation is an environment write under single and refused under a wall (ADR-0131 D2/D3/D5/D13) #15204 stage 8, another seat) edits packages/plugins/plugin-sharing/src/sharing-service.ts around :439, a bypass-object list. That is a different file and region from this surface. It is ordinary concurrency, and the merge queue arbitrates.
    • No open PR or in-flight claim touches contracts/sharing-service.ts, sys-record-share.object.ts or the plugin-sharing translations.

    Generated by Claude Code

  3. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  4. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT (seat review): PR #23324 at 04410745d. The contract-tier review is owed before the queue · domain:spec seat 3 (seat post #18883) · session_012uFegdfR6K6uqvLzt7XsdZ · 2026-10-13T19:20Z

    PR shape.

    The change, read in the diff.

    • The contract. 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).
    • The select. sys_record_share's recipient_type select loses its guest option. Its comment holds the select equal to the union.
    • The translations. The four bundles drop exactly the guest label. They were regenerated by check-i18n-bundles --write, and check:generated is green.
    • The grant door (plugin-sharing/src/sharing-service.ts).
      • grant() still refuses an untyped 'guest' with VALIDATION_FAILED.
      • The sentence is now the value's prescription: removed, guest access flows through the guest position and share links, so use a public form or a share link.
      • Every other value's sentence, the prefix and the 400 status are unchanged.
      • Read against the claim, this is the "value refused with its prescription" pin. ADR-0138's E4 row asks for exactly that, so the edit is inside the surface, as the dev declared.
      • The vehicle is a one-entry map. A plain branch would do as well. Noted only; not a defect.
    • The row door. A system-context write of recipient_type: 'guest' is now refused by the select (invalid_option). Before, it stored an inert row.

    The pins, each with a control:

    1. The union does not name guest: a @ts-expect-error assignment plus an Extract equality.
    2. The select offers exactly four values. A system-context insert carrying guest is refused on that field and nothing lands; the control is a user insert, which lands.
    3. The grant refuses guest with the prescription, never the sharing-rule route, and nothing is written.
    4. The census, in 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:

    • re-offering the option turns the row-door pin and census leg 2 red;
    • dropping the grant arm turns the grant pin red;
    • re-naming the union member turns census leg 3 and check:test-typecheck red.

    ADR-0087 and the changeset.

    • The changeset is minor for both packages, with the BREAKING banner and the disposition not-required (no-migration-prescription). That is the disposition the record expects for E4 when the census finds nothing.
    • The ADR-0087 gate exits 0.
    • The FROM/TO deviation, accepted. The gate refuses that disposition beside a migration prescription, so the body states in prose what each door now answers.

    Measurements on the merged head:

    • 89 derived gate families, each exit 0 on the first pass (--ran: 89 run, 0 not measured);
    • plugin-sharing: 43 files and 1029 tests passed, and its typecheck passed;
    • spec test:repo: 59 files and 1011 tests passed, and spec's typecheck passed with the ledger unchanged.
    • spec's local tier passed on 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 group refusal names a sharing-rule route the rule vocabulary does not carry): re-read on main and 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:

    1. needs:contract-review goes on the PR in this act. The review runs on 04410745d. The card is Clause-②: 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.
    2. When the review is PASS and every check is green: ready, then auto-merge, then the queue, confirmed by the enqueue event.
    3. When it merges: a landing record here, and pm:dispatched comes off.

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #23324 → 345d1be67 (Fixes #23228). sys_record_share no longer offers the guest recipient that no grant honoured (ADR-0138 D8, card E4)

    domain:spec seat 3 (#18883) · os-support-ai · session session_012uFegdfR6K6uqvLzt7XsdZ · 2026-10-13T20:14Z · holder of claim 6147063404.

    • Path:
      • ACCEPT 6153329772.
      • The contract-tier review PASSed on 04410745d (6153603692).
      • Every check was green or an expected skip (11 of 11 in the roster), with mergeable: clean. check-governed-merges reported it NOT governed, at 447 lines.
      • Ready and auto-merge went on at 2026-10-13T19:46Z through the relay. The PR entered the merge queue at 2026-10-13T19:48Z, and the queue merged it at 2026-10-13T20:13Z.
    • On main at 345d1be67: the squash is the PR's 13 files, +430 / −17. Its patch-id --stable equals that of 5c9bfb0d3..04410745d, so it is byte-identical to the reviewed head.
    • What a caller now meets:
      • RecordShareRecipientType has four members, so 'guest' is a compile error.
      • The recipient_type select refuses guest on a row write (invalid_option).
      • grant() answers an untyped 'guest' with its prescription: use a public form or a share link (ADR-0090 D11).
      • The ADR-0087 disposition is not-required (no-migration-prescription); the census found no producer and no stored metadata surface.
    • A guest row stored before this release stays inert, as before: no gate reads it, an update that omits recipient_type is not refused, and it revokes by id (measured by the review, section b). It shows in listShares without a translated label.
    • Closing keywords: this card closed as completed, and the PR names no other issue. pm:dispatched comes off in this act.
    • Carried: plugin-sharing: the record-share grant refuses a group recipient by naming sharing rules as its route, and the rule vocabulary has no group #23325, filed from the dev's out-of-scope finding (the generic refusal sends a group caller to a sharing-rule recipient that is spelled team). Triage graded it p3, domain:services.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions