@happyvertical/smrt-ingestion 0.57.21 → 0.57.23

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.
Files changed (87) hide show
  1. package/FEEDBACK.md +104 -0
  2. package/PROPOSALS.md +77 -0
  3. package/README.md +126 -0
  4. package/REVIEW.md +272 -4
  5. package/agents/execution.md +72 -0
  6. package/dist/chunks/{execution-9CfjxX8I.js → execution-BzVQax0n.js} +421 -23
  7. package/dist/chunks/execution-BzVQax0n.js.map +1 -0
  8. package/dist/chunks/{feedback-CuUzWKzP.js → feedback-D3UTF8fm.js} +119 -21
  9. package/dist/chunks/feedback-D3UTF8fm.js.map +1 -0
  10. package/dist/chunks/{extraction-CJ-zrbg0.js → proposal-contracts-77Uo1B1j.js} +39 -39
  11. package/dist/chunks/proposal-contracts-77Uo1B1j.js.map +1 -0
  12. package/dist/chunks/{reviewer-instruction-Db3jDN9a.js → reviewer-instruction-CEc3AOuU.js} +2 -2
  13. package/dist/chunks/{reviewer-instruction-Db3jDN9a.js.map → reviewer-instruction-CEc3AOuU.js.map} +1 -1
  14. package/dist/dto.d.ts +69 -0
  15. package/dist/dto.d.ts.map +1 -1
  16. package/dist/execution-contracts.d.ts +4 -1
  17. package/dist/execution-contracts.d.ts.map +1 -1
  18. package/dist/execution-dto.d.ts +16 -0
  19. package/dist/execution-dto.d.ts.map +1 -1
  20. package/dist/execution.d.ts +58 -3
  21. package/dist/execution.d.ts.map +1 -1
  22. package/dist/execution.js +2 -2
  23. package/dist/feedback-dto.d.ts +12 -4
  24. package/dist/feedback-dto.d.ts.map +1 -1
  25. package/dist/feedback-internal.d.ts +9 -0
  26. package/dist/feedback-internal.d.ts.map +1 -1
  27. package/dist/feedback.d.ts +3 -0
  28. package/dist/feedback.d.ts.map +1 -1
  29. package/dist/index.js +2 -2
  30. package/dist/manifest.json +321 -3
  31. package/dist/models.d.ts +15 -0
  32. package/dist/models.d.ts.map +1 -1
  33. package/dist/models.js +73 -2
  34. package/dist/models.js.map +1 -1
  35. package/dist/policy.d.ts +3 -0
  36. package/dist/policy.d.ts.map +1 -1
  37. package/dist/proposal-errors.d.ts +4 -0
  38. package/dist/proposal-errors.d.ts.map +1 -1
  39. package/dist/proposal-sdk.d.ts +54 -1
  40. package/dist/proposal-sdk.d.ts.map +1 -1
  41. package/dist/proposals.d.ts +2 -1
  42. package/dist/proposals.d.ts.map +1 -1
  43. package/dist/proposals.js +215 -10
  44. package/dist/proposals.js.map +1 -1
  45. package/dist/provider-failure.d.ts +3 -0
  46. package/dist/provider-failure.d.ts.map +1 -0
  47. package/dist/redaction.d.ts +9 -0
  48. package/dist/redaction.d.ts.map +1 -0
  49. package/dist/review-dto.d.ts +14 -4
  50. package/dist/review-dto.d.ts.map +1 -1
  51. package/dist/review-host.d.ts.map +1 -1
  52. package/dist/server.d.ts +82 -10
  53. package/dist/server.d.ts.map +1 -1
  54. package/dist/server.js +366 -47
  55. package/dist/server.js.map +1 -1
  56. package/dist/smrt-knowledge.json +128 -22
  57. package/dist/svelte/components/ActionReview.svelte +39 -9
  58. package/dist/svelte/components/ActionReview.svelte.d.ts +8 -1
  59. package/dist/svelte/components/ActionReview.svelte.d.ts.map +1 -1
  60. package/dist/svelte/components/EvidenceViewer.svelte +60 -4
  61. package/dist/svelte/components/EvidenceViewer.svelte.d.ts +12 -1
  62. package/dist/svelte/components/EvidenceViewer.svelte.d.ts.map +1 -1
  63. package/dist/svelte/components/InboxView.svelte +160 -18
  64. package/dist/svelte/components/InboxView.svelte.d.ts +28 -1
  65. package/dist/svelte/components/InboxView.svelte.d.ts.map +1 -1
  66. package/dist/svelte/components/IntakeWorkspace.svelte +27 -0
  67. package/dist/svelte/components/IntakeWorkspace.svelte.d.ts +19 -0
  68. package/dist/svelte/components/IntakeWorkspace.svelte.d.ts.map +1 -0
  69. package/dist/svelte/components/ReviewView.svelte +264 -23
  70. package/dist/svelte/components/ReviewView.svelte.d.ts +18 -1
  71. package/dist/svelte/components/ReviewView.svelte.d.ts.map +1 -1
  72. package/dist/svelte/components/TriageView.svelte +669 -0
  73. package/dist/svelte/components/TriageView.svelte.d.ts +16 -0
  74. package/dist/svelte/components/TriageView.svelte.d.ts.map +1 -0
  75. package/dist/svelte/i18n.d.ts +39 -0
  76. package/dist/svelte/i18n.d.ts.map +1 -1
  77. package/dist/svelte/i18n.js +39 -0
  78. package/dist/svelte/index.d.ts +1 -0
  79. package/dist/svelte/index.d.ts.map +1 -1
  80. package/dist/svelte/index.js +1 -0
  81. package/dist/svelte/view-utils.d.ts +65 -0
  82. package/dist/svelte/view-utils.d.ts.map +1 -1
  83. package/dist/svelte/view-utils.js +145 -0
  84. package/package.json +13 -13
  85. package/dist/chunks/execution-9CfjxX8I.js.map +0 -1
  86. package/dist/chunks/extraction-CJ-zrbg0.js.map +0 -1
  87. package/dist/chunks/feedback-CuUzWKzP.js.map +0 -1
package/FEEDBACK.md CHANGED
@@ -30,6 +30,110 @@ an authorized existing event; it cannot supply a correctness label or fabricate
30
30
  an execution result. An initial denial does not mutate retention. Once authorized,
31
31
  a narrowed privacy ceiling survives business rollback and later revocation.
32
32
 
33
+ Observed `action_decision` events carry the decision's `decisionMode`
34
+ (`review` or `review_own`) and `selfDecision` flag from the durable decision
35
+ record; `selfDecision` is true whenever the decider submitted the item, in
36
+ either mode. A submitter confirming their own proposal (#4085) is observed as a
37
+ decision by that actor, never as reviewer correctness evidence; consumers that
38
+ derive reviewer agreement must exclude `selfDecision: true` events, and nothing
39
+ in this package turns them into automation eligibility.
40
+
41
+ ## Reviewer routing choices
42
+
43
+ A `reviewer_routing` record is appended inside the owning transaction when
44
+ feedback is configured and `capture` is authorized, at the moment the choice is
45
+ final: `resolveItem` (REVIEW.md) records `choice: 'none'` keyed
46
+ `routing:<requestId>`, and `submitDecision` records `choice: 'handler'` keyed
47
+ `routing:<decision requestId>` when a reviewer **approves** a reviewer-selected
48
+ action. Handler-choice capture requires `mode: 'review'`: a `review_own`
49
+ submitter approval authorizes normally but appends no reviewer-routing example or
50
+ rule support. A reviewer using the reviewer path may still record an explicit
51
+ routing choice for their own receipt; it does not judge any model suggestion.
52
+ Starting an action records nothing; rejecting or deferring it never produces an
53
+ example. The record needs no model suggestion: it binds the current
54
+ completed/partial attempt (`attemptId`, output digest, analysis revision, frozen
55
+ evidence hashes and the extracted text as `query`), the acting reviewer
56
+ (`reviewer`, also `approvedBy` for a handler choice), the actor who started the
57
+ action (`startedBy`), the start reason, and for a handler choice the
58
+ `handlerId@version`, exact approved arguments and the approved action binding
59
+ (revision and binding hash). Denied or oversized capture, an unavailable source
60
+ pin or malformed evidence skip the projection and leave the authoritative
61
+ decision or disposition intact; database write failures still roll everything
62
+ back. There is no
63
+ supersession: a correction after approval (`previewProposal` on the current
64
+ revision) changes the revision, which retires the old record from retrieval and
65
+ support until the new revision is approved and captured again; a correction
66
+ before approval records nothing until that revision is approved.
67
+
68
+ Settledness is a selection-time condition. Once a generation used an example,
69
+ or a rule suggestion stored its support, the lineage re-checks (reading the
70
+ generation, reviewing or deciding its actions, reading or adopting the rule,
71
+ and replaying `suggestRoutingRule`, which returns the stored suggestion) verify:
72
+
73
+ - every record: its digest, that it is not superseded, retention (no
74
+ redaction or deletion), the unchanged source attempt digest and frozen
75
+ evidence hashes, and live `retrieve` authority in the same tenant and
76
+ confidential scope with every generation recipient allowed by the source's
77
+ provider policy;
78
+ - an `interpretation` record (unchanged from #3676): its action's current
79
+ revision, binding hash, attempt, handler@version and arguments; the action's
80
+ target preconditions at their recorded revisions unless the action
81
+ `succeeded`; the prompt, configuration and model versions; and that the action
82
+ is not reviewer-selected;
83
+ - a `reviewer_routing` record with `choice: 'handler'`: its action's current
84
+ revision, binding hash, attempt, handler@version and arguments, that the
85
+ action is still reviewer-selected, and that its targets are still accessible
86
+ (not their revisions: the record means the handler choice, not target state);
87
+ - a `reviewer_routing` record with `choice: 'none'`: it has no action, so the
88
+ source item is re-checked under the first currently offered receiving handler
89
+ that still authorizes it.
90
+
91
+ Rule support (`suggestRoutingRule`, and on read, adoption and replay of a stored
92
+ suggestion) additionally requires: at least two records from distinct source
93
+ items; `judgment: 'correct'` with matching prompt/configuration/model versions
94
+ for an interpretation record; `choice: 'handler'` for a routing record; each
95
+ record's handler@version equal to the receiving binding's; and each source's
96
+ live routing projection (or full arguments when no projection is configured)
97
+ equal to the receiving action's. Settledness of a routing source is checked only
98
+ when the support is first selected.
99
+
100
+ A later disposition, approval lapse, outcome change or target write of a routing
101
+ source never revokes it.
102
+
103
+ Retrieval scans `interpretation` and `reviewer_routing` records together under
104
+ the same bounds and lexical matcher. A routing example is returned only when its
105
+ digest verifies, its attempt and evidence hashes are unchanged, the source item
106
+ is still readable in the same tenant and confidential scope, every generation
107
+ recipient is allowed by the source's live provider policy, and (for a chosen
108
+ handler) the action is still reviewer-selected and settled — applied
109
+ (`executing`, `succeeded`) or `authorized` with an unexpired approval, on an item
110
+ without a disposition — its binding is still the current revision, and the handler with its candidate
111
+ references is currently offered to the receiver. A `none` choice is
112
+ revalidated under the first currently offered receiving handler the source item
113
+ authorizes. Routing examples carry `kind: 'reviewer_routing'`,
114
+ `judgment: 'correct'`, `routing: { choice, reason }`, `handlerId: 'none'` with an
115
+ empty action for closed items, and no model/prompt/configuration versions, since
116
+ no suggestion was judged. They are included in provider input and selection
117
+ provenance exactly like interpretation examples and are rechecked by the same
118
+ lineage gates.
119
+
120
+ `suggestRoutingRule` accepts `reviewer_routing` support only for `choice: 'handler'`
121
+ records whose action is settled in the same sense when the support is chosen
122
+ (a lapsed, unapplied approval or a disposition excludes it) and whose handler, version and
123
+ full arguments match the receiving binding and each other, from distinct source
124
+ items, under the live routing projection; `none`, rejected, deferred and
125
+ re-corrected records never support a rule. Suggestions and adoptions keep every existing #3676
126
+ rule: no catalog, candidate, permission or automation expansion, and
127
+ `automaticActionEligible: false`. Reviewer-selected actions themselves are refused
128
+ by `authorizeAutomatic`. They are routing choices, never labels for a model
129
+ suggestion: `recordFeedback` refuses an interpretation judgment on one (even
130
+ when the item also carries a generation, and whatever its decision), retrieval
131
+ and rule support ignore interpretation records bound to one, and `ActionReview`
132
+ hides the correctness buttons for them. A disposition says nothing about
133
+ whether a suggestion was right, so interpretation judgments on a resolved item
134
+ still count. Only
135
+ `startReviewerAction` mints that origin; `createAction` refuses it.
136
+
33
137
  ## Retrieval and provider boundaries
34
138
 
35
139
  `retrieveFeedback({ itemId, query })` derives the current offered catalog,
package/PROPOSALS.md CHANGED
@@ -85,6 +85,25 @@ Expired, cancelled, revoked, retried or superseded workers cannot read or publis
85
85
  current lease and each offered handler/candidate are rechecked immediately before
86
86
  each outbound generative/decision boundary and before publication.
87
87
 
88
+ ### Inline interpretation
89
+
90
+ Receipts created with `dispatch: 'inline'` (text/structured parts only) run the
91
+ same stages in-process through `interpretInline(itemId, { requestId, extraction
92
+ })`: an immutable extraction revision claimed under a fenced lease and extracted
93
+ with the built-in UTF-8 extractor, then `prepareGeneration` and a fenced
94
+ interpretation revision generated by `generateProposals`. The analysis lease,
95
+ catalog digest and live catalog/candidate checks, source pin, provenance and
96
+ `StageFailure` categories are identical to the job path; no dispatch intent is
97
+ ever deliverable and nothing is enqueued. A retry with the same `requestId`
98
+ replays the recorded revisions rather than re-preparing against a superseded
99
+ source, also under concurrency. The interpretation append rechecks, under the
100
+ item lock, that its pinned extraction is still the latest revision and refuses
101
+ a superseded pin: first through the general latest-extraction check that
102
+ every interpretation append runs (`GenerationSourceStaleError`), then by the
103
+ inline check that the pinned attempt is still current (`Generation source changed`). Generated suggestions still require explicit preview and a human
104
+ decision, either through the reviewer surface or the submitter-scoped surface
105
+ described in [REVIEW.md](REVIEW.md).
106
+
88
107
  ## Providers and validation
89
108
 
90
109
  `createSDKProposalGenerator(client, identity, {maxTokens,timeoutMs})` uses an
@@ -161,6 +180,64 @@ The SDK adapter constrains replies with a `json_schema` response format built
161
180
  from the validator's schema and the fixed instructions describe the shape; a
162
181
  custom `ProposalGenerator` receives no schema, so its raw reply meets the
163
182
  validator directly and the same reason is recorded.
183
+ The provider-facing schema is made portable (`portableResponseSchema`, #4260):
184
+ keywords that common OpenAI-compatible structured-output backends reject are
185
+ removed at schema positions, including inside handler argument schemas
186
+ (`uniqueItems`, `contains`, `multipleOf`, `format`, `patternProperties`,
187
+ `propertyNames`, `min`/`maxProperties`, `not`, `if`/`then`/`else`,
188
+ `dependent*`, `unevaluated*`, and the `default`/`examples` annotations; the
189
+ full list and reasons are in `src/proposal-sdk.ts`). With `uniqueItems`, for
190
+ example, vLLM's xgrammar feature check flags the schema, its auto mode routes
191
+ it to llguidance, and llguidance answers
192
+ `400 Grammar error: Unimplemented keys: ["uniqueItems"]`. This only loosens the
193
+ grammar: the validator still enforces the constraints, including unique
194
+ `alternatives` and `missingFields`, and the fixed instructions ask for distinct
195
+ entries. The exception is `format`, which is advisory: the validator runs Ajv
196
+ with `validateFormats: false`, so `format` is enforced nowhere. When
197
+ `patternProperties` is removed, the same object's `additionalProperties` is
198
+ removed too, so the grammar does not shrink to `{}`. The `contentSchema` annotation is omitted as well, so nested unsupported
199
+ constraints cannot leak through an unvisited schema position. A backend may
200
+ recursively inspect typed schema-like objects even inside `enum` or `const`
201
+ data. When such data contains an unsupported keyword, the whole containing
202
+ `enum`/`const` constraint is omitted conservatively; literal data is never
203
+ rewritten. Harmless property names and ordinary enum/const data stay intact.
204
+ The original validator remains authoritative for omitted literal constraints.
205
+ There is no automatic `json_object` fallback.
206
+
207
+ Invariant: the provider copy only ever accepts a superset of what the original
208
+ accepts. Each removed keyword is a conjunct, so removing it only widens. The two
209
+ places where removal would narrow are rewritten:
210
+ - every `oneOf` becomes `anyOf` (or joins an existing `anyOf` through `allOf`),
211
+ because loosened `oneOf` branches could both match and reject a value the
212
+ original accepted;
213
+ - an object that loses `patternProperties` also loses `additionalProperties`.
214
+
215
+ Provider-only `$ref`, `$dynamicRef`, and `$recursiveRef` constraints are also
216
+ removed. Rewriting or deleting a subschema can invalidate pointer or anchor
217
+ targets, including targets inside nested `$id` resources. Dropping references
218
+ conservatively widens the grammar without resolving untrusted schema resources;
219
+ property names and enum/const data named `$ref` remain unchanged. Referenced
220
+ constraints, recursion, and dynamic scope remain enforced by the original
221
+ validator, so provider guidance for reference-based arguments is less precise.
222
+
223
+ The validator still checks the original, including `oneOf` exclusivity. A
224
+ property-style regression checks original-valid ⇒ portable-valid across the
225
+ removed keywords. Handler argument schemas may still carry `allOf` through
226
+ unchanged; its support across backends is unverified, so check such a handler
227
+ against the deployed backend.
228
+
229
+ A generator call that throws ends the attempt as `provider_error` with the
230
+ `unavailable` warning and `provenance.failure` `{category: 'unavailable',
231
+ reason}`. The reason is value-free by construction: a provider's own text can
232
+ quote the prompt or document, so it is never copied. The reason is built only
233
+ from a fixed phrase and, for an SDK `AIError` only, the HTTP status (from a
234
+ structured field, or from the exact `<status> ...` / `TypeSafe request failed
235
+ (<status>): ...` shapes the SDK produces), the SDK error code (from the closed
236
+ list of codes the SDK raises) and, for a schema rejection, quoted tokens that
237
+ are JSON Schema keywords. Any other error contributes at most a structured
238
+ numeric status. For example: `provider rejected the response schema (HTTP 400,
239
+ code API_ERROR); unsupported response-schema keywords: uniqueItems`, or just
240
+ `provider call failed`. It is cut at 500 characters.
164
241
 
165
242
  `createSDKProposalDecisionClient(client)` adapts an existing SDK client’s optional
166
243
  capability without changing its errors or protocol. Optional `decision` uses public core `executeDecision` and the existing SDK
package/README.md CHANGED
@@ -197,11 +197,22 @@ Mount the `/svelte` inbox and review components through an authenticated host us
197
197
  the browser-safe `IntakeReviewHost` DTO contract. See [REVIEW.md](REVIEW.md) for
198
198
  exact revision-bound callbacks, live evidence access, logical split corrections,
199
199
  assignment ownership, and the maintained upload-to-domain-result browser fixture.
200
+ `IntakeWorkspace` puts the inbox, the selected item with its original inline,
201
+ the message box and approve-and-advance on one page with keyboard next/previous;
202
+ it uses the same callbacks and adds no authority (REVIEW.md, "Single-page triage
203
+ workspace").
200
204
 
201
205
  Optional explicit feedback and scoped example retrieval are documented in
202
206
  [FEEDBACK.md](FEEDBACK.md). Approval and execution outcomes remain separate from
203
207
  interpretation correctness; routing policy adoption is explicit and versioned.
204
208
 
209
+ Reviewers can start any offered handler for an item (`startReviewerAction`) or
210
+ close it as handled without action (`resolveItem`); the published host adapter
211
+ wires both as `startAction`/`resolve` and exposes the offered `catalog`. Hosts
212
+ with their own transport add the two POST operations behind the same session,
213
+ CSRF and reviewer checks as `decide`. The `intake_dispositions` table requires
214
+ a manifest migration before upgrading.
215
+
205
216
  ## Recipe preview
206
217
 
207
218
  `ingestion.inbox` and `ingestion.review` declare the user-facing inbox and
@@ -248,6 +259,121 @@ and `maxSuggestions` are each capped at 20; `timeoutMs` is capped at 2147483647.
248
259
  Invalid limits are rejected when constructing `IngestionService`, before receipt
249
260
  or interpretation. See [REVIEW.md](REVIEW.md) for the published server review host.
250
261
 
262
+ ## Trusted internal sources
263
+
264
+ A host with a trusted internal source, such as chat messages recorded in
265
+ smrt-messages, can receive **by reference**, interpret **inline**, and let the
266
+ **submitter** confirm their own proposal (#4085). None of these modes changes
267
+ copied receipts: their identity, validation, queueing and retention are
268
+ untouched, and a replay that switches mode conflicts.
269
+
270
+ **Receive by reference.** A part may be `{ partId, reference: { owner, id,
271
+ version? }, snapshot: { text } | { structured } }` instead of bytes. The
272
+ service derives UTF-8 bytes once (`text/plain` or canonical `application/json`;
273
+ text, reference fields and owner identity containing NUL or unpaired
274
+ surrogates are rejected `invalid` on every engine, and `expireOwned` refuses
275
+ them, so distinct owners can never collapse onto one stored key),
276
+ hashes them into the receipt identity, and stores only that snapshot in the
277
+ durable evidence row; no AssetStore object or asset record is created, and
278
+ `readEvidence` serves the snapshot after the same hash/length check. The
279
+ reference is identity and provenance, never authority. Such parts must be
280
+ text or structured; files still arrive as copied bytes.
281
+
282
+ **Owner-bound retention.** `retention: { kind: 'owner', owner: { kind, id },
283
+ version, expiresAt, replayUntil, acceptAfter }` binds the receipt to its
284
+ owner's lifecycle. The time deadlines remain required and validated exactly as
285
+ before: they are the hard privacy ceiling, and `sweepRetention` and execution's
286
+ policy ceiling still apply. When the owner is deleted the host calls
287
+ `expireOwned({ kind, id })` from its deletion path; every active receipt bound
288
+ to that owner is revoked and removed through the existing `expire` /
289
+ `repairDeletions` protocol, which redacts the snapshot together with every
290
+ other payload, and the deleted delivery is never re-admitted. The owner key is
291
+ recorded in the indexed `owner_key` column (`<kind>:<id>`, so `kind` cannot
292
+ contain `:`). Deletion redaction blanks `owner_key` and `submitter_id` together
293
+ with every payload: only the receipt identity (`delivery_key`) and the captured
294
+ ceiling survive, and the ceiling loses `execution.principalId` unless an
295
+ external action is still `executing` or `outcome_unknown`. So once nothing is
296
+ left to reconcile, a redacted row no longer links an owner to an actor, and a
297
+ repeated `expireOwned` finds nothing. Redaction also overwrites every
298
+ identity- or content-derived digest (`payload_digest`, `scope_digest`, analysis
299
+ `input_digest`, attempt `output_digest`, plan `digest` and proposal, decision
300
+ and execution `binding_hash`) with the fixed marker `redacted`; successful
301
+ result digests remain as the replay tombstone. The principal is retained only
302
+ for an external action that was still in flight when the item was redacted,
303
+ because reconciling it authorizes under that principal; it is dropped when that
304
+ reconciliation reaches a final state, or by the next `repairDeletions` /
305
+ `sweepRetention` pass. For a self-approvable receipt the principal is the
306
+ submitter, so until then it identifies the sender. Later deletion sweeps pass
307
+ this redacted ceiling to the host's `authorize({ operation: 'delete' })`, so
308
+ delete authorization must not depend on `execution.principalId`: a host that
309
+ requires it would refuse, and the sweep would stop with later deletion intents
310
+ still pending. A successful result digest
311
+ is a plain SHA-256 of the canonical result, so it can confirm a guessed result:
312
+ keep handler results to record ids or opaque references, never content.
313
+
314
+ A replay of a deleted delivery is rejected and never becomes fresh work. While
315
+ deletion is still pending it returns `rejected/expired`; once redaction has
316
+ completed the identity digests are gone, so it returns `rejected/conflict`.
317
+ Sources treat both as terminal and must not retry. `delivery_key` stays in plaintext because it is
318
+ the unique receipt identity and the package holds no secret to key it: hosts
319
+ must use an opaque value (for example a random UUID recorded with the message)
320
+ and must not encode owner ids, sender ids or content in `deliveryKey`.
321
+
322
+ Host ordering for owner deletion: first stop issuing receipts for the owner,
323
+ then call `expireOwned`, and call it again once in-flight receipts settle (or
324
+ from a periodic sweep). A receipt still in flight when its owner is expired
325
+ returns `{ kind: 'rejected', category: 'expired' }` rather than `accepted`, but
326
+ one that reserved its row after the first `expireOwned` is only found by the
327
+ later call; until then the time ceiling bounds it. Services are confidential-scope bound, so
328
+ a host whose owner has receipts in several confidential scopes calls
329
+ `expireOwned` from each scope's service; a foreign tenant or scope sees zero.
330
+
331
+ **Inline mode.** `receive({ ..., dispatch: 'inline' })` requires text/structured
332
+ parts only, records the route provenance but **no deliverable dispatch intent**
333
+ for any revision (every intent is retained as `completed`, so no worker or
334
+ `repairDispatches` ever picks it up), and does not enqueue. The host then calls
335
+ `interpretInline(itemId, { requestId, extraction: { configurationRevision,
336
+ limits } })`, which runs in-process exactly what the job path runs: an
337
+ immutable `stage: 'extract'` revision claimed under a fenced lease and
338
+ extracted with the built-in UTF-8 extractor, then `prepareGeneration` and the
339
+ `stage: 'interpret'` revision with the same analysis lease, catalog-live
340
+ checks, provenance and failure categories as `generateProposals`. A retry with
341
+ the same `requestId` replays the immutable revisions, including when two
342
+ identical calls race. The interpretation is appended under the item lock only
343
+ while its pinned extraction is still the latest revision; if another request
344
+ reprocessed the item meanwhile it fails with `GenerationSourceStaleError` (the
345
+ general latest-extraction check every interpretation append runs, #4228) or,
346
+ for a pin whose extraction attempt was replaced, `Generation source changed`. The result reports each
347
+ stage's attempt and safe error and, when generation completed, the authorized
348
+ `getCompletedAnalysis` snapshot. Inline is for send-triggered suggestions that
349
+ must not depend on the scheduler; it is not a bypass of review or execution.
350
+
351
+ **Submitter-scoped approval.** The service records `submitter_id` as the
352
+ authenticated `scope.actorId` of the receiving service, server-side; payload
353
+ and source metadata never supply it. `listOwnProposals({ cursor?, limit? })`
354
+ lists only this actor's own active submissions with their saved review pages,
355
+ reloaded under current item/handler/target authority, without the reviewer
356
+ inbox. A submission the actor can no longer read is skipped before projection
357
+ and never named by the cursor; a readable one whose reviews are denied by
358
+ ingestion authority is returned as `{ item, reviews: null, unavailable: true }`
359
+ with no reason, so one revoked grant never hides the others. `submitOwnDecision(input)` accepts `approve`, `reject` or `defer` (never
360
+ `correct`) under the `review_own` authority: the host's execution `authorize`
361
+ must return `submitter: true` for that actor, the application policy must opt
362
+ in with `submitterReview: true` (narrower layers may only disable it), and the
363
+ item's recorded submitter must equal the current actor. Every other check is
364
+ the ordinary one, and `applyAction` remains the single once-only effect path
365
+ under the approver's live grants. Because the effect runs as the receipt's
366
+ captured `capturedCeiling.execution.principalId`, `review_own` additionally
367
+ requires that principal to be the submitter itself (`submitter_id ===
368
+ scope.actorId === principalId`), checked under the item lock at decision time
369
+ and again at apply; a receipt captured for a bot or service principal can only
370
+ be approved by a reviewer. The decision is recorded with `mode` (`review` or
371
+ `review_own`) and `selfDecision`, which is true whenever the deciding actor is
372
+ the item's submitter, in either mode (mirrored as `decisionMode` /
373
+ `selfDecision` on feedback events), so a self-confirmation is never mistaken
374
+ for independent review and never contributes to automation eligibility. See
375
+ [REVIEW.md](REVIEW.md) and [execution integration](agents/execution.md).
376
+
251
377
  ## Dispatch route ownership and legacy recovery
252
378
 
253
379
  A receipt retains a versioned, server-owned `jobTarget` snapshot, and every