-
Notifications
You must be signed in to change notification settings - Fork 0
ADR 0009 manifest based drift detection
Status: Accepted
myace pull writes a compiled profile's files to disk and then never hears
from that directory again. Two related but distinct questions come up once
someone actually depends on that pulled output: has the source profile
changed on the server since the pull (stale), and has the pulled
output itself been hand-edited locally since (locally modified)? A
correct answer to the second question requires knowing, file by file, what
was actually written at pull time — the server has no visibility into a
user's local disk and never should.
This also needs to support three call sites with different needs: a
one-shot myace check, a continuous myace watch, and (optionally) a
web-UI Sync Dashboard aggregating reports across a user's machines. Sending
every local file's content to the server on every check to answer "did this
change" would be wasteful bandwidth for something a local hash comparison
already answers, and would mean the server needs to store a full copy of
every user's local disk state just to support a lightweight CLI check.
myace pull writes .myace/<target>.manifest.json next to the files it
just wrote: {profile_id, profile_name, target, compiled_hash, pulled_at, files: {filename: sha256(content)}} — a per-file hash of what
was actually written (not necessarily the server's raw response; a file
the user declined to overwrite keeps its old hash) plus the server's
whole-output compiled_hash at pull time.
myace check/watch do two independent diffs, entirely client-side except
for one small network call:
- Locally modified — recompute each manifest-tracked file's hash from disk right now and compare against the stored hash. Zero network calls.
-
Stale — call the new
GET /profiles/{id}/compile-status?target=X(Epic 2.1), which returns just{compiled_hash, updated_at}, and compare against the manifest's storedcompiled_hash. This is transfer-cheap (no file content crosses the wire) but not compute-cheap — it still resolves the same artifacts and runs the sametranslate()as a full compile server-side, documented honestly oncompute_compile_status()(backend/app/services/compiler.py) rather than oversold as free.
No server-side state is created by pull, check, or watch in their
default form. The only server-side table this phase adds
(SyncStatus) is fed exclusively by an explicit --report flag — nothing
is sent unless the user opts in, and reports are scoped to the reporting
user only (never cross-user visible), addressed further in
invariants.md.
- Server records what was pulled, at pull time, keyed by (user, profile, target) — rejected. This still can't answer "was the local file hand-edited" (the server has no view into local disk state after the pull completes), so a local manifest would still be needed for that half regardless — making a server-side pull-record redundant with the manifest for the one thing it could do, and useless for the thing that actually motivated this feature.
-
No manifest; full recompile + full diff on every check — rejected.
Every
check/watchtick would ship the entire compiled output back and forth (andwatch's interval-based polling would do this on a timer, indefinitely) purely to hash-compare it against local files that could be compared directly. The manifest replaces "ship the content to diff it" with "store the hash once, diff locally forever after." -
Report drift status to the server by default — rejected as a privacy
regression: a CLI tool silently telling a server what a user has (or
hasn't) hand-edited on their own machine, without being asked, is the
kind of default this project explicitly avoids.
--reportis opt-in on bothcheckandwatch, every time.
-
.myace/*.manifest.jsonbecomes a de facto local file-format contract: the CLI's ownpull/check/watchare the only readers/writers today, but the CI Action (Epic 2.6) and, in principle, other tooling can read it too. It's intentionally flat/unversioned for this first cut — a breaking format change later needs a compatibility story this ADR does not yet define. - The web Sync Dashboard is only ever as complete as what users have
explicitly reported. It is not, and is not intended to be, an
authoritative live inventory of every machine a profile has been pulled
onto — it shows exactly what
--reporthas sent it, nothing more. A machine that never runscheck --report/watch --reportis invisible to it, by design. -
compile-status's cost trade-off meanswatch's interval polling still costs the server a full artifact-gather-and-translate per tick, per watched target, across every user runningwatch. If that becomes a real load concern, the natural next step is caching compiled output keyed by a hash of the profile's resolved inputs (collections + artifacts + adapter version) socompute_compile_status()can return a cached hash instead of recomputing — deliberately left as future work rather than solved speculatively here. - A file the user declines to overwrite during
pullis recorded in the manifest with its old on-disk hash, not the new server hash — otherwise the very nextcheckwould report "in sync" for a file that provably isn't.
Generated from docs/ by scripts/sync_wiki.py. Back to repo
- Home
- Architecture
- Data Model
- Invariants
- Extending MyACE
- Debugging
-
ADR Index
- ADR-0001-canonical-ir-as-markdown-with-frontmatter
- ADR-0002-session-cookie-auth
- ADR-0003-ownership-based-authorization
- ADR-0004-github-export-via-rest-api
- ADR-0005-email-password-baseline-auth
- ADR-0006-encrypted-admin-editable-secrets
- ADR-0007-additive-user-role-column
- ADR-0008-collection-moderation-state-machine
- ADR-0009-manifest-based-drift-detection
- ADR-0010-structured-handoff-field
- ADR-0011-public-demo-sandbox
- ADR-0012-manual-collection-freshness-verification
- ADR-0013-post-hoc-unpublish
- Adapter Research