hachure 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,225 @@
1
+ # Status Derivation — Specification
2
+
3
+ **Function:** `status = f(claim, evidence, events, policy, authorityTrace, now)`
4
+ **Version constant:** `STATUS_FUNCTION_VERSION` (currently `"1"`)
5
+ **Source of truth:** `src/status.ts` in `@kontourai/surface`
6
+
7
+ ---
8
+
9
+ ## Principle
10
+
11
+ Claim status is a pure, versioned, deterministic function (ADR 0003 §7). Given the
12
+ same inputs and the same `STATUS_FUNCTION_VERSION`, any conforming implementation
13
+ must derive the same status. There is no stored status field that overrides
14
+ computation; the derived status is always recomputed from the input bundle at
15
+ evaluation time.
16
+
17
+ `now` is an explicit input so that time-based staleness checks are reproducible.
18
+ A caller that wants a point-in-time view fixes `now` before evaluating; there are
19
+ no clock-tick events and no background expiry.
20
+
21
+ Reproducibility guarantee: if two independent implementations receive the same
22
+ `(claim, evidence, events, policies, authorityTrace, now)` and the same
23
+ `STATUS_FUNCTION_VERSION`, they must return the same `TrustStatus`.
24
+
25
+ ---
26
+
27
+ ## Inputs
28
+
29
+ | Input | Type | Required | Description |
30
+ |---|---|---|---|
31
+ | `claim` | `Claim` | yes | The claim being evaluated. |
32
+ | `evidence` | `Evidence[]` | yes | All evidence items whose `claimId` matches the claim. |
33
+ | `events` | `VerificationEvent[]` | yes | All verification events for the entire bundle; the function filters by `claimId`. |
34
+ | `policies` | `VerificationPolicy[]` | yes | All policies in the bundle; the function resolves the applicable policy internally. |
35
+ | `now` | `Date` | no (defaults to wall clock) | The evaluation timestamp for freshness checks. |
36
+ | `authorityTrace` | `AuthorityTrace[]` | no (defaults to `[]`) | Active authority records enabling dispute resolution. |
37
+
38
+ `deriveClaimStatus` (the public versioned entry point) resolves the policy and
39
+ partitions evidence before calling the internal `deriveTrustStatus` function.
40
+
41
+ ---
42
+
43
+ ## Evidence partitioning
44
+
45
+ Before the fold, evidence is partitioned by `supportStrength`:
46
+
47
+ - **`"entails"`** (default when `supportStrength` is absent) — fully entails the
48
+ claim; satisfies policy requirement checks and corroboration counts.
49
+ - **`"cited"`** — contextual support only; does not satisfy required-evidence
50
+ policy checks and does not count toward corroboration.
51
+
52
+ Only entailing evidence is passed to `deriveTrustStatus`. Cited evidence is
53
+ available to callers but does not influence status derivation.
54
+
55
+ ---
56
+
57
+ ## Policy resolution
58
+
59
+ Before the fold, the applicable `VerificationPolicy` is resolved from the policies
60
+ array using this priority order:
61
+
62
+ 1. If `claim.verificationPolicyId` is set and a policy with that `id` exists, it wins.
63
+ 2. Otherwise, find a policy whose `claimType` exactly matches `claim.claimType`.
64
+ 3. Otherwise, walk the `parentType` chain declared by policies (most-specific first)
65
+ and pick the first match.
66
+ 4. If no policy is found, `policy` is `undefined` and the fold proceeds without one.
67
+
68
+ The first policy declared for a given `claimType` wins; later declarations do not
69
+ silently override.
70
+
71
+ ---
72
+
73
+ ## The fold
74
+
75
+ The fold is an ordered sequence of checks. The first matching branch terminates
76
+ the evaluation and returns its status. No subsequent checks are applied.
77
+
78
+ ### Step 1: Authority-gated dispute resolution
79
+
80
+ Check for the most recent verification event (sorted most-recent-first by `createdAt`)
81
+ that satisfies both of these conditions:
82
+
83
+ - `event.resolvesDispute === true`
84
+ - The event's `actor` has an active `AuthorityTrace` at the time of the decision
85
+
86
+ An `AuthorityTrace` is active at a given `eventCreatedAt` if all of the following hold:
87
+
88
+ - `trace.actorRef === event.actor`
89
+ - `trace.revokedAt` is absent, or `trace.revokedAt > eventCreatedAt`
90
+ - `trace.validFrom` is absent, or `trace.validFrom <= eventCreatedAt`
91
+ - `trace.validUntil` is absent, or `trace.validUntil >= eventCreatedAt`
92
+ - If `event.authorityRef` is set, `trace.authorityRef === event.authorityRef`
93
+
94
+ If such a resolution event is found:
95
+
96
+ - Check whether any evidence item satisfies **all** of:
97
+ - `evidence.passing === false`
98
+ - `evidence.blocking !== false`
99
+ - `Date.parse(evidence.observedAt) > Date.parse(resolutionEvent.createdAt)`
100
+
101
+ If such a "newer blocking failure" exists, return **`disputed`** (the resolution
102
+ is overridden by fresh contradicting evidence).
103
+
104
+ - Otherwise, return `resolutionEvent.status` directly. This is the authority-gated
105
+ resolution outcome.
106
+
107
+ ### Step 2: Terminal event statuses
108
+
109
+ Filter all events to those matching `claim.id`, sort most-recent-first by `createdAt`.
110
+ Let `latestEvent` be the first (most recent) event.
111
+
112
+ If `latestEvent` exists and its `status` is one of `"rejected"`, `"disputed"`,
113
+ `"superseded"`, or `"stale"` — return that status. These are terminal: they are
114
+ not overridden by evidence inspection.
115
+
116
+ ### Step 3: Assumed from event
117
+
118
+ If `latestEvent` exists and `latestEvent.status === "assumed"` — return **`assumed`**.
119
+
120
+ ### Step 4: Verified event path
121
+
122
+ If `latestEvent` exists and `latestEvent.status === "verified"`:
123
+
124
+ #### 4a. Staleness check
125
+
126
+ If a policy is present, check whether the verification is stale based on
127
+ `policy.validityRule.kind`:
128
+
129
+ - **`"commit"`** — stale if `claim.currentIntegrityRef` is set AND none of the
130
+ evidence items linked by `latestEvent.evidenceIds` carry an `integrityRef` equal
131
+ to `claim.currentIntegrityRef`. (If `claim.currentIntegrityRef` is absent, not stale.)
132
+ - **`"duration"`** — stale if `now > verifiedTime + (policy.validityRule.durationDays × 86400000 ms)`,
133
+ where `verifiedTime = Date.parse(latestEvent.verifiedAt ?? latestEvent.createdAt)`.
134
+ - **`"historical"` or `"manual"`** — never stale by time or commit change.
135
+ - If `policy` is absent — not stale.
136
+
137
+ If stale: return **`stale`**.
138
+
139
+ #### 4b. Policy evidence gap check
140
+
141
+ If a policy is present, check whether all required evidence types and methods are
142
+ present among entailing evidence items:
143
+
144
+ - Build the set of `evidenceType` values from entailing evidence.
145
+ - Build the set of `method` values from entailing evidence.
146
+ - Compute `missingTypes = policy.requiredEvidence` items not in the type set.
147
+ - Compute `missingMethods = (policy.requiredMethods ?? [])` items not in the method set.
148
+ - Corroboration gap: `policy.requiresCorroboration === true` and `entailingEvidence.length < 2`.
149
+
150
+ If any gap exists: return **`proposed`** (the event says verified but policy
151
+ requirements are not met — the claim is effectively a proposed state).
152
+
153
+ #### 4c. Blocking failure check
154
+
155
+ Check whether any evidence item satisfies both:
156
+ - `evidence.passing === false`
157
+ - `evidence.blocking !== false` (i.e., `blocking` is `true` or absent/`undefined`)
158
+
159
+ If such evidence exists: return **`disputed`**.
160
+
161
+ #### 4d. Verified
162
+
163
+ Return **`verified`**.
164
+
165
+ ### Step 5: Claim-level status baseline
166
+
167
+ If there is no verification event (or `latestEvent.status` was not one of the above):
168
+
169
+ - If `claim.status === "proposed"` — return **`proposed`**.
170
+ - If `claim.status === "assumed"` — return **`assumed`**.
171
+
172
+ ### Step 6: No policy
173
+
174
+ If no policy is resolved (`policy` is `undefined`):
175
+
176
+ - Return **`proposed`** if `evidence.length > 0` (there is evidence but no policy to
177
+ evaluate it against).
178
+ - Return **`unknown`** if `evidence.length === 0`.
179
+
180
+ ### Step 7: Policy evidence presence
181
+
182
+ If a policy is present but no verification event exists:
183
+
184
+ - Build the set of `evidenceType` values from all evidence (not just entailing, at
185
+ this step — but in practice the partitioning above was already applied upstream).
186
+ - If `policy.requiredEvidence` is a subset of the evidence type set: return **`proposed`**.
187
+ - Otherwise: return **`unknown`**.
188
+
189
+ ---
190
+
191
+ ## Derivation ceiling
192
+
193
+ For derived claims (claims built from other claims via `derivationEdges` or
194
+ `derivedFrom`), the status of the derived claim cannot exceed the weakest input
195
+ claim status. This ceiling is applied by the derivation layer before the fold
196
+ receives the claim; it is reflected in `DerivationChangeRecord` entries in the
197
+ report.
198
+
199
+ ---
200
+
201
+ ## Status ordering (for ceiling purposes)
202
+
203
+ From weakest to strongest: `unknown` < `rejected` < `superseded` < `disputed` <
204
+ `stale` < `assumed` < `proposed` < `verified`.
205
+
206
+ ---
207
+
208
+ ## Output
209
+
210
+ `deriveClaimStatus` returns `{ status: TrustStatus; policyId: string | undefined }`.
211
+ `policyId` is the `id` of the resolved policy, or `undefined` if none was found.
212
+
213
+ ---
214
+
215
+ ## Versioning
216
+
217
+ `STATUS_FUNCTION_VERSION` is a string exported by `@kontourai/surface`. It is
218
+ incremented when the algorithm changes in a way that could produce different outputs
219
+ for the same inputs. `InquiryRecord.statusFunctionVersion` captures which version
220
+ was active at resolution time, enabling re-evaluation when the algorithm version
221
+ changes.
222
+
223
+ Conforming implementations must declare which `STATUS_FUNCTION_VERSION` value they
224
+ implement. Implementations claiming version `"1"` must satisfy all conformance
225
+ cases in `spec/conformance/`.