feat(data): enable.files goes live — opt-in gate for the generic Attachments surface (#2727) - #2742
Merged
Merged
Conversation
…chments surface (#2727) The last dead ObjectCapabilities flag gets its enforcement contract. enable.files stays opt-IN (default false) — the Attachments panel is a new surface, not an existing behavior: - plugin-audit sys_attachment beforeInsert hook: attachment join rows may only target objects explicitly declaring enable.files true; absent block/flag, explicit false, and unknown parent objects all reject fail-closed with 403 FILES_DISABLED (CLONE_DISABLED/FEEDS_DISABLED pattern). - mapDataError maps FILES_DISABLED → 403 with the gated target object (generic data routes bypass sendError's .status passthrough — the #2707 lesson applied at introduction time). - Field.file / Field.image stay independent: they store the file URL in the record's own column and never create sys_attachment rows. - Liveness ledger: enable.files dead→live, authorWarn dropped — ObjectCapabilities is now 100% live; describe() and reference docs state the real contract; cli lint contract tests re-anchored on `versioning` as the canonical misleading-dead example. Verified: unit (4 new gate tests, FILES_DISABLED mapping, lint contract, 32/139/464 suites green), check:liveness + check:api-surface green, and live E2E on a fresh showcase — presigned 3-step upload → attach to a files:true probe object 201; attach targeting a no-flag object → 403 FILES_DISABLED; stable download 200. Browser-verified via the console dev server: panel renders with upload/list/download/delete on the files:true object and is absent on a no-flag object. Companion objectui PR ships RecordAttachmentsPanel. Closes #2727. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 4 package(s): 98 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
added a commit
that referenced
this pull request
Jul 10, 2026
…tion instead of wall clock (#2744) (#2749) The test asserted the wrapped after-event call returned in <20ms; shared CI runners exceed that (observed 23ms on PR #2742), failing an unrelated PR. Replace with an ordering assertion: right after the wrapper resolves, the handler's side effect must not have happened yet — the handler's 30ms timer is a macrotask and cannot fire between the wrapper's resolution and the synchronous check, so this is deterministic regardless of runner speed, while still failing if the wrapper ever starts awaiting the handler (calls would already hold 'done'). Closes #2744. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 10, 2026
Closed
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Jul 17, 2026
…ycle e2e (objectstack-ai#2755) The dogfood the issue asked for: the objectstack-ai#2742 E2E only ever drove a seeded admin. New in-process matrix boots a purpose-built fixture (att_case public / att_secret private+owner / att_nofiles) with the REAL serve pairing (StorageServicePlugin + AuditPlugin) via a new BootOptions.extraPlugins seam in @objectstack/verify, signs up genuine members, and drives the presigned three-step upload + /data path. Pinned findings: - anonymous upload 401; owner_id + uploaded_by server-stamped (spoof attempt loses) - FILES_DISABLED enforced e2e for the first time - ATTACHMENT_PARENT_ACCESS: member cannot attach to an invisible record - DOGFOOD FINDING: the everyone baseline (member_default) carries NO delete bit (ADR-0090 D5) — an ungranted member cannot delete even their own attachment (PERMISSION_DENIED before the attachment gate); apps enable attachment management via an ordinary granted set (the fixture ships one), after which uploader-delete works and stranger-delete stays 403 - KNOWN GAP pins with follow-up pointers: sys_attachment listing does not inherit parent visibility; downloads stay anonymous capability URLs - Part 1 e2e: tombstone on last ref, shared-file survival, re-attach un-tombstone, sweep reaps expired tombstones AND deletes the bytes on disk, NULL deleted_at / fresh tombstones / committed rows survive, hook-bypass re-reference is un-tombstoned by sweep-time re-verification, abandoned pending uploads reaped - multiTenant cross-org block (skips without the enterprise package) Also: guarded reap now deletes per id — the engine's delete path reads where.id as a scalar target, so the previous $in filter was bound as an object by the SQLite driver. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0187NT3Qer9oep5dCRb9b8Lt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2727。#2707 收口后唯一剩下的 dead ObjectCapabilities flag 接线为 live——至此
enable块 100% live,编译期 liveness authorWarn 对 ObjectCapabilities 归零。契约(opt-IN,与 feeds/activities 的 opt-out 相反)
enable.files默认保持false:附件面板是新表面不是既有行为,opt-in 语义正确且零行为回归。sys_attachmentbeforeInsert hook——join 行只允许指向显式声明enable: { files: true }的对象;缺 enable 块、缺 flag、显式 false、未知对象一律 fail-closed 403FILES_DISABLED(复用CLONE_DISABLED/FEEDS_DISABLED模式)。mapDataError增加FILES_DISABLED分支([spec/cli] 对象 enable.files/trackHistory/activities/feeds 为 dead property:schema 接受、liveness 仅告警不拦 #2707 教训:通用 data 路由不走sendError的.status透传,引入错误码时同步接映射,这次没等冒烟抓 400 才补)。Field.file/Field.image不受影响:字段附件存记录自身列,从不写 sys_attachment。enable.filesdead→live、撤 authorWarn;README 计数 object 35 live/13 dead;cli lint 契约测试改锚versioning为 canonical 误导性 dead 例子;describe()/references 文档改为真实契约。验证
check:liveness✓、check:api-surface✓files:true探针对象 attach 201;指向无 flag 对象 attach → 403FILES_DISABLED(带 code+目标对象);稳定下载端点 200 返回原字节files:true对象渲染(上传按钮/列表/下载/删除),无 flag 对象不渲染配套
RecordAttachmentsPanel)。合并顺序:本 PR 先(闸门先于面板)。🤖 Generated with Claude Code