-
Notifications
You must be signed in to change notification settings - Fork 3
plat 435
Status: agent_go migrated and guarded on main (2026-10-04), not deployed; workspace/ module and multi-user e2e fixture left (below). Opened 2026-10-04 after PLAT-434, the latest of a long series.
The same folder exists in two spellings: logical (Chats/Code/projects/p, what clients and manifests use) and
physical (_users/<id>/Chats/Code/projects/p, what storage and the runtime use). Both are plain strings, and every
caller decides for itself how to compare or strip them. Counted on main 2026-10-04: 82 production Go files mention
_users/, 17 raw HasPrefix/TrimPrefix checks on it, and at least three normalisers that do not agree
(normalizeConversationWorkspace strips any user's prefix, canonicalChatHistoryWorkspacePath only the caller's,
session_workspace.go another variant). A new caller that picks the wrong one works on a single-user laptop and RTS and
breaks only on multi-user servers, which is why tests and local runs miss it. Earlier instances: PLAT-170 (presentation
tools got canonical paths), crew Slack apps and bot destinations stored with the wrong form (commits 31b0734b8,
a7eff2376, e4937721d, 3363fefc5), PLAT-324 (tabs lost on reload), the 2026-09 secrets path reversal in DECISIONS.
- One type
WorkspaceRef{User, Logical}built by oneParseWorkspace(path)(accepts both spellings; keeps the owner when the prefix is present) with.Logical(),.Physical(user),.Owner(),.SameAs(other),.IsProject(),.Product(). No other code strips or tests_users/. - Replace the 17 raw checks and the three normalisers with it, one package at a time, each with the two-spelling test.
- A guard test that fails when production code outside that package contains
"_users/"string handling, so a new caller cannot reintroduce it. Run every path-sensitive test under both spellings (a shared test helper). - Run the e2e suites on a multi-user fixture, not only the single-user one.
PLAT-434 fixed the Code/Crew UI-control instance.
-
agent_go/pkg/workspacerefbuilt:Ref,Parse,MustParse,Logical()(strips ANY owner: which product/project),SameFor(user, other)/SameAs/OwnedBy/OwnedByOrUnowned(identity for access),Physical(user),PhysicalKeepOwner(user),Project()/IsProject()over the singleProjectRootstable,SanitizeUserID(one implementation). Table tests for every spelling;reftest.BothSpellingsshared helper. - Migrated (commit 2):
normalizeConversationWorkspace(Logical),canonicalChatHistoryWorkspacePath/pkg/common.CanonicalSessionWorkspace(bothworkspaceref.CanonicalFor),workspacePathsMatchForUser(SameFor),projectProductForPath(Ref.Project; single project-root table,codeproduct.ProjectsRoottakes it from there),sanitizeUserIDForPath+pkg/common+pkg/chathistorysanitizers (SanitizeUserID),ClassifySessionWorkspace,CodeProjectRoot. - Pre-existing failures on main 0cf79e4cf, unrelated: TestPrivateCodeCallerIsSeparateFromCrewWithSameProjectID, TestSalesCrewCatalogHasInstallableRoles, TestCrewProductSurfaceE2E (native-subagents undeclared).
- Migrated (commit 3, cmd/server):
cleanAgentProfileWorkspace,productConversationRuntimeWorkspace,crewProjectOwnerID/isCrewProjectPath/crewProjectOwnedByCaller, place MCP roots (cleanAttachRoot,isCodePlaceRoot,placeRootOf,attachRootForCaller,placeMCPCanAttach),command_routesshape check,ui_control_routesownership,workspaceProxyPathIsOtherUser,chat_history_persistence(restored-path check, legacy owner check,ownedWorkProjectWorkspacePath). Tests:workspaceref_sites_test.go(both spellings + another user's physical path).Parsecounts a mid-path_userssegment as a prefix only for absolute paths (a relativeChats/x/_users/bob/yis an ordinary folder name; the old code stripped it). - Found while migrating:
crewProjectOwnedByCallertreated an absolute doc-root path of ANOTHER user's crew as the caller's own (the_users/prefix test missed it);crewProjectOwnerIDread the owner from an uncleaned_users/alice/../bob/.... Both now go throughParse. - Also failing on main, unrelated: TestGetRelayCommandCatalogWithoutGenericRuntimeRegistration.
- Migrated (commit 4, cmd/server/services):
routeWorkspaceUserID,SameSlackScopePath(owner-agnostic only when one side is logical, as before),physicalBotScopeOwner; testservices/workspaceref_sites_test.go. - Migrated (commit 5):
sparkquillproduct.runtimeRoot,videoproduct.profileWorkspaceRoot(an escaping..path now maps to a non-existent folder, not to the user's whole tree; old code joined it, so../bob/xreached_users/bob/x),pkg/browser.captureWorkspace,livefeed.IsPlanPath,presentations.workspaceDatabasePath. Tests in each package. - Migrated (commit 6, cmd/server parsers and builders):
externalIsCrewRoot,parseCrewPath,isOtherOwnerCrewPath, shared-asset parsing (shared_assets.go,shared_assets_crew.go),codeFilesDeletionProtected,browserProjectKey,costOverviewRoot/costOverviewIsCode,isChatsWriteFolder(a relativex/_users/y/chatsno longer counts: only an absolute path carries a document root),workspaceReadAllowed,workspaceGitWriteAllowed, code-peer root check; physical-path builders (PhysicalPath/PhysicalPathOf) in agent_profile_routes, chat_submission_journal, crew_functions, code_peer_functions, slack_trigger, chat_history_persistence, command_routes, custom_command_tools, browser_live, workspace_git, instructions. Tests added toworkspaceref_sites_test.go. - Migrated (commit 7): storage layers now name the directory only through
workspaceref.UsersDir; unsanitized-owner builders usePhysicalPathOf;pkg/chathistory/workspace_api_store.gousesPhysicalPath. Opened PLAT-440 for the call sites that build a physical path from a raw or differently sanitized user id. - Guard:
pkg/workspaceref/guard_test.gofails on a_userspath string literal anywhere in production code (prose with whitespace ignored) and on anyworkspaceref.UsersDiruse outside an explicit allowlist (9 storage/migration files, each with its reason;cmd/testing/e2e fixtures are the only literal allowlist entry). It also fails when an allowlist entry stops needing its exemption.
Counts in production agent_go Go files, before -> after: files mentioning _users 90 -> 72 (comments included);
non-comment _users lines 118 -> 27 (all workspaceref.UsersDir uses in the 9 allowlisted storage/migration files, the
cmd/testing fixtures and the package itself); raw HasPrefix/TrimPrefix/Contains/Split checks on _users 38 -> 0.
Tests: cmd/server full run has 13 failures, the same 13 that fail on the pre-435 baseline 5f14ea26b (Relay catalog,
Sales catalog, product-surface e2e, provider accounts, playbooks, native terminal, tier/LLM config, Code caller);
everything touched by this work passes.
-
workspace/module (separate Go module, own copy of the rule):utils.UsersDirectory,handlers/query.go:240(other-user refusal),handlers/documents.go:508,server.go:68(default folders),skill_sync.go:186(regexp on_users/<id>/Chats/<product>/projects/<p>/skills),slots/slots.go:246(Code path shape),slots/config.go:130(docs-root prefix), andutils.SanitizeUserID(falls back toDEFAULT_USER_ID, not"default": PLAT-440). These are access checks; migrating them needsworkspacerefimportable from that module (move it to a shared module) and a behaviour review, so they are not done here. - Item 4 of the proposal: run the e2e suites on a multi-user fixture.
- PLAT-440: call sites that build a physical path from an unsanitized user id (
PhysicalPathOf). - Storage/migration files still name the directory through
workspaceref.UsersDir(allowlist inguard_test.go).
Auto-synced from docs/ on main. Edit there, not here.