Files
Agent-System-Backend/docs/iam-u3-account-write-fence-inventory.md
weichen 7492feab42 Fence delegated project projection by its real operator
Why: RDM delegated rename rebuilt the creator Host and could commit a Backend project rename after the delegated operator was disabled.

Evidence: SQLite and isolated PostgreSQL targeted checks cover operator/creator disable order, two processes, sorted locks, auth-off local compatibility, and HTTP/command Host wiring; exact committed-head replay remains pending.

Impact: Adds a request-bound single-project extension capability and checks operator plus creator in the final Backend transaction. Global ScopedHost cannot derive delegated authority; RDM cross-database atomicity remains outside the promise.

Refs: Approved Documents delegated-projection amendment 5783940, U3 A1-A4 candidate adcd8fe, Dev feature/iam-u3-account-directory-recovery.
2026-10-14 08:17:31 +00:00

22 KiB
Raw Permalink Blame History

IAM U3 A1: account write fence inventory

Status: stage 4–5 implementation inventory; the fixed candidate is the commit containing this file, and stage 6 human review remains pending. The route table records the 4053c4d baseline finding and the intended final sink; its Now column is historical, not a claim about the amended candidate. Scope follows the approved Documents A1–A4 amendment fb3a8214df963697ebd745ad11eb852ed9444fe3 and approved delegated-actor amendment 5783940e4c434102cbca8c22bd282d87b28fa840.

The 66 non-GET /api registrations were enumerated from internal/server/server.go. Paths below omit /api. A means s.owner(r) (auth-off: implicit store.OwnerDefault); B means body owner, K redeemed-key owner, S current password-session owner. Target resource owner never substitutes for actor. In the historical Now column, T means the baseline already had a final Store check; M means a Backend DB write lacked one; E means an external or process-local effect; — means no business mutation or permitted credential cleanup. D means a short Store transaction with actor account lock and enabled read before resource/scope/conversation locks; F means stage immutable bytes, then D for the visible DB reference; X means last effective-state check before external action plus cancellation; C means D at each command handler's actual sink; P means D for Backend projection and X at the separate RDM callback. Vnn identifies the route for traceability; verification uses representative concurrency cases and complete source-to-sink review, rather than one duplicate test per route.

# Route → handler (source line) Actor Final sink Now Required fence Verify
01 POST /auth/login → login (accounts.go:148) B CreateSessionVerified T retain account/version transaction check V01
02 POST /auth/login-key/redeem → redeemLoginKey (accounts.go:106) K RedeemLoginKey; failed-attempt security counter T retain account/grant transaction check V02
03 POST /auth/recovery-grants/redeem → redeemLoginKey (accounts.go:106) K same as 02 T same as 02 V03
04 POST /auth/logout → logout (accounts.go:202) A DeleteSession credential cleanup — permit cleanup after disable V04
05 POST /auth/password → changePassword (accounts.go:214) S checked setup/forced/active password Store transaction T retain session/account recheck V05
06 POST /admin/users → createAdminUser (accounts.go:321) A CreatePendingAccount T retain actor enabled/admin transaction check V06
07 PATCH /me/profile → patchMeProfile (accounts.go:394) S PatchAccountProfile T retain actor/target transaction check V07
08 PATCH /admin/users/{userId}/profile → patchAdminAccountProfile (accounts.go:419) A PatchAccountProfile T retain actor/target transaction check V08
09 PUT /admin/users/{userId}/state → setAdminAccountState (accounts.go:444) A SetAccountEnabled T retain shared account lock and in-tx actor check V09
10 POST /admin/users/{userId}/recovery-proposals → createAdminRecoveryProposal (recovery.go:7) A CreateRecoveryProposal T retain actor/target transaction check V10
11 DELETE /admin/users/{userId}/recovery-proposals/{id} → revokeAdminRecoveryProposal (recovery.go:47) A RevokeRecoveryProposal T retain actor/target transaction check V11
12 POST /auth/recovery-proposals/{id}/confirm → confirmRecoveryProposal (recovery.go:81) S ConfirmRecoveryProposal T retain owner/session proof in transaction V12
13 POST /auth/recovery-proposals/{id}/rotate-grant → rotateRecoveryGrant (recovery.go:105) S RotateRecoveryGrant T retain owner/session proof in transaction V13
14 POST /admin/users/{owner}/password → resetAdminUserPassword (accounts.go:468) A 410 response only — no business sink V14
15 POST /admin/users/{owner}/login-keys → issueAdminLoginKey (accounts.go:478) A 410 response only — no business sink V15
16 DELETE /admin/users/{owner}/login-keys/{id} → revokeAdminLoginKey (accounts.go:494) A RevokeLoginKeyCascade M D: lock/recheck admin actor too, then target/grant V16
17 POST /projects → createProject (server.go:713) A CreateProject or RDM Create + Host projection M/E P V17
18 PATCH /projects/{id} → patchProject (server.go:734) A RDM Rename or RenameProject; UpdateProjectSettings M/E P; D for every settings write V18
19 DELETE /projects/{id} → deleteProject (server.go:812) A RDM Archive or DeleteProject M/E P V19
20 POST /projects/{id}/conversations → createConversation (server.go:875) A CreateConversation[InProject]; RDM Work precheck M D; keep domain work check V20
21 PATCH /conversations/{id} → patchConversation (server.go:914) A update, permission, note, flags: separate Store writes M D at each final write V21
22 DELETE /conversations/{id} → deleteConversation (server.go:950) A DeleteConversation M D V22
23 POST /conversations/{id}/messages → postMessage (server.go:956) A message, run, message-run link; optional Bus dispatch M/E D per write; C for command; actor lock before conversation lock V23
24 POST /conversations/{id}/rewind → rewindFiles (rewind.go:27) A checkpoint files, then AppendMessage M/E X for rewind, D for message V24
25 POST /conversations/{id}/regenerate → regenerateMessage (borrow.go:159) A message flags/exclusion, regen marker, rewind files, new run M/E D per write, X for rewind V25
26 POST /messages/{id}/exclude → excludeMessage (borrow.go:122) A ExcludeRunGroup(messageID) M D; derive conversation owner in transaction V26
27 POST /messages/{id}/variant → selectVariant (borrow.go:146) A SelectVariant(messageID) M D; derive conversation owner in transaction V27
28 POST /secrets → createSecret (borrow.go:21) A AddSecret M D V28
29 POST /secrets/{id}/activate → activateSecret (borrow.go:39) A ActivateSecret M D V29
30 PATCH /secrets/{id} → renameSecret (borrow.go:43) A RenameSecret M D V30
31 DELETE /secrets/{id} → deleteSecret (borrow.go:54) A DeleteSecret M D V31
32 POST /context-entries → saveContextEntry (borrow.go:64) A SaveContextEntry M D V32
33 DELETE /context-entries/{id} → deleteContextEntry (borrow.go:78) A DeleteContextEntry M D V33
34 POST /prompt-commands → saveUserPromptCommand (borrow.go:87) A SaveUserCommand, then process Bus register M/E D; X before Bus mutation V34
35 DELETE /prompt-commands/{id} → deleteUserPromptCommand (borrow.go:102) A DeleteUserCommand, then Bus unregister M/E D; X before Bus mutation V35
36 POST /runs/{id}/cancel → cancelRun (server.go:1278) A PG EnqueueCommand or local cancel handle M/E D for enqueue with in-tx run owner; X locally V36
37 POST /hitl/{id}/resolve → resolveHITL (server.go:1314) A local HITL/card/event writes or PG EnqueueCommand M D at each Store sink, including queue V37
38 POST /cards/{id}/actions/{actionId} → cardAction (server.go:1401) A Cards action or Bus confirm, then receipt message M/E D for card/message; C for confirmed handler V38
39 POST /cards/{id}/pin → cardPin (server.go:1453) A card version write M D V39
40 POST /cards/{id}/dismiss → cardDismiss (server.go:1462) A card version/event write M D V40
41 POST /cards/{id}/to_project → cardToProject (server.go:1386) A project card copy and original dismissal M D at both writes V41
42 POST /projects/{id}/uploads → upload (server.go:1469) A Blob + artifact row, card, knowledge/vector index M/E F; D at later card/index writes V42
43 PUT /artifacts/{id}/content → putArtifactContent (server.go:1651) A in-place Blob overwrite before SQL UPDATE M/E F using new immutable blob ref, guarded DB pointer swap V43
44 DELETE /artifacts/{id} → deleteArtifact (server.go:1656) A metadata, index/vector rows, Blob deletion M/E D for visible rows; X for post-commit GC V44
45 POST /profiles → saveProfile (server.go:1667) A profile Store row M D V45
46 DELETE /profiles/{id} → deleteProfile (server.go:1683) A profile Store row M D V46
47 POST /prompts → savePromptModule (server.go:2023) A prompt Store row M D V47
48 DELETE /prompts/{id} → deletePromptModule (server.go:2044) A prompt Store row M D V48
49 PUT /settings/user → putSettingsUser (server.go:1717) A settings transaction, then separate snapshot row M D for settings and snapshot V49
50 PUT /settings/project/{id} → putSettingsProject (server.go:1726) A settings transaction, then separate snapshot row M D for settings and snapshot V50
51 PUT /settings/system → putSettingsSystem (server.go:1782) A system settings and snapshot under synthetic owner M D with request actor, not OwnerSystemRow V51
52 POST /tools → createUserTool (server.go:1815) A process Registry register; future HTTP tool execution E X for registration and each tool execution V52
53 DELETE /tools/{name} → deleteUserTool (server.go:1834) A process Registry unregister E X V53
54 POST /plugins/{id}/enabled → setPluginEnabled (platform_api.go:132) A SetPluginEnabled upsert M D V54
55 POST /plugins/{id}/activation → setSurfaceActivation (platform_api.go:251) A activation transaction, separate audit row M D for both writes V55
56 POST /surfaces → submitSurface (platform_api.go:194) A snapshot transaction, then event/pending row M D for snapshot/event writes V56
57 POST /surfaces/{id}/actions → submitSurfaceAction (platform_api.go:271) A action insert, Bus dispatch, action-state update M/E D for both action writes; C for dispatch V57
58 POST /plugins/reload → reloadPlugins (server.go:1908) A process registry/sidecar changes and UpsertPlugin M/E X for process changes; D for DB writes V58
59 POST /commands/{id} → execCommand (server.go:1211) A Bus audit/card/pending or arbitrary command handler/RDM M/E C, X for external handler V59
60 POST /projects/{id}/knowledge → addKnowledge (server.go:2055) A AddKnowledge batch transaction M D; in-tx project/owner check V60
61 POST /tasks → createBackgroundTask (platform_api.go:16) A task insert M D V61
62 POST /tasks/{id} → updateBackgroundTask (platform_api.go:52) A task update M D V62
63 POST /memory → putMemory (platform_api.go:69) A memory upsert M D V63
64 POST /audit → postAudit (platform_api.go:112) A client-origin business audit row M D; security-denial audit is a separate exception V64
65 POST /admin/backup → backup (server.go:2224) A directory + SQLite backup file E X and explicit admin authorization V65
66 POST /admin/recovery-holds/{runId}/release → releaseRecoveryHold (accounts.go:526) A recovery-hold UPDATE M D with actor admin check and run/hold owner lookup V66

The route count is not a full write count. GET /api/llm/budget → llmBudget (platform_api.go) calls AccountTokens(owner, 0, budget); the amended Store returns a read-only total when tokens are zero. V67 therefore has no business write. Ordinary authenticated GETs can refresh session last_seen_at as credential maintenance; session/API-key in-flight revocation remains a separate impact analysis.

Host and run manager exits

ID Entry and actor Final sink Now → required fence Verify
H01 inprocHost.KVPut, host owner (hostcaps.go:295) PutPluginKV M → D VH01
H02 inprocHost.Audit, host owner; actor label is plugin:<id> (hostcaps.go:309) AppendAuditEvent M → D using host owner VH02
H03 KnowledgeWrite/Publish/Delete, host owner (hostcaps.go:350-424) knowledge rows, vector rows; publish deletes then writes M/E → D at every DB sink; X before embedding/remote model VH03
H04 TaskCreate/Update, host owner (hostcaps.go:427-443) background-task rows M → D VH04
H05 EnsureProject/RenameOwnedProject, host owner (project_lifecycle.go:125-135) Backend projection rows, RDM callback upstream M/E → actor D before project-name lock; X at callback VH05
H06 SurfaceSave, host owner (hostcaps.go:481) surfacesvc.Submit snapshot/event M → D at Store sinks VH06
H07 Complete, host owner (hostcaps.go:319) external LLM request E → X with context cancellation VH07
H08 scoped Host reconstructed from owner/plugin ID (hostcaps.go:103) same H01-H07 methods M/E → same final sink fences; early requireEffective alone is insufficient VH08
G01 startClaimedRun/subagent spawn, run owner (runmgr.go:1443,1924) CreateRun, UpdateRun and admission rows M → D for business state; keep failure-only cleanup explicit VG01
G02 Emit, AskHITL, cards, plan/todos (runmgr.go:676-935) events, HITL/card, plan/todo rows M → D at each final Store write VG02
G03 WriteArtifact, run owner (runmgr.go:968) Blob, artifact, event, knowledge/vector rows M/E → F then D for each later write VG03
G04 ExecuteTool, run owner (runmgr.go:1080-1167) Registry HTTP/command/filesystem side effects, output artifacts E/M → X before external call; C/D for command and output sinks VG04
G05 stageInputs, checkpoint recording/rewind and subagent transcript (inputs.go:17-90, rewind.go:37-99, subagent_transcript.go:46-84) workspace files E → X before each effect and cancellation; no cross-system atomic claim VG05
G06 ResolveHITL, run/card owner (runmgr.go:1780-1860) HITL/card/event and possible run writes M → D VG06
G07 background notice delivery and subagent completion (runmgr.go:1982-2041,2444-2455) run/message/event plus transcript files M/E → D for business rows, X for files VG07
G08 settleRunWithFence, run owner (runmgr.go:1700-1722) final run + assistant message T for enabled check in store/recovery.go:184-220; retain narrow failure-state exception VG08
G09 OnUsage, run owner (app.go:668-680) usage and budget audit rows M → D or documented accounting-only exception VG09

At the 4053c4d baseline, Host/runmgr effective-account queries were early checks, Bus confirmation and Surface writes had separate sinks, and artifact content could be overwritten in place before a checked SQL update. These are the reasons for the sink-level mapping below; file paths and old line numbers in the historical tables are navigational rather than final-SHA evidence.

Amended candidate mapping and limits

This section supersedes the historical Now column when checking the amended candidate. V01–V15 retain their existing account/credential transaction or response-only behavior; V16 uses actor-checked login-key revocation. V17–V23 route project, conversation, message, run creation and link writes through account-checked Store entries. V24–V35 use guarded file/message and borrow/secret/context/command entries; V25 prepares the message exclusion and regeneration marker in one checked transaction, then consumes the marker only with successful run settlement. V36–V44 guard command queue, HITL, card, surface/event, artifact metadata and index writes. Artifact content updates switch immutable blob references in the checked transaction. V45–V51 guard profile, prompt, settings and snapshot writes, using the request actor for system settings. V52–V59 combine last checks for process/HTTP/tool effects with guarded plugin, surface, card, command pending and audit DB writes. V60–V66 use guarded knowledge, task, memory, audit and hold updates. V65 refuses whole-database backup under authenticated mode because administrator status does not grant access to other owners' business content; auth-off backup has a last check. V67 is read-only at zero tokens.

VH01–VH04/VH06/VH08 use the Host owner and final Store account checks for Backend-owned rows. H05 delegated rename now derives a single-project projection from the current authenticated lifecycle or command Host; its final Store transaction locks the real operator and creator in stable order, checks both effective states and verifies the persisted project owner. The global NewScopedHost cannot derive this delegated capability; its original creator-owned methods remain. VH07 checks immediately before its external model call and passes the caller context. VG01–VG09 use checked run, admission/lease, event, HITL, card, artifact, index, notice, command and settle sinks. File staging, transcript, tool HTTP/search/code execution and overlay writeback check just before effects and propagate cancellation. Failed/cancelled/interrupted run sealing, lease release, notice deletion and failed-start marker deletion are narrowly scoped safety cleanup; positive token usage is accounting for model work already incurred.

The H05/V17–V19 late-write blocker was reproduced in the earlier candidate and repaired under the separately approved Documents 5783940 plan. Tests cover creator/operator effective state, a disable between early check and final write on SQLite/PG, a two-process PG disable, opposite lock order progress, and both HTTP PATCH and no-conversation command Host sources. The RDM database authorization/intent commit is still separate from Backend's account/project transaction; a permission withdrawal or cross-database failure is handled by the existing I1 failure intent and recovery path rather than a zero-window assertion. Completion and stage 6 claims require exact-HEAD receipts and human review.

The account lock closes the account-enabled versus Backend-owned business write race on the same database. It does not prove that a session or API key revoked after request authentication cannot complete a write while that account remains enabled; that is a distinct credential-version fence and outside this amendment's promise. RDM commits in its own database and external requests, processes and files cannot share the Backend transaction. The last check can race with those effects, and a multi-file operation can leave earlier files committed when a later check fails. No cross-database or external zero-window claim is made.

Sink discovery still required: Backend source alone cannot enumerate every dynamically registered Bus handler, plugin sidecar command, or the RDM callback's independent database writes. V17–V19, V23, V38, V57–V59 and VH05 must record the actual composed handlers and their final sinks in the tested composition; an unknown or unguarded handler blocks the A1 completion claim. The external HTTP tool destination and filesystem actions depend on runtime configuration, so VG04/VG05 must verify the actual enabled tool set and cancellation points.

Common implementation and verification gate

  1. Build one short Store write-transaction primitive: Begin → PG actor advisory lock (account:<owner>) → SELECT enabled ... FOR UPDATE → existing resource/scope/conversation/admission locks → resource authorization and write → commit. The actor lock must precede all other PG locks, including project-name locks; participating account owners use sorted order, then bootstrap lock where required. SetAccountEnabled already takes the account lock (store/account_state.go:20-45); lockAccountUsers supplies sorted PG account locking (store/account_model.go:115-145). SQLite uses _txlock=immediate (store/store.go:188-199). In auth-off mode, only a missing OwnerDefault row is implicitly enabled; an existing disabled row rejects (app/account_guard.go:10-23). No DB connection or transaction spans request body decoding, embedding, tool execution, RDM callback, or full HTTP lifetime.
  2. Make methods with only resource IDs derive owner inside the guarded transaction. Pass actor separately for system settings (OwnerSystemRow), delegated Host calls, commands, queue commands, audit and background jobs. Preserve project/duty authorization as a separate condition; account enabled does not grant resource access. For files, stage invisible immutable bytes and switch the visible DB reference in D; failed transaction deletes staged bytes, post-commit old-blob GC may continue. RDM's independent database keeps the explicit cross-database window from the amendment.
  3. Review every V/VH/VG route against its final sink on the fixed candidate SHA. Use deterministic pause-and-disable tests for representative project, message/run, artifact, settings, command/surface and Host/runmgr paths on SQLite and isolated PostgreSQL. Test account lock ordering with two PG connections. Verify the existing credential routes' in-transaction ordering. For external effects, verify the last check and cancellation and state the remaining check-to-effect window. Record exact command, tested SHA and output in the Dev task card; local SELF is not independent REPLAY or CI GATE.

Stage 6 stops if any Backend-owned business write lacks a final actor-enabled transaction fence, if artifact content remains visibly mutable after disable via in-place overwrite, or if an external/RDM effect is described as same-DB atomic. Session/API-key in-flight revocation is a separate amendment impact analysis; enabled checks do not prove credential-revocation fencing.