Skip to content

Typed materialization leaves successive functional values open until explicit reconciliation #899

Description

@Maya-Kid

Typed materialization can introduce two current values for a functional state attribute without reconciling their timeline. For the #875 observation replay, a cup observed on the desk and later on the shelf retained both open typed rows; running rule derivation still left the desk-dependent step current.

This is a boundary/contract question as well as an implementation gap: should materialization reconcile changed functional predicates, or should callers be required to invoke reconciliation explicitly? A successful projection currently does not establish that its functional timeline is settled.

Reproduction

Declare location as a functional state attribute. Create two separately dated documents (08:00 desk, 08:10 shelf), insert their open statements, explicitly bind the phrase to location, then call typed materialization. These are store-level operations with no model and no HTTP adapter. The earlier desk row remains open. Calling temporal::reconcile_predicate explicitly closes the earlier interval; a subsequent derivation run retracts the current step while retaining its historical interval.

Expected boundary to settle: either reconcile affected functional timelines as part of the projection path, with tests for conflicts and history, or make the required caller-side reconciliation explicit. This report does not propose treating every stale statement as false or automatically resolving ambiguous observations.

Executable evidence

The complete fixture and reproduction are retained at the immutable experimental commit 4864e2f19e3ab0cee58d5ef4eade6212b3ae1af6 on the fork. Its base includes #884 and dev@9f50e3c. The test was explicitly executed with a real PostgreSQL 16/pgvector database and fails at the assertion shown below (exit 101), not during compilation or setup. It is kept outside #898's upstream test diff; it is a proposed behavior reproduction, not an ignored upstream acceptance test.

With that checkout and a migrated, dedicated test database:

UTOPIA_TEST_REQUIRE_DB=1 cargo test --locked -p utopia-store --test store \
  a_plan_step_follows_its_premise::a_later_bound_statement_closes_the_earlier_place \
  -- --ignored --exact --test-threads=1

UTOPIA_DATABASE_URL must point to that database. The helper definitions are in the linked file. The reproducing test is:

Store-level reproduction
#[tokio::test]
#[ignore = "#875 gap: materialize writes typed rows without timeline reconciliation"]
async fn a_later_bound_statement_closes_the_earlier_place() -> anyhow::Result<()> {
    let Some(url) = utopia_store::test_db::url() else {
        return Ok(());
    };
    let pool = PgPool::connect(&url).await?;
    let f = seed(&pool, "issue875-materialize-timeline").await?;

    let run = async {
        let (_, c1) = document(&pool, &f, "obs-1.json", T1, "cup-7 is on desk").await?;
        let (_, c2) = document(&pool, &f, "obs-2.json", T2, "cup-7 is on shelf").await?;
        statement(&pool, &f, c1, "desk", T1).await?;
        statement(&pool, &f, c2, "shelf", T2).await?;
        bind_is_on(&pool, &f).await?;
        materialize::materialize(&pool, f.kb).await?;
        let desk = typed_desk(&pool, &f).await?;
        assert_eq!(desk.len(), 1);
        assert!(
            desk[0].valid_to.is_some() || desk[0].valid_to_precision.is_some(),
            "location is functional and a later place was materialized, \
             yet the desk row is still open: {desk:?}"
        );
        anyhow::Ok(())
    }
    .await;

    cleanup(&pool, f.org).await?;
    run
}

Refs #875 and #898. No production fix is included in this report.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions