Assync Runtime Storage¶
Location¶
Per-project mission/run/finding/etc. state lives in .assync-runtime/
inside that project's own repository — gitignored (see the repository
root .gitignore), never part of the public framework snapshot.
Collections: projects, missions, runs, reports, findings,
evidence, synthesis, decisions, authorizations, locks, audit
(storage/filesystem.py::RUNTIME_COLLECTIONS).
The cross-project registry (which projects Assync knows about at all)
is a separate concern from any single project's own runtime data — it
defaults to ~/.assync-runtime/registry/, overridable via the
ASSYNC_REGISTRY_ROOT environment variable (commands/_common.py).
This keeps "which projects exist" (user-machine-level) cleanly separate
from "what happened within project X" (local to project X's repository).
Atomicity (storage/atomic.py)¶
Every canonical JSON record is written via: temp file in the same
directory → flush → fsync (best-effort) → atomic os.replace(). A crash
mid-write leaves the original record untouched — verified directly in
test_storage.py::test_interrupted_temp_file_does_not_replace_canonical_record,
which plants a stray .tmp file and confirms it is never picked up as the
canonical record. The audit log uses an append-only JSON Lines file
(append_jsonl) — historical events are never edited in place.
Path Safety (storage/filesystem.py::FilesystemStore)¶
Every record ID is validated (core/ids.py::validate_id — filesystem-safe
characters only, no .., no /, no \0) and every resolved path is
checked to still be under the store's own root before any read or write —
rejecting traversal outright, not merely discouraging it.
Project Isolation¶
Each project gets its own FilesystemStore instance, rooted at that
project's own .assync-runtime/. There is no code path that lets one
store instance read another project's records — isolation is structural
(separate root paths), not a runtime permission check that could be
bypassed.
Schema Versioning¶
Every persisted record includes a schema_version field (currently 1
throughout). Parsing goes through explicit from_dict/to_dict methods
on each model — there is no generic silent-reinterpretation path for an
unrecognized schema.
Mission Locks¶
See mission-orchestrator.md — locks live under the locks collection
inside the same runtime root, using the same atomic-write mechanism.
MVP Limitations¶
- No schema-migration tooling exists yet; a
schema_versionbump would currently require a manual, out-of-band migration. - The registry store and a project's own runtime store are both
FilesystemStoreinstances but are never the same instance — there is no automated check today that a registry root and a project's own runtime root can never accidentally coincide, beyond the nested-path rejection inprojects/service.py::register_project.