Scaffold calcalist: repo, toolchain, config model and doctor
Initialise the project per the approved implementation plan (M0).
- Pin the toolchain with devbox: Rust 1.97.1, pimsync 0.5.11, jujutsu 0.44.0,
and radicale 3.7.8 for later integration tests.
- Add the configuration model with full referential validation, reporting
every problem in one pass rather than short-circuiting on the first.
- Add `calcalist doctor`, verifying pimsync's presence and version series,
the state directory, and the configuration.
- Define the whole CLI surface; only `doctor` acts, the rest exit 2 rather
than pretending to work.
- Rewrite SPECS.md: Rust naming conventions in place of the JS/TS style
lines, a `devbox run check` gate instead of a pre-commit hook (jj runs no
git hooks), and the sync semantics the spec previously left unstated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 09:10:16 +03:00
|
|
|
# This file is automatically @generated by Cargo.
|
|
|
|
|
# It is not intended for manual editing.
|
|
|
|
|
version = 4
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "anstream"
|
|
|
|
|
version = "1.0.0"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"anstyle",
|
|
|
|
|
"anstyle-parse",
|
|
|
|
|
"anstyle-query",
|
|
|
|
|
"anstyle-wincon",
|
|
|
|
|
"colorchoice",
|
|
|
|
|
"is_terminal_polyfill",
|
|
|
|
|
"utf8parse",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "anstyle"
|
|
|
|
|
version = "1.0.14"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "anstyle-parse"
|
|
|
|
|
version = "1.0.0"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"utf8parse",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "anstyle-query"
|
|
|
|
|
version = "1.1.5"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"windows-sys",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "anstyle-wincon"
|
|
|
|
|
version = "3.0.11"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"anstyle",
|
|
|
|
|
"once_cell_polyfill",
|
|
|
|
|
"windows-sys",
|
|
|
|
|
]
|
|
|
|
|
|
Add the aggregation engine and a local sync cycle
The core of M1: everything needed to reconcile source calendars against an
aggregate, short of getting events in and out over the network.
- ical: surgical line-level editing. Each logical line keeps a byte range into
the original, so untouched lines are emitted verbatim and only edited ones are
rebuilt. Parsing and re-serialising would drop every property we do not model.
- ical: content hashing excludes DTSTAMP and LAST-MODIFIED. Servers rewrite them
on every store, so hashing them would report a change on every cycle forever.
- provenance: aggregate UIDs derived as blake3(aggregate, source, source_uid),
length-prefixed so field boundaries cannot collide. Deriving rather than
recording makes the state file a cache, and makes our own mirrors recognisable,
which is what stops writes echoing back around.
- mirror: the transforms. An aggregate copy must be scheduling inert, so writing
it never mails invitations for a meeting already invited from its source.
Google can suppress notification and keeps real attendees; CalDAV cannot, so
the guest list is demoted to inert data and a declined meeting is marked
TRANSP:TRANSPARENT. Writing an edit back uses the source as donor for what the
demotion removed, so editing a time cannot silently drop the guests.
- reconcile: pure decision engine. Only the source changed updates the mirror,
only the aggregate changed writes back, both changed keeps the source and logs
a conflict.
- Mass-deletion guard takes an absolute floor as well as a fraction: a share
alone is meaningless at small counts, where deleting the only event is 100%.
- sync refuses to run when an aggregate's configured target differs from the
recorded one, before reconciling. Otherwise the new empty target would read as
an aggregate whose every event was deleted, and delete propagation would then
remove them from every source.
Found by end-to-end testing: writing an item already present under a different
filename created a duplicate rather than replacing it, because filenames are
derived from the UID while pimsync picks its own. Writes now carry the path they
supersede. Covered by a regression test.
79 tests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 09:55:06 +03:00
|
|
|
[[package]]
|
|
|
|
|
name = "arrayvec"
|
|
|
|
|
version = "0.7.8"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "bitflags"
|
|
|
|
|
version = "2.13.2"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "blake3"
|
|
|
|
|
version = "1.8.7"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "6d9e454fc11f76977dc803893aff6304ed33d6a26efae8696573bea74baa27ae"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"arrayvec",
|
|
|
|
|
"cc",
|
|
|
|
|
"cfg-if",
|
|
|
|
|
"constant_time_eq",
|
|
|
|
|
"cpufeatures",
|
|
|
|
|
]
|
|
|
|
|
|
Scaffold calcalist: repo, toolchain, config model and doctor
Initialise the project per the approved implementation plan (M0).
- Pin the toolchain with devbox: Rust 1.97.1, pimsync 0.5.11, jujutsu 0.44.0,
and radicale 3.7.8 for later integration tests.
- Add the configuration model with full referential validation, reporting
every problem in one pass rather than short-circuiting on the first.
- Add `calcalist doctor`, verifying pimsync's presence and version series,
the state directory, and the configuration.
- Define the whole CLI surface; only `doctor` acts, the rest exit 2 rather
than pretending to work.
- Rewrite SPECS.md: Rust naming conventions in place of the JS/TS style
lines, a `devbox run check` gate instead of a pre-commit hook (jj runs no
git hooks), and the sync semantics the spec previously left unstated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 09:10:16 +03:00
|
|
|
[[package]]
|
|
|
|
|
name = "calcalist"
|
|
|
|
|
version = "0.1.0"
|
|
|
|
|
dependencies = [
|
Add the aggregation engine and a local sync cycle
The core of M1: everything needed to reconcile source calendars against an
aggregate, short of getting events in and out over the network.
- ical: surgical line-level editing. Each logical line keeps a byte range into
the original, so untouched lines are emitted verbatim and only edited ones are
rebuilt. Parsing and re-serialising would drop every property we do not model.
- ical: content hashing excludes DTSTAMP and LAST-MODIFIED. Servers rewrite them
on every store, so hashing them would report a change on every cycle forever.
- provenance: aggregate UIDs derived as blake3(aggregate, source, source_uid),
length-prefixed so field boundaries cannot collide. Deriving rather than
recording makes the state file a cache, and makes our own mirrors recognisable,
which is what stops writes echoing back around.
- mirror: the transforms. An aggregate copy must be scheduling inert, so writing
it never mails invitations for a meeting already invited from its source.
Google can suppress notification and keeps real attendees; CalDAV cannot, so
the guest list is demoted to inert data and a declined meeting is marked
TRANSP:TRANSPARENT. Writing an edit back uses the source as donor for what the
demotion removed, so editing a time cannot silently drop the guests.
- reconcile: pure decision engine. Only the source changed updates the mirror,
only the aggregate changed writes back, both changed keeps the source and logs
a conflict.
- Mass-deletion guard takes an absolute floor as well as a fraction: a share
alone is meaningless at small counts, where deleting the only event is 100%.
- sync refuses to run when an aggregate's configured target differs from the
recorded one, before reconciling. Otherwise the new empty target would read as
an aggregate whose every event was deleted, and delete propagation would then
remove them from every source.
Found by end-to-end testing: writing an item already present under a different
filename created a duplicate rather than replacing it, because filenames are
derived from the UID while pimsync picks its own. Writes now carry the path they
supersede. Covered by a regression test.
79 tests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 09:55:06 +03:00
|
|
|
"blake3",
|
Scaffold calcalist: repo, toolchain, config model and doctor
Initialise the project per the approved implementation plan (M0).
- Pin the toolchain with devbox: Rust 1.97.1, pimsync 0.5.11, jujutsu 0.44.0,
and radicale 3.7.8 for later integration tests.
- Add the configuration model with full referential validation, reporting
every problem in one pass rather than short-circuiting on the first.
- Add `calcalist doctor`, verifying pimsync's presence and version series,
the state directory, and the configuration.
- Define the whole CLI surface; only `doctor` acts, the rest exit 2 rather
than pretending to work.
- Rewrite SPECS.md: Rust naming conventions in place of the JS/TS style
lines, a `devbox run check` gate instead of a pre-commit hook (jj runs no
git hooks), and the sync semantics the spec previously left unstated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 09:10:16 +03:00
|
|
|
"clap",
|
|
|
|
|
"serde",
|
Add the aggregation engine and a local sync cycle
The core of M1: everything needed to reconcile source calendars against an
aggregate, short of getting events in and out over the network.
- ical: surgical line-level editing. Each logical line keeps a byte range into
the original, so untouched lines are emitted verbatim and only edited ones are
rebuilt. Parsing and re-serialising would drop every property we do not model.
- ical: content hashing excludes DTSTAMP and LAST-MODIFIED. Servers rewrite them
on every store, so hashing them would report a change on every cycle forever.
- provenance: aggregate UIDs derived as blake3(aggregate, source, source_uid),
length-prefixed so field boundaries cannot collide. Deriving rather than
recording makes the state file a cache, and makes our own mirrors recognisable,
which is what stops writes echoing back around.
- mirror: the transforms. An aggregate copy must be scheduling inert, so writing
it never mails invitations for a meeting already invited from its source.
Google can suppress notification and keeps real attendees; CalDAV cannot, so
the guest list is demoted to inert data and a declined meeting is marked
TRANSP:TRANSPARENT. Writing an edit back uses the source as donor for what the
demotion removed, so editing a time cannot silently drop the guests.
- reconcile: pure decision engine. Only the source changed updates the mirror,
only the aggregate changed writes back, both changed keeps the source and logs
a conflict.
- Mass-deletion guard takes an absolute floor as well as a fraction: a share
alone is meaningless at small counts, where deleting the only event is 100%.
- sync refuses to run when an aggregate's configured target differs from the
recorded one, before reconciling. Otherwise the new empty target would read as
an aggregate whose every event was deleted, and delete propagation would then
remove them from every source.
Found by end-to-end testing: writing an item already present under a different
filename created a duplicate rather than replacing it, because filenames are
derived from the UID while pimsync picks its own. Writes now carry the path they
supersede. Covered by a regression test.
79 tests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 09:55:06 +03:00
|
|
|
"serde_json",
|
|
|
|
|
"tempfile",
|
Scaffold calcalist: repo, toolchain, config model and doctor
Initialise the project per the approved implementation plan (M0).
- Pin the toolchain with devbox: Rust 1.97.1, pimsync 0.5.11, jujutsu 0.44.0,
and radicale 3.7.8 for later integration tests.
- Add the configuration model with full referential validation, reporting
every problem in one pass rather than short-circuiting on the first.
- Add `calcalist doctor`, verifying pimsync's presence and version series,
the state directory, and the configuration.
- Define the whole CLI surface; only `doctor` acts, the rest exit 2 rather
than pretending to work.
- Rewrite SPECS.md: Rust naming conventions in place of the JS/TS style
lines, a `devbox run check` gate instead of a pre-commit hook (jj runs no
git hooks), and the sync semantics the spec previously left unstated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 09:10:16 +03:00
|
|
|
"thiserror",
|
|
|
|
|
"toml",
|
|
|
|
|
]
|
|
|
|
|
|
Add the aggregation engine and a local sync cycle
The core of M1: everything needed to reconcile source calendars against an
aggregate, short of getting events in and out over the network.
- ical: surgical line-level editing. Each logical line keeps a byte range into
the original, so untouched lines are emitted verbatim and only edited ones are
rebuilt. Parsing and re-serialising would drop every property we do not model.
- ical: content hashing excludes DTSTAMP and LAST-MODIFIED. Servers rewrite them
on every store, so hashing them would report a change on every cycle forever.
- provenance: aggregate UIDs derived as blake3(aggregate, source, source_uid),
length-prefixed so field boundaries cannot collide. Deriving rather than
recording makes the state file a cache, and makes our own mirrors recognisable,
which is what stops writes echoing back around.
- mirror: the transforms. An aggregate copy must be scheduling inert, so writing
it never mails invitations for a meeting already invited from its source.
Google can suppress notification and keeps real attendees; CalDAV cannot, so
the guest list is demoted to inert data and a declined meeting is marked
TRANSP:TRANSPARENT. Writing an edit back uses the source as donor for what the
demotion removed, so editing a time cannot silently drop the guests.
- reconcile: pure decision engine. Only the source changed updates the mirror,
only the aggregate changed writes back, both changed keeps the source and logs
a conflict.
- Mass-deletion guard takes an absolute floor as well as a fraction: a share
alone is meaningless at small counts, where deleting the only event is 100%.
- sync refuses to run when an aggregate's configured target differs from the
recorded one, before reconciling. Otherwise the new empty target would read as
an aggregate whose every event was deleted, and delete propagation would then
remove them from every source.
Found by end-to-end testing: writing an item already present under a different
filename created a duplicate rather than replacing it, because filenames are
derived from the UID while pimsync picks its own. Writes now carry the path they
supersede. Covered by a regression test.
79 tests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 09:55:06 +03:00
|
|
|
[[package]]
|
|
|
|
|
name = "cc"
|
|
|
|
|
version = "1.4.5"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"find-msvc-tools",
|
|
|
|
|
"shlex",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "cfg-if"
|
|
|
|
|
version = "1.0.4"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|
|
|
|
|
Scaffold calcalist: repo, toolchain, config model and doctor
Initialise the project per the approved implementation plan (M0).
- Pin the toolchain with devbox: Rust 1.97.1, pimsync 0.5.11, jujutsu 0.44.0,
and radicale 3.7.8 for later integration tests.
- Add the configuration model with full referential validation, reporting
every problem in one pass rather than short-circuiting on the first.
- Add `calcalist doctor`, verifying pimsync's presence and version series,
the state directory, and the configuration.
- Define the whole CLI surface; only `doctor` acts, the rest exit 2 rather
than pretending to work.
- Rewrite SPECS.md: Rust naming conventions in place of the JS/TS style
lines, a `devbox run check` gate instead of a pre-commit hook (jj runs no
git hooks), and the sync semantics the spec previously left unstated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 09:10:16 +03:00
|
|
|
[[package]]
|
|
|
|
|
name = "clap"
|
|
|
|
|
version = "4.6.6"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"clap_builder",
|
|
|
|
|
"clap_derive",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "clap_builder"
|
|
|
|
|
version = "4.6.6"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"anstream",
|
|
|
|
|
"anstyle",
|
|
|
|
|
"clap_lex",
|
|
|
|
|
"strsim",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "clap_derive"
|
|
|
|
|
version = "4.6.4"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"heck",
|
|
|
|
|
"proc-macro2",
|
|
|
|
|
"quote",
|
|
|
|
|
"syn",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "clap_lex"
|
|
|
|
|
version = "1.1.0"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "colorchoice"
|
|
|
|
|
version = "1.0.5"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
|
|
|
|
|
Add the aggregation engine and a local sync cycle
The core of M1: everything needed to reconcile source calendars against an
aggregate, short of getting events in and out over the network.
- ical: surgical line-level editing. Each logical line keeps a byte range into
the original, so untouched lines are emitted verbatim and only edited ones are
rebuilt. Parsing and re-serialising would drop every property we do not model.
- ical: content hashing excludes DTSTAMP and LAST-MODIFIED. Servers rewrite them
on every store, so hashing them would report a change on every cycle forever.
- provenance: aggregate UIDs derived as blake3(aggregate, source, source_uid),
length-prefixed so field boundaries cannot collide. Deriving rather than
recording makes the state file a cache, and makes our own mirrors recognisable,
which is what stops writes echoing back around.
- mirror: the transforms. An aggregate copy must be scheduling inert, so writing
it never mails invitations for a meeting already invited from its source.
Google can suppress notification and keeps real attendees; CalDAV cannot, so
the guest list is demoted to inert data and a declined meeting is marked
TRANSP:TRANSPARENT. Writing an edit back uses the source as donor for what the
demotion removed, so editing a time cannot silently drop the guests.
- reconcile: pure decision engine. Only the source changed updates the mirror,
only the aggregate changed writes back, both changed keeps the source and logs
a conflict.
- Mass-deletion guard takes an absolute floor as well as a fraction: a share
alone is meaningless at small counts, where deleting the only event is 100%.
- sync refuses to run when an aggregate's configured target differs from the
recorded one, before reconciling. Otherwise the new empty target would read as
an aggregate whose every event was deleted, and delete propagation would then
remove them from every source.
Found by end-to-end testing: writing an item already present under a different
filename created a duplicate rather than replacing it, because filenames are
derived from the UID while pimsync picks its own. Writes now carry the path they
supersede. Covered by a regression test.
79 tests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 09:55:06 +03:00
|
|
|
[[package]]
|
|
|
|
|
name = "constant_time_eq"
|
|
|
|
|
version = "0.4.2"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "cpufeatures"
|
|
|
|
|
version = "0.3.1"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"libc",
|
|
|
|
|
]
|
|
|
|
|
|
Scaffold calcalist: repo, toolchain, config model and doctor
Initialise the project per the approved implementation plan (M0).
- Pin the toolchain with devbox: Rust 1.97.1, pimsync 0.5.11, jujutsu 0.44.0,
and radicale 3.7.8 for later integration tests.
- Add the configuration model with full referential validation, reporting
every problem in one pass rather than short-circuiting on the first.
- Add `calcalist doctor`, verifying pimsync's presence and version series,
the state directory, and the configuration.
- Define the whole CLI surface; only `doctor` acts, the rest exit 2 rather
than pretending to work.
- Rewrite SPECS.md: Rust naming conventions in place of the JS/TS style
lines, a `devbox run check` gate instead of a pre-commit hook (jj runs no
git hooks), and the sync semantics the spec previously left unstated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 09:10:16 +03:00
|
|
|
[[package]]
|
|
|
|
|
name = "equivalent"
|
|
|
|
|
version = "1.0.2"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
|
|
|
|
|
Add the aggregation engine and a local sync cycle
The core of M1: everything needed to reconcile source calendars against an
aggregate, short of getting events in and out over the network.
- ical: surgical line-level editing. Each logical line keeps a byte range into
the original, so untouched lines are emitted verbatim and only edited ones are
rebuilt. Parsing and re-serialising would drop every property we do not model.
- ical: content hashing excludes DTSTAMP and LAST-MODIFIED. Servers rewrite them
on every store, so hashing them would report a change on every cycle forever.
- provenance: aggregate UIDs derived as blake3(aggregate, source, source_uid),
length-prefixed so field boundaries cannot collide. Deriving rather than
recording makes the state file a cache, and makes our own mirrors recognisable,
which is what stops writes echoing back around.
- mirror: the transforms. An aggregate copy must be scheduling inert, so writing
it never mails invitations for a meeting already invited from its source.
Google can suppress notification and keeps real attendees; CalDAV cannot, so
the guest list is demoted to inert data and a declined meeting is marked
TRANSP:TRANSPARENT. Writing an edit back uses the source as donor for what the
demotion removed, so editing a time cannot silently drop the guests.
- reconcile: pure decision engine. Only the source changed updates the mirror,
only the aggregate changed writes back, both changed keeps the source and logs
a conflict.
- Mass-deletion guard takes an absolute floor as well as a fraction: a share
alone is meaningless at small counts, where deleting the only event is 100%.
- sync refuses to run when an aggregate's configured target differs from the
recorded one, before reconciling. Otherwise the new empty target would read as
an aggregate whose every event was deleted, and delete propagation would then
remove them from every source.
Found by end-to-end testing: writing an item already present under a different
filename created a duplicate rather than replacing it, because filenames are
derived from the UID while pimsync picks its own. Writes now carry the path they
supersede. Covered by a regression test.
79 tests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 09:55:06 +03:00
|
|
|
[[package]]
|
|
|
|
|
name = "errno"
|
|
|
|
|
version = "0.3.14"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"libc",
|
|
|
|
|
"windows-sys",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "fastrand"
|
|
|
|
|
version = "2.5.0"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "find-msvc-tools"
|
|
|
|
|
version = "0.1.12"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "getrandom"
|
|
|
|
|
version = "0.4.3"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"cfg-if",
|
|
|
|
|
"libc",
|
|
|
|
|
"r-efi",
|
|
|
|
|
]
|
|
|
|
|
|
Scaffold calcalist: repo, toolchain, config model and doctor
Initialise the project per the approved implementation plan (M0).
- Pin the toolchain with devbox: Rust 1.97.1, pimsync 0.5.11, jujutsu 0.44.0,
and radicale 3.7.8 for later integration tests.
- Add the configuration model with full referential validation, reporting
every problem in one pass rather than short-circuiting on the first.
- Add `calcalist doctor`, verifying pimsync's presence and version series,
the state directory, and the configuration.
- Define the whole CLI surface; only `doctor` acts, the rest exit 2 rather
than pretending to work.
- Rewrite SPECS.md: Rust naming conventions in place of the JS/TS style
lines, a `devbox run check` gate instead of a pre-commit hook (jj runs no
git hooks), and the sync semantics the spec previously left unstated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 09:10:16 +03:00
|
|
|
[[package]]
|
|
|
|
|
name = "hashbrown"
|
|
|
|
|
version = "0.17.1"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "heck"
|
|
|
|
|
version = "0.5.0"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "indexmap"
|
|
|
|
|
version = "2.14.2"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"equivalent",
|
|
|
|
|
"hashbrown",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "is_terminal_polyfill"
|
|
|
|
|
version = "1.70.2"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
|
|
|
|
|
Add the aggregation engine and a local sync cycle
The core of M1: everything needed to reconcile source calendars against an
aggregate, short of getting events in and out over the network.
- ical: surgical line-level editing. Each logical line keeps a byte range into
the original, so untouched lines are emitted verbatim and only edited ones are
rebuilt. Parsing and re-serialising would drop every property we do not model.
- ical: content hashing excludes DTSTAMP and LAST-MODIFIED. Servers rewrite them
on every store, so hashing them would report a change on every cycle forever.
- provenance: aggregate UIDs derived as blake3(aggregate, source, source_uid),
length-prefixed so field boundaries cannot collide. Deriving rather than
recording makes the state file a cache, and makes our own mirrors recognisable,
which is what stops writes echoing back around.
- mirror: the transforms. An aggregate copy must be scheduling inert, so writing
it never mails invitations for a meeting already invited from its source.
Google can suppress notification and keeps real attendees; CalDAV cannot, so
the guest list is demoted to inert data and a declined meeting is marked
TRANSP:TRANSPARENT. Writing an edit back uses the source as donor for what the
demotion removed, so editing a time cannot silently drop the guests.
- reconcile: pure decision engine. Only the source changed updates the mirror,
only the aggregate changed writes back, both changed keeps the source and logs
a conflict.
- Mass-deletion guard takes an absolute floor as well as a fraction: a share
alone is meaningless at small counts, where deleting the only event is 100%.
- sync refuses to run when an aggregate's configured target differs from the
recorded one, before reconciling. Otherwise the new empty target would read as
an aggregate whose every event was deleted, and delete propagation would then
remove them from every source.
Found by end-to-end testing: writing an item already present under a different
filename created a duplicate rather than replacing it, because filenames are
derived from the UID while pimsync picks its own. Writes now carry the path they
supersede. Covered by a regression test.
79 tests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 09:55:06 +03:00
|
|
|
[[package]]
|
|
|
|
|
name = "itoa"
|
|
|
|
|
version = "1.0.18"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "libc"
|
|
|
|
|
version = "0.2.189"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "linux-raw-sys"
|
|
|
|
|
version = "0.12.1"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "memchr"
|
|
|
|
|
version = "2.8.3"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "once_cell"
|
|
|
|
|
version = "1.21.4"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
|
|
|
|
|
Scaffold calcalist: repo, toolchain, config model and doctor
Initialise the project per the approved implementation plan (M0).
- Pin the toolchain with devbox: Rust 1.97.1, pimsync 0.5.11, jujutsu 0.44.0,
and radicale 3.7.8 for later integration tests.
- Add the configuration model with full referential validation, reporting
every problem in one pass rather than short-circuiting on the first.
- Add `calcalist doctor`, verifying pimsync's presence and version series,
the state directory, and the configuration.
- Define the whole CLI surface; only `doctor` acts, the rest exit 2 rather
than pretending to work.
- Rewrite SPECS.md: Rust naming conventions in place of the JS/TS style
lines, a `devbox run check` gate instead of a pre-commit hook (jj runs no
git hooks), and the sync semantics the spec previously left unstated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 09:10:16 +03:00
|
|
|
[[package]]
|
|
|
|
|
name = "once_cell_polyfill"
|
|
|
|
|
version = "1.70.2"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "proc-macro2"
|
|
|
|
|
version = "1.0.107"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"unicode-ident",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "quote"
|
|
|
|
|
version = "1.0.47"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"proc-macro2",
|
|
|
|
|
]
|
|
|
|
|
|
Add the aggregation engine and a local sync cycle
The core of M1: everything needed to reconcile source calendars against an
aggregate, short of getting events in and out over the network.
- ical: surgical line-level editing. Each logical line keeps a byte range into
the original, so untouched lines are emitted verbatim and only edited ones are
rebuilt. Parsing and re-serialising would drop every property we do not model.
- ical: content hashing excludes DTSTAMP and LAST-MODIFIED. Servers rewrite them
on every store, so hashing them would report a change on every cycle forever.
- provenance: aggregate UIDs derived as blake3(aggregate, source, source_uid),
length-prefixed so field boundaries cannot collide. Deriving rather than
recording makes the state file a cache, and makes our own mirrors recognisable,
which is what stops writes echoing back around.
- mirror: the transforms. An aggregate copy must be scheduling inert, so writing
it never mails invitations for a meeting already invited from its source.
Google can suppress notification and keeps real attendees; CalDAV cannot, so
the guest list is demoted to inert data and a declined meeting is marked
TRANSP:TRANSPARENT. Writing an edit back uses the source as donor for what the
demotion removed, so editing a time cannot silently drop the guests.
- reconcile: pure decision engine. Only the source changed updates the mirror,
only the aggregate changed writes back, both changed keeps the source and logs
a conflict.
- Mass-deletion guard takes an absolute floor as well as a fraction: a share
alone is meaningless at small counts, where deleting the only event is 100%.
- sync refuses to run when an aggregate's configured target differs from the
recorded one, before reconciling. Otherwise the new empty target would read as
an aggregate whose every event was deleted, and delete propagation would then
remove them from every source.
Found by end-to-end testing: writing an item already present under a different
filename created a duplicate rather than replacing it, because filenames are
derived from the UID while pimsync picks its own. Writes now carry the path they
supersede. Covered by a regression test.
79 tests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 09:55:06 +03:00
|
|
|
[[package]]
|
|
|
|
|
name = "r-efi"
|
|
|
|
|
version = "6.0.0"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "rustix"
|
|
|
|
|
version = "1.1.4"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"bitflags",
|
|
|
|
|
"errno",
|
|
|
|
|
"libc",
|
|
|
|
|
"linux-raw-sys",
|
|
|
|
|
"windows-sys",
|
|
|
|
|
]
|
|
|
|
|
|
Scaffold calcalist: repo, toolchain, config model and doctor
Initialise the project per the approved implementation plan (M0).
- Pin the toolchain with devbox: Rust 1.97.1, pimsync 0.5.11, jujutsu 0.44.0,
and radicale 3.7.8 for later integration tests.
- Add the configuration model with full referential validation, reporting
every problem in one pass rather than short-circuiting on the first.
- Add `calcalist doctor`, verifying pimsync's presence and version series,
the state directory, and the configuration.
- Define the whole CLI surface; only `doctor` acts, the rest exit 2 rather
than pretending to work.
- Rewrite SPECS.md: Rust naming conventions in place of the JS/TS style
lines, a `devbox run check` gate instead of a pre-commit hook (jj runs no
git hooks), and the sync semantics the spec previously left unstated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 09:10:16 +03:00
|
|
|
[[package]]
|
|
|
|
|
name = "serde"
|
|
|
|
|
version = "1.0.229"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"serde_core",
|
|
|
|
|
"serde_derive",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "serde_core"
|
|
|
|
|
version = "1.0.229"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"serde_derive",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "serde_derive"
|
|
|
|
|
version = "1.0.229"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"proc-macro2",
|
|
|
|
|
"quote",
|
|
|
|
|
"syn",
|
|
|
|
|
]
|
|
|
|
|
|
Add the aggregation engine and a local sync cycle
The core of M1: everything needed to reconcile source calendars against an
aggregate, short of getting events in and out over the network.
- ical: surgical line-level editing. Each logical line keeps a byte range into
the original, so untouched lines are emitted verbatim and only edited ones are
rebuilt. Parsing and re-serialising would drop every property we do not model.
- ical: content hashing excludes DTSTAMP and LAST-MODIFIED. Servers rewrite them
on every store, so hashing them would report a change on every cycle forever.
- provenance: aggregate UIDs derived as blake3(aggregate, source, source_uid),
length-prefixed so field boundaries cannot collide. Deriving rather than
recording makes the state file a cache, and makes our own mirrors recognisable,
which is what stops writes echoing back around.
- mirror: the transforms. An aggregate copy must be scheduling inert, so writing
it never mails invitations for a meeting already invited from its source.
Google can suppress notification and keeps real attendees; CalDAV cannot, so
the guest list is demoted to inert data and a declined meeting is marked
TRANSP:TRANSPARENT. Writing an edit back uses the source as donor for what the
demotion removed, so editing a time cannot silently drop the guests.
- reconcile: pure decision engine. Only the source changed updates the mirror,
only the aggregate changed writes back, both changed keeps the source and logs
a conflict.
- Mass-deletion guard takes an absolute floor as well as a fraction: a share
alone is meaningless at small counts, where deleting the only event is 100%.
- sync refuses to run when an aggregate's configured target differs from the
recorded one, before reconciling. Otherwise the new empty target would read as
an aggregate whose every event was deleted, and delete propagation would then
remove them from every source.
Found by end-to-end testing: writing an item already present under a different
filename created a duplicate rather than replacing it, because filenames are
derived from the UID while pimsync picks its own. Writes now carry the path they
supersede. Covered by a regression test.
79 tests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 09:55:06 +03:00
|
|
|
[[package]]
|
|
|
|
|
name = "serde_json"
|
|
|
|
|
version = "1.0.151"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"itoa",
|
|
|
|
|
"memchr",
|
|
|
|
|
"serde",
|
|
|
|
|
"serde_core",
|
|
|
|
|
"zmij",
|
|
|
|
|
]
|
|
|
|
|
|
Scaffold calcalist: repo, toolchain, config model and doctor
Initialise the project per the approved implementation plan (M0).
- Pin the toolchain with devbox: Rust 1.97.1, pimsync 0.5.11, jujutsu 0.44.0,
and radicale 3.7.8 for later integration tests.
- Add the configuration model with full referential validation, reporting
every problem in one pass rather than short-circuiting on the first.
- Add `calcalist doctor`, verifying pimsync's presence and version series,
the state directory, and the configuration.
- Define the whole CLI surface; only `doctor` acts, the rest exit 2 rather
than pretending to work.
- Rewrite SPECS.md: Rust naming conventions in place of the JS/TS style
lines, a `devbox run check` gate instead of a pre-commit hook (jj runs no
git hooks), and the sync semantics the spec previously left unstated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 09:10:16 +03:00
|
|
|
[[package]]
|
|
|
|
|
name = "serde_spanned"
|
|
|
|
|
version = "1.1.1"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"serde_core",
|
|
|
|
|
]
|
|
|
|
|
|
Add the aggregation engine and a local sync cycle
The core of M1: everything needed to reconcile source calendars against an
aggregate, short of getting events in and out over the network.
- ical: surgical line-level editing. Each logical line keeps a byte range into
the original, so untouched lines are emitted verbatim and only edited ones are
rebuilt. Parsing and re-serialising would drop every property we do not model.
- ical: content hashing excludes DTSTAMP and LAST-MODIFIED. Servers rewrite them
on every store, so hashing them would report a change on every cycle forever.
- provenance: aggregate UIDs derived as blake3(aggregate, source, source_uid),
length-prefixed so field boundaries cannot collide. Deriving rather than
recording makes the state file a cache, and makes our own mirrors recognisable,
which is what stops writes echoing back around.
- mirror: the transforms. An aggregate copy must be scheduling inert, so writing
it never mails invitations for a meeting already invited from its source.
Google can suppress notification and keeps real attendees; CalDAV cannot, so
the guest list is demoted to inert data and a declined meeting is marked
TRANSP:TRANSPARENT. Writing an edit back uses the source as donor for what the
demotion removed, so editing a time cannot silently drop the guests.
- reconcile: pure decision engine. Only the source changed updates the mirror,
only the aggregate changed writes back, both changed keeps the source and logs
a conflict.
- Mass-deletion guard takes an absolute floor as well as a fraction: a share
alone is meaningless at small counts, where deleting the only event is 100%.
- sync refuses to run when an aggregate's configured target differs from the
recorded one, before reconciling. Otherwise the new empty target would read as
an aggregate whose every event was deleted, and delete propagation would then
remove them from every source.
Found by end-to-end testing: writing an item already present under a different
filename created a duplicate rather than replacing it, because filenames are
derived from the UID while pimsync picks its own. Writes now carry the path they
supersede. Covered by a regression test.
79 tests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 09:55:06 +03:00
|
|
|
[[package]]
|
|
|
|
|
name = "shlex"
|
|
|
|
|
version = "2.0.1"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
|
|
|
|
|
Scaffold calcalist: repo, toolchain, config model and doctor
Initialise the project per the approved implementation plan (M0).
- Pin the toolchain with devbox: Rust 1.97.1, pimsync 0.5.11, jujutsu 0.44.0,
and radicale 3.7.8 for later integration tests.
- Add the configuration model with full referential validation, reporting
every problem in one pass rather than short-circuiting on the first.
- Add `calcalist doctor`, verifying pimsync's presence and version series,
the state directory, and the configuration.
- Define the whole CLI surface; only `doctor` acts, the rest exit 2 rather
than pretending to work.
- Rewrite SPECS.md: Rust naming conventions in place of the JS/TS style
lines, a `devbox run check` gate instead of a pre-commit hook (jj runs no
git hooks), and the sync semantics the spec previously left unstated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 09:10:16 +03:00
|
|
|
[[package]]
|
|
|
|
|
name = "strsim"
|
|
|
|
|
version = "0.11.1"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "syn"
|
|
|
|
|
version = "3.0.5"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"proc-macro2",
|
|
|
|
|
"quote",
|
|
|
|
|
"unicode-ident",
|
|
|
|
|
]
|
|
|
|
|
|
Add the aggregation engine and a local sync cycle
The core of M1: everything needed to reconcile source calendars against an
aggregate, short of getting events in and out over the network.
- ical: surgical line-level editing. Each logical line keeps a byte range into
the original, so untouched lines are emitted verbatim and only edited ones are
rebuilt. Parsing and re-serialising would drop every property we do not model.
- ical: content hashing excludes DTSTAMP and LAST-MODIFIED. Servers rewrite them
on every store, so hashing them would report a change on every cycle forever.
- provenance: aggregate UIDs derived as blake3(aggregate, source, source_uid),
length-prefixed so field boundaries cannot collide. Deriving rather than
recording makes the state file a cache, and makes our own mirrors recognisable,
which is what stops writes echoing back around.
- mirror: the transforms. An aggregate copy must be scheduling inert, so writing
it never mails invitations for a meeting already invited from its source.
Google can suppress notification and keeps real attendees; CalDAV cannot, so
the guest list is demoted to inert data and a declined meeting is marked
TRANSP:TRANSPARENT. Writing an edit back uses the source as donor for what the
demotion removed, so editing a time cannot silently drop the guests.
- reconcile: pure decision engine. Only the source changed updates the mirror,
only the aggregate changed writes back, both changed keeps the source and logs
a conflict.
- Mass-deletion guard takes an absolute floor as well as a fraction: a share
alone is meaningless at small counts, where deleting the only event is 100%.
- sync refuses to run when an aggregate's configured target differs from the
recorded one, before reconciling. Otherwise the new empty target would read as
an aggregate whose every event was deleted, and delete propagation would then
remove them from every source.
Found by end-to-end testing: writing an item already present under a different
filename created a duplicate rather than replacing it, because filenames are
derived from the UID while pimsync picks its own. Writes now carry the path they
supersede. Covered by a regression test.
79 tests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 09:55:06 +03:00
|
|
|
[[package]]
|
|
|
|
|
name = "tempfile"
|
|
|
|
|
version = "3.27.0"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"fastrand",
|
|
|
|
|
"getrandom",
|
|
|
|
|
"once_cell",
|
|
|
|
|
"rustix",
|
|
|
|
|
"windows-sys",
|
|
|
|
|
]
|
|
|
|
|
|
Scaffold calcalist: repo, toolchain, config model and doctor
Initialise the project per the approved implementation plan (M0).
- Pin the toolchain with devbox: Rust 1.97.1, pimsync 0.5.11, jujutsu 0.44.0,
and radicale 3.7.8 for later integration tests.
- Add the configuration model with full referential validation, reporting
every problem in one pass rather than short-circuiting on the first.
- Add `calcalist doctor`, verifying pimsync's presence and version series,
the state directory, and the configuration.
- Define the whole CLI surface; only `doctor` acts, the rest exit 2 rather
than pretending to work.
- Rewrite SPECS.md: Rust naming conventions in place of the JS/TS style
lines, a `devbox run check` gate instead of a pre-commit hook (jj runs no
git hooks), and the sync semantics the spec previously left unstated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 09:10:16 +03:00
|
|
|
[[package]]
|
|
|
|
|
name = "thiserror"
|
|
|
|
|
version = "2.0.20"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"thiserror-impl",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "thiserror-impl"
|
|
|
|
|
version = "2.0.20"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"proc-macro2",
|
|
|
|
|
"quote",
|
|
|
|
|
"syn",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "toml"
|
|
|
|
|
version = "1.1.5+spec-1.1.0"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "12c0ba9680044b4ce98d391a62094047eada0d64860b80166c39f4a6b5640785"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"indexmap",
|
|
|
|
|
"serde_core",
|
|
|
|
|
"serde_spanned",
|
|
|
|
|
"toml_datetime",
|
|
|
|
|
"toml_parser",
|
|
|
|
|
"toml_writer",
|
|
|
|
|
"winnow",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "toml_datetime"
|
|
|
|
|
version = "1.1.1+spec-1.1.0"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"serde_core",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "toml_parser"
|
|
|
|
|
version = "1.1.3+spec-1.1.0"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"winnow",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "toml_writer"
|
|
|
|
|
version = "1.1.2+spec-1.1.0"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "unicode-ident"
|
|
|
|
|
version = "1.0.24"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "utf8parse"
|
|
|
|
|
version = "0.2.2"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "windows-link"
|
|
|
|
|
version = "0.2.1"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "windows-sys"
|
|
|
|
|
version = "0.61.2"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
|
|
|
|
dependencies = [
|
|
|
|
|
"windows-link",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "winnow"
|
|
|
|
|
version = "1.0.4"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
|
Add the aggregation engine and a local sync cycle
The core of M1: everything needed to reconcile source calendars against an
aggregate, short of getting events in and out over the network.
- ical: surgical line-level editing. Each logical line keeps a byte range into
the original, so untouched lines are emitted verbatim and only edited ones are
rebuilt. Parsing and re-serialising would drop every property we do not model.
- ical: content hashing excludes DTSTAMP and LAST-MODIFIED. Servers rewrite them
on every store, so hashing them would report a change on every cycle forever.
- provenance: aggregate UIDs derived as blake3(aggregate, source, source_uid),
length-prefixed so field boundaries cannot collide. Deriving rather than
recording makes the state file a cache, and makes our own mirrors recognisable,
which is what stops writes echoing back around.
- mirror: the transforms. An aggregate copy must be scheduling inert, so writing
it never mails invitations for a meeting already invited from its source.
Google can suppress notification and keeps real attendees; CalDAV cannot, so
the guest list is demoted to inert data and a declined meeting is marked
TRANSP:TRANSPARENT. Writing an edit back uses the source as donor for what the
demotion removed, so editing a time cannot silently drop the guests.
- reconcile: pure decision engine. Only the source changed updates the mirror,
only the aggregate changed writes back, both changed keeps the source and logs
a conflict.
- Mass-deletion guard takes an absolute floor as well as a fraction: a share
alone is meaningless at small counts, where deleting the only event is 100%.
- sync refuses to run when an aggregate's configured target differs from the
recorded one, before reconciling. Otherwise the new empty target would read as
an aggregate whose every event was deleted, and delete propagation would then
remove them from every source.
Found by end-to-end testing: writing an item already present under a different
filename created a duplicate rather than replacing it, because filenames are
derived from the UID while pimsync picks its own. Writes now carry the path they
supersede. Covered by a regression test.
79 tests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 09:55:06 +03:00
|
|
|
|
|
|
|
|
[[package]]
|
|
|
|
|
name = "zmij"
|
|
|
|
|
version = "1.0.23"
|
|
|
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|