This document defines the verification plan for the AgentFS MVP contract in AGENTFS_MVP_CONTRACT.md, and the end-to-end gates for the next AgentFS features defined in AGENTFS_DESIGN_PROPOSAL.md.
The goal is to make development testable before implementation starts. Every test below should prove one contract boundary for:
Agent / FS / Grant / Commit / Event
The core MVP slice is direct commit governance. Hooks、AGENTS.md protected paths、path-scoped grants、proposal/approval are extension gates. This plan now records both the MVP proof set and the implemented v1 extension proof set.
| Layer | Scope | Evidence |
|---|---|---|
| Unit contract | IDs, schemas, roles, paths, errors | crate unit tests |
| Provider store | local agent identity persistence | section-provider tests |
| Control plane | FS metadata, grants, attach, commit, events | sectiond tests |
| CLI integration | user-visible commands and JSON output | section-cli integration tests |
| End-to-end | two agents sharing one backing FS | CLI test with separate data dirs |
| Regression | existing source/path sync behavior | existing sync/path tests |
Use local filesystem OpenDAL provider for MVP tests:
temp/
control-service-data/
remote/
owner-data/
writer-data/
reader-data/
owner.toml
writer.toml
reader.toml
owner-root/
writer-root/
reader-root/
Each agent test config uses a distinct data_dir so agent identity and local
state are isolated. All agents point at the same backing remote/ directory
through the same server-managed SourceProfile.
Sharing tests must run through Section Control Service. Endpoint tests start a
sectiond serve HTTP Control Service. Client configs only know the endpoint;
the service owns agent identity, installation identity, grants, shares, source
profiles, and short-lived sync credentials.
| Case | Expected |
|---|---|
| generate agent id | matches agt_[0-9a-f]{32} |
| generate fs id | matches fs_[0-9a-f]{32} |
| generate commit id | matches cmt_[0-9a-f]{32} |
| generate grant id | matches grt_[0-9a-f]{32} |
| generate event id | matches evt_[0-9]{13}_[0-9a-f]{16} |
| generate many IDs | no duplicate in test sample |
| Case | Expected |
|---|---|
| empty path | accepted as FS root |
docs/readme.md |
accepted |
/docs/readme.md |
rejected |
docs//readme.md |
rejected |
docs/../secret |
rejected |
.section/agentfs/fs.json |
rejected as reserved metadata |
.section/root.json |
local discovery metadata; rejected as shared user content |
.section/user-note.txt |
rejected as reserved metadata |
| Role | Expected capabilities |
|---|---|
owner |
read, commit, manage |
reader |
read |
writer |
read, commit |
manager |
read, manage |
Additional checks:
- unknown role fails with a typed error,
managercannot grantowner,- owner grant cannot be revoked.
Each JSON record must serialize and deserialize with schema_version: 1:
fs.json- grant record
- commit record
heads/current.json- event record
- head lock record
Invalid or missing required fields must fail with malformed_shared_metadata.
Every AgentFS JSON CLI failure returns:
{
"error": {
"code": "grant_denied",
"message": "...",
"retryable": false
}
}Required codes:
unknown_agentunknown_fsgrant_deniedstale_basereserved_metadata_pathmaterialization_failedmalformed_shared_metadatametadata_write_conflict
| Case | Steps | Expected |
|---|---|---|
| register agent | register agent-a |
identity persisted with agt_ id |
| identify agent | reopen store and identify | same id and name returned |
| replace display name | register same local identity with new name | id remains stable, name updates |
| isolated agents | use two data dirs | distinct agent ids |
| missing identity | identify before register | returns absent identity or unknown_agent at command boundary |
| Case | Steps | Expected |
|---|---|---|
| owner creates FS | agent logged in, source profile exists, fs create project |
FS exists, source profile is bound, owner grant exists, head exists with null commit |
| owner grant | create FS | owner grant exists with owner role |
| create event | create FS | fs.created event exists |
| duplicate source name | create existing FS/source | stable error; no partial metadata overwrite |
| create without agent | no local identity | unknown_agent |
| metadata init failure | backing source becomes unwritable during create | create fails; Control Service rows, local source cache, and remote .section metadata are cleaned; retry create succeeds |
| Case | Steps | Expected |
|---|---|---|
| list created FS | create one FS | list returns FS metadata |
| ignore non-AgentFS source | add plain source | fs list does not treat it as FS |
| malformed metadata | corrupt fs.json |
malformed_shared_metadata |
| lookup by id | use fs_id |
resolves FS |
| lookup by name | use FS name/source name | resolves FS |
| Case | Steps | Expected |
|---|---|---|
| owner grants writer | fs grant project agent-b --role writer |
writer grant stored and grant.created event emitted |
| manager grants reader | owner grants manager, manager grants reader | reader grant stored |
| reader cannot grant | reader runs grant command | grant_denied |
| manager cannot grant owner | manager grants owner | grant_denied |
| revoke grant | owner revokes writer | grant has revoked_at_ms, grant.revoked emitted |
| revoked writer attach | revoke then attach | grant_denied |
| revoked writer commit | revoke then commit | grant_denied |
| Case | Steps | Expected |
|---|---|---|
| owner attach | owner attaches own FS | local binding created, root marker written |
| writer attach | writer has writer grant | attach succeeds and syncs current materialized files |
| reader attach | reader has reader grant | attach succeeds |
| unknown agent attach | no identity | unknown_agent |
| no grant attach | registered but ungranted agent | grant_denied |
| marker contents | inspect .section/root.json |
includes schema_version, fs_id, source_profile_id, agent_id, installation_id, base_commit_id |
| attach event | attach succeeds | fs.attached event emitted |
| failed materialized head | head commit is failed_to_materialize |
attach reports non-ready state |
| Case | Steps | Expected |
|---|---|---|
| writer commits file create | edit docs/a.md, apply message |
commit accepted, head updated, remote file materialized |
| writer commits update | edit existing file | path op is update |
| writer commits delete | delete existing file | path op is delete |
| reader cannot commit | reader edits then apply | grant_denied, no commit record |
| empty message | --message " " |
rejected before metadata write |
| empty commit | no dirty paths | rejected before metadata write |
| stale base | local marker base differs from head | stale_base, no new commit |
| reserved metadata | dirty .section/agentfs/fs.json |
ignored in dirty detection and rejected if explicitly included |
| malformed head | corrupt heads/current.json |
malformed_shared_metadata |
| pending materialization | current head pending or failed | commit blocked with materialization_failed |
| Case | Steps | Expected |
|---|---|---|
| successful materialization | commit accepted | commit state becomes materialized, event commit.materialized includes materialization state and changed paths |
| materialization failure | make backing source unwritable or invalid | commit remains head, state becomes failed_to_materialize, events commit.materialization_failed and fs.error are emitted |
| retry materialization | retry failed commit | same commit id used; no duplicate accepted commit |
| block next commit | head failed materialization | next commit apply fails |
| Case | Steps | Expected |
|---|---|---|
| event IDs sort | create multiple events | lexical sort matches creation order by ms prefix |
| replay after id | list events after first id | later events returned |
| immutable event | write event then replay | existing event content unchanged |
| commit event data | commit accepted | event subject is commit id, actor is committing agent |
| grant event data | grant created/revoked | event subject is grant id |
| Case | Steps | Expected |
|---|---|---|
remote scan sees .section |
run source sync | Section metadata is not treated as user content |
local scan sees .section |
run source sync | .section remains skipped locally |
| remote manifest cache | metadata exists remotely | manifest excludes .section/** |
| inventory accelerator | inventory includes .section/agentfs/fs.json |
reserved path is filtered |
section agent login
section agent identify
Expected:
- normal output is human-readable,
--jsonoutput includesok: trueandagent,- repeated login identifies the same agent when the same account is used,
- identify before login fails or returns a clear absent identity according to final command contract.
section fs create project --source-profile test-profile
section fs list
section fs grant project agt_... --role writer
section fs revoke project agt_...
section fs share project agt_...
section fs available
section fs accept shr_...
section fs attach project /tmp/writer-root
section fs status project
Expected:
- JSON output is stable and machine-readable,
- grant-denied failures use the AgentFS error shape,
- status reports FS head, role, base commit, dirty state, and materialization state.
section commit status /tmp/writer-root
section commit apply /tmp/writer-root --message "update file"
Expected:
- status reports dirty paths and stale-base state,
- apply accepts all dirty paths under the attached root,
- accepted commit id is returned,
- failure cases return typed AgentFS errors.
section watch /tmp/owner-root --once
Expected:
- source/path events continue to work,
- AgentFS events are identifiable by kind or stream,
commit.acceptedis observable after commit.
End-to-end tests are product proofs, not control-plane unit tests. They must exercise the system from an agent's point of view and prove this product model:
agent creates FS
agent owns FS
agent grants other agents access
agents attach FS as normal files
agents edit locally
agents commit through policy
accepted commits become shared truth
all agents can observe accepted FS mutations
An AgentFS E2E passes only when it proves the difference between local work and shared truth:
local edit != shared truth
accepted commit == shared truth mutation
E2E tests must use the released CLI shape and filesystem behavior:
- run the
sectionbinary through the CLI test harness, - run a Section Control Service endpoint or local harness for sharing and credential tests,
- use separate config files and
data_dirvalues for each agent, - use one shared backing source for the FS,
- use separate local roots for each attached agent,
- perform user work through ordinary file operations under local roots,
- avoid calling
sectiondRust APIs directly.
E2E tests may inspect shared metadata files under .section/agentfs/ as an
oracle, but the primary assertions should be made through CLI output and
observable filesystem state.
Product E2E for events must use section watch or section fs events.
Metadata file inspection is only a secondary oracle.
Each complete E2E suite must answer these questions from the agent perspective:
| Product Question | E2E Evidence |
|---|---|
| Which FS do I own? | owner can create, list, status, attach, grant |
| Which FS can I access? | granted writer/reader can attach; ungranted agent cannot |
| What files can I read? | attached roots materialize accepted shared truth |
| What files can I change locally? | agents can edit local files with ordinary filesystem operations |
| What changes am I allowed to commit? | commit succeeds or fails according to active grant capability |
| What changed while I was away? | second agent can observe a newer accepted commit and materialized file |
| Which mutation affected truth? | commit record and head identify the accepted mutation |
| Which agent changed it? | commit record and event actor identify the committing agent |
| Which policy allowed it? | MVP checks active grant role/capabilities; final proof records the authorizing grant or owner authority |
| What happens to rejected work? | local draft remains local and backing source/head remain unchanged |
Every AgentFS E2E should start from an isolated fixture:
temp/
remote/
owner-data/
writer-data/
reader-data/
stranger-data/
owner.toml
writer.toml
reader.toml
stranger.toml
owner-root/
writer-root/
reader-root/
stranger-root/
All agents use the same backing remote/ through the local filesystem provider.
Each agent has a distinct data_dir so identity, grants observed by commands,
and local root bindings cannot accidentally share local state.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | owner | agent login |
authenticated agt_ identity and installation |
| 2 | owner | fs create project --source-profile test-profile |
fs_ created; owner grant exists |
| 3 | owner | fs list and fs status project |
owner can discover and inspect FS |
| 4 | owner | inspect remote/.section/agentfs/ |
fs.json, head, owner grant, fs.created event exist |
This proves agent-owned filesystem creation and initial governance metadata.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | owner | fs attach project owner-root |
owner root marker records null base head |
| 2 | writer | agent login |
distinct agt_ identity and installation |
| 3 | owner | fs grant project <writer_id> --role writer |
writer has read and commit capability |
| 4 | owner | fs share project <writer_id> |
server-side share exists |
| 5 | writer | fs available then fs accept <share_id> |
writer accepts the FS through the control service |
| 6 | writer | fs attach project writer-root |
writer gets short-lived sync credential and materializes current shared truth |
| 7 | writer | write writer-root/docs/note.txt |
remote file does not exist yet |
| 8 | writer | commit status writer-root |
dirty path reports docs/note.txt as create |
| 9 | writer | commit apply writer-root --message "add note" |
commit id returned; materialization succeeds |
| 10 | test | inspect head and commit metadata | head points to commit; commit actor is writer |
| 11 | test | inspect remote file | remote/docs/note.txt exists with writer content |
| 12 | owner | attach a fresh root or sync existing root | owner observes writer's accepted content |
This proves the core product statement: local draft is not shared truth until a policy-accepted commit advances the FS head.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | owner | grant reader to reader |
reader has read only |
| 2 | reader | attach reader-root |
attach succeeds |
| 3 | reader | edit reader-root/draft.txt |
edit stays local |
| 4 | reader | commit apply reader-root --message "reader draft" |
grant_denied |
| 5 | test | inspect head, commits, remote file | no accepted commit; remote unchanged |
This proves grants govern acceptance into shared truth, not local write syscalls.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | stranger | log in without grant or share | local identity exists |
| 2 | stranger | fs attach project stranger-root |
grant_denied |
| 3 | test | inspect stranger-root |
no AgentFS root marker is written |
This proves access to an FS is not implicit from knowing the backing source.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | owner | grant writer to writer |
writer can attach and commit |
| 2 | owner | grant reader to same writer, or revoke writer |
active commit capability removed |
| 3 | writer | edit an already attached root | local draft can exist |
| 4 | writer | commit apply |
grant_denied |
| 5 | test | inspect remote and head | draft did not become shared truth |
This proves current active grants, not historical local access, authorize commits.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | owner | grant writer to writer-a and writer-b |
both can attach |
| 2 | writer-a | attach at head H0 |
marker base is H0 |
| 3 | writer-b | attach at head H0 |
marker base is H0 |
| 4 | writer-a | edit and commit | head advances to H1 |
| 5 | writer-b | edit and commit from stale root | stale_base |
| 6 | test | inspect remote/head | writer-a content remains current truth |
This proves accepted commits are based on the current FS head and stale local work cannot silently replace newer truth.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | writer | attach FS | local root created |
| 2 | writer | inspect user-visible local root | .section/** is not materialized as ordinary content |
| 3 | writer | create normal files and commit | commit paths exclude .section/** |
| 4 | owner | attach fresh root | owner sees only user files plus local root marker |
This proves governance metadata is shared control state, not ordinary FS user content.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | owner | create FS | fs.created observable |
| 2 | owner | grant writer | grant.created observable |
| 3 | writer | commit | commit.accepted and commit.materialized observable |
| 4 | observer | replay events or watch | events identify kind, actor, subject, and FS |
This proves accepted mutations are observable. The current implementation may verify event files directly; the final product gate requires CLI event replay or watch.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | owner | fs create over a non-empty backing source |
rejected; no source registration |
| 2 | owner | attach backing source root as working root | rejected; no local marker |
| 3 | owner | metadata initialization fails during fs create |
rejected; service rows and local source cache are rolled back; retry works |
| 4 | writer | commit symlink path | rejected; target outside root does not materialize |
| 5 | writer | inspect fs attach --json |
source options and credentials are not exposed |
This proves common filesystem edge cases do not collapse the product boundary.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | owner | grant manager to manager |
manager has read and manage capability |
| 2 | manager | grant reader or writer to another agent | grant succeeds and event is emitted |
| 3 | manager | attach and edit local root | local draft can exist |
| 4 | manager | commit apply |
grant_denied |
| 5 | test | inspect head and remote | manager draft did not become shared truth |
This proves manage authority is distinct from commit authority.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | owner | create, attach, and commit baseline file | head points to baseline commit |
| 2 | external process | mutate backing file directly outside AgentFS | backing source now differs from governed base |
| 3 | writer | commit status or commit apply from old local root |
remote_drift is surfaced |
| 4 | test | inspect head and commit records | no accepted commit silently blesses external drift |
This proves external backing-source changes are not governed commits. The E2E must prove the mutation is not silently accepted as AgentFS truth.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | writer | edit local file to version A | dirty path reports version A |
| 2 | writer | start commit flow | commit preflight records intended path/version |
| 3 | writer or test harness | mutate the local file to version B during commit window | commit either uses a frozen snapshot or rejects |
| 4 | test | compare commit record and remote bytes | accepted commit metadata matches materialized bytes |
This proves the accepted commit record describes what actually became shared truth. If the implementation cannot create this race deterministically, the E2E can use a test hook or slow provider later; until then this remains a required product-complete scenario.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | owner | create and attach FS | head has no commit |
| 2 | test harness | make AgentFS event writes fail | event stream cannot accept new event |
| 3 | owner | commit apply dirty local file |
command fails |
| 4 | test | inspect head and backing source | head is unchanged; user file is not materialized |
This proves a commit.accepted event is required before head advances. It does
not yet prove every Control Service mutation and every backing-source mirror
write are one distributed transaction.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | writer | commit a dirty path while backing source fails materialization | commit remains accepted with failed_to_materialize; commit.materialization_failed and fs.error are emitted |
| 2 | writer | attempt another commit | command fails before accepting a new commit |
| 3 | owner or writer | inspect status | FS reports non-ready materialization state |
| 4 | test | inspect head | head remains on the failed accepted commit |
This proves governance truth can advance independently from file materialization, and that the system does not accept follow-up mutations while the head is not materialized.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | owner | grant writer to writer |
writer is authorized on the FS |
| 2 | owner | create a server-side share for writer |
share record exists in Section Control Service |
| 3 | writer | log in and run fs available |
writer sees the shared FS |
| 4 | writer | fs accept <share_id> |
service validates share and grant, then returns source profile and short-lived credential |
| 5 | writer | attach | attach succeeds using server-issued credential |
| 6 | test | inspect writer local store | accepted FS and credential binding exist; source long-lived key is absent |
This proves grants lead to usable access from the grantee's perspective. The required product-complete E2E is server-side share discovery and accept.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | writer | attach using one spelling of a path | root marker and local binding are written |
| 2 | writer | run commit/status using an equivalent canonical path | command resolves the same attached root |
| 3 | writer | try overlapping parent/child roots | attach or bind rejects ambiguous overlap |
| 4 | test | inspect local bindings | one FS cannot accidentally treat another root marker as user content |
This proves local root identity does not depend on fragile path spelling or overlapping directory layouts.
后续 E2E 只从 Agent 视角证明产品能力。
规则:
- 每个后续功能至少有一条端到端测试。
- 主要断言通过 CLI、local root、remote materialized files、watch/events 完成。
- metadata 文件可以作为辅助 oracle,但不能替代用户可见行为。
- 需要制造 race、失败、hook 输出时,可以使用测试 harness,但最终断言仍然走公开命令。
执行顺序:
| 阶段 | 目标 | 场景 |
|---|---|---|
| P0 | 治理真相不能错 | 16-24 |
| P1 | 多 Agent 使用体验完整 | 25-28 |
| P2 | 自动化和规则层 | 29-33 |
额外 fixture:
temp/
control-service-data/
observer-data/
manager-data/
writer-b-data/
credential-broker/
hooks/
hook-output/
corrupt-remote/
测试 harness 可以提供:
- 可暂停 materialization 的 provider,用来测试 snapshot isolation。
- 可失败一次的 provider,用来测试 materialization repair。
- Section Control Service endpoint / harness,用来测试 share、discovery、grant、credential。
- 临时 hook script,用来记录输入 JSON、返回成功或失败。
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | owner | create FS and grant writer to writer-a and writer-b |
both writers can attach |
| 2 | writer-a | attach at head H0 using path spelling A |
local mount state records canonical root and base H0 |
| 3 | writer-a | run fs status using equivalent path spelling B |
status resolves the same mount |
| 4 | writer-b | commit a file | head advances to H1 |
| 5 | writer-a | edit local file and run fs status --json |
dirty: true, stale: true, base H0, head H1 |
| 6 | writer-a | commit apply |
stale_base, no new commit |
This proves Section compares local work against the mounted base, not only the current remote files.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | writer | edit docs/note.txt to version A |
commit status reports version A dirty |
| 2 | writer | start commit apply with a paused materialization provider |
staging snapshot is created before metadata write |
| 3 | test harness | change live local file to version B before materialization resumes | live root now differs from staging |
| 4 | harness | resume materialization | remote file contains version A |
| 5 | test | inspect commit record and fs status |
commit path hash matches version A; version B remains local dirty work |
This proves accepted commit metadata describes the bytes that became shared truth.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | writer | commit with provider configured to fail materialization once | commit is accepted; head points to it; state is failed_to_materialize |
| 2 | writer | run another commit apply |
rejected with materialization_failed |
| 3 | owner | fs status --json |
non-ready materialization state is visible |
| 4 | writer | commit repair <root> --commit <commit_id> after provider recovers |
same commit id becomes materialized |
| 5 | writer | run a new commit | new commit can proceed after repair |
This proves repair fixes the accepted head instead of creating a second truth.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | owner | create valid FS good |
status succeeds |
| 2 | test harness | create another lookup candidate with malformed .section/agentfs/fs.json |
corrupt candidate exists |
| 3 | owner | fs status good --json |
succeeds; may include warning about unrelated bad metadata |
| 4 | owner | resolve the corrupt candidate directly | fails with malformed_shared_metadata |
| 5 | test harness | create two FS records with same source name | lookup by source name fails with ambiguous_fs_ref |
| 6 | owner | lookup by exact fs_id |
exact id still resolves |
This proves bad metadata is isolated to the affected FS or lookup candidate.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | owner | create FS, grant writer, writer commits twice | multiple AgentFS events exist |
| 2 | observer | fs events <fs> --json |
events have strictly increasing seq |
| 3 | observer | fs events <fs> --after <seq> |
replay returns only later events |
| 4 | test harness | try to pre-create or overwrite an existing event id before a command writes | command fails or creates a different event; old event is unchanged |
| 5 | observer | replay again | event order is still by seq, not filesystem list order |
This proves events are append-only and replayable.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | owner | create AgentFS-backed source | source record is marked as AgentFS-owned |
| 2 | owner | run source sync on that source |
rejected with AgentFS guardrail error |
| 3 | owner | run source remove or source bind |
rejected with AgentFS guardrail error |
| 4 | owner | try any low-level force path | rejected; MVP has no low-level bypass |
| 5 | owner | run path inspect/compare/resolve under the root |
rejected with AgentFS guardrail error |
This proves low-level source/path commands do not silently bypass AgentFS.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | stranger | fs attach project stranger-root --json |
error.code = grant_denied, retryable = false |
| 2 | writer | stale commit apply --json |
error.code = stale_base, retryable = true |
| 3 | owner | status corrupt metadata with --json |
error.code = malformed_shared_metadata |
| 4 | writer | commit while head failed materialization with --json |
error.code = materialization_failed, retryable = true |
| 5 | test harness | hold metadata lock, then run grant or commit with --json |
error.code = metadata_write_conflict, retryable = true |
| 6 | writer | commit a local path that is not UTF-8 | error.code = non_utf8_path, no accepted commit |
This proves Agent callers can make decisions from stable error codes.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | writer | commit file docs/api |
file materializes |
| 2 | writer | replace docs/api with directory docs/api/index.md in one commit |
rejected with path_type_conflict before metadata write |
| 3 | test | inspect head and remote after failure | head is unchanged; remote still has the original file |
| 4 | writer | delete a non-empty directory as an explicit delete commit | commit succeeds; remote subtree is removed cleanly |
| 5 | test | inspect working copy after delete | dirty paths are clean |
This proves commit acceptance rejects ambiguous same-path type replacement before shared truth advances, and that explicit directory deletes materialize cleanly.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | writer | attach using /tmp/project/../project |
mount stores canonical root |
| 2 | writer | run fs status /tmp/project |
resolves the same mount_id |
| 3 | writer | attach child root under existing root | rejected |
| 4 | writer | attach parent root over existing root | rejected |
| 5 | writer | attach backing source root as working root | rejected |
This proves root identity is stable and nested roots cannot leak control files.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | owner | fs grant project <writer_id> --role writer |
writer has commit authority |
| 2 | owner | fs share project <writer_id> |
server-side share record is created |
| 3 | writer | fs available --json |
writer sees the shared FS after login |
| 4 | writer | fs accept <share_id> |
service validates identity, share, and grant |
| 5 | writer | fs attach project writer-root |
source profile and short-lived sync credential are bound locally |
| 6 | writer | inspect local store through fs status writer-root --json |
accepted FS exists; no long-lived source key is printed |
| 7 | writer | edit and commit | commit succeeds through accepted access |
| 8 | owner | revoke or expire another share, then writer tries accept | revoked or expired share cannot be accepted |
This proves a grant can become usable access through the service control plane.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | observer | start watch <owner-root> --agentfs --json |
process waits for AgentFS events |
| 2 | writer | commit a file | commit.accepted and commit.materialized occur |
| 3 | observer | read watch output | events include stream: agentfs, seq, fs_id, actor, subject |
| 4 | observer | stop watch, then run fs events <fs> --after <seq> |
replay resumes after last seen event |
This proves a passive Agent can observe changes without reading metadata files.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | owner | grant writer to writer |
grant id is returned |
| 2 | writer | commit a file | commit succeeds |
| 3 | observer | fs events <fs> --json |
commit.accepted.data.authorized_by.grant_id equals the grant id |
| 4 | owner | revoke the writer grant | grant is no longer active |
| 5 | observer | replay old commit event and inspect commit metadata oracle | old audit still points to original grant id |
| 6 | owner | commit a file | audit shows authorized_by.type = owner |
This proves every accepted mutation explains which authority allowed it.
| Step | Actor | Command or action | Expected |
|---|---|---|---|
| 1 | owner | create FS and grant reader, writer, manager | roles exist |
| 2 | each agent | fs status <root-or-fs> --json |
output includes agent id, role, capabilities, head, base, materialization state |
| 3 | writer | edit local file | status reports dirty count |
| 4 | writer | become stale after another commit | status reports stale |
| 5 | owner | put FS into failed materialization state | status reports non-ready state and warning |
This proves fs status is enough for an Agent to decide whether it can act.
| 步骤 | 角色 | 操作 | 预期 |
|---|---|---|---|
| 1 | owner | hooks add project --name record -- <script> |
hook 定义被保存 |
| 2 | writer | commit 一个文件 | commit 正常成功 |
| 3 | hook script | 把收到的 JSON 写到 hook-output/ |
JSON 包含 commit.materialized、fs id、commit id、actor |
| 4 | test harness | 检查 writer 本地 hook-run store | 本地记录了一次 run |
| 5 | hook script | 读取提交进程里的测试环境变量 | 读不到,说明没有继承父进程环境 |
| 6 | reader | 尝试 add hook | grant_denied |
| 7 | owner | 添加失败 hook 后再次 commit | commit 仍然成功,本地记录失败 run |
这个场景证明:Hooks v1 只在普通提交已落地后做本地自动化,不改变共享事实。
| 步骤 | 角色 | 操作 | 预期 |
|---|---|---|---|
| 1 | owner | 给 FS 添加 hook | hook 定义被保存 |
| 2 | unmounted agent | 这个 FS 没有 attach local root | 本地状态没有 root |
| 3 | owner | commit 一个文件 | commit 成功 |
| 4 | test harness | 检查 unmounted agent 本地 hook-run store | 没有 run |
这个场景证明:hook 执行是本地行为,不创建假的 skipped run。
| 步骤 | 角色 | 操作 | 预期 |
|---|---|---|---|
| 1 | owner | create FS | 控制面保存 FS 记录 |
| 2 | test harness | 破坏底层同步目录里的 head 元数据 | 底层同步目录不可用于解析当前状态 |
| 3 | owner | hooks add/list/remove project ... |
全部成功 |
这个场景证明:添加、查看、删除 hook 是权限和控制面行为,不依赖底层同步目录。
| 步骤 | 角色 | 操作 | 预期 |
|---|---|---|---|
| 1 | owner | commit AGENTS.md,机器块里包含 protected_paths |
规则成为 active rules |
| 2 | writer | commit 普通路径 | 成功 |
| 3 | writer | commit protected path | grant_denied,head 不前进 |
| 4 | writer | commit invalid AGENTS.md |
agent_rules_invalid,远端旧规则不变 |
| 5 | owner | commit protected path | 成功 |
这个场景证明:AGENTS.md v1 只执行机器可读的 protected_paths,不解释自然语言,也不执行 required_checks。
| 步骤 | 角色 | 操作 | 预期 |
|---|---|---|---|
| 1 | owner | grant contributor to contributor |
contributor has read and propose, not commit |
| 2 | contributor | edit and run commit apply |
grant_denied |
| 3 | contributor | commit propose <root> --message "change" |
proposal id returned; head unchanged |
| 4 | manager-only | try accept | accept is rejected; head unchanged |
| 5 | owner | accept proposal | proposal becomes accepted commit; head advances |
| 6 | owner | accept stale proposal | stale_base |
| 7 | owner | reject open proposal | proposal becomes rejected; head unchanged |
这个场景证明:proposal/approval 是 direct commit 之外的独立路径,accept 时重新检查 head。
| 步骤 | 角色 | 操作 | 预期 |
|---|---|---|---|
| 1 | owner | grant writer to writer with scope docs/** |
writer has scoped commit authority |
| 2 | writer | edit docs/a.md and commit |
commit succeeds |
| 3 | writer | edit src/a.rs and commit |
rejected with path_scope_denied |
| 4 | writer | edit both docs/b.md and src/b.rs in one commit |
entire commit is rejected |
| 5 | observer | inspect accepted docs commit audit | authorized_by includes grant id, path_scopes, and matched_path_scopes |
这个场景证明:path scopes 限制哪些本地改动可以成为共享事实。
P2 features are extension gates. They should not block the AgentFS core product complete call, but each feature must pass its own E2E before it is exposed as implemented.
| Feature | Why It Matters | Current Status |
|---|---|---|
| Hooks | commit 成为 shared truth 后,需要本地自动化。 | 已实现第一版:本地、非阻塞、只支持 commit.materialized,已有 e2e 覆盖。 |
AGENTS.md rules |
FS-local rules should affect Agent behavior through explicit machine-readable policy. | 已实现第一版:只执行 protected_paths,不执行 required_checks。 |
| Proposal/approval | Some collaborators should propose changes without direct commit authority. | 已实现第一版:contributor propose,owner accept/reject,accept 时检查 freshness。 |
| Path-scoped grants | Coarse FS-wide writer grants may be too broad. | 已实现第一版:grant --scope,commit path 检查,audit 记录命中 scope。 |
The current CLI product tests live mainly in:
crates/section-cli/tests/agentfs_e2e.rscrates/section-cli/tests/agentfs_control_plane.rs
It covers the product behaviors that are implemented today:
| Test | E2E Scenarios Covered |
|---|---|
e2e_writer_commit_becomes_shared_truth_for_owner |
owner creates FS; writer commit becomes shared truth; fs accept returns service-issued credential binding with expiry; fs attach、commit status、commit apply refresh service-issued credentials before backing-source access; owner observes accepted content; staging snapshot、repair、repair 后继续 commit、fs events replay、watch --agentfs、event seq、authorized_by、JSON error payload 都可验证 |
agentfs_control_plane::http_control_service_shares_without_client_source_profile_or_keys |
sectiond serve HTTP Control Service owns SourceProfile/backing source; owner/writer clients only configure endpoint; tampered writer auth token is rejected; restored writer discovers、accepts、attaches、commits without client-side source profile or backing-source keys |
e2e_fs_ref_resolves_source_name_and_rejects_ambiguity |
fs status resolves by source name; duplicate source-name refs fail with ambiguous_fs_ref; exact fs id still resolves |
e2e_bad_metadata_in_unrelated_source_does_not_block_fs_lookup |
unrelated source 里坏的 .section/agentfs/fs.json 不会阻塞健康 FS 的 fs status 和 fs events |
e2e_rejects_invalid_shared_metadata_schema_and_links |
corrupted commit schema, wrong head FS link, and wrong event FS link fail with malformed_shared_metadata |
e2e_grants_control_attach_manage_and_commit_authority |
reader denied commit but can status/events; ungranted agent denied attach/status/events; downgrade removes commit authority; manager can grant、share、revoke but cannot commit; fs status exposes role、capabilities、dirty、next_actions |
e2e_revoke_removes_commit_access_and_blocks_pending_share_accept |
fs revoke removes commit access from an attached writer, emits replayable grant.revoked, prevents owner grant revoke, and blocks pending share accept after backing grant revoke |
agentfs_control_plane::reader_cannot_commit_and_ungranted_agent_cannot_attach |
reader cannot commit; ungranted attach fails; low-level source sync、source bind、source remove、write、path inspect cannot mutate or inspect AgentFS-backed source as normal source/path |
e2e_grant_survives_backing_event_mirror_failure |
grant 的 backing event mirror 写失败时,Control Service 事件仍能通过 fs events 看到,writer 仍能 accept、attach、commit |
e2e_stale_writer_cannot_overwrite_new_truth |
stale writer cannot commit over a newer accepted head; 篡改本地 marker 不能绕过 trusted mount base;fs status exposes stale state and sync next action |
e2e_backing_source_drift_cannot_be_committed_over |
backing source 被外部直接修改后,commit 返回 remote_drift;head、backing file、accepted event 数量都不变 |
e2e_hardening_rejects_unsafe_backing_source_and_attach_root |
non-empty backing source rejected; backing root cannot be attached as working root |
e2e_create_failure_rolls_back_service_and_local_cache |
create metadata 初始化失败时,Control Service rows、本地 AgentFS source cache、远端 .section metadata 都会回滚;恢复后可以重新 create |
e2e_attach_canonicalizes_local_root_identity |
attach 使用带 .. 的路径拼写时,返回 JSON、root marker、fs status 都记录 canonical local root |
e2e_fs_status_reports_corrupt_local_marker |
corrupt .section/root.json is reported as malformed_shared_metadata by fs status <local path> --json instead of being swallowed as unknown_fs |
e2e_section_directory_is_not_committed_as_user_content |
local .section/** files are ignored by commit dirty detection and do not become shared user content |
e2e_commit_preflight_rejects_empty_message_and_empty_commit |
empty commit message and empty dirty set fail before head advances or new accepted event is created |
e2e_reattach_moves_single_local_root |
reattaching the same FS moves the active local root, removes the old marker, and status resolves only from the new root |
e2e_rejects_file_dir_type_replacement_before_acceptance |
file/dir type replacement is rejected before a new accepted commit; head and backing source remain unchanged |
e2e_non_empty_directory_delete_materializes_cleanly |
non-empty directory delete commits as shared truth; remote subtree is removed and working copy becomes clean |
e2e_materialization_failure_emits_fs_error_event |
real backing-source materialization failure leaves failed head, emits commit.materialization_failed and fs.error, and keeps unwritten file out of backing source |
e2e_event_write_failure_does_not_advance_commit_head |
commit.accepted event 写失败时,commit 命令失败;head 不前进,用户文件不物化 |
e2e_rejects_non_utf8_commit_paths |
非 UTF-8 本地文件名不会被 lossy 转成共享路径;commit 返回 non_utf8_path,head 和 accepted event 不变 |
e2e_commit_success_survives_local_marker_update_failure |
accepted/materialized commit still succeeds when final local marker update fails; warning is returned and trusted mount base is updated |
e2e_hardening_rejects_symlink_commit_paths |
symlink paths cannot materialize files outside the working root |
e2e_hooks_v1_run_local_post_materialized_automation |
Hooks v1 runs local post-materialized automation, does not inherit parent env, records success/failure locally, and does not block accepted commits |
e2e_hooks_management_uses_control_service_without_backing_source_read |
hook add/list/remove uses Control Service and does not depend on backing source metadata reads |
e2e_path_scoped_grant_restricts_commit_paths |
scoped writer can commit allowed paths, cannot commit out-of-scope or mixed changes, and commit audit records matched scopes |
e2e_agents_md_protected_paths_are_enforced |
AGENTS.md protected paths reject writer changes, owner can change protected paths, invalid rules do not replace remote rules |
e2e_proposal_approval_flow_keeps_head_governed |
contributor can propose but not commit; manager-only cannot accept; owner accept advances head; stale accept fails; reject closes proposal |
The list above is the current proof set for the AgentFS core plus implemented v1 extension gates.
The MVP baseline still means direct commit governance. Extension gates are verified separately and must not be treated as fallback behavior.
Still unsupported:
- blocking hooks,
AGENTS.md required_checks,- deny path scopes,
- proposal expiry,
- low-level force bypass mode for AgentFS-backed sources.
Low-level source/path tests remain regression tests for the sync substrate. They are not proof of AgentFS governance.
Run existing tests to prove AgentFS does not break source/path infrastructure:
cargo test -p section-provider
cargo test -p sectiond
cargo test -p section-cli --test path_control_plane
cargo test -p section-cli --test sync_control_plane
cargo test -p section-cli --testsMVP 不能暗示已经支持这些能力:
| Surface | Assertion |
|---|---|
| Hooks | MVP 基线不包含 hook;Hooks v1 作为后续功能单独验证 |
AGENTS.md |
MVP 基线只同步文件;protected_paths v1 作为后续功能单独验证 |
| path-scoped grants | MVP 基线只有整个 FS 级别的 grant;path scopes v1 单独验证 |
| proposals/approvals | MVP 基线只有直接 commit;proposal/approval v1 单独验证 |
AgentFS 核心完整完成,必须同时满足:
- P0 和 P1 场景都有 E2E。
- 所有 E2E 都通过公开 CLI 跑通。
- owner、reader、writer、manager 的权限行为都有测试。
- share、available、accept、attach 的跨机业务路径有测试。
- commit 会检查权限、base、dirty、reserved path。
- commit 使用 staging snapshot,metadata 和物化文件一致。
- 本地 marker 更新失败不会让已 accepted/materialized 的 commit 被报告为失败。
- accepted commit 记录
authorized_by。 - materialization 失败会阻塞后续 commit。
commit repair修复同一个 commit。fs status --json能告诉 Agent 当前能不能行动。fs events和watch --agentfs能让 Agent 观察变化。- 每个公开 AgentFS JSON 错误都有稳定
error.code、retryable、details。 - 低层
source/path命令不能绕过 AgentFS。 - 现有 source/path 回归测试仍然通过。
- 未实现的 P2 功能不能暴露成可用功能。
- 设计文档、测试计划、CLI 行为一致。
| 完成标准 | 当前证据 | 状态 |
|---|---|---|
| P0 和 P1 场景都有 E2E | agentfs_e2e.rs 覆盖 owner/writer/reader/manager、share/accept/attach、commit、events/status、failure/repair、metadata lock、guardrails;agentfs_control_plane.rs 覆盖服务端 source profile/credential/cache 和低层 source/path 拒绝 |
covered |
| 所有用户行为 E2E 都通过公开 CLI 跑通 | E2E 使用 run_section 执行 section CLI;测试 harness 只负责启动 HTTP Control Service 或制造故障 |
covered |
| owner、reader、writer、manager 权限行为都有测试 | e2e_grants_control_attach_manage_and_commit_authority,并覆盖 manager grant/share/revoke、owner grant 不可 revoke |
covered |
| share、available、accept、attach 的跨机业务路径有测试 | http_control_service_shares_without_client_source_profile_or_keys 证明客户端只配置 endpoint 也能 share/available/accept/attach/commit;e2e_writer_commit_becomes_shared_truth_for_owner、e2e_revoke_removes_commit_access_and_blocks_pending_share_accept、writer_share_accept_attach_commit_and_owner_observes_truth、client_seed_cannot_overwrite_existing_source_profile 覆盖本地 harness 和 credential refresh |
covered |
| commit 检查权限、base、dirty、reserved path | 权限:e2e_grants_control_attach_manage_and_commit_authority;base:e2e_stale_writer_cannot_overwrite_new_truth;dirty:e2e_commit_preflight_rejects_empty_message_and_empty_commit;reserved path:e2e_section_directory_is_not_committed_as_user_content |
covered |
| commit 使用 staging snapshot,metadata 和物化文件一致 | e2e_writer_commit_becomes_shared_truth_for_owner 检查 staging manifest;同一测试里 live root 后续修改保持 dirty work,repair 用原 snapshot 物化原内容 |
covered |
| 本地 marker 更新失败不会让已 accepted/materialized 的 commit 被报告为失败 | e2e_commit_success_survives_local_marker_update_failure |
covered |
accepted commit 记录 authorized_by |
e2e_writer_commit_becomes_shared_truth_for_owner 和 writer_share_accept_attach_commit_and_owner_observes_truth 检查 commit/event 的 grant authority |
covered |
| materialization 失败会阻塞后续 commit | e2e_materialization_failure_emits_fs_error_event 和 e2e_writer_commit_becomes_shared_truth_for_owner 的 failed-head block |
covered |
commit repair 修复同一个 commit |
e2e_writer_commit_becomes_shared_truth_for_owner 检查 repair 返回同一 commit_id、恢复原 staging 内容,并且 repair 后新 commit 可以继续 |
covered |
fs status --json 能告诉 Agent 当前能不能行动 |
e2e_grants_control_attach_manage_and_commit_authority、e2e_stale_writer_cannot_overwrite_new_truth、e2e_fs_status_reports_corrupt_local_marker |
covered |
fs events 和 watch --agentfs 能让 Agent 观察变化 |
e2e_writer_commit_becomes_shared_truth_for_owner、e2e_grant_survives_backing_event_mirror_failure |
covered |
每个公开 AgentFS JSON 错误都有稳定 error.code、retryable、details |
assert_json_error 在 E2E/control-plane tests 中统一检查 code、retryable、details;覆盖 grant_denied、stale_base、remote_drift、materialization_failed、metadata_write_conflict、malformed_shared_metadata、non_utf8_path、unknown_fs、ambiguous_fs_ref、operation_failed |
covered |
低层 source/path 命令不能绕过 AgentFS |
reader_cannot_commit_and_ungranted_agent_cannot_attach、config_source_named_like_agentfs_source_cannot_bypass_file_router |
covered |
| 现有 source/path 回归测试仍然通过 | Regression Tests section defines cargo test -p section-cli --tests; verification record is kept in issue triage |
covered |
| 未实现的 P2 功能不能暴露成可用功能 | MVP Out-Of-Scope Assertions keeps Hooks、AGENTS.md enforcement、path-scoped grants、proposals out of core completion |
covered |
| 设计文档、测试计划、CLI 行为一致 | This audit is the source of truth for #46; inconsistent path diagnostic wording was removed | covered |
P2 功能单独完成。每个 P2 功能完成时,必须满足:
- 有自己的 E2E。
- 不破坏 AgentFS 核心 E2E。
- 错误也走统一 JSON 错误格式。
- 文档和实现一致。