Skip to content

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_version bump would currently require a manual, out-of-band migration.
  • The registry store and a project's own runtime store are both FilesystemStore instances 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 in projects/service.py::register_project.