Skip to content

feat: views[].ownRead — 参加者の 1 ページだけを自分の行に絞る - #76

Merged
snakajima merged 1 commit into
mainfrom
feat/view-own-read
Aug 29, 2026
Merged

feat: views[].ownRead — 参加者の 1 ページだけを自分の行に絞る#76
snakajima merged 1 commit into
mainfrom
feat/view-own-read

Conversation

@snakajima

@snakajima snakajima commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

参加者のあるページだけを「読み手が出した行」に絞る opt-in を足します。MulmoTerminal の雑誌アプリ(書き手のデスク)が動機ですが、鍵自体は汎用です。

なぜ既定では絞れないのか

participantScope は公開コレクションを own の分岐に届く前に all で確定させます:

if (participantRead.includes(cid)) return { cid, scope: "all" };
if (app.public?.enabled === true && (app.public.read ?? []).includes(cid)) return { cid, scope: "all" };
// ↓ ここまで来ない

これは正しい既定で、変えていません。 行が世界に読めるのに書き手にだけ少なく見せるのは、他人が読めるものを隠していることになるからです。関数のコメントが言うとおり、公開分岐は後付けではありません。

表現できないのは別の画面です。雑誌の書き手のデスクは「自分が出したものを直す」ための場所で、そこに全記事を渡すのは —— 本文込みで、ルールは欄を落とせないので —— 増えない一覧を描くために書庫と一緒に増える読みになります。

live と同じ、collections の部分集合:

{
  "id": "write",
  "audience": "participant",
  "collections": ["articles"],
  "ownRead": ["articles"],
  "live": ["articles"]
}

宣言しなければ既存アプリの投影は1 バイトも変わりません(テストで固定)。

2 つ、はっきりさせておきたいこと

権限ではありません。 行の読める範囲は一切動かず、動くのは 1 ページのクエリだけです。public.read の外のコレクションでは参加者は元から自分の行しか読めないので、この鍵は何も変えません。私的なものが私的なのはルールがそう言うからであって、ここではない。

新しい語彙を投影しません。 出るのは scope: "own" と既存の emailField / uidField / ownDocId だけで、この鍵を知らないホストも正しく where を組みます。なので APP_PROTOCOL は動かしていません(ProjectedViewCollection.limit の「A READER THAT DOES NOT KNOW THIS KEY...」と同じ論法)。mulmoserver の変更もルール変更も不要で、viewConstraintsownConstraints が既に scope === "own" を処理しています(live の購読も同じ関数を通ります)。

中身

  • ownScopeparticipantScope から切り出しました。 複製ではなく分割です —— 2 つ目の写しは「own とは何か」で拡大側と縮小側がずれる場所になります。ownRead が渡すクエリは、ルールが認めるクエリと同じでなければなりません。
  • ゲート 3 件、どれも「小さくなる」ではなく「壊れて見える」ものです:
    • collections に無い cid — ページはそのクエリを渡されていないので絞る対象が無い(live と同じ算術)
    • participant 以外 — member は全件が仕事、public の訪問者は誰でもない可能性がある。どちらも投影側が何も honour しないので、著者は「鍵が効かない」と悩むことになる
    • own の当てが無い宣言emailFielduidFieldidFrom: "auth.uid" も無いと ownScope は null を返し、tierViews がそのコレクションを落とす。ページは何も渡されず、絞る前の全件より少なくなる。しかも無言。3 つのうちこれが一番長い文言なのはそのためです
  • 既存の limit × own の拒否を、アプリ全体ではなくそのビューのスコープを見る形に変えました。1 つのアプリの 2 つの participant ページが別スコープを持てるようになったので、participantScope だけを見ると別のページについて答えることになります(通してはいけない組を通し、問題ない組を断る)。
  • max-lines のラチェットを 1303 → 1344。 設定のコメントが言う「RAISED IN THE CHANGE THAT GROWS THE FILE」に従っています。ownRead は refusal の 1 家族として入るので、分割ではなく数値を上げるのが筋だと判断しました。

確認

  • yarn test 568 全緑(新規 13 件)
  • mutation で赤くなることを確認済み: 投影の opt-in を外すと 2 件、limit の判定をアプリ全体に戻すと 1 件が、ちょうどそれぞれ落ちます
  • yarn check:apps — 実アプリ 10 本すべて publish(既存の宣言に影響が無いことの実地確認)
  • yarn lint / yarn typecheck / yarn format clean

この後

@receptron/sharedapp を公開したら MulmoTerminal 側で bump し、稼働中の雑誌アプリ(ai-journal)の participant ビューで使います。そこでは /m//p/ が 1 つの desk.html を共有できなくなるので(/p/ では「一覧には全員の記事が並びます」が嘘になる)、ページを分けます。magazine.md テンプレートにも入れる予定です。

work in mulmoterminal

Summary by Sourcery

Support participant views that opt in to displaying only the current reader’s rows while preserving existing defaults and access rules.

New Features:

  • Add an opt-in views[].ownRead setting that narrows participant-page reads to the current participant’s own rows for selected collections.

Bug Fixes:

  • Apply limit validation against each view’s effective scope so per-view own-row reads are validated correctly.

Enhancements:

  • Reuse the existing own-row scope logic for view-level projection without changing permissions or the application protocol.
  • Validate that ownRead targets view collections, is used only on participant views, and has a configured way to identify the reader’s rows.

Build:

  • Increase the publish-check max-lines threshold to accommodate the new validation rules.

Tests:

  • Add coverage for normalization, projection, per-view and per-tier behavior, live subscriptions, validation failures, and limit interactions.

Summary by CodeRabbit

  • New Features

    • Participant views can now opt in to showing only the signed-in reader’s own rows for selected collections.
    • Different views can independently use own-row or broader participant data access.
    • Live subscriptions respect the narrowed own-row scope.
  • Bug Fixes

    • Added validation to reject unsupported, incorrectly scoped, or incomplete own-row configurations.
    • Preserved existing behavior for public collections and member views.

`participantScope` は公開コレクションを own の分岐に届く前に `all` で確定させる。
それは正しい既定で、行が世界に読めるのに書き手にだけ少なく見せるのは「隠している」
ことになるから。ただしそれで表現できない画面がある —— 雑誌の書き手のデスクは
「自分が出したものを直す」ための場所で、そこに全記事(本文込み。ルールは欄を
落とせない)を渡すのは、増えない一覧を描くために書庫と一緒に増える読みになる。

`views[].ownRead` は `collections` の部分集合で、`live` と同じ形。宣言しなければ
既存アプリの投影は 1 バイトも変わらない。

**権限ではない。** 行の読める範囲は一切動かず、動くのは 1 ページのクエリだけ。
`public.read` の外のコレクションでは参加者は元から自分の行しか読めず、この鍵は
何も変えない。私的なものが私的なのはルールがそう言うからで、ここではない。

**新しい語彙を投影しない。** 出るのは `scope: "own"` と既存の
`emailField` / `uidField` / `ownDocId` だけで、この鍵を知らないホストも正しく
`where` を組む。だから `APP_PROTOCOL` は動かない(`ProjectedViewCollection.limit`
と同じ論法)。**mulmoserver の変更もルール変更も不要。**

- `ownScope` を `participantScope` から切り出した(複製ではなく分割 —— 2 つ目の
  写しは「own とは何か」で両者がずれる場所になる)
- ゲート 3 件: `collections` の部分集合であること、participant 以外は拒否
  (member は全件が仕事、public には持ち主が居ない)、own の当てが無い宣言は拒否
  (投影から落ちてページは何も渡されず、絞る前より少なくなる。しかも無言)
- 既存の limit × own の拒否を、アプリ全体ではなく**そのビューの**スコープを見る形に。
  1 つのアプリの 2 つの participant ページが別スコープを持てるようになったため
- max-lines のラチェットを 1303 -> 1344(設定のコメントが言う「育てた変更の中で上げる」)

test 568 全緑、`check:apps` の実アプリ 10 本も全て publish。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRe3fgJnWNLRAqrjvjpHLn

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @snakajima, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 11 hours and 10 minutes by commenting @sourcery-ai review. Upgrade to get a review now.

@coderabbitai

coderabbitai Bot commented Aug 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d21dee5f-07ea-4776-89fc-ae48f6fc7641

📥 Commits

Reviewing files that changed from the base of the PR and between 66f03f8 and 3fd5c70.

📒 Files selected for processing (6)
  • eslint.config.js
  • src/appViews.ts
  • src/publishChecks.ts
  • src/publishManifest.ts
  • src/publishProject.ts
  • test/test_viewOwnRead.ts

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

Changes

Participant own-row view reads

Layer / File(s) Summary
View contract and normalization
src/publishManifest.ts, src/appViews.ts, test/test_viewOwnRead.ts
Views accept optional ownRead collections. Normalization preserves the field. ownScope resolves email-, UID-field-, and UID-document-based ownership.
Own-read validation and view limits
src/publishChecks.ts, eslint.config.js, test/test_viewOwnRead.ts
Publication checks reject invalid collections, audiences, and ownership declarations. Limit checks use the selected view scope.
Published scope projection
src/publishProject.ts, test/test_viewOwnRead.ts
Participant views use ownScope for opted-in collections and retain participant scope otherwise. Member views remain unchanged. Tests cover page isolation and live subscriptions.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 3fd5c

The opt-in setting narrows selected participant views to the reader’s own rows while preserving existing defaults, access rules, and ownership selectors; invalid declarations are rejected. No actionable merge-blocking risk remains after normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant AuthoredApp
  participant viewProblems
  participant tierViews
  participant ownScope
  AuthoredApp->>viewProblems: validate views[].ownRead
  viewProblems->>ownScope: resolve ownership selector
  ownScope-->>viewProblems: ProjectedViewCollection or null
  AuthoredApp->>tierViews: publish normalized participant view
  tierViews->>ownScope: resolve opted-in collection scope
  ownScope-->>tierViews: scope: "own"
Loading

Suggested reviewers: isamu

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: adding views[].ownRead to limit one participant view to the participant's own rows.
Docstring Coverage ✅ Passed Docstring coverage is 81.82% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 11 functions across 6 files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/view-own-read

Warning

Some tools did not complete. Review the errors below.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

eslint.config.js

Oops! Something went wrong! :(

ESLint: 10.9.0

Error: The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it.
at /.eslint-tmp/node_modules/eslint/lib/config/config-loader.js:145:10
at async loadTypeScriptConfigFileWithJiti (/.eslint-tmp/node_modules/eslint/lib/config/config-loader.js:144:3)
at async loadConfigFile (/.eslint-tmp/node_modules/eslint/lib/config/config-loader.js:265:11)
at async ConfigLoader.calculateConfigArray (/.eslint-tmp/node_modules/eslint/lib/config/config-loader.js:588:23)
at async #calculateConfigArray (/.eslint-tmp/node_modules/eslint/lib/config/config-loader.js:369:19)
at async Promise.all (index 0)
at async findFiles (/.eslint-tmp/node_modules/eslint/lib/eslint/eslint-helpers.js:637:25)
at async ESLint.lintFiles (/.eslint-tmp/node_modules/eslint/lib/eslint/eslint.js:1027:21)
at async Object.execute (/.eslint-tmp/node_modules/eslint/lib/cli.js:386:14)
at async main (/.eslint-tmp/node_modules/eslint/bin/eslint.js:191:19)

src/appViews.ts

ESLint skipped: the matched ESLint configuration already failed (unknown).

src/publishChecks.ts

ESLint skipped: the matched ESLint configuration already failed (unknown).

  • 3 others

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai

sourcery-ai Bot commented Aug 29, 2026

Copy link
Copy Markdown

Reviewer's Guide

Introduces an opt-in views[].ownRead feature that narrows selected participant-page queries to the reader's own rows without changing permissions, protocol output, default projections, or member behavior; adds targeted validation, per-view limit handling, and comprehensive tests.

Flow diagram for per-view ownRead projection

flowchart TD
    A[NormalizedView with ownRead] --> B[viewOwnReadProblems]
    B -->|valid participant subset with own identity mapping| C[tierViews]
    B -->|invalid declaration| D[Publish refusal]
    C --> E[scopeFor]
    E -->|ownRead includes cid| F[ownScope]
    E -->|otherwise| G[participantScope]
    F --> H[Existing own query constraints]
    G --> H
    H --> I[Per-view limitFor]
Loading

File-Level Changes

Change Details Files
Adds the optional views[].ownRead declaration and preserves it through view normalization.
  • Introduces ownRead as an optional subset of the view's collections.
  • Keeps the field absent when undeclared to preserve existing projections byte-for-byte.
  • Documents that the option narrows a query rather than changing permissions or protocol vocabulary.
src/appViews.ts
src/publishManifest.ts
Projects opted-in participant collections with the existing own-row scope while leaving default and member behavior unchanged.
  • Extracts reusable ownScope logic from participantScope so public widening remains the default.
  • Applies ownScope only when a participant view explicitly lists the collection in ownRead.
  • Retains existing scope: "own" selectors, allowing older hosts to construct the same query.
src/appViews.ts
src/publishProject.ts
Adds validation for invalid own-row view declarations and makes limit checks view-specific.
  • Rejects collections outside collections, non-participant audiences, and declarations without an available own-row identity selector.
  • Evaluates limit-versus-own-scope compatibility using the individual view's effective scope rather than the app-wide participant scope.
src/publishChecks.ts
Adds comprehensive coverage for normalization, projection, tier isolation, live queries, refusal cases, and limit interactions.
  • Verifies public collections remain whole by default and become own-scoped only on opted-in participant pages.
  • Covers independent scopes across pages and unchanged member projections.
  • Tests all three validation gates and the per-view limit refusal behavior.
test/test_viewOwnRead.ts
Raises the publish-check max-lines threshold to accommodate the new validation family.
  • Updates the lint ratchet from 1303 to 1344 lines and documents the reason.
eslint.config.js

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@snakajima

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 29, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@snakajima
snakajima merged commit f115651 into main Aug 29, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant