Skip to content

feat(runtime): state the id contract once on every uuid parameter - #2007

Open
ohdearquant wants to merge 13 commits into
mainfrom
fix/uuid-param-id-contract
Open

feat(runtime): state the id contract once on every uuid parameter#2007
ohdearquant wants to merge 13 commits into
mainfrom
fix/uuid-param-id-contract

Conversation

@ohdearquant

Copy link
Copy Markdown
Owner

describe_verb appends a single shared ID_PARAM_CONTRACT to every parameter declared param_type=uuid, instead of the contract being restated per parameter across each pack's HandlerDef or left implicit.

The contract it states: a full UUID is namespace-agnostic (the caller already knows the specific record), while a short hex prefix is a namespace-scoped resolution — a search — that can match nothing or match ambiguously across a namespace the caller does not share. Saying this once explains why some verbs accept a prefix and others require a full UUID: the asymmetry tracks whether the operation can assume the caller's namespace, rather than being an inconsistency between verbs.

Entity creation additionally echoes entity_type_normalized when an alias substitution was applied, so a caller can see both what it requested and what was actually stored. Case-only folding (case/hyphen/space folding to the same word) is deliberately not reported as a substitution.

Coverage: the shared contract on uuid params, single-entity and bulk create alias echo, and the negative case-folding arm.

Local gates at this head: cargo fmt --check clean, cargo check -p khive-pack-kg -p khive-runtime --all-targets clean, cargo clippy same scope clean, cargo test -p khive-pack-kg -p khive-runtime 1766 passed / 0 failed.

describe_verb appends a single shared ID_PARAM_CONTRACT to every
parameter declared param_type=uuid, instead of the contract being
restated per parameter across each pack HandlerDef or left implicit.

A full UUID is namespace-agnostic: the caller already knows the
specific record, so it resolves the same way everywhere. A short hex
prefix is a namespace-scoped resolution -- a search -- so it can match
nothing or match ambiguously across a namespace the caller does not
share. Stating this once explains why some verbs accept a prefix while
others require a full UUID: the asymmetry tracks whether the operation
can assume the caller namespace, rather than being an inconsistency
between verbs.

Entity creation additionally echoes entity_type_normalized when an
alias substitution was applied, so a caller can see what it requested
and what was actually stored. Case-only folding is not reported as a
substitution.

Tests cover the shared contract on uuid params, the single and bulk
create alias echo, and the negative case-folding arm.
@ohdearquant
ohdearquant marked this pull request as ready for review August 16, 2026 23:59
…ra-doc link

`describe_verb` is public and `ID_PARAM_CONTRACT` is private, so the bracketed
form trips rustdoc::private_intra_doc_links under -D warnings and fails the doc
build. A plain code span conveys the same thing to a reader of the public docs
without an allow attribute or widening the constant's visibility.
The shared ID_PARAM_CONTRACT is published through describe_verb to every
caller of help, and it said short hex prefix resolution is scoped to the
caller's namespace. The by-ID verbs (get/update/delete/merge) resolve
prefixes through resolve_prefix_unfiltered, which applies no namespace
predicate at all, matching their already-unfiltered full-UUID path: by-ID
access is namespace-agnostic and the Gate, not storage-layer filtering, is
the authorization seam (ADR-007 Rev 6). The code was correct and the
published description was its negation, which is the worse direction of the
two, since a caller trusting the text would expect a prefix to be confined
to its own namespace.

resolve_prefix_unfiltered's own doc comment in this crate already states
the true rule, so the wire-facing string contradicted the code it
describes. Correct the constant and the surrounding prose that encoded the
same wrong claim.

Make the help test discriminate. It previously asserted only that the
description contains "namespace", which passes identically whether the
contract states the rule or its negation, so it stayed green while the
contract was wrong. It now pairs a must-match on the actual rule with a
must-not-match on the wrong one; either arm alone still admits a contract
that merely mentions namespaces without committing to anything.

@ohdearquant ohdearquant left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review. Posted by this repository's automated pull-request review pipeline; this is not a human read and does not gate the merge by itself.

Verdict on head 8c24c5d: REQUEST-CHANGES, 2 blocking findings. Finding details are delivered to the review's recipients rather than posted here. Do not merge this head while blocking findings are outstanding; a pipeline comment on a newer head supersedes this one.

ohdearquant and others added 10 commits August 18, 2026 06:15
Two doc sites still described by-ID prefix resolution as namespace-scoped
after ADR-007 Rev 6 made it fully unfiltered:

- atomic_prepare.rs's require_uuid doc named resolve_uuid_unfiltered while
  calling it a "namespace-scoped search" in the same sentence.
- pack.rs's ID_PARAM_CONTRACT enumerated get/update/delete/merge but
  omitted link, whose source_id/target_id resolve through the identical
  unfiltered path (khive-pack-kg's handle_link calls resolve_uuid_unfiltered
  for both endpoints).

Also trims the now-redundant "namespace-unfiltered under ADR-007" clause
from each by-ID ParamDef description in khive-pack-kg, since describe_verb
already appends that fact once via the shared ID_PARAM_CONTRACT constant.

No behavior change — by-ID prefix resolution was already unfiltered; this
corrects the text to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ingest validates the optional thread_id syntactically only — no existence
or namespace lookup — so its declared resolution mode now states exactly
that; the parameter description says the same in words.
The parameter-contract documentation linked VerbRegistry::describe_verb
from khive-types, which cannot resolve a link into a downstream crate,
and describe_verb's own documentation linked the private contract
helper. Both references become plain code spans; sentences unchanged.
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