* Use chrono for UTC/calendar conversion instead of hand-rolled copies Replaces the hand-rolled civil_from_days and Day::to_days in crates/ai-core/src/history/report.rs with chrono, added as a workspace dependency (std-only, so ai-core still compiles for wasm). The day/month grouping, ISO and CSV timestamps and the Day/Month display are byte-identical and pinned by the existing tests. AGENTS.md now says time goes through chrono. Follow-up to storytold/cadcraft#1785 * history: preserve out-of-range conversion failure in civil_from_days civil_from_days used `i32::try_from(..).unwrap_or(0)` before calling NaiveDate::from_num_days_from_ce_opt, so a CE day count past i32 (e.g. from Day::from_days(i64::MAX)) passed a valid day 0 to chrono and read as 0000-12-31 instead of the 0000-01-01 fallback the None arm gives for every other out-of-range date. Convert with `.ok().and_then(..)` so both the integer-conversion and NaiveDate-construction failures share the one fallback. Regression test covers i64::MAX and i64::MIN. Doc comments on the chrono-touched to_days/from_days/civil_from_days now state the out-of-range behaviour (0 / 0000-01-01) for the docstring gate. * Convert every i32 year, not only chrono's range Day holds any i32 year, but NaiveDate stops near ±262143, so years like 262144 read as 0 / 0000-01-01. The Gregorian calendar repeats every 400 years: chrono now converts the day within its cycle and whole cycles are added back, in both directions. Values match the pre-chrono formula (tested for 262144, -262145, i32::MAX and i32::MIN). Nonexistent days still read as 0, and day counts whose year doesn't fit i32 still read as 0000-01-01. * Name the 400-year range extension as the one exception to the time rule The rule said never to hand-roll calendar math, but the range fix (shifting by whole 400-year Gregorian cycles so dates past chrono's year range keep their old output) is cycle arithmetic, so the rule and the code disagreed. The rule now names that exception: the library still computes month, day and time, and only whole cycles are carried in the year. * Say the library computes month and day; time-of-day is plain arithmetic Narrows the range-extension wording: the date library computes the calendar fields, and splitting seconds into hours, minutes and seconds is ordinary arithmetic, which is what the code does.
Craft AI model
craft-ai-core is the data model and contract for AI in the Craft Apps: connections, models,
capabilities, roles (tools), requests, jobs, consent, request history, the shared store's file
format, and the declarative route spec. It has no I/O, no async runtime and compiles for
wasm32-unknown-unknown. It depends only on serde and serde_json.
One connection is set up once (in the launcher or any app) and every app that has the tool uses it. Each app keeps its own interface for each tool.
Boundaries
- Never crashes: the crate denies
unwrap,expect,panicand friends, and the workspace forbidsunsafe. - No secrets: a connection holds a reference to a secret (
SecretRef), never the value.SecretStringdoes not print. - No app code: an app is only an
AppIdstring. The contract is described in docs/ai-shared-layer.md and the declarative route spec in docs/ai-declarative-adapter.md.
Later pull requests add the desktop half (secret stores and the shared store file), the client, the provider adapters and egui widgets as separate crates.
Platforms
All platforms, including wasm32-unknown-unknown (checked in CI). No I/O.
Usage
Depend on it for the types. Worked examples: tests/custom_tools.rs, tests/mock_conformance.rs, tests/spec_calls.rs; the contract is in docs/ai-shared-layer.md.