feat: build access level labels from their bits and project names

Labels came from a fixed table matched by subset, so Storage (no bits)
matched any level the table lacked, and a level was named after the first
entry it contained: the server's Brain/MINDS 2.0 folders (0x10000) showed
as Storage and 0x10104 as CBS or PW Open.

Labels are now composed from the bits. Projects (bit 16 and up) take the
names the server reports to their members through users/current/, and
otherwise Project<n>; restrict slot 1 is CBS and any other Restrict<n>.
A level with no meaning is shown as Unknown (0x...). chacl reads keys
built the same way, refuses public and storage before any request, and
lists the user's projects with their keys when given an invalid level.
This commit is contained in:
2026-11-13 16:53:25 +09:00
parent 6612d2f660
commit a2bc9bb642
9 changed files with 386 additions and 38 deletions
+9 -5
View File
@@ -1,5 +1,6 @@
import json
from argparse import Namespace
from dataclasses import field
from typing import Any, Final
from pydantic.dataclasses import dataclass
@@ -26,6 +27,7 @@ class LsCommandContext:
is_json: bool
is_quiet: bool
is_recursive: bool
projects: dict[int, str] = field(default_factory=dict)
class LsCommand(BaseCommand):
@@ -85,6 +87,7 @@ class LsCommand(BaseCommand):
is_json,
is_quiet,
is_recursive,
client.project_names(),
)
if context.is_json:
cls._ls_json(context, folder, files)
@@ -110,7 +113,7 @@ class LsCommand(BaseCommand):
length = {key: len(label) if not context.is_quiet else 0 for key, label in cls.LABELS.items()}
for sub_folder in folder.sub_folders:
sub_laboratory_name = cls._laboratory_name(context, sub_folder.laboratory_id)
length["acl"] = max(length["acl"], len(sub_folder.access_level_name))
length["acl"] = max(length["acl"], len(sub_folder.access_level_label(context.projects)))
length["laboratory"] = max(length["laboratory"], len(sub_laboratory_name))
length["size"] = max(length["size"], len(str(sub_folder.size)))
length["date"] = max(length["date"], len(sub_folder.updated_at_name))
@@ -119,7 +122,7 @@ class LsCommand(BaseCommand):
length["size"] = max(length["size"], len(str(file.size)))
length["date"] = max(length["date"], len(file.updated_at_name))
length["name"] = max(length["name"], len(file.name))
length["acl"] = max(length["acl"], len(folder.access_level_name))
length["acl"] = max(length["acl"], len(folder.access_level_label(context.projects)))
length["laboratory"] = max(length["laboratory"], len(context.laboratory.name))
return length
@@ -159,14 +162,15 @@ class LsCommand(BaseCommand):
for sub_folder in sorted(folder.sub_folders, key=lambda x: x.name):
sub_laboratory_name = cls._laboratory_name(context, sub_folder.laboratory_id)
sub_folder_type = "[d]" if sub_folder.lock is False else "[l]"
sub_folder_acl = sub_folder.access_level_label(context.projects)
print(
f"{sub_folder_type:{length['type']}}\t{sub_folder.access_level_name:{length['acl']}}\t"
f"{sub_folder_type:{length['type']}}\t{sub_folder_acl:{length['acl']}}\t"
f"{sub_laboratory_name:{length['laboratory']}}\t{sub_folder.size:{length['size']}}\t"
f"{sub_folder.updated_at_name:{length['date']}}\t{sub_folder.name:{length['name']}}"
)
for file in sorted(files, key=lambda x: x.name):
print(
f"{'[f]':{length['type']}}\t{folder.access_level_name:{length['acl']}}\t"
f"{'[f]':{length['type']}}\t{folder.access_level_label(context.projects):{length['acl']}}\t"
f"{context.laboratory.name:{length['laboratory']}}\t{file.size:{length['size']}}\t"
f"{file.updated_at_name:{length['date']}}\t{file.name:{length['name']}}"
)
@@ -183,7 +187,7 @@ class LsCommand(BaseCommand):
"pid": folder.pid,
"name": folder.name,
"size": folder.size,
"access_level": folder.access_level_name,
"access_level": folder.access_level_label(context.projects),
"lock": folder.lock,
"laboratory": cls._laboratory_name(context, folder.laboratory_id),
"description": folder.description,