Extraction profiles: versioned identity through compatible entry points - #94
Conversation
188350c to
08d0de0
Compare
08d0de0 to
418e58f
Compare
418e58f to
585adbb
Compare
585adbb to
6551c22
Compare
6551c22 to
a84b862
Compare
a84b862 to
9182417
Compare
9182417 to
f52238a
Compare
f52238a to
ec4e5b6
Compare
345c7a4 to
dd4d7bf
Compare
60619aa to
e426439
Compare
e426439 to
3f403d8
Compare
| ) { | ||
|
|
||
| init { | ||
| sourceRevision?.let { revision -> |
There was a problem hiding this comment.
is data class validation on init standard practice?
Suppose, theoretically, a secondary constructor were added; would init be executed?
If the pattern gets used in many places, perhaps introduce ValidationAware interface?
There was a problem hiding this comment.
init runs for every construction path. A secondary constructor in Kotlin has to delegate to the primary one, and the primary's init blocks run as part of that delegation, so there is no constructor that skips it. copy() goes through the primary constructor too. That is why the check lives there and not in a factory: there is no way to get an instance that was not checked.
On a ValidationAware interface: what would it add? The check has to run at construction to keep an invalid instance from existing at all; an interface method a caller has to remember to invoke is weaker than that. Where the same rule shows up in several types (the bounds in SourceIdentityBounds are the case today) the shared piece is the rule, and each type's init calls it. Happy to revisit if a use case needs validation after construction.
| } | ||
| // The request is the one door everything the caller asked for comes through, so the | ||
| // context and the request always agree about a call. | ||
| request.sourceLocator?.let { ctx = ctx.withSourceLocator(it) } |
There was a problem hiding this comment.
if im not mistken sourceLocator has been already validated in init
There was a problem hiding this comment.
Different job. init checks that a revision, when present, names the same source the locator does. The ?.let here is null handling: sourceLocator is optional on the request, and the context only gets one when the caller supplied one. Nothing is validated twice.
3f403d8 to
5ab4ae7
Compare
5ab4ae7 to
8ab78d0
Compare
ExtractionContentProfileRef gives source-independent extraction behavior a stable name-and-version identity, and ExtractionRunRef ships ahead of the run store that will key on it; both are opaque, and the host authorizes and binds what they name. The context carries them beside perspective, schema, and tenant as independent dimensions, proven by a constructional matrix test. Every legacy entry-point signature stays present, open, and reached: the pre-profile declarations delegate forward to the wide forms, the wide forms never route back, and subclass overrides of the old signatures still intercept file calls — pinned by descriptor, finality, compile-level override, and dispatch tests, because overridable and reached are different claims. Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
ExtractionRunRef had no consumer on this branch: nothing read it, and no write stamped a run with it. It comes back on the run-lineage branch together with the lineage write that gives it a reason to exist. The prose is pulled back to what ships. DICE carries an ExtractionContentProfileRef to the extractor and persists no run or claim association, so the documents now claim only that identity is name and version together and that a republished profile yields a reference the host can tell apart from the old one. A pin test guards the removal: it asserts ExtractionRunRef is absent, fixes the persistAndProject overload count, and sweeps generic field, return, and parameter signatures across the carrier types so a run reference cannot re-enter unnoticed. Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
The remember entry points were growing one parameter per feature: the train had added wide overloads carrying revision and profile, and the next slices wanted run, products and pass index. ExtractionRequest carries locator, source revision and extraction profile behind one validated value, each entry point gains a single request overload, and the wide overloads are deleted before ever shipping. The signatures main publishes stay byte-identical, pinned by a compile-level test, and the run reference lands as a request field on the next slice with no signature change. Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
8ab78d0 to
08a69e8
Compare
PR 5 of the extraction-integrity train (closes the entry-point half of #66; refs #67), stacked on #93.
ExtractionRequestis the entry-point surface: one value object carrying everything a caller says about an extraction beyond the text and the user —sourceLocator,sourceRevision,profile. Each entry point is exactly two declarations: the signaturemainpublishes, byte-identical, plus one overload taking the request.ExtractionContentProfileRefnames a stable name+version identity for source-independent extraction behavior; opaque by design — DICE carries the identity, the host authorizes and binds what it names, and profiles select no provider, model, or credential. The request validates its own coupling ininit: a revision requires a locator whose key it matches, so a mismatched pair can never reach an entry point.Changed in this review round:
rememberTextFromSourceandrememberFileFromSourceno longer exist as names, so the wide forms never ship in a release and Return typed product outcomes for batched and linked extraction passes #69 has nothing to deprecate. The next dimension extraction learns about arrives as a field on the request while the entry-point signatures stay put.SourceAnalysisContext; the context keepsprofileas the downstream read point — a hostPropositionExtractorreceives the whole context on everyextract(chunk, context)call, and the run record reads it there once the coordinator lands.currentRunparameter that was accepted and then dropped at persistence is removed here; a reflection sweep fails the build if any request member mentions a run before the consuming write exists. It returns as the request's fourth field on Extraction runs: proposition lineage #101, together with the lineage write that consumes it.Breaking changes: none — including for subclasses. Every pre-request entry-point signature stays present, open, and reached, pinned four ways: the declared descriptor set, non-finality assertions, a subclass override called on every published arity (positional and named), and Java interop with
@Overridecompile proofs. New constructor parameters follow the stated Kotlin-synthetics boundary.Opt-in and status: EXPERIMENTAL (
@ApiStatus.Experimental). The trigger is passing anExtractionRequest; legacy calls behave byte-identically, andExtractionRequest.NONEdispatches exactly as the legacy call it resembles, so a subclass overriding only the six-argumentrememberTextstill intercepts file ingestion.The dispatch shape — every call funnels to one terminal method, and legacy overrides stay reached:
flowchart TD A[legacy rememberText] --> W[rememberText with request] B[legacy rememberFile] --> X[rememberFile with request] X --> W E[async event paths] --> W W --> C[buildContext] C --> D[SourceAnalysisContext: locator, revision, profile, perspective, schema, tenant] S[a subclass override of a legacy signature stays reached] -.-> XOverridable and reached are different claims, so they are tested separately.