Scaffold cargo workspace + heph-core foundation
Kick off Phase 1 (v1 prototype) per tech-spec §11.1. Sets up the Cargo
workspace and the first TDD slice of heph-core:
- Migration runner + §4.5 SQLite schema (nodes, tasks, links, aliases,
users, oplog, sync_state, conflicts), versioned via PRAGMA user_version.
- Clock-injected `Clock` trait (no ambient wall-clock reads; §2).
- `Store` trait + `LocalStore` SQLite backend with node create/get,
bootstrapping the single local user (oidc_sub NULL, §13).
- Node model (kinds: doc/task/project/tag/journal).
Repo housekeeping: fill AGENTS.md Project Structure (last template TODO),
ignore /target, add self-bootstrapping .forgejo/scripts/build that runs
cargo fmt/clippy/test in CI (§9), changelog fragment.
Tests green: 4 unit tests (migration version, local-user idempotency,
create/get round-trip, missing-node None).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 18:52:15 -07:00
|
|
|
//! SQLite-backed [`Store`] implementation.
|
|
|
|
|
//!
|
|
|
|
|
//! `LocalStore` opens a SQLite file directly. The exclusive-lock handoff of
|
|
|
|
|
//! tech-spec §3.1 is layered on by `hephd` when it owns the file; the store
|
|
|
|
|
//! itself stays a thin, synchronous SQLite wrapper so it is trivially testable
|
|
|
|
|
//! against an in-memory database.
|
heph-core: tasks, links, canonical-context doc
Slice 3 (tech-spec §4.2–§4.3, §6). Refactor the SQLite layer into focused
submodules (nodes/tasks/links) behind a thin delegating Store impl so a
transaction can span several.
- Model: Attention (white/orange/red/blue), TaskState
(outstanding/done/dropped), LinkType, Link, Task, NewTask.
- create_task: in one transaction mints the task node + tasks row, the
canonical context doc, the canonical-context link, and an optional
in-project link. get_task / set_task_state / set_task_attention.
- Links CRUD: add_link, outgoing_links, backlinks (non-tombstoned).
- update_node: a body change re-runs extraction and reconciles this
node's wiki links — diff-based and idempotent, resolved via alias then
exact title (owner-scoped); unresolved targets link on a later edit
once the target exists; dropped targets are tombstoned.
20 tests green (12 unit + 8 integration).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 19:02:35 -07:00
|
|
|
//!
|
|
|
|
|
//! The query logic lives in focused submodules ([`nodes`], [`tasks`], [`links`])
|
|
|
|
|
//! as free functions over a `&Connection`; the [`Store`] impl here is a thin
|
|
|
|
|
//! delegating layer so a transaction can span several of them.
|
Scaffold cargo workspace + heph-core foundation
Kick off Phase 1 (v1 prototype) per tech-spec §11.1. Sets up the Cargo
workspace and the first TDD slice of heph-core:
- Migration runner + §4.5 SQLite schema (nodes, tasks, links, aliases,
users, oplog, sync_state, conflicts), versioned via PRAGMA user_version.
- Clock-injected `Clock` trait (no ambient wall-clock reads; §2).
- `Store` trait + `LocalStore` SQLite backend with node create/get,
bootstrapping the single local user (oidc_sub NULL, §13).
- Node model (kinds: doc/task/project/tag/journal).
Repo housekeeping: fill AGENTS.md Project Structure (last template TODO),
ignore /target, add self-bootstrapping .forgejo/scripts/build that runs
cargo fmt/clippy/test in CI (§9), changelog fragment.
Tests green: 4 unit tests (migration version, local-user idempotency,
create/get round-trip, missing-node None).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 18:52:15 -07:00
|
|
|
|
heph-core: tasks, links, canonical-context doc
Slice 3 (tech-spec §4.2–§4.3, §6). Refactor the SQLite layer into focused
submodules (nodes/tasks/links) behind a thin delegating Store impl so a
transaction can span several.
- Model: Attention (white/orange/red/blue), TaskState
(outstanding/done/dropped), LinkType, Link, Task, NewTask.
- create_task: in one transaction mints the task node + tasks row, the
canonical context doc, the canonical-context link, and an optional
in-project link. get_task / set_task_state / set_task_attention.
- Links CRUD: add_link, outgoing_links, backlinks (non-tombstoned).
- update_node: a body change re-runs extraction and reconciles this
node's wiki links — diff-based and idempotent, resolved via alias then
exact title (owner-scoped); unresolved targets link on a later edit
once the target exists; dropped targets are tombstoned.
20 tests green (12 unit + 8 integration).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 19:02:35 -07:00
|
|
|
mod links;
|
2026-05-31 19:14:22 -07:00
|
|
|
mod log;
|
Scaffold cargo workspace + heph-core foundation
Kick off Phase 1 (v1 prototype) per tech-spec §11.1. Sets up the Cargo
workspace and the first TDD slice of heph-core:
- Migration runner + §4.5 SQLite schema (nodes, tasks, links, aliases,
users, oplog, sync_state, conflicts), versioned via PRAGMA user_version.
- Clock-injected `Clock` trait (no ambient wall-clock reads; §2).
- `Store` trait + `LocalStore` SQLite backend with node create/get,
bootstrapping the single local user (oidc_sub NULL, §13).
- Node model (kinds: doc/task/project/tag/journal).
Repo housekeeping: fill AGENTS.md Project Structure (last template TODO),
ignore /target, add self-bootstrapping .forgejo/scripts/build that runs
cargo fmt/clippy/test in CI (§9), changelog fragment.
Tests green: 4 unit tests (migration version, local-user idempotency,
create/get round-trip, missing-node None).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 18:52:15 -07:00
|
|
|
mod migrations;
|
heph-core: tasks, links, canonical-context doc
Slice 3 (tech-spec §4.2–§4.3, §6). Refactor the SQLite layer into focused
submodules (nodes/tasks/links) behind a thin delegating Store impl so a
transaction can span several.
- Model: Attention (white/orange/red/blue), TaskState
(outstanding/done/dropped), LinkType, Link, Task, NewTask.
- create_task: in one transaction mints the task node + tasks row, the
canonical context doc, the canonical-context link, and an optional
in-project link. get_task / set_task_state / set_task_attention.
- Links CRUD: add_link, outgoing_links, backlinks (non-tombstoned).
- update_node: a body change re-runs extraction and reconciles this
node's wiki links — diff-based and idempotent, resolved via alias then
exact title (owner-scoped); unresolved targets link on a later edit
once the target exists; dropped targets are tombstoned.
20 tests green (12 unit + 8 integration).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 19:02:35 -07:00
|
|
|
mod nodes;
|
|
|
|
|
mod tasks;
|
Scaffold cargo workspace + heph-core foundation
Kick off Phase 1 (v1 prototype) per tech-spec §11.1. Sets up the Cargo
workspace and the first TDD slice of heph-core:
- Migration runner + §4.5 SQLite schema (nodes, tasks, links, aliases,
users, oplog, sync_state, conflicts), versioned via PRAGMA user_version.
- Clock-injected `Clock` trait (no ambient wall-clock reads; §2).
- `Store` trait + `LocalStore` SQLite backend with node create/get,
bootstrapping the single local user (oidc_sub NULL, §13).
- Node model (kinds: doc/task/project/tag/journal).
Repo housekeeping: fill AGENTS.md Project Structure (last template TODO),
ignore /target, add self-bootstrapping .forgejo/scripts/build that runs
cargo fmt/clippy/test in CI (§9), changelog fragment.
Tests green: 4 unit tests (migration version, local-user idempotency,
create/get round-trip, missing-node None).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 18:52:15 -07:00
|
|
|
|
|
|
|
|
pub use migrations::latest_version;
|
|
|
|
|
|
|
|
|
|
use std::path::Path;
|
|
|
|
|
|
heph-core: tasks, links, canonical-context doc
Slice 3 (tech-spec §4.2–§4.3, §6). Refactor the SQLite layer into focused
submodules (nodes/tasks/links) behind a thin delegating Store impl so a
transaction can span several.
- Model: Attention (white/orange/red/blue), TaskState
(outstanding/done/dropped), LinkType, Link, Task, NewTask.
- create_task: in one transaction mints the task node + tasks row, the
canonical context doc, the canonical-context link, and an optional
in-project link. get_task / set_task_state / set_task_attention.
- Links CRUD: add_link, outgoing_links, backlinks (non-tombstoned).
- update_node: a body change re-runs extraction and reconciles this
node's wiki links — diff-based and idempotent, resolved via alias then
exact title (owner-scoped); unresolved targets link on a later edit
once the target exists; dropped targets are tombstoned.
20 tests green (12 unit + 8 integration).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 19:02:35 -07:00
|
|
|
use rusqlite::{Connection, OptionalExtension};
|
Scaffold cargo workspace + heph-core foundation
Kick off Phase 1 (v1 prototype) per tech-spec §11.1. Sets up the Cargo
workspace and the first TDD slice of heph-core:
- Migration runner + §4.5 SQLite schema (nodes, tasks, links, aliases,
users, oplog, sync_state, conflicts), versioned via PRAGMA user_version.
- Clock-injected `Clock` trait (no ambient wall-clock reads; §2).
- `Store` trait + `LocalStore` SQLite backend with node create/get,
bootstrapping the single local user (oidc_sub NULL, §13).
- Node model (kinds: doc/task/project/tag/journal).
Repo housekeeping: fill AGENTS.md Project Structure (last template TODO),
ignore /target, add self-bootstrapping .forgejo/scripts/build that runs
cargo fmt/clippy/test in CI (§9), changelog fragment.
Tests green: 4 unit tests (migration version, local-user idempotency,
create/get round-trip, missing-node None).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 18:52:15 -07:00
|
|
|
use ulid::Ulid;
|
|
|
|
|
|
|
|
|
|
use crate::clock::Clock;
|
|
|
|
|
use crate::error::Result;
|
heph-core: tasks, links, canonical-context doc
Slice 3 (tech-spec §4.2–§4.3, §6). Refactor the SQLite layer into focused
submodules (nodes/tasks/links) behind a thin delegating Store impl so a
transaction can span several.
- Model: Attention (white/orange/red/blue), TaskState
(outstanding/done/dropped), LinkType, Link, Task, NewTask.
- create_task: in one transaction mints the task node + tasks row, the
canonical context doc, the canonical-context link, and an optional
in-project link. get_task / set_task_state / set_task_attention.
- Links CRUD: add_link, outgoing_links, backlinks (non-tombstoned).
- update_node: a body change re-runs extraction and reconciles this
node's wiki links — diff-based and idempotent, resolved via alias then
exact title (owner-scoped); unresolved targets link on a later edit
once the target exists; dropped targets are tombstoned.
20 tests green (12 unit + 8 integration).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 19:02:35 -07:00
|
|
|
use crate::model::{Attention, Link, LinkType, NewNode, NewTask, Node, Task, TaskState};
|
2026-05-31 19:07:16 -07:00
|
|
|
use crate::ranking::RankedTask;
|
Scaffold cargo workspace + heph-core foundation
Kick off Phase 1 (v1 prototype) per tech-spec §11.1. Sets up the Cargo
workspace and the first TDD slice of heph-core:
- Migration runner + §4.5 SQLite schema (nodes, tasks, links, aliases,
users, oplog, sync_state, conflicts), versioned via PRAGMA user_version.
- Clock-injected `Clock` trait (no ambient wall-clock reads; §2).
- `Store` trait + `LocalStore` SQLite backend with node create/get,
bootstrapping the single local user (oidc_sub NULL, §13).
- Node model (kinds: doc/task/project/tag/journal).
Repo housekeeping: fill AGENTS.md Project Structure (last template TODO),
ignore /target, add self-bootstrapping .forgejo/scripts/build that runs
cargo fmt/clippy/test in CI (§9), changelog fragment.
Tests green: 4 unit tests (migration version, local-user idempotency,
create/get round-trip, missing-node None).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 18:52:15 -07:00
|
|
|
use crate::store::Store;
|
|
|
|
|
|
|
|
|
|
/// A SQLite file (or in-memory database) opened directly as a backend.
|
|
|
|
|
pub struct LocalStore {
|
|
|
|
|
conn: Connection,
|
|
|
|
|
owner_id: String,
|
|
|
|
|
clock: Box<dyn Clock>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl LocalStore {
|
|
|
|
|
/// Open (creating if needed) a SQLite database at `path`.
|
|
|
|
|
pub fn open(path: impl AsRef<Path>, clock: Box<dyn Clock>) -> Result<Self> {
|
|
|
|
|
let conn = Connection::open(path)?;
|
|
|
|
|
Self::init(conn, clock)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Open a throwaway in-memory database — for tests.
|
|
|
|
|
pub fn open_in_memory(clock: Box<dyn Clock>) -> Result<Self> {
|
|
|
|
|
let conn = Connection::open_in_memory()?;
|
|
|
|
|
Self::init(conn, clock)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn init(conn: Connection, clock: Box<dyn Clock>) -> Result<Self> {
|
|
|
|
|
conn.execute_batch("PRAGMA foreign_keys = ON;")?;
|
|
|
|
|
migrations::migrate(&conn)?;
|
|
|
|
|
let owner_id = ensure_local_user(&conn, clock.as_ref())?;
|
|
|
|
|
Ok(LocalStore {
|
|
|
|
|
conn,
|
|
|
|
|
owner_id,
|
|
|
|
|
clock,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The id of the user whose data this store reads/writes.
|
|
|
|
|
///
|
|
|
|
|
/// For a local-only instance this is the single generated local user
|
|
|
|
|
/// (`oidc_sub = NULL`, tech-spec §13).
|
|
|
|
|
pub fn owner_id(&self) -> &str {
|
|
|
|
|
&self.owner_id
|
|
|
|
|
}
|
heph-core: tasks, links, canonical-context doc
Slice 3 (tech-spec §4.2–§4.3, §6). Refactor the SQLite layer into focused
submodules (nodes/tasks/links) behind a thin delegating Store impl so a
transaction can span several.
- Model: Attention (white/orange/red/blue), TaskState
(outstanding/done/dropped), LinkType, Link, Task, NewTask.
- create_task: in one transaction mints the task node + tasks row, the
canonical context doc, the canonical-context link, and an optional
in-project link. get_task / set_task_state / set_task_attention.
- Links CRUD: add_link, outgoing_links, backlinks (non-tombstoned).
- update_node: a body change re-runs extraction and reconciles this
node's wiki links — diff-based and idempotent, resolved via alias then
exact title (owner-scoped); unresolved targets link on a later edit
once the target exists; dropped targets are tombstoned.
20 tests green (12 unit + 8 integration).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 19:02:35 -07:00
|
|
|
}
|
Scaffold cargo workspace + heph-core foundation
Kick off Phase 1 (v1 prototype) per tech-spec §11.1. Sets up the Cargo
workspace and the first TDD slice of heph-core:
- Migration runner + §4.5 SQLite schema (nodes, tasks, links, aliases,
users, oplog, sync_state, conflicts), versioned via PRAGMA user_version.
- Clock-injected `Clock` trait (no ambient wall-clock reads; §2).
- `Store` trait + `LocalStore` SQLite backend with node create/get,
bootstrapping the single local user (oidc_sub NULL, §13).
- Node model (kinds: doc/task/project/tag/journal).
Repo housekeeping: fill AGENTS.md Project Structure (last template TODO),
ignore /target, add self-bootstrapping .forgejo/scripts/build that runs
cargo fmt/clippy/test in CI (§9), changelog fragment.
Tests green: 4 unit tests (migration version, local-user idempotency,
create/get round-trip, missing-node None).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 18:52:15 -07:00
|
|
|
|
heph-core: tasks, links, canonical-context doc
Slice 3 (tech-spec §4.2–§4.3, §6). Refactor the SQLite layer into focused
submodules (nodes/tasks/links) behind a thin delegating Store impl so a
transaction can span several.
- Model: Attention (white/orange/red/blue), TaskState
(outstanding/done/dropped), LinkType, Link, Task, NewTask.
- create_task: in one transaction mints the task node + tasks row, the
canonical context doc, the canonical-context link, and an optional
in-project link. get_task / set_task_state / set_task_attention.
- Links CRUD: add_link, outgoing_links, backlinks (non-tombstoned).
- update_node: a body change re-runs extraction and reconciles this
node's wiki links — diff-based and idempotent, resolved via alias then
exact title (owner-scoped); unresolved targets link on a later edit
once the target exists; dropped targets are tombstoned.
20 tests green (12 unit + 8 integration).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 19:02:35 -07:00
|
|
|
/// A fresh ULID, as a string id.
|
|
|
|
|
pub(crate) fn new_id() -> String {
|
|
|
|
|
Ulid::new().to_string()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Placeholder HLC string until the real hybrid logical clock lands (§12).
|
|
|
|
|
/// Zero-padded epoch ms keeps it lexically sortable in the meantime.
|
|
|
|
|
pub(crate) fn hlc_for(now_ms: i64) -> String {
|
|
|
|
|
format!("{now_ms:016}")
|
Scaffold cargo workspace + heph-core foundation
Kick off Phase 1 (v1 prototype) per tech-spec §11.1. Sets up the Cargo
workspace and the first TDD slice of heph-core:
- Migration runner + §4.5 SQLite schema (nodes, tasks, links, aliases,
users, oplog, sync_state, conflicts), versioned via PRAGMA user_version.
- Clock-injected `Clock` trait (no ambient wall-clock reads; §2).
- `Store` trait + `LocalStore` SQLite backend with node create/get,
bootstrapping the single local user (oidc_sub NULL, §13).
- Node model (kinds: doc/task/project/tag/journal).
Repo housekeeping: fill AGENTS.md Project Structure (last template TODO),
ignore /target, add self-bootstrapping .forgejo/scripts/build that runs
cargo fmt/clippy/test in CI (§9), changelog fragment.
Tests green: 4 unit tests (migration version, local-user idempotency,
create/get round-trip, missing-node None).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 18:52:15 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Ensure a single local user exists, returning its id.
|
|
|
|
|
fn ensure_local_user(conn: &Connection, clock: &dyn Clock) -> Result<String> {
|
|
|
|
|
if let Some(id) = conn
|
|
|
|
|
.query_row(
|
|
|
|
|
"SELECT id FROM users ORDER BY created_at LIMIT 1",
|
|
|
|
|
[],
|
|
|
|
|
|r| r.get::<_, String>(0),
|
|
|
|
|
)
|
|
|
|
|
.optional()?
|
|
|
|
|
{
|
|
|
|
|
return Ok(id);
|
|
|
|
|
}
|
heph-core: tasks, links, canonical-context doc
Slice 3 (tech-spec §4.2–§4.3, §6). Refactor the SQLite layer into focused
submodules (nodes/tasks/links) behind a thin delegating Store impl so a
transaction can span several.
- Model: Attention (white/orange/red/blue), TaskState
(outstanding/done/dropped), LinkType, Link, Task, NewTask.
- create_task: in one transaction mints the task node + tasks row, the
canonical context doc, the canonical-context link, and an optional
in-project link. get_task / set_task_state / set_task_attention.
- Links CRUD: add_link, outgoing_links, backlinks (non-tombstoned).
- update_node: a body change re-runs extraction and reconciles this
node's wiki links — diff-based and idempotent, resolved via alias then
exact title (owner-scoped); unresolved targets link on a later edit
once the target exists; dropped targets are tombstoned.
20 tests green (12 unit + 8 integration).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 19:02:35 -07:00
|
|
|
let id = new_id();
|
Scaffold cargo workspace + heph-core foundation
Kick off Phase 1 (v1 prototype) per tech-spec §11.1. Sets up the Cargo
workspace and the first TDD slice of heph-core:
- Migration runner + §4.5 SQLite schema (nodes, tasks, links, aliases,
users, oplog, sync_state, conflicts), versioned via PRAGMA user_version.
- Clock-injected `Clock` trait (no ambient wall-clock reads; §2).
- `Store` trait + `LocalStore` SQLite backend with node create/get,
bootstrapping the single local user (oidc_sub NULL, §13).
- Node model (kinds: doc/task/project/tag/journal).
Repo housekeeping: fill AGENTS.md Project Structure (last template TODO),
ignore /target, add self-bootstrapping .forgejo/scripts/build that runs
cargo fmt/clippy/test in CI (§9), changelog fragment.
Tests green: 4 unit tests (migration version, local-user idempotency,
create/get round-trip, missing-node None).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 18:52:15 -07:00
|
|
|
conn.execute(
|
|
|
|
|
"INSERT INTO users (id, oidc_sub, name, created_at) VALUES (?1, NULL, 'local', ?2)",
|
|
|
|
|
(&id, clock.now_ms()),
|
|
|
|
|
)?;
|
|
|
|
|
Ok(id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Store for LocalStore {
|
|
|
|
|
fn create_node(&mut self, input: NewNode) -> Result<Node> {
|
|
|
|
|
let now = self.clock.now_ms();
|
heph-core: tasks, links, canonical-context doc
Slice 3 (tech-spec §4.2–§4.3, §6). Refactor the SQLite layer into focused
submodules (nodes/tasks/links) behind a thin delegating Store impl so a
transaction can span several.
- Model: Attention (white/orange/red/blue), TaskState
(outstanding/done/dropped), LinkType, Link, Task, NewTask.
- create_task: in one transaction mints the task node + tasks row, the
canonical context doc, the canonical-context link, and an optional
in-project link. get_task / set_task_state / set_task_attention.
- Links CRUD: add_link, outgoing_links, backlinks (non-tombstoned).
- update_node: a body change re-runs extraction and reconciles this
node's wiki links — diff-based and idempotent, resolved via alias then
exact title (owner-scoped); unresolved targets link on a later edit
once the target exists; dropped targets are tombstoned.
20 tests green (12 unit + 8 integration).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 19:02:35 -07:00
|
|
|
nodes::create(&self.conn, &self.owner_id, now, input)
|
Scaffold cargo workspace + heph-core foundation
Kick off Phase 1 (v1 prototype) per tech-spec §11.1. Sets up the Cargo
workspace and the first TDD slice of heph-core:
- Migration runner + §4.5 SQLite schema (nodes, tasks, links, aliases,
users, oplog, sync_state, conflicts), versioned via PRAGMA user_version.
- Clock-injected `Clock` trait (no ambient wall-clock reads; §2).
- `Store` trait + `LocalStore` SQLite backend with node create/get,
bootstrapping the single local user (oidc_sub NULL, §13).
- Node model (kinds: doc/task/project/tag/journal).
Repo housekeeping: fill AGENTS.md Project Structure (last template TODO),
ignore /target, add self-bootstrapping .forgejo/scripts/build that runs
cargo fmt/clippy/test in CI (§9), changelog fragment.
Tests green: 4 unit tests (migration version, local-user idempotency,
create/get round-trip, missing-node None).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 18:52:15 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn get_node(&self, id: &str) -> Result<Option<Node>> {
|
heph-core: tasks, links, canonical-context doc
Slice 3 (tech-spec §4.2–§4.3, §6). Refactor the SQLite layer into focused
submodules (nodes/tasks/links) behind a thin delegating Store impl so a
transaction can span several.
- Model: Attention (white/orange/red/blue), TaskState
(outstanding/done/dropped), LinkType, Link, Task, NewTask.
- create_task: in one transaction mints the task node + tasks row, the
canonical context doc, the canonical-context link, and an optional
in-project link. get_task / set_task_state / set_task_attention.
- Links CRUD: add_link, outgoing_links, backlinks (non-tombstoned).
- update_node: a body change re-runs extraction and reconciles this
node's wiki links — diff-based and idempotent, resolved via alias then
exact title (owner-scoped); unresolved targets link on a later edit
once the target exists; dropped targets are tombstoned.
20 tests green (12 unit + 8 integration).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 19:02:35 -07:00
|
|
|
nodes::get(&self.conn, id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn update_node(
|
|
|
|
|
&mut self,
|
|
|
|
|
id: &str,
|
|
|
|
|
title: Option<String>,
|
|
|
|
|
body: Option<String>,
|
|
|
|
|
) -> Result<Node> {
|
|
|
|
|
let now = self.clock.now_ms();
|
|
|
|
|
nodes::update(&mut self.conn, &self.owner_id, now, id, title, body)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn create_task(&mut self, input: NewTask) -> Result<Task> {
|
|
|
|
|
let now = self.clock.now_ms();
|
|
|
|
|
tasks::create(&mut self.conn, &self.owner_id, now, input)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn get_task(&self, node_id: &str) -> Result<Option<Task>> {
|
|
|
|
|
tasks::get(&self.conn, node_id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn set_task_state(&mut self, node_id: &str, state: TaskState) -> Result<Task> {
|
|
|
|
|
let now = self.clock.now_ms();
|
2026-05-31 19:14:22 -07:00
|
|
|
tasks::set_state(&mut self.conn, &self.owner_id, now, node_id, state)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn skip_recurrence(&mut self, node_id: &str) -> Result<Task> {
|
|
|
|
|
let now = self.clock.now_ms();
|
|
|
|
|
tasks::skip(&self.conn, now, node_id)
|
heph-core: tasks, links, canonical-context doc
Slice 3 (tech-spec §4.2–§4.3, §6). Refactor the SQLite layer into focused
submodules (nodes/tasks/links) behind a thin delegating Store impl so a
transaction can span several.
- Model: Attention (white/orange/red/blue), TaskState
(outstanding/done/dropped), LinkType, Link, Task, NewTask.
- create_task: in one transaction mints the task node + tasks row, the
canonical context doc, the canonical-context link, and an optional
in-project link. get_task / set_task_state / set_task_attention.
- Links CRUD: add_link, outgoing_links, backlinks (non-tombstoned).
- update_node: a body change re-runs extraction and reconciles this
node's wiki links — diff-based and idempotent, resolved via alias then
exact title (owner-scoped); unresolved targets link on a later edit
once the target exists; dropped targets are tombstoned.
20 tests green (12 unit + 8 integration).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 19:02:35 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn set_task_attention(&mut self, node_id: &str, attention: Attention) -> Result<Task> {
|
|
|
|
|
let now = self.clock.now_ms();
|
|
|
|
|
tasks::set_attention(&self.conn, now, node_id, attention)
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-31 19:07:16 -07:00
|
|
|
fn next(&self, scope: Option<&str>, limit: usize) -> Result<Vec<RankedTask>> {
|
|
|
|
|
let now = self.clock.now_ms();
|
|
|
|
|
tasks::next(&self.conn, &self.owner_id, now, scope, limit)
|
|
|
|
|
}
|
|
|
|
|
|
heph-core: tasks, links, canonical-context doc
Slice 3 (tech-spec §4.2–§4.3, §6). Refactor the SQLite layer into focused
submodules (nodes/tasks/links) behind a thin delegating Store impl so a
transaction can span several.
- Model: Attention (white/orange/red/blue), TaskState
(outstanding/done/dropped), LinkType, Link, Task, NewTask.
- create_task: in one transaction mints the task node + tasks row, the
canonical context doc, the canonical-context link, and an optional
in-project link. get_task / set_task_state / set_task_attention.
- Links CRUD: add_link, outgoing_links, backlinks (non-tombstoned).
- update_node: a body change re-runs extraction and reconciles this
node's wiki links — diff-based and idempotent, resolved via alias then
exact title (owner-scoped); unresolved targets link on a later edit
once the target exists; dropped targets are tombstoned.
20 tests green (12 unit + 8 integration).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 19:02:35 -07:00
|
|
|
fn add_link(&mut self, src_id: &str, dst_id: &str, link_type: LinkType) -> Result<Link> {
|
|
|
|
|
let now = self.clock.now_ms();
|
|
|
|
|
links::add(&self.conn, now, src_id, dst_id, link_type)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn outgoing_links(&self, id: &str) -> Result<Vec<Link>> {
|
|
|
|
|
links::outgoing(&self.conn, id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn backlinks(&self, id: &str) -> Result<Vec<Link>> {
|
|
|
|
|
links::backlinks(&self.conn, id)
|
Scaffold cargo workspace + heph-core foundation
Kick off Phase 1 (v1 prototype) per tech-spec §11.1. Sets up the Cargo
workspace and the first TDD slice of heph-core:
- Migration runner + §4.5 SQLite schema (nodes, tasks, links, aliases,
users, oplog, sync_state, conflicts), versioned via PRAGMA user_version.
- Clock-injected `Clock` trait (no ambient wall-clock reads; §2).
- `Store` trait + `LocalStore` SQLite backend with node create/get,
bootstrapping the single local user (oidc_sub NULL, §13).
- Node model (kinds: doc/task/project/tag/journal).
Repo housekeeping: fill AGENTS.md Project Structure (last template TODO),
ignore /target, add self-bootstrapping .forgejo/scripts/build that runs
cargo fmt/clippy/test in CI (§9), changelog fragment.
Tests green: 4 unit tests (migration version, local-user idempotency,
create/get round-trip, missing-node None).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 18:52:15 -07:00
|
|
|
}
|
2026-05-31 19:14:22 -07:00
|
|
|
|
|
|
|
|
fn log_append(&mut self, task_id: &str, text: &str) -> Result<()> {
|
|
|
|
|
let now = self.clock.now_ms();
|
|
|
|
|
let tx = self.conn.transaction()?;
|
|
|
|
|
log::append(&tx, &self.owner_id, now, task_id, text)?;
|
|
|
|
|
tx.commit()?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn log_tail(&self, task_id: &str, n: usize) -> Result<Vec<String>> {
|
|
|
|
|
log::tail(&self.conn, task_id, n)
|
|
|
|
|
}
|
Scaffold cargo workspace + heph-core foundation
Kick off Phase 1 (v1 prototype) per tech-spec §11.1. Sets up the Cargo
workspace and the first TDD slice of heph-core:
- Migration runner + §4.5 SQLite schema (nodes, tasks, links, aliases,
users, oplog, sync_state, conflicts), versioned via PRAGMA user_version.
- Clock-injected `Clock` trait (no ambient wall-clock reads; §2).
- `Store` trait + `LocalStore` SQLite backend with node create/get,
bootstrapping the single local user (oidc_sub NULL, §13).
- Node model (kinds: doc/task/project/tag/journal).
Repo housekeeping: fill AGENTS.md Project Structure (last template TODO),
ignore /target, add self-bootstrapping .forgejo/scripts/build that runs
cargo fmt/clippy/test in CI (§9), changelog fragment.
Tests green: 4 unit tests (migration version, local-user idempotency,
create/get round-trip, missing-node None).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 18:52:15 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
use crate::clock::FixedClock;
|
|
|
|
|
|
|
|
|
|
fn store_at(now_ms: i64) -> LocalStore {
|
|
|
|
|
LocalStore::open_in_memory(Box::new(FixedClock(now_ms))).expect("open in-memory store")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn migrations_bring_schema_to_latest() {
|
|
|
|
|
let store = store_at(0);
|
|
|
|
|
let v: i64 = store
|
|
|
|
|
.conn
|
|
|
|
|
.query_row("PRAGMA user_version", [], |r| r.get(0))
|
|
|
|
|
.unwrap();
|
|
|
|
|
assert_eq!(v, latest_version());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn opening_twice_is_idempotent_for_the_local_user() {
|
|
|
|
|
let conn = Connection::open_in_memory().unwrap();
|
|
|
|
|
conn.execute_batch("PRAGMA foreign_keys = ON;").unwrap();
|
|
|
|
|
migrations::migrate(&conn).unwrap();
|
|
|
|
|
let a = ensure_local_user(&conn, &FixedClock(1)).unwrap();
|
|
|
|
|
let b = ensure_local_user(&conn, &FixedClock(2)).unwrap();
|
|
|
|
|
assert_eq!(a, b);
|
|
|
|
|
}
|
|
|
|
|
}
|