本文へ移動
松葉 / Matsuba ・ 開発過程資料

Box のアクティビティ/ Member Timeline

同じ記録を、業務の履歴として読み下せる形で出す

3 つの読み口のうち 2 つ目です。データの元は全社の監査ログとまったく同じ 1 本ですが、こちらは業務の履歴として読める形に整えて Box のメンバー全員へ出します。1 つの Box には社内の担当者と取引先の担当者が同時に入るため、何を出すかの線引きがこの章の主題です。

01

何が見えるかWhat Members See

2 つのビューがあります。メンバー全員が見る 48 種と、Owner だけが見る 4 種です。合わせて 52 種で、この数は自動テストで固定しています。

48 + 4 = 52 Visible Operations
ビュー分類数中身
アクティビティ
メンバー全員
ファイル14アップロード・差し替え・削除・復元・改名・移動・版の復元・完全削除・アーカイブとその解除・保持の設定と解除・メタデータの編集・ゴミ箱を空にする
フォルダ11作成・改名・削除・移動・復元・アーカイブとその解除・保持の設定と解除・完全削除・メタデータの編集
メンバー7追加と招待・招待の受諾・ロールの変更・除外・自分から退出・オーナーの引き継ぎ・参加申請の承認
共有・受け渡し7共有リンクの発行と無効化・全社側からの強制失効・ファイルリクエストの発行と無効化・同じく強制失効・提出の取り込み
コメント3投稿・編集・削除
Box 設定6作成・基本情報の変更・公開範囲の変更・保持期間の変更・版の保持期間の変更・Drive 同期の切り替え
アクセス履歴
Owner のみ
読み取り4ダウンロード・プレビュー・一括ダウンロード・共有リンク経由のアクセス

あえて出していないもの

163 種のうち、ここに出るのは 52 種だけです。残りを出さない理由は 3 つに分かれます。

  • システム内部の配管(ウイルス検査の進行・Drive 同期の各段・通知の作成と送信)。業務として意味を持つ場面では、それぞれの画面が状態を出しています。ここに並べると本来読みたい行が埋もれます。
  • 読み取り系(ダウンロード・プレビュー)。これはアクセス履歴の側にあります。
  • Box が見えなくなる操作(Box の無効化・削除)。その操作が済んだ時点で、この画面自体が開けません。
  • メンバー系のうち招待の辞退・取り消し・期限切れも外しました。誰が辞退したかを Box の全員に見せる必要はなく、行数だけが増えるためです。
1 つだけ、過去分が出ないものがあります: 「ゴミ箱を空にする」は、この 52 種を決めたときに記録する処理がまだ無いことが分かり、あわせて追加しました。したがって、追加より前に実行された分は表示されません。「表示対象なのに 1 件も出ない」ことを不具合と読まれないよう、ここに明記しておきます。
02

権限をデータベース側で固定するAuthorization in the Database

Box のメンバーは、記録のテーブルを直接参照できません。通り道は box_activity_for_member という関数 1 本だけです。この関数は「持ち主の権限で動く関数」(SECURITY DEFINER)で、参照の制限を越えてテーブルを読める代わりに、関数の中で 4 つの検証を済ませます。

図 08 · 関数の中で行う 4 つの検証 — どこで弾かれ、何が返るか
アクティビティタブ → API → 関数を呼ぶ実行の許可は「ログイン済み」にだけ与えている(誰にでも開いてはいない)閲覧者は引数にしない接続情報から取り出す。引数にすると他人の識別子で Owner の画面が開く① 閲覧者が接続情報から取れるか取れなければ、そこで打ち切る「メンバーではない」として弾く空の値は許可側に倒さない② 招待を承諾済みのメンバーか承諾していない招待は「未参加」として扱う404 — Box の存在ごと伏せる「権限がありません」とも言わない③ Box が無効化・削除されていないかメンバーの行は残るので、メンバー判定だけでは漏れる無効化 → Owner 以外を非メンバー扱い削除 → 全員を非メンバー扱いどちらも 404④ アクセス履歴のビューなら Owner かロールが取れない場合も許可しない403 — 画面はメインへ静かに戻すエラーを見せない(存在を匂わせない)行を返すメイン 48 種 / アクセス履歴 4 種(関数の中に配列で固定)項目は対応表を通したものだけ・新しい順・1 回の上限あり管理者の抜け道は持たないメンバーでなければ、システム管理者でも非メンバーとして扱われる

①〜④ はすべて「空の値なら拒否」の向きに倒してあります。判定に使う値が取れなかったときに通してしまうと、条件が 1 つ崩れるだけで全部が開くためです。この 4 通りの弾かれ方は、実際のデータベースに対してロール別に走らせる自動テストで固定しています。

表示対象を関数の中に固定する

「メンバー全員に見せる 48 種」「Owner だけの 4 種」の一覧は、関数の中に配列として直接書いてあります。アプリ側の定数はその写しで、画面の選択肢と入力の検証にだけ使います。

こうしている理由は、「ダウンロード履歴を一般のメンバーに見せない」という境界をデータベース層で固定するためです。アプリ側の条件分岐だけで守っていると、実装の 1 か所を間違えただけで漏れます。増減させるときは「関数の配列」「共有の定数」「表示文」の 3 か所を同時に直す必要があり、ずれたら自動テストが落ちます(定数の全項目が関数から返り得るか、定数の外が混ざっていないかを突き合わせています)。

03

内部情報を構造的に出さないStructural Masking

操作ごとの自由項目には、IP・端末情報・トークン・内部の識別子まで入り得ます。これを「出さないもののリストを作って除く」方式では守れません。出してよいものの対応表を通す方式にしています。

図 09 · 出してよい項目の対応表 — 表に無いものは通り道が無い
記録に入っているもの操作ごとの対応表画面に出る形版の番号・ロールの変更前後期限・件数・移動先のフォルダ名コメントの冒頭 120 文字綴りは実装の時期で不統一表にある操作ごとに「実際のキー →安定した名前」へ写す足りないものは結合で導くそのまま出る「Rev.3 に戻しました」「Uploader から Editor に変更」メールアドレス共有リンクの受取人・提出者・招待の相手記録には入れていない引き直して伏せる発行元のテーブルから取り、必ず伏せ字にしてから返す生のキーは応答に存在しない伏せ字だけが出るt***@example.comIP アドレス・端末情報トークン・内部の識別子申告されたハッシュ値記録には入っている表に無い写す先が定義されていない通り道が無い出す・出さないの判断が要らないなぜ「出さないもののリスト」にしないのか将来、記録に新しい項目が増えたとき — 除外リストは「出てしまう」側に倒れ、対応表は「出ない」側に倒れる

対応表は操作ごとに持っています。記録の自由項目のキーの綴りは実装の時期によって不統一(英字の区切り方が 2 通り混在)で、単純な「許可するキーの一覧」では受け止められなかったためです。表に載っていない操作は「追加の項目なし」になり、表示は固定の文と対象名だけになります。

04

メールの伏せ字Masked Emails

ここは実装のあいだに 2 回直した箇所です。1 回目で塞いだつもりが塞がっておらず、2 回目で塞がりました。何が抜けたのかを書き残します。

Two Rounds of Fixing the Same Leak
段やったこと残っていた穴
1 回目記録の自由項目に入っているアドレスそのものを、対象名の欄へ流し込まないようにした。専用の欄へ隔離し、伏せ字にしてから対象名へ畳む形にした未承諾の招待相手が対象の行では、アドレスの「@より前」がそのまま出続けていた
2 回目「@より前」が出る発生源を塞ぎ、5 つの経路すべてを伏せ字に畳んだなし(機械検査を、完全なアドレス形だけでなく「@より前」の一致・部分一致まで見る形に強化)

なぜ「@より前」が出てしまったのか

Box に社外の相手を招待すると、その相手のアカウントが先に作られます。そのとき表示名の欄には、招待先アドレスの「@より前」がそのまま入ります(まだ本人が名前を入れていないので、他に入れるものがない)。この行はログインできないため、本人が受諾するまで表示名は一生「@より前」のままです。

アクティビティの側は「対象の名前が取れたらそれを出し、取れなければアドレスを伏せ字にして出す」という順で組んでいました。ところが上記の行では名前が取れてしまうので、伏せ字にする道に入らず、そのまま表示されていました。

対処は、「まだ一度もログインしていない、確認も済んでいない」アカウントの表示名を、名前として採らないようにしたことです(この条件は、ほかの画面でメンバーを検索するときに使っているものと同じ形にそろえました)。名前として採らないので伏せ字の道に入り、t***@example.com の形で表示されます。

この漏れが実務上どう見えていたか: 社内と取引先 A と取引先 B が同居する Box で、取引先 A が取引先 B の担当者のアドレスを知り得る状態でした。招待中の相手の一覧は Owner だけが見られる作りなのに、アクティビティタブだけがその境界を越えていた形です。加えて、受諾されないまま失効・取り消された招待先のアドレスも記録に残り続けるため、恒久的に見えていました。

機械検査の作り方

応答の JSON を文字列にして「@を含むアドレス形が入っていないか」を見る検査は、1 回目のときに通っていました(@が無いので当たらない)。現在の検査は、投入したアドレスの「@より前」と一致も部分一致もしないことまで見ます。

伏せ字にする規則そのものは実装 1 本にしてあり、画面向けの応答と CSV の両方が同じ関数を通ります。「同じ 1 行を両方へ流して結果が一致すること」と「その関数の外に独自の実装が生えていないこと」の 2 つを、それぞれ自動テストで見ています。

05

日本語の表示文Message Builder

API が返すのは構造化された行だけで、文章は作りません。「○○さんが △△ をアップロードしました」という文の組み立ては、画面側の純粋な関数 1 本が担っています。

分けている理由は、文章を作る場所が 2 つになると必ず食い違うからです。API 側でも文を作り、画面側でも作ると、片方だけ直された状態が生まれます。文章の置き場は画面側の 1 か所、というのを規律にしています(CSV では文章の代わりに「項目名=値」を並べた列を出しており、こちらも日本語の文は作りません)。

Examples of Generated Sentences
操作表示される文値の出どころ
アップロード見積書_0807.pdf をアップロードしました対象名
差し替え見積書_0807.pdf を差し替えました(Rev.3)結合で導出
改名旧名.pdf を 新名.pdf に変更しました対応表
移動見積書.pdf を 見積 へ移動しました結合で導出
版の復元見積書.pdf を以前のバージョンに戻しました(Rev.2)対応表
ロールの変更田中 太郎 さんのロールを Uploader から Editor に変更しました対応表
共有リンクの発行見積書.pdf の共有リンクを発行しました(期限: 7 日)対応表
コメント見積書.pdf にコメントしました: 「承認しました」専用の欄
ゴミ箱を空にするゴミ箱を空にしました(12 件)対応表
共有リンク経由のアクセス共有リンク経由でアクセスがありました(受取人: t***@example.com)引き直し + 伏せ字

52 種すべてに文があり、網羅は自動テストで固定しています。値が取れなかったときは例外を投げず、退化した文に落とします(「別のフォルダへ移動しました」のように、取れた情報だけで成り立つ文)。未知の操作が来たときも、「○○さんが操作を行いました(操作名)」の受け皿の文になります。行が消えるより、情報が少なくても行が並ぶほうがよいという判断です。

削除済みの対象: 対象のファイルが完全に削除されていると名前が取れません。この場合は「(削除済み)」と表示し、リンクにはしません。ファイル名は、記録の自由項目に残っていればそれを使うので、ゴミ箱にあるあいだ・論理削除の状態では名前が出ます。
06

タイムラインの画面Timeline UI

Box 詳細の 3 番目のタブです。1 行は「誰が・何をした」の文と、その下に小さくアイコンと相対時刻が並ぶ 2 段構成です。

(画面の画像は掲載していません)
日付の見出しでまとまり、スクロールしても見出しが上に留まります。同じ人が続けて操作した行は、2 行目以降のアバターを畳んで縦の細い罫でつなぎます。社外の方の行には社外のバッジが付きます。

行の作り

  • 1 行目: 操作した人の名前(社外なら社外バッジ)+ 文 + 対象へのリンク。リンクを押すと、ファイル詳細・そのフォルダ・メンバータブ・共有リンクタブ・コメントの位置へ飛びます。
  • 2 行目: 操作の種別を表すアイコン(14px)と相対時刻。時刻にカーソルを載せると絶対時刻が出ます。
  • アバター: 人以外が操作した行(共有リンク・ファイルリクエスト・システム)は、アバターの代わりに丸いアイコンのタイルに置き換わります。表示名は「共有リンク受取人(t***@example.com)」のように、種類と伏せ字を併記します。
  • 削除済みの利用者: 頭文字を出さないニュートラルなアバターと「削除済みユーザー」。消えた名前を頭文字で復元しません。
行の左に色付きの縦罫を引かない判断: 当初の案では、操作の種類ごとに行の左へ 4 色の縦罫を引く予定でした。これは採用していません。和モダンの構えでは細い罫は 1 画面に 1〜2 本が上限で、色だけで種類を伝えるのは読み上げ環境でも弱いためです。種類の手掛かりは「2 行目のアイコン」「文そのもの」「種別の絞り込み」の 3 つで担っています。縦罫は同じ人の連続を束ねる用途だけに使います。

日付の区切り

「今日」「昨日」「8月7日(木)」「2025年12月3日」(年を跨ぐときだけ年付き)で区切ります。区切りは日本時間です。見出しは画面上部に貼り付いて追従するので、長くスクロールしても「いつの話か」を見失いません。

絞り込み 3 種

Three Filters
絞り込み選択肢と挙動
期間全期間(既定)/直近 7 日/直近 30 日/期間を指定(カレンダー)。既定を「直近 7 日」にしない理由は、静かな Box で初めて開いた人が空を見るほうが害が大きいためです。量は続きの読み込みが吸収します
操作の種別カテゴリ見出し付きのチェックリスト。カテゴリごとの一括チェックができ、一部だけ選んだ状態は中間表示になります。選択肢の日本語は共有の定数から導出し、画面側に並行の一覧を作りません
操作者Box のメンバーから検索して選択。アバター・名前・社外バッジを表示。タイムラインのアバターを押すと、その人で絞り込みが入ります

絞り込みが 1 つでも効いているときは、ツールバーのすぐ下に取り外せるラベルが並び、「すべてクリア」も置きます。いま何で絞っているかが分かり、1 タップで戻せる形です。脱退したメンバーの行から絞り込んだ場合など、名前を解決できない相手は「指定の操作者」と表示します。

(画面の画像は掲載していません)
カテゴリごとに束ねてあり、見出しのチェックで一括選択できます。一部だけ選んだカテゴリは中間の表示になります。
(画面の画像は掲載していません)
アバターと名前、社外の方には社外バッジが付きます。検索して選べます。

URL に何を載せ、何を載せないか

表示中のビュー・期間・操作の種別・操作者は URL に載ります。URL を渡せば「あの期間の、あの人の操作」を別のメンバーにそのまま見せられます。

一方、続きの位置を示す印は載せません。載せると「途中から始まる不完全な一覧」を共有してしまうためです。

URL の形と API の形をそろえない: 操作の種別は複数選べます。URL 上はカンマ区切り(読みやすさと短さ)で、API へ渡すときは同じキーの繰り返しに組み替えます。両者を無理にそろえていません。URL は人が見るもの、API の形は仕様上の要請、と割り切っています。

続きの読み込みと終端

  • 1 ページ 30 件。末端に近づくと自動で次を読み、押せるボタンも常設します(キーボードだけで操作する場合に、自動発火に頼らせないため)。
  • これ以上ない場合は「これ以上表示できるアクティビティはありません」と中立に示します。「2 年より前は保管庫にあります」といった説明は置いていません(Box のメンバーにとっては操作できない話で、置くと不要な問い合わせを生むため)。
  • 500 件を超えても表示のための特別な仕組み(仮想スクロール)は入れていません。実測で十分に動いており、必要が出た時点で入れる方針です。
07

アクセス履歴を Owner 限定にする理由Owner-Only Access Log

ダウンロード・プレビュー・一括ダウンロード・共有リンク経由のアクセスの 4 種は、Box の Owner だけが見られます。

1 つの Box には、社内の担当者と、複数の取引先の担当者が同時に入ります。ここでダウンロードの履歴を全員に見せると、取引先 A が取引先 B の行動を観察できる状態になります。「どの見積を、いつ、何回開いたか」は商談の状況を推測させる情報で、業務上も心理的にも共有すべきものではありません。

一方で、Box の中で何が持ち出されたかを把握する責任は Owner にあります。だから Owner には出します。この境界は関数の中で固定されており、システム管理者であってもその Box の Owner でなければ開けません(全社の立場で見る場合は、監査ログの側が正しい経路です)。

  • サブタブは Owner にしか描画しません。非 Owner が URL で直接開こうとした場合は、エラーを見せずにメインのビューへ静かに寄せます(「そこに何かある」ことすら匂わせない)。
  • 共有リンク経由のアクセスの受取人は、常に伏せ字です。完全な開示はシステム管理者の経路だけです。
  • サブタブの下には「ダウンロード・プレビュー・共有リンク経由のアクセスの記録です。Box オーナーのみが閲覧できます。」という 1 行を常設しています。誰に見えているかを、見ている本人にも明示する意図です。
(画面の画像は掲載していません)
ダウンロードとプレビュー、共有リンク経由のアクセスが時系列で並びます。共有リンク経由の行では受取人が伏せ字で表示されます。上部には「Box オーナーのみが閲覧できます」の 1 行が常設されています。
08

CSV の取り出しOwner Export

Owner は、いま見ている条件をそのまま持ち出せます。理由の入力は要りません(自分の Box の業務データだからです)。

(画面の画像は掲載していません)
いま見ているビューと絞り込みが読み取り専用のラベルとして並び、期間を選び直せます。期間の長さに応じて「すぐダウンロードできます」か「完了後にメールでお送りします」の案内が切り替わります。

10 列。表計算で開ける形にする

全社の監査ログの CSV(19 列)とは意図的に別物にしています。あちらは機械で読み直す監査の成果物、こちらは人が表計算で開いて確認する業務の記録です。

Admin CSV vs Box CSV
項目全社の監査ログBox のアクティビティ
列数1910
時刻世界標準時(ISO 表記)日本時間(yyyy-MM-dd HH:mm:ss)
文字コードの目印付けない付ける
圧縮するしない
個人情報生のまま(IP・端末情報を含む)伏せ字のみ。IP・端末情報は列が存在しない
理由の入力必須(10〜256 文字)不要
想定する使い方別のシステムへ取り込む・長期保管その場で開いて確認する・関係者へ渡す

日本時間・目印つき・非圧縮にしているのは、ダブルクリックで開いて文字化けしないためです。列名にも時刻の帯を書き込んで(occurred_at_jst)、どの時刻で読むべきかがファイル単体で分かるようにしています。

10 列の中身は、時刻・操作名・操作名の日本語・操作者名・社内か社外か・対象の種類・対象名・受取人(伏せ字)・提出者(伏せ字)・詳細です。最後の「詳細」には、対応表を通った項目を「項目名=値」の形でつないだ文字列が入ります。日本語の文章は作りません(文章の置き場を 2 つにしないため)。

(画面の画像は掲載していません)
日本時間の時刻から始まり、操作名と日本語、操作者、対象、伏せ字のメール、詳細の列が並びます。ダブルクリックで開いて文字化けしません。

その場で流すか、メールで届けるか

  • 期間を指定していて、その長さが 31 日以下なら、その場でダウンロードが始まります。
  • それ以外(全期間を含む)は、バッチへ渡します。202 で受け付けたあとダイアログは進行の表示に変わり、閉じても構いません(完了時にメールとアプリ内通知が届きます)。
  • 完了メールから戻ると、URL に依頼の識別子が付いていて同じダイアログが再開します。成功していればその場でダウンロードできます。
  • 保存期間(7 日)を過ぎた依頼から戻った場合は、期限切れの表示と再取り出しの導線を出します。何も起きない、という終わり方にしません。
  • 1 時間の上限(5 回)に達した場合は、ダイアログの中に「1 時間にエクスポートできる回数の上限(5 回)に達しました」と出します。
要求した人の資格で取り出す: バッチ側での取り出しは、要求した人の資格で先ほどの関数を通します。したがって、依頼したあとに Owner を外された場合、そのバッチは失敗します。これは仕様です。依頼した時点の権限で通してしまうと、権限を外したのに中身が届く、という状態を許すことになります。
09

読み上げと操作への配慮Accessibility

この画面は「読む」ことが主目的なので、読み上げ環境での辿りやすさに寄せています。

Accessibility Decisions
対象どうしたかなぜ
タイムライン全体更新され続ける記事の列として扱い、各行を記事として印を付ける。行には表示文をそのまま読み上げ用の名前として持たせる行の途中で読み飛ばされず、1 行ずつ辿れる
日付の区切り見出しの要素で構成し、区切りそのものに名前を付けない名前を付けると「領域」として扱われ、日付の数だけ領域が増殖します。日付の移動は見出しの移動で足りる
続きの読み込み自動の発火に加えて、常にボタンを置くキーボードだけの操作でも、末端まで到達できる
相対時刻機械が読める時刻の属性を付け、絶対時刻はカーソルを載せたときの説明として関連付ける「3 分前」だけでは正確な時刻が分からない。正確な時刻が要る用途は CSV が担う
サブタブ左右キーで移動でき、選択状態を明示するほかのタブ(Box のタブ・ファイル詳細)と同じ操作感にそろえる
種類の伝え方色だけで伝えない。アイコンと文章が常に伴う色を識別しにくい環境でも、種類が読み取れる

画面の幅が狭いときは、3 つの絞り込みを 1 つのボタンに畳んで下から出るシートにまとめます。シートの中身は横並びのときとまったく同じ実装で、幅ごとに別の作りを持たせていません。CSV のボタンだけはツールバーの右端に残します。