Let a source push statements in the open contract - #884
Conversation
a8a69d2 to
5768f7a
Compare
…model must read back
A statements source takes the extraction contract itself (e/s/n) on
POST /sources/{id}/statements with the api push's identity, versions and
tombstones; the payload is one chunk and extraction parses it instead of
prompting, so a pushed statement is an open statement and reaches the typed
graph only through alignment. Record 0054.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Wayland Yang <wayland0916@gmail.com>
… are still computed Only the chat client is absent when a chunk is the contract itself; the embedding model that #877 uses for name vectors comes from the same settings row and applies to pushed names as much as extracted ones. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Wayland Yang <wayland0916@gmail.com>
ae6d07c to
f7a619a
Compare
|
Review (posted as a comment because GitHub refuses a formal review on a PR under the same account). The design and the door are solid: constant-time token check, every write scoped by the source's own base, unknown keys refused at the door, deterministic re-serialisation, no migration, and ADR 0054 reads true for decisions 1, 2 and 4 to 7. One thing breaks decision 3 and the PR's own example, so please fix it before merging. Blocking: a pushed statement's Two smaller asks
Pre-existing and not blocking: the body is buffered before the token check (axum's default 2 MiB), same as the existing CI green, DCO fine, i18n in both packs. Happy to merge once the time path is fixed. |
…se an unlisted subject at the door `locate_time` begins with `quote?`, and the door requires a pushed statement's quote to be null, so every `when` / `ended` on a pushed statement fell into the TIME_NOT_IN_QUOTE branch: the fact landed with only doc_time and a drop signal, and no time mention was ever recorded. On the pushed path the chunk is the payload, so the words are looked up in the chunk directly. The DB test now asserts the mention lands; it fails without this change. The door now resolves each statement's subject and each name's entity against `e`, folding whitespace and case as `name_key` does, and answers 422 instead of letting extraction drop the item silently as UNKNOWN_REF. An object not in `e` still lands as a literal value, as it does for a model's reply. The route carries a body limit of four payloads so the 2 MiB default is no longer buffered before the token check; the documented 64 KiB refusal stays a 422 with its message. Tests added: tombstone marks and a new push revives, a tombstone for an unknown identity is a no-op, the missing Authorization header, the 64 KiB limit over HTTP, the 200-item limit, an `n` entity not in `e`, a non-null `n` quote, and the folded subject that must pass. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Wayland Yang <wayland0916@gmail.com>
|
Updated in a0b3875, all three points. Time words (blocking). On the pushed path the words are now located in the chunk directly (the chunk is the payload), instead of going through Unlisted subject / Tests. Added: tombstone marks and a new push revives ( The route also carries Local: clippy clean, the five statements tests and the |
|
Re-reviewed a0b3875. All three points are addressed: the pushed branch locates time words in the chunk itself ( |
…i source's Found by running the server end to end: the token came back once at creation, then GET /token and POST /rotate-token answered 404 because both still gated on kind == "api". One predicate, has_push_token, now names the two kinds that carry a push token, and creation, view and rotation use it. A test signs in as the editor, reads the token back, rotates it, and checks that the old token is refused and the new one pushes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Wayland Yang <wayland0916@gmail.com>
|
Ran the branch end to end against a real server (fresh database, port 8319, job worker on, no chat model): register → workspace → base → create a Statements source → read its token → push the record's example over HTTP → wait for the worker → read the graph back. All 26 checks pass at ecf32fb: door refusals (401 wrong or missing key, 422 unknown key, 422 unlisted subject), created/unchanged/updated/marked_missing, one JSON document with two versions, the open statement on the entity as an unbound relation, the It found one gap, fixed in ecf32fb: GET The |
Record 0054, cut 1. Surfaced by #875: a system that already holds
{thing, relation, value, when}could only get it in by spelling it into prose, chunking it on a model-sized budget, and paying a model call to read the prose back into a statement.What lands
statementssource kind (next toapi, same push token, same identity/version/tombstone semantics, same run history) andPOST /sources/{id}/statements.e/s/n, the same positions the model is asked to fill) inside theapipush's envelope (external_id,doc_time,deleted). No second representation.statementssource, extraction parses the chunk instead of prompting for it and then continues unchanged (identity, described things, name facts, evidence, time mentions, qualifiers, drop signals). The job runs with no chat model configured.fact_evidencealready means by "not located" (0061).apisource (status, token dialog, guide link);Bracesicon; both language packs; the ingest guide has a new section and its chooser table a new column.No migration:
sources.kindis unconstrained text.Not in this cut
Batch pushes, a typed write, a table importer (0036), synthesised quotes, per-item confidence. Listed with reasons in the record.
Verified
cargo fmt --all --check,cargo clippy --workspace --all-targets -- -D warnings: clean.a_source_kind_is_listed_once(frontend list ↔ backend enum, order included): passes.sources_statements_tests.rs): the door refuses unknown keys / a non-null quote / a short item / a blank identity / an emptys(no DB); against pgvector/pgvector:pg16 on a fresh database: a pushed statement becomes one open fact with phraseis on, both entities, and an evidence row on the payload's own chunk with null quote and offsets, with no chat model configured; a second push under the same identity isunchangedthenupdatedwith a second version; the route answers 422 / 404 (anapisource) / 401.cargo test -p utopia-serveron that fresh database: 398 passed, 0 failed.pnpm guard,pnpm typecheck,pnpm test(132),pnpm build: pass.One thing worth knowing if you run the DB tests locally: a dev server connected to the same database will have its job worker pick up the
process_documentjobs the route tests enqueue and fail them against its own data dir. Point tests at a database the server is not using.Refs #875.
🤖 Generated with Claude Code