Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
4ccddc5
[T-33] world state memory 的地基:三層各自一張表,可信度類別不落地
pkyosx Aug 31, 2026
e32579a
[T-33] 對象目錄進兩條開機路徑:只給目錄不給正文,而且截斷會自己出聲
pkyosx Aug 31, 2026
39aad9c
[T-33] 改名 world_state_memory → lore:名字定案了,一次改到底
pkyosx Aug 31, 2026
ee2977a
[T-33] migration 改號 00063 → 00064:兩包各自都綠,合起來才炸
pkyosx Aug 31, 2026
1be7fef
[T-33] 私密條目取消:一道半套的牆比沒有牆更危險
pkyosx Aug 31, 2026
1a6df9a
[T-33] 未審過與被合併的對象不該進每一個人的開機檔
pkyosx Aug 31, 2026
37830bb
[T-33] migration 改號 00064 → 00066:撞號避開了,順序卻反過來
pkyosx Aug 31, 2026
bf29508
[why] 註解裡寫了一個 SQL 空字串,整棵樹的 gofmt 閘就紅了
pkyosx Aug 31, 2026
7c679b8
[why] 「不再撈取」不是一種動作,是三種,而理由決定誰有權按
pkyosx Aug 31, 2026
e9d32cd
[why] 「退役了」和「退役到讀得到的人真的讀不到了」是兩件事,而只有前者有測試
pkyosx Aug 31, 2026
ce4403b
[why] 目錄被截掉幾個,印完那一行就丟了——沒人數過的「漏」等於沒漏過
pkyosx Aug 31, 2026
c88a381
[why] 每個人開機都會讀到那一行,而改掉它一個字都不會有人紅
pkyosx Aug 31, 2026
4e61d74
[why] 那道權限閘只有測試在推它,而「退役不是刪除」靠的那條回頭路根本沒有門
pkyosx Sep 1, 2026
e63af9f
[T-33] 新增條目的入口:規則寫好了,而沒有任何工具寫得進去
pkyosx Sep 1, 2026
70a6ab4
[T-33] conformance 那條 happy row 量到的是 pytest 的順序,不是 server
pkyosx Sep 1, 2026
3e8f081
[T-33] 擷取路:這一跳全部的價值就是挑選條件,而挑錯不會有人叫
pkyosx Sep 1, 2026
5454ca8
[why] 那段註解自己寫著判準,而碼從來沒照著做——七支測試也沒發現
pkyosx Sep 1, 2026
feab543
[T-33] 第③跳:原文一直在那裡,而沒有任何一條路把它交出去
pkyosx Sep 1, 2026
8311f16
[T-33] WIP:保住 lore 資料層接線,因為這台機器要被換掉
pkyosx Sep 2, 2026
8282fde
[T-33] 把搶救下來的資料層接線修到真的編得過
8thEdition Sep 2, 2026
cf65aef
[T-33] 傳承分頁四個畫面:能查的真的查,查不到的說出缺哪條路
8thEdition Sep 2, 2026
8bcf5df
[T-33] 傳承分頁移到「任務」右邊:他說的「案件」是主題改過的字
8thEdition Sep 2, 2026
81da5dc
[T-33] 傳承分頁重做:一頁、可以真的按核可,而不是一份貼給使用者看的 spec
8thEdition Sep 2, 2026
5db5e86
[T-33] 清掉 93 個沒有人用的傳承字典條目
8thEdition Sep 2, 2026
9969be6
[T-33] falsify 與 instance 改成必填,並把舊裁定的殘骸逐個拔掉
8thEdition Sep 2, 2026
8b24b01
[T-33] 傳承對象審核三條路:它們的權限從來沒有被走過
8thEdition Sep 2, 2026
dc926d5
[T-33] 「有沒有用到我們寫的記憶」問不出來,因為只有攤開目錄那一次留了痕
8thEdition Sep 2, 2026
6b2ba96
[T-33] 佇列列的 suggestion 斷言之前鑑別力是零,釘死它實際走的那一支
8thEdition Sep 2, 2026
0b22ba8
[T-33] 提案送整份新版本,因為「他描述的改動」跟「套下去變成什麼」不一致時看起來完全正常
8thEdition Sep 2, 2026
079d959
Merge remote-tracking branch 'origin/t-33/conformance-lore-routes' in…
8thEdition Sep 2, 2026
7dd3829
Merge remote-tracking branch 'origin/t-33/lore-tab-ui' into HEAD
8thEdition Sep 2, 2026
0165685
Merge remote-tracking branch 'origin/t-33/lore-feedback-proposals' in…
Sep 2, 2026
97c524f
fix(frontend): 重新產生 schema.ts —— 兩包各自產生過一次,合併後的檔不是正確的聯集
8thEdition Sep 2, 2026
f36f080
[why] 提案清單宣稱 newest first,實作卻用隨機 id 排序 —— 審查者一半機率先讀到提案人自己已經換掉的版本
8thEdition Sep 2, 2026
6f1d423
[schema+dal] 傳承條目從六格改成負責人定的五格 —— 只做 DB schema 與 DAL 這一層
Sep 3, 2026
95d6c70
[api+spec] 傳承條目五格接到線上 —— API 層、openapi 與產生檔補齊,順便執行兩道新裁定
Sep 3, 2026
5627156
[frontend+conformance+seeds] 傳承條目五格補齊最後一層 —— 座艙、一致性測試與說明文件
Sep 3, 2026
5ea570f
[seeds] 傳承說明文件補三處:「物」其實有驗、缺一道品質門檻、§4 那句讀成「填得出格子就該寫」
Sep 3, 2026
131a573
[T-33] 傳承(lore)功能開關:一個站一個開關,預設關
Sep 3, 2026
3eb2afb
[schema+dal+api+spec] 提案帶得動第 5 格 —— 執行 rc-e5c34500face,並讓審核者看得出改了哪幾筆
Sep 3, 2026
564f371
[why] 這包送 CI 會紅在 gofmt,而三道 drift gate 全綠看起來像已經檢查過了
pkyosx Sep 3, 2026
6432df3
[why] 搜尋工具的參數說明叫 agent 去比對三個已經不存在的欄位,而同一份說明的另一段已經改對了
pkyosx Sep 3, 2026
089ebeb
[why] 兩條規則只寫在註解裡,把它們拆掉整套測試 rc=0、一支都沒說話
pkyosx Sep 3, 2026
4d1aeaa
[why] 「復活只有 owner 能按」旁邊有一條沒人守的側門,拆掉它整套測試 rc=0
pkyosx Sep 3, 2026
70b7ac8
[why] 核可提案的機制早就完整,而沒有任何人按得下去——缺的不是碼,是那道裁定
pkyosx Sep 4, 2026
67dfbb8
[why] owner 要過的「好例子」寫成了產物,而寫記憶的人手上只有定義沒有範本
pkyosx Sep 4, 2026
bcddfa5
[why] 三支 migration 的號碼已經被主線佔走或越過,帶著它們合進去站台會起不來
pkyosx Sep 4, 2026
75be3cb
[why] 開機說明裡寫死一組欄位規格,等於保證它會過期而且沒有人會發現
pkyosx Sep 4, 2026
e89d36f
[why] 我叫他不要重複寫,卻拿掉了他為什麼會以為自己沒重複的那個機制
pkyosx Sep 4, 2026
f155f77
Merge remote-tracking branch 'origin/main' into t-33/lore-land
Sep 4, 2026
7fc143e
Merge commit 'e89d36fc' into t33-merge-main
Sep 4, 2026
8ac0905
Merge remote-tracking branch 'origin/main' into t33-merge-main
pkyosx Sep 4, 2026
64609e6
[why] 那一句的收件人是寫這套機制的人,不是用它的人
pkyosx Sep 4, 2026
fc77242
[why] 我把一個「還沒有人裁定的路由規則」寫成了規格,而它跟同一份文件既有的判準互相矛盾
pkyosx Sep 4, 2026
7791fc1
[why] 被壓掉的那一批,不成比例地集中在「這件事失敗時不會有痕跡」
pkyosx Sep 4, 2026
936049d
merge(T-33): 併入 origin/main c887c325(#392 進主線)
pkyosx Sep 4, 2026
8e59a99
[why] 第 5 格省略會不會被擋,寫入與提案兩條路答案相反,而共用層只寫了其中一條
Sep 4, 2026
9867f67
[why] 合併主線之後 x-mcp.order 有兩個 126,而 git 不會把「號碼撞號」當成衝突
Sep 4, 2026
a8616b3
Merge remote-tracking branch 'origin/main' into t33-merge-main
Sep 4, 2026
8b38bed
[why] migration.lock 進主線之後,本包的三支還不在那份清單裡
Sep 4, 2026
81e71f5
Merge remote-tracking branch 'origin/main' into t33-merge-main
Sep 4, 2026
964b110
Merge remote-tracking branch 'origin/main' into t33-merge-main
Sep 4, 2026
68edfab
[why] #394 把 KindAssistant 改名成 KindStaff,而 git 把那次改名合得乾乾淨淨
Sep 4, 2026
d5c290a
[why] 把 lore 三支 migration 改號到 00081/82/83,並拿掉這個測試檔裡最後的裸號碼
Sep 4, 2026
b7362a9
Merge remote-tracking branch 'origin/main' into t33-merge-main
pkyosx Sep 4, 2026
8e181cc
[why] a reviewer could not tell a typo from a name whose lore was all…
pkyosx Sep 5, 2026
45a8c3b
Merge origin/main (85c2dd6a) into t-33/lore-land
pkyosx Sep 5, 2026
92ee4d5
[why] three comments claimed a verification that was false, and one o…
pkyosx Sep 5, 2026
eaf4a4d
[why] a mechanical rule was telling the owner which button to press, …
pkyosx Sep 5, 2026
7773afe
[why] a correction about an expiring count grew a new expiring count …
pkyosx Sep 5, 2026
033f677
Merge origin/main (766ebf4f) into t-33/lore-land
pkyosx Sep 5, 2026
46a11bf
[why] 拿掉 suggestion 的時候沒有掃到所有描述它的地方
pkyosx Sep 5, 2026
b7e6fd9
[why] adapter.ts 的區段標題跟它自己的內文互相矛盾,距離數字又是一顆會自己過期的雷
pkyosx Sep 5, 2026
7761598
[why] 我上一顆把標題從「說得太寬」改成「說得太窄」,而太窄的那句在事實上是錯的
pkyosx Sep 5, 2026
2f8e22b
[why] 一排一按就送出的不可逆動作,其中有幾顆按下去是錯的
pkyosx Sep 5, 2026
d5e3874
[why] 確認畫面上那句「這個名字會就此消失」是假的,而它會把人推去做更糟的那個選擇
pkyosx Sep 5, 2026
1d5c7a5
[why] 我在同一顆 commit 裡拿掉一條恆真的斷言,然後在隔壁自己種了一條
pkyosx Sep 5, 2026
5a8756b
[why] jsdom 證得到那三件事被 render 了,一個字都說不出它們放不放得下
pkyosx Sep 5, 2026
9246937
Merge remote-tracking branch 'origin/main' into t33-merge-main
pkyosx Sep 5, 2026
5a09cec
Merge remote-tracking branch 'origin/main' into t33-merge-main
pkyosx Sep 5, 2026
50fd9f1
Merge origin/main (f5a61445) into T-33 lore land
claude Sep 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions conformance/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

- 標準入口:`conformance/run.sh --target go`。Python target 已退役,不要另造回滾路徑。server 使用核心自動配埠(預設 port 0,讀回實際值);需要重現時才明設 `OC_CONF_PORT`。
- DB、`oc.toml`、config 都在 mktemp/隔離目錄,透過 `OC_CONFIG` 注入;不讀寫 repo 根 config。prod guard 以現行 source 的 production ports/identity/residue 判斷,保護現行與退役 port,不能用硬編單一 port 取代。
- `run.sh` 只在 `ocwarden` 不存在時才 stage `bindist/`,staged 的 `mcp-catalog.json` 可能比 HEAD 舊,會生出與當前改動無關的假紅;跑前先 `bash bin/build-bindist`。
- teardown 只處理本次捕獲的 listener PID,禁止模糊 process kill。conformance 只需要 `OC_TARGET_URL` 與 `OC_OWNER_PASSWORD`;其餘 fixture 身分由 HTTP login/hire/mint 建立。

## 3. auth、REST 與 error envelope
Expand Down
36 changes: 36 additions & 0 deletions conformance/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,42 @@ def _auth(token: str) -> dict[str, str]:
return {"Authorization": f"Bearer {token}"}


@pytest.fixture(scope="session", autouse=True)
def lore_feature_enabled(client: httpx.Client, owner_token: str) -> None:
"""T-33: switch the LORE feature ON for the whole run, once.

The feature ships OFF (settings ``lore.enabled``, default false — the owner
asked for that in as many words), so on a fresh throwaway database every
``/api/lore/*`` row answers 403 and every lore case in this suite would be
asserting the refusal instead of the behaviour it names.

🔴 THE DEFAULT IS ASSERTED HERE, BEFORE IT IS CHANGED, and that assertion is
the only place this black-box suite can make it: the target is a brand-new
database exactly once, at the start of the run. Reading ``lore_enabled`` back
as ``false`` first is what makes turning it on afterwards a decision rather
than a step that would have passed either way.

⚠️ WHAT THIS LEAVES UNCOVERED, STATED RATHER THAN IMPLIED: every case after
this fixture runs sees the feature ON, so the OFF behaviour of the routes
(403 with the spoken refusal) is NOT exercised here. It is exercised in
server/ocserverd/lore_toggle_t33_test.go, which sweeps all eleven addresses
in both states with controls. Switching the feature off and on again between
cases in this suite would make every lore case order-dependent, which is a
worse trade than the gap.
"""
r = client.get("/api/settings", headers=_auth(owner_token))
assert r.status_code == 200, f"read settings: {r.status_code} {r.text}"
assert r.json().get("lore_enabled") is False, (
"a fresh station reports lore_enabled=%r — the owner asked for the lore "
"feature to ship OFF by default" % r.json().get("lore_enabled")
)
r = client.patch(
"/api/settings", json={"lore_enabled": True}, headers=_auth(owner_token)
)
assert r.status_code == 200, f"enable lore: {r.status_code} {r.text}"
assert r.json().get("lore_enabled") is True, r.text


@dataclass(frozen=True)
class AgentIdentity:
"""One minted agent: roster member id + its scope="agent" JWT."""
Expand Down
84 changes: 84 additions & 0 deletions conformance/routes_manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -1258,5 +1258,89 @@
"method": "POST",
"path": "/api/tasks/{task_id}/artifact/{artifact_id}/replace",
"requires": "agent"
},
{
"auth": "gated",
"mcp_tool": "retire_lore_entry",
"method": "POST",
"path": "/api/lore/entries/{entry_id}/retire",
"requires": "agent"
},
{
"auth": "gated",
"mcp_tool": null,
"method": "POST",
"path": "/api/lore/entries/{entry_id}/revive",
"requires": "owner"
},
{
"auth": "gated",
"mcp_tool": "write_lore_entry",
"method": "POST",
"path": "/api/lore/entries",
"requires": "agent"
},
{
"auth": "gated",
"mcp_tool": "search_lore_entries",
"method": "POST",
"path": "/api/lore/search",
"requires": "agent"
},
{
"auth": "gated",
"mcp_tool": "get_lore_entry",
"method": "GET",
"path": "/api/lore/entries/{entry_id}",
"requires": "agent"
},
{
"auth": "gated",
"mcp_tool": "get_lore_revision",
"method": "GET",
"path": "/api/lore/entries/{entry_id}/revisions/{revision_id}",
"requires": "agent"
},
{
"auth": "gated",
"mcp_tool": "list_pending_lore_entities",
"method": "GET",
"path": "/api/lore/entities/pending",
"requires": "admin_agent"
},
{
"auth": "gated",
"mcp_tool": "approve_lore_entity",
"method": "POST",
"path": "/api/lore/entities/{entity_id}/approve",
"requires": "admin_agent"
},
{
"auth": "gated",
"mcp_tool": "merge_lore_entity",
"method": "POST",
"path": "/api/lore/entities/{entity_id}/merge",
"requires": "admin_agent"
},
{
"auth": "gated",
"mcp_tool": "propose_lore_change",
"method": "POST",
"path": "/api/lore/entries/{entry_id}/proposals",
"requires": "agent"
},
{
"auth": "gated",
"mcp_tool": "list_lore_proposals",
"method": "GET",
"path": "/api/lore/entries/{entry_id}/proposals",
"requires": "agent"
},
{
"auth": "gated",
"mcp_tool": "accept_lore_proposal",
"method": "POST",
"path": "/api/lore/entries/{entry_id}/proposals/{proposal_id}/accept",
"requires": "admin_agent"
}
]
180 changes: 180 additions & 0 deletions conformance/test_auth_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -1212,6 +1212,186 @@ def build(ctx: "Ctx", identity: str) -> dict:
),
body={"edits": [{"old": "", "new": "conformance patch probe"}]},
),
# ── T-33 lore 對象審核 ─────────────────────────────────────────────────────
# The review queue's three rows sit on the admin_agent floor (owner ruling
# rc-139a5ab99a19), so an ORDINARY AGENT IS 403 HERE while it is 200 on the
# lore rows below — that asymmetry is the whole point of these three cells
# and nothing else in the suite states it. Approving publishes a name into
# every agent's boot subject directory and merging rewrites which subject an
# entry belongs to; neither is an agent curating what it knows.
#
# The two acts aim at an entity id NOTHING carries, so every at-or-above-floor
# cell is a 404 and every below-floor cell is a derived 403 — which is the
# deny-first order being pinned: an agent must not learn from this route
# whether an entity exists. The 200 faces are in test_rest_happy.py, which is
# where a row may seed a pending entity first.
"GET /api/lore/entities/pending": Route(requires="admin_agent"),
"POST /api/lore/entities/{entity_id}/approve": Route(
requires="admin_agent",
path="/api/lore/entities/en-conf-no-such-entity/approve",
body={"reason": "conformance authz probe"},
overrides={"admin_agent": 404, "owner": 404},
),
"POST /api/lore/entities/{entity_id}/merge": Route(
requires="admin_agent",
path="/api/lore/entities/en-conf-no-such-entity/merge",
body={"into": "en-conf-no-such-target"},
overrides={"admin_agent": 404, "owner": 404},
),
# ── T-33 lore ────────────────────────────────────────────────────────────
# The two governance rows aim at an entry id NOTHING carries, so every
# at-or-above-floor cell is a 404. That stays deliberate even now that a
# create route exists: the choke these rows pin is the FLOOR, which is
# decided before the id is ever looked up, and a matrix cell that had to
# seed an entry first would be testing the seeding as much as the floor. A
# warden is refused 403 (derived) while an agent gets as far as the lookup.
# The 200 faces of all three routes are pinned in test_rest_happy.py, which
# is where a row is allowed to do setup.
#
# 🔴 WHAT THIS ROW DOES NOT COVER, said plainly so nobody reads more into a
# green than it carries: the per-REASON split (an agent may file `expired`
# and `merged`, only the owner may file `falsified`) is invisible here,
# because it lives below the floor gate and behind an entry that has to
# exist. It is pinned in the server unit tests
# (api_lore_governance_route_t33_test.go), against real HTTP requests.
"POST /api/lore/entries/{entry_id}/retire": Route(
requires="agent",
path="/api/lore/entries/e-conf-no-such-entry/retire",
body={"reason": "expired"},
overrides={
"agent_self": 404,
"agent_other": 404,
"admin_agent": 404,
"owner": 404,
},
),
"POST /api/lore/entries/{entry_id}/revive": Route(
requires="owner",
path="/api/lore/entries/e-conf-no-such-entry/revive",
body={"reason": "conformance revive probe"},
overrides={"owner": 404},
),
# 🔴 THE WRITE ROW HAS NO OVERRIDES, AND THAT ASYMMETRY IS THE POINT: unlike
# its two siblings it needs no pre-existing entry, so every at-or-above-floor
# cell is a real 200 that really wrote something. The floor is principalAgent
# because writing down what you just learned is an agent's own act — the
# owner's ruling of 2026-09-01 was that review happens AFTER the write, and a
# higher floor here would have put him back in front of every one of them.
# (That ruling stands. ⚠️ His 2026-09-02 ruling rc-714eea33c6ed — which made
# `falsify` and `instance` purely required — has NO LANDING PLACE in 五格:
# neither cell exists any more. It was not overturned; the 2026-09-03 format
# change left it with nothing to apply to.)
#
# 🔴 THE BODY MUST STAY WELL-FORMED FOR THIS ROW TO MEAN ANYTHING. 第 1、2 格
# are refused blank, and a 422 from a malformed body would read identically in
# every cell whatever the floor said — hiding the very thing this row pins.
"POST /api/lore/entries": Route(
requires="agent",
body={
"trigger": "the authz matrix is probing this row",
"content": "a floor is decided before the body is ever read",
"retire_when": "a cell answers 200 without the floor ever being consulted",
"problem": "this very row, run against every identity in the matrix",
"origin": "agent:conformance-authz",
"subjects": ["agent:conformance-authz"],
},
),
# Retrieval sits at the same floor as writing, and the empty body is the
# point: every condition on this route is optional, so the floor is the ONLY
# thing standing between a caller and an answer. A warden gets 403 here for
# the same reason it does on the write row.
"POST /api/lore/search": Route(
requires="agent",
body={},
),
# 🔴 THE TWO READ ROWS AIM AT AN ENTRY THAT DOES NOT EXIST, so every
# at-or-above-floor cell is a 404 — the floor is decided before the id is
# ever looked up, which is exactly what these rows pin. The 200 faces are in
# test_rest_happy.py, which is where a row may seed one first.
"GET /api/lore/entries/{entry_id}": Route(
requires="agent",
path="/api/lore/entries/lore-conf-no-such-entry",
overrides={
"agent_self": 404,
"agent_other": 404,
"admin_agent": 404,
"owner": 404,
},
),
"GET /api/lore/entries/{entry_id}/revisions/{revision_id}": Route(
requires="agent",
path="/api/lore/entries/lore-conf-no-such-entry/revisions/1",
overrides={
"agent_self": 404,
"agent_other": 404,
"admin_agent": 404,
"owner": 404,
},
),
# 🔴 THE TWO PROPOSAL ROWS AIM AT AN ENTRY NOTHING CARRIES, and the POST's
# body is deliberately WELL-FORMED: the shape checks run before the entry is
# looked up, so a body that would have been refused 422 would hide the floor
# behind a validation error and every cell would read the same whatever the
# floor said. With a valid body every at-or-above-floor cell is the 404 the
# lookup produces, which is what these rows pin.
#
# 🔴 WHAT THESE ROWS DO NOT COVER: the base-digest refusal (409) lives below
# the floor gate and behind an entry that has to exist, so it is invisible
# here. It is pinned in test_rest_happy.py
# (test_lore_proposal_refuses_a_base_digest_that_is_not_current) and in the
# server unit tests, against real HTTP requests.
"POST /api/lore/entries/{entry_id}/proposals": Route(
requires="agent",
path="/api/lore/entries/lore-conf-no-such-entry/proposals",
body={
"kind": "remove",
"base_sha256": "0" * 64,
"encountered": "the authz matrix is probing this row",
"fault": "stale",
"evidence": "this very row, run against every identity in the matrix",
},
overrides={
"agent_self": 404,
"agent_other": 404,
"admin_agent": 404,
"owner": 404,
},
),
"GET /api/lore/entries/{entry_id}/proposals": Route(
requires="agent",
path="/api/lore/entries/lore-conf-no-such-entry/proposals",
overrides={
"agent_self": 404,
"agent_other": 404,
"admin_agent": 404,
"owner": 404,
},
),
# 🔴 THE ONE LORE PROPOSAL ROW WHOSE FLOOR IS admin_agent, AND THAT
# ASYMMETRY WITH THE TWO ROWS ABOVE IS THE POINT OF THIS CELL. Filing a
# proposal changes nothing, so it sits at the agent floor; ACCEPTING one
# rewrites an entry somebody else wrote and replaces 第 5 格 wholesale.
# The owner ruled the floor on rc-a896af93d4f9 (「你 + 銀月(沿用現有
# 前例)」, the precedent being the entity review queue), and the route
# table is the only place that ruling is written down — so an ordinary
# agent's 403 here is the ruling itself, not a detail. If this row ever
# goes green at "agent", any member can rewrite any other member's memory
# by filing a proposal and accepting it himself.
#
# The at-or-above cells are 404s: the proposal id names nothing, and the
# lookup runs AFTER the class gate — which is exactly what makes a 404
# here proof that the gate let them through.
"POST /api/lore/entries/{entry_id}/proposals/{proposal_id}/accept": Route(
requires="admin_agent",
path=(
"/api/lore/entries/lore-conf-no-such-entry/proposals/"
"lp-conf-no-such-proposal/accept"
),
overrides={
"admin_agent": 404,
"owner": 404,
},
),
# ── insight (T-3809) ─────────────────────────────────────────────────────
# The role journal's third block. Its authz face is the lessons face with
# the task_type axis removed — three rows, same three floors, same handler
Expand Down
Loading
Loading