Skip to content

A phrase decision records the inputs it considered, so staleness is a changed basis rather than a clock - #878

Merged
WaylandYang merged 2 commits into
devfrom
feat/alignment-cut2
Sep 23, 2026
Merged

WaylandYang merged 2 commits into
devfrom
feat/alignment-cut2

Conversation

@WaylandYang

Copy link
Copy Markdown
Contributor

Implements the lifecycle #807 asked for as a design rather than another local fix, recorded as ADR 0053. Stacked on #876; this PR shows only its own diff once that merges.

The idea in one line: a phrase decision stores a fingerprint of the inputs it considered, and it is stale when the fingerprint of the current inputs differs. Nothing is compared to a clock any more.

What changes

  • phrase_bindings.basis (migration 0072): the fingerprint of both endpoint classes' ancestor closures, whether the object is a value, and the admitted candidate properties with their updated_at. Agent decisions carry it; a person's decision does not, because it is never re-evaluated by the agent.
  • Candidates are admitted through the class hierarchy. A property declared on legal_entity is now a candidate for an organization signature (Design the lifecycle of phrase alignment decisions and reevaluation #807's first bug), and the candidate line tells the model fits by inheritance: organization is a subclass of legal_entity; the system prompt says that this is a fit. Widening candidates in code without showing the basis made the model answer null (Respect inherited phrase candidates and retire unsupported bindings #801's finding).
  • The two silences become recorded outcomes: no admissible property → none with votes.reason = "no_candidates", so a projection whose support is gone retires at the next materialisation (Design the lifecycle of phrase alignment decisions and reevaluation #807's second bug); more than CANDIDATE_LIMITundecided with reason = "too_many_candidates" and the count, which puts it in the alignment queue for a person instead of requeueing a run that can never execute. The queue card says so in both languages.
  • The requeue condition reloads properties, classes, signatures and bindings at the end of the run and compares fingerprints against those, and it reads live signatures only. That is what makes an edit committed while the model was answering detectably stale (An ontology edit during type alignment can be hidden by the response timestamp #795's phrase half: the first run notices its own basis is already stale and queues another), and what stops an orphaned row, left behind when an endpoint class changes, from queueing one job per round forever (the three-rounds reproduction in the review of Respect inherited phrase candidates and retire unsupported bindings #801).
  • ADR 0053; a dated revision note on 0044 decision 3; design/ontology.md; index rows for 0053 and for 0052, which feat(api): serve retained document content and versions #860 had left out of both tables.

Verified against pgvector/pgvector:pg16 on a fresh database (numbers below), with seven lifecycle regressions driven by a scripted model against real PostgreSQL:

  • a property declared on an ancestor is offered with its basis and bound; unchanged inputs leave no queued work
  • removing the parent edge retires the projection with no model call
  • adding the parent edge reopens a structural none
  • overflow is recorded for a person with no model call and no requeue, and recovers when the candidate set shrinks
  • an endpoint class change moves the signature; the old row stays as a cached decision and stops looping
  • an edit during the model request leaves the decision stale for the next run
  • a person's decision made during the request is not overwritten

plus unit tests for inheritance fit and for the closure over a diamond hierarchy. The four existing negative_binding_definition_edit regressions still pass; phrase_bindings::stale stays for them and for anything else that wants the coarse timestamp signal, but the worker no longer reads it.

Not in this PR, said in the record: the kind-word aligner keeps timestamp staleness (#795's reproduction is on align_types; same design, different inputs, next cut), and there is no revision table of decisions.

cargo fmt --all --check                                     ok
cargo clippy --workspace --all-targets -- -D warnings       clean
cargo test -p utopia-store   (UTOPIA_TEST_REQUIRE_DB=1)      331 passed, 0 failed
cargo test -p utopia-server  (UTOPIA_TEST_REQUIRE_DB=1)      394 passed, 0 failed   (+9: 7 lifecycle, 2 unit)
cargo test -p utopia-cli                                     passes (CURRENT_SCHEMA_VERSION bumped to 71 for migration 0072)
cd web && pnpm build && pnpm test                            tsc clean, 132 passed

🤖 Generated with Claude Code

WaylandYang and others added 2 commits September 23, 2026 16:41
… changed basis rather than a clock

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: WaylandYang <145302500+WaylandYang@users.noreply.github.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: WaylandYang <145302500+WaylandYang@users.noreply.github.com>
@WaylandYang
WaylandYang merged commit 5fc36e7 into dev Sep 23, 2026
4 checks passed
@WaylandYang
WaylandYang deleted the feat/alignment-cut2 branch September 23, 2026 08:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant