Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions docs/architecture/cognition-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,16 @@ interface is the source of truth for concrete API names.
## Mock recomputation rules

The first mock graph has three layers: file text, file-level summaries, and
repo/query context. File-level summaries read one file input. Repo context reads
all known file summaries and seeds missing summaries from known file inputs on
its first build. Query context reads repo context and materializes repo context
when needed. When a new file input appears after repo context already exists, or
repo/query context. File-level summaries read one file input. A workspace file
registry tracks active file inputs. Repo context reads summaries for active
files and seeds missing summaries from registered file inputs on its first
build. Query context reads repo context and materializes repo context when
needed. When a new file input appears after repo context already exists, or
after query context has observed repo context, the runtime dirties the
corresponding file-level summary and repo context so repo context can adopt the
new dependency on the next recomputation.
new dependency on the next recomputation. Removing a file unregisters it,
removes its file text and summary artifacts, and dirties repo/query context so
the next recomputation excludes the deleted file.

When an input changes, the store marks transitive dependents dirty. Recomputing
dirty artifacts proceeds only when their dependencies are clean, so unrelated
Expand Down
39 changes: 39 additions & 0 deletions lib/cognition/cognition_test.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,45 @@ test "set_input only accepts FileText Text pairs" {
inspect(store.get_value(FileText("a.mbt")) is None, content="true")
}

///|
test "known_files tracks FileText inputs in sorted order" {
let store = CognitionStore::new()
let _ = store.set_input(FileText("b.mbt"), Text("two"))
let _ = store.set_input(FileText("a.mbt"), Text("one"))

inspect(store.known_files().join(","), content="a.mbt,b.mbt")
}

///|
test "remove_file unregisters file and recomputes repo without deleted summary" {
let store = CognitionStore::new()
let _ = store.set_input(FileText("a.mbt"), Text("one"))
let _ = store.set_input(FileText("b.mbt"), Text("two"))
let _ = store.compute(QueryContext("what changed?"))

inspect(store.remove_file("missing.mbt"), content="false")
inspect(store.remove_file("a.mbt"), content="true")
inspect(store.known_files().join(","), content="b.mbt")
inspect(store.get_value(FileText("a.mbt")) is None, content="true")
inspect(store.get_value(FileSummary("a.mbt")) is None, content="true")
inspect(store.is_dirty(RepoSummary), content="true")
inspect(store.is_dirty(QueryContext("what changed?")), content="true")

let recomputed = store.recompute_dirty()
inspect(recomputed.contains(RepoSummary), content="true")
inspect(recomputed.contains(QueryContext("what changed?")), content="true")
let deps = store.dependencies_of(RepoSummary)
inspect(deps.contains(FileSummary("a.mbt")), content="false")
inspect(deps.contains(FileSummary("b.mbt")), content="true")
match store.get_value(RepoSummary) {
Some(Summary(summary)) => {
inspect(summary.contains("a.mbt"), content="false")
inspect(summary.contains("b.mbt"), content="true")
}
_ => fail("expected repo summary")
}
}

///|
test "changing one FileText marks its FileSummary dirty" {
let store = CognitionStore::new()
Expand Down
2 changes: 2 additions & 0 deletions lib/cognition/pkg.generated.mbti
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,12 @@ pub fn CognitionStore::dirty_keys(Self) -> Array[CognitionKey]
pub fn CognitionStore::get_revision(Self, CognitionKey) -> Revision?
pub fn CognitionStore::get_value(Self, CognitionKey) -> CognitionValue?
pub fn CognitionStore::is_dirty(Self, CognitionKey) -> Bool
pub fn CognitionStore::known_files(Self) -> Array[String]
pub fn CognitionStore::mark_dirty(Self, CognitionKey) -> Unit
pub fn CognitionStore::new() -> Self
pub fn CognitionStore::recompute_count(Self, CognitionKey) -> Int
pub fn CognitionStore::recompute_dirty(Self) -> Array[CognitionKey]
pub fn CognitionStore::remove_file(Self, String) -> Bool
pub fn CognitionStore::set_input(Self, CognitionKey, CognitionValue) -> Bool

pub(all) enum CognitionValue {
Expand Down
102 changes: 86 additions & 16 deletions lib/cognition/store.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/// Mutable store for cognition values, revisions, dependencies, and dirtiness.
pub(all) struct CognitionStore {
priv values : Map[CognitionKey, CognitionValue]
priv workspace_files : Map[String, Bool]
priv revisions : Map[CognitionKey, Revision]
priv dependencies : Map[CognitionKey, Array[CognitionKey]]
priv reverse_dependencies : Map[CognitionKey, Array[CognitionKey]]
Expand All @@ -15,6 +16,7 @@ pub fn CognitionStore::new() -> CognitionStore {
let dependencies : Map[CognitionKey, Array[CognitionKey]] = Map::default()
{
values: Map::default(),
workspace_files: Map::default(),
revisions: Map::default(),
dependencies,
reverse_dependencies: Map::default(),
Expand All @@ -40,12 +42,48 @@ pub fn CognitionStore::set_input(
}
}

///|
/// Return the active workspace file paths known to the cognition store.
pub fn CognitionStore::known_files(self : CognitionStore) -> Array[String] {
let paths = self.workspace_files.keys().to_array()
paths.sort_by(fn(a, b) { a.compare(b) })
paths
}

///|
/// Remove one workspace file input and dirty derived artifacts that referenced it.
pub fn CognitionStore::remove_file(
self : CognitionStore,
path : String,
) -> Bool {
if !self.workspace_files.contains(path) {
false
} else {
let text_key = FileText(path)
let summary_key = FileSummary(path)
self.workspace_files.remove(path)
if self.repo_context_is_observed() {
self.mark_dirty(RepoSummary)
}
let _ = self.dirty_dependents(summary_key)
let _ = self.dirty_dependents(text_key)
self.remove_artifact_state(text_key)
self.remove_artifact_state(summary_key)
let _ = self.reactive.bump_revision()
true
}
}

///|
fn CognitionStore::store_input(
self : CognitionStore,
key : CognitionKey,
value : CognitionValue,
) -> Unit {
match key {
FileText(path) => self.workspace_files[path] = true
_ => ()
}
self.values[key] = value.copy_value()
self.bump_revision(key)
self.dirty.remove(key)
Expand Down Expand Up @@ -206,6 +244,45 @@ fn CognitionStore::bump_revision(
self.revisions[key] = revision
}

///|
fn CognitionStore::remove_artifact_state(
self : CognitionStore,
key : CognitionKey,
) -> Unit {
self.values.remove(key)
self.revisions.remove(key)
self.dirty.remove(key)
self.recompute_counts.remove(key)
self.remove_dependency_record(key)
}

///|
fn CognitionStore::remove_dependency_record(
self : CognitionStore,
key : CognitionKey,
) -> Unit {
match self.dependencies.get(key) {
Some(old_inputs) => {
for input in old_inputs {
match self.reverse_dependencies.get(input) {
Some(dependents) => {
let remaining = dependents.filter(fn(k) { k != key })
if remaining.length() == 0 {
self.reverse_dependencies.remove(input)
} else {
self.reverse_dependencies[input] = remaining
}
}
None => ()
}
}
self.dependencies.remove(key)
self.reactive.bump_dependency_revision()
}
None => ()
}
}

///|
fn CognitionStore::collect_dirty_dependents(
self : CognitionStore,
Expand Down Expand Up @@ -389,17 +466,11 @@ fn CognitionStore::recompute_query_context(
fn CognitionStore::ensure_file_summaries_for_known_texts(
self : CognitionStore,
) -> Unit {
for key, _ in self.values {
match key {
FileText(path) => {
let summary_key = FileSummary(path)
if !self.values.contains(summary_key) ||
self.dirty.contains(summary_key) {
self.recompute_file_summary(path)
self.dirty.remove(summary_key)
}
}
_ => ()
for path in self.known_files() {
let summary_key = FileSummary(path)
if !self.values.contains(summary_key) || self.dirty.contains(summary_key) {
self.recompute_file_summary(path)
self.dirty.remove(summary_key)
}
}
}
Expand All @@ -409,13 +480,12 @@ fn CognitionStore::known_file_summary_keys(
self : CognitionStore,
) -> Array[CognitionKey] {
let keys : Array[CognitionKey] = []
for key, _ in self.values {
match key {
FileSummary(_) => keys.push(key)
_ => ()
for path in self.known_files() {
let summary_key = FileSummary(path)
if self.values.contains(summary_key) {
keys.push(summary_key)
}
}
keys.sort_by(fn(a, b) { a.compare(b) })
keys
}

Expand Down
Loading