Repository navigation
feat(service-mailbox): sys_mail_message, its record associations and the restricted unassociated pool — the inbound-mail capability (manage_mailboxes, requires: mailbox) - #22993
Conversation
…, one probe for every parent-derived read gate Claude-Session: https://claude.ai/code/session_013tMiyVNzCwaUhgfUe6gg8y Co-authored-by: Claude <noreply@anthropic.com>
…nd both read gates (WIP) Claude-Session: https://claude.ai/code/session_013tMiyVNzCwaUhgfUe6gg8y Co-authored-by: Claude <noreply@anthropic.com>
…wcase requires mailbox Claude-Session: https://claude.ai/code/session_013tMiyVNzCwaUhgfUe6gg8y Co-authored-by: Claude <noreply@anthropic.com>
…e data API on a showcase boot Claude-Session: https://claude.ai/code/session_013tMiyVNzCwaUhgfUe6gg8y Co-authored-by: Claude <noreply@anthropic.com>
…t shape; changeset Claude-Session: https://claude.ai/code/session_013tMiyVNzCwaUhgfUe6gg8y Co-authored-by: Claude <noreply@anthropic.com>
…xes for platform and organization administrators Claude-Session: https://claude.ai/code/session_013tMiyVNzCwaUhgfUe6gg8y Co-authored-by: Claude <noreply@anthropic.com>
…ass is a row on the authority page; llms.txt counts the new package Claude-Session: https://claude.ai/code/session_013tMiyVNzCwaUhgfUe6gg8y Co-authored-by: Claude <noreply@anthropic.com>
…ed) and the lockstep package count Claude-Session: https://claude.ai/code/session_013tMiyVNzCwaUhgfUe6gg8y Co-authored-by: Claude <noreply@anthropic.com>
…tests; cli pins the mailbox provider identity Claude-Session: https://claude.ai/code/session_013tMiyVNzCwaUhgfUe6gg8y Co-authored-by: Claude <noreply@anthropic.com>
…ject write door Claude-Session: https://claude.ai/code/session_013tMiyVNzCwaUhgfUe6gg8y Co-authored-by: Claude <noreply@anthropic.com>
system-context.mdx: main's page taken, then the inbound-mail gates' row re-inserted as row 47 and the later rows, the prose references and the census ledger re-derived by the census gate (gen:system-context-census). backward-compatibility.mdx: main's prose taken, the lockstep package count re-derived by check-lockstep-package-count --fix (71). Claude-Session: https://claude.ai/code/session_013tMiyVNzCwaUhgfUe6gg8y Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013tMiyVNzCwaUhgfUe6gg8y Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 8 package(s): 74 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 14 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 156 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 2d0ee5ddd39af53b73d3e82d42ef496402f4f67d && git checkout 2d0ee5ddd39af53b73d3e82d42ef496402f4f67d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin df7a137923be503bf6fea15a6fc944d3ba06df6f 204aa2e8d4ced58a1f6bbf7df3a2381b33ac5780 && git checkout -B drift-repro df7a137923be503bf6fea15a6fc944d3ba06df6f && git merge --no-ff 204aa2e8d4ced58a1f6bbf7df3a2381b33ac5780
node scripts/docs-audit/affected-docs.mjs --json df7a137923be503bf6fea15a6fc944d3ba06df6f
|
…s (packages/core) Claude-Session: https://claude.ai/code/session_013tMiyVNzCwaUhgfUe6gg8y Co-authored-by: Claude <noreply@anthropic.com>
system-context.mdx: main's page taken, then the inbound-mail gates' row re-inserted as row 47 and the later rows, the prose references and the census ledger re-derived by the census gate (gen:system-context-census). Claude-Session: https://claude.ai/code/session_013tMiyVNzCwaUhgfUe6gg8y Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Isolated at-tier subagent of Head and checks. The REST ① Derived judgmentsPublished schema (
Other public surfaces: The objects against the build sheet (stage-0 §3.1 and §3.2, the claim's identity half), each RIGHT:
The two read gates (
Generated and derived files, each RIGHT: the tenancy census (87 registered, 52 in reach; three new The governed edit, ② Semver level
Clause-②: ③ Boundary flagsDev report
Dev report
The seat's review One note from the ADR this PR touches, escalated, not a finding against the diff: ADR-0139 D4 and its E5/E7 cards enumerate six probe consumers as read on Landing: this record satisfies the contract-review requirement a Implemented-by: VERDICT: PASS |
维护者速读
|
|
Batch #331 governed row G1 · maintainer 「同意」 · director seat, summon #39 · 2026-10-12T22:45Z Recorded by the director seat ( Landing is the owning seat's: |
Fixes #22870
Clause-②: yes (widening)
PR 2 of 2 for #22870 (PR 1,
FieldSchema.mailAssociation, landed ase5687cb6b). Built from the stage-0 report6115941082§3.1, §3.2, §3.4, §3.5 and §4 row 2, under ruling6116489638(Q2 → C3, Q4 → A). This PR completes the card's scope: the inbound-mail objects, their registration, both read gates, the boot wiring,manage_mailboxesand themailboxcapability token. The ingest half (connector-imap, ingest, dedupe, auto-reply suppression, re-match, MIME parsing, the source marker,sys_mailbox's connector columns) stays with #22871.What changes
@objectstack/service-mailbox(ADR-0029 D1/D2: a capability owns its objects, registration and gates).MailboxServicePluginregisters threeengine-ownedobjects, data APIgetandlistonly:sys_mail_message: the RFC 5322 mirror (stage-0 §3.1, 18 columns).body_htmlistextarea, neverhtml.enable.files: true, so attachments aresys_attachmentrows whose gate inherits this object's gate. The unique key is(mailbox_id, dedupe_key)per organization.sys_mail_association: one row per message-to-record link (§3.2), with the ActivityPointer pairobject_nameandrecord_id(referenceVia: 'object_name'). The unique key(mail_message_id, object_name, record_id)makes re-match idempotent.sys_mailbox: the identity half only,name,addressandowner_user_id, withrequiredPermissions: ['manage_mailboxes'].mail-read-visibility.ts, §3.4), registered as data middleware onfind,findOne,countandaggregate, so a list'stotalagrees with its rows:manage_mailboxes. A pool message has no association, so an ordinary member reads it as404 RECORD_NOT_FOUND.manage_mailboxes.errorand names the consequence.manage_mailboxes(Q4 → A) is declared inPLATFORM_CAPABILITIESwithscope: 'org'. It is held by default throughADMIN_FULL_ACCESS_CAPABILITIESand byorganization_admin, and so by its derived no-bypass variant. Its readers are both gates andsys_mailbox.requiredPermissions.mailboxcapability token: it is inPLATFORM_CAPABILITY_TOKENSandPLATFORM_CAPABILITY_PROVIDERS(open edition), and it has aCAPABILITY_PROVIDERSrow in core.@objectstack/cliand@objectstack/verifydepend on the package, soserveandbootStackmount it. The showcase now declaresrequires: [..., 'mailbox']. The token is not on the always-on slate.resolveReadableParentIdsmoves to@objectstack/core(packages/core/src/security/readable-parent-ids.ts). The plugin-audit export and the service-storage private copy are deleted, and both packages import it from core. The two bodies were byte-identical (compared before the move), so no existing gate changes its answer.service-mailboxgroup inplatform-object-names.ts;attribution; the message is anchored to its mailbox, the association to its message);platform-object-tenancy-census.json, regenerated: 87 registered, 52 in reach;manage_mailboxes);llms.txtpackage count (70);content/docs/permissions/system-context.mdx, for the gates'isSystembypass. Later rows are renumbered, therows 1–62androw 60references follow, and the census gate's ledger and self-test track the shift.Verification record (head
204aa2e8d)Showcase boot through the data API:
packages/qa/dogfood/test/mail-message-pool-read-gate.dogfood.test.ts, 9/9. This is the §3.4 probe table, and every refusal asserts bothstatusandcode:200404,RECORD_NOT_FOUND404,RECORD_NOT_FOUNDtotalis 1totalis 3manage_mailboxesholder, and the seeded platform admin, read the pool (exemption control)200403control)403,PERMISSION_DENIEDsys_mailbox(object grant held, capability not)403,PERMISSION_DENIED; the holder gets200Ablation (scripts/ablation-replace.mjs, then a rebuild, then scripts/ablation-dist-preflight.mjs):
installMailReadVisibilityreturned before registering either middleware. The marker reacheddist/in 2 built files. The mutated tree's dts step failed on a narrowing the mutation itself caused, but the JS was emitted, and the preflight proves the marker is in it.expected 200 to be 404, the foreign read likewise, the listtotalwas 2 instead of 1, and the association list likewise.git diff HEADis empty,git status --porcelainis empty, the marker is absent fromdist/, and the suite is 9/9 again.dist/. Its reading was discarded and the ablation was redone with the marker in a string literal.Package suites:
@objectstack/service-mailbox14/14: plugin registration, the provider identity, the capability pins, object shape, and the write-door check with its control;@objectstack/core2381;@objectstack/plugin-audit743;@objectstack/service-storage846;@objectstack/plugin-security4057 passed, 45 skipped;@objectstack/verify250;@objectstack/platform-objects1083;@objectstack/example-showcase409;@objectstack/cliunit 4265, with the 2 identity-pin failures fixed and re-run;src/kernel,src/security,src/identityandsrc/system/constants, 1738;typecheck: green for core, plugin-audit, service-storage, plugin-security, verify, service-mailbox, dogfood, example-showcase, spec and cli.
eslint (
--no-inline-config) on the 42 changed.ts/.mjsfiles: 0 errors, 0 warnings. This is a targeted run, not a proof of narrowing; the repo-widepnpm lintis CI's.Gates:
node scripts/pm/dispatch-gates.mjs --commandsderived 155 commands on204aa2e8d, and all exit 0. The--ranreconciliation reports 155 derived, 155 run and 0 NOT-MEASURED, a derived zero.check:generated: all 14 artifacts up to date.check:platform-checklist(outside per-PR CI) exits 1 with 8 problems on the basee23eccb16and the same 8 on the head; the problem sets are identical line for line. The hoist removed no symbol that the checklist anchors.Size: 2891 changed lines (+2739 / -152, 58 files, generated files included), under 3000.
Acceptance notes
@objectstack/core. All three consumers already depend on it, and it already hostswithoutOperationPrivateKeys. AReadableParentIdsEnginestructural type keeps core free of an engine dependency. ADR-0139 symbol-anchored the old location, so its one pointer is re-anchored topackages/core/src/security/readable-parent-ids.ts#resolveReadableParentIds, the pointer alone with no prose change. This putsdocs/adr/**, a Tier H path, in the diff, so the PR lands only on a maintainer approval.packages/cli/package.jsonandpackages/verify/package.json: the provider table's readers must declare the provider package;.changeset/config.json: the new package joins the fixed group;packages/qa/dogfood/test/…: the showcase boot test lives with the other showcase boots, because a test inside the package would need@objectstack/verifyand the example as dev dependencies, a cycle;packages/cli/test/serve-capability-identity.test.ts: the identity pin;content/docs/permissions/system-context.mdxandscripts/check-system-context-census.mjs: required by the census gate;packages/spec/llms.txt,content/docs/protocol/backward-compatibility.mdxandscripts/publish-smoke-pack.mjs: package counts;docs/adr/0139-engine-internal-semi-join-leaf-readable-parent-ids.md: the anchor the hoist moved;packages/services/service-mailbox/README.md: the README itsfiles[]lists;scripts/i18n-coverage-baseline.json: the new package's coverage entry.manage_mailboxesandmailboxland together with their readers in this PR. Thepackages/spec/liveness/README.mdfield-cell sentence was not ridden.sys_mail_message, associated at ingest through the seam — phase 1 of #8998 inbound mail #22871 lands. Every object isengine-ownedwithgetandlistonly, so no author or API caller can create rows that nothing reads.sys_mail_message, associated at ingest through the seam — phase 1 of #8998 inbound mail #22871's matcher.维护者速读(草稿)
@objectstack/service-mailbox能力包。应用在requires里写上'mailbox'后,平台会注册三张入站邮件系统表:邮箱(sys_mailbox)、邮件镜像(sys_mail_message)、邮件与业务记录的关联(sys_mail_association)。同时装上两道读取门,并新增能力名manage_mailboxes,平台管理员和组织管理员默认持有。另把一个“判断调用者能否读某条父记录”的公共函数从两个包挪进@objectstack/core,ADR-0139 里指向它的那一个链接随之更新,ADR 正文不变。sys_mail_message(the inbound mirror object) and the association seam — phase 1 of #8998 inbound mail, spec seat #22870)要求“未匹配到任何记录的邮件池默认受限,只有邮箱管理员和邮箱所有者可见,不对全组织开放”。现在普通成员只能读到与自己能看的记录关联的邮件;读池中邮件得到 404,按“不存在”处理,不泄露存在性。真正收信的 IMAP 接入属于 connector-imap + minimal service-mailbox: shared-mailbox (forward/BCC) ingest intosys_mail_message, associated at ingest through the seam — phase 1 of #8998 inbound mail #22871,本 PR 不包含。mailbox能力时才注册,不声明的部署零影响。公共函数的搬家经逐字节比对,原附件、评论、动态、审计日志的读取门行为不变,对应 dogfood 套件全绿。代价是组织管理员默认多了一个能力。回滚:revert 本 PR 即可;由于还没有写入方,不会留下数据。docs/adr/**(只是 ADR-0139 的一个链接),需要你在 PR 上点一次 Approve,之后由席位走合并队列落地。Generated by Claude Code