@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.
- package/FEEDBACK.md +104 -0
- package/PROPOSALS.md +77 -0
- package/README.md +126 -0
- package/REVIEW.md +272 -4
- package/agents/execution.md +72 -0
- package/dist/chunks/{execution-9CfjxX8I.js → execution-BzVQax0n.js} +421 -23
- package/dist/chunks/execution-BzVQax0n.js.map +1 -0
- package/dist/chunks/{feedback-CuUzWKzP.js → feedback-D3UTF8fm.js} +119 -21
- package/dist/chunks/feedback-D3UTF8fm.js.map +1 -0
- package/dist/chunks/{extraction-CJ-zrbg0.js → proposal-contracts-77Uo1B1j.js} +39 -39
- package/dist/chunks/proposal-contracts-77Uo1B1j.js.map +1 -0
- package/dist/chunks/{reviewer-instruction-Db3jDN9a.js → reviewer-instruction-CEc3AOuU.js} +2 -2
- package/dist/chunks/{reviewer-instruction-Db3jDN9a.js.map → reviewer-instruction-CEc3AOuU.js.map} +1 -1
- package/dist/dto.d.ts +69 -0
- package/dist/dto.d.ts.map +1 -1
- package/dist/execution-contracts.d.ts +4 -1
- package/dist/execution-contracts.d.ts.map +1 -1
- package/dist/execution-dto.d.ts +16 -0
- package/dist/execution-dto.d.ts.map +1 -1
- package/dist/execution.d.ts +58 -3
- package/dist/execution.d.ts.map +1 -1
- package/dist/execution.js +2 -2
- package/dist/feedback-dto.d.ts +12 -4
- package/dist/feedback-dto.d.ts.map +1 -1
- package/dist/feedback-internal.d.ts +9 -0
- package/dist/feedback-internal.d.ts.map +1 -1
- package/dist/feedback.d.ts +3 -0
- package/dist/feedback.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/manifest.json +321 -3
- package/dist/models.d.ts +15 -0
- package/dist/models.d.ts.map +1 -1
- package/dist/models.js +73 -2
- package/dist/models.js.map +1 -1
- package/dist/policy.d.ts +3 -0
- package/dist/policy.d.ts.map +1 -1
- package/dist/proposal-errors.d.ts +4 -0
- package/dist/proposal-errors.d.ts.map +1 -1
- package/dist/proposal-sdk.d.ts +54 -1
- package/dist/proposal-sdk.d.ts.map +1 -1
- package/dist/proposals.d.ts +2 -1
- package/dist/proposals.d.ts.map +1 -1
- package/dist/proposals.js +215 -10
- package/dist/proposals.js.map +1 -1
- package/dist/provider-failure.d.ts +3 -0
- package/dist/provider-failure.d.ts.map +1 -0
- package/dist/redaction.d.ts +9 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/review-dto.d.ts +14 -4
- package/dist/review-dto.d.ts.map +1 -1
- package/dist/review-host.d.ts.map +1 -1
- package/dist/server.d.ts +82 -10
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +366 -47
- package/dist/server.js.map +1 -1
- package/dist/smrt-knowledge.json +128 -22
- package/dist/svelte/components/ActionReview.svelte +39 -9
- package/dist/svelte/components/ActionReview.svelte.d.ts +8 -1
- package/dist/svelte/components/ActionReview.svelte.d.ts.map +1 -1
- package/dist/svelte/components/EvidenceViewer.svelte +60 -4
- package/dist/svelte/components/EvidenceViewer.svelte.d.ts +12 -1
- package/dist/svelte/components/EvidenceViewer.svelte.d.ts.map +1 -1
- package/dist/svelte/components/InboxView.svelte +160 -18
- package/dist/svelte/components/InboxView.svelte.d.ts +28 -1
- package/dist/svelte/components/InboxView.svelte.d.ts.map +1 -1
- package/dist/svelte/components/IntakeWorkspace.svelte +27 -0
- package/dist/svelte/components/IntakeWorkspace.svelte.d.ts +19 -0
- package/dist/svelte/components/IntakeWorkspace.svelte.d.ts.map +1 -0
- package/dist/svelte/components/ReviewView.svelte +264 -23
- package/dist/svelte/components/ReviewView.svelte.d.ts +18 -1
- package/dist/svelte/components/ReviewView.svelte.d.ts.map +1 -1
- package/dist/svelte/components/TriageView.svelte +669 -0
- package/dist/svelte/components/TriageView.svelte.d.ts +16 -0
- package/dist/svelte/components/TriageView.svelte.d.ts.map +1 -0
- package/dist/svelte/i18n.d.ts +39 -0
- package/dist/svelte/i18n.d.ts.map +1 -1
- package/dist/svelte/i18n.js +39 -0
- package/dist/svelte/index.d.ts +1 -0
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +1 -0
- package/dist/svelte/view-utils.d.ts +65 -0
- package/dist/svelte/view-utils.d.ts.map +1 -1
- package/dist/svelte/view-utils.js +145 -0
- package/package.json +13 -13
- package/dist/chunks/execution-9CfjxX8I.js.map +0 -1
- package/dist/chunks/extraction-CJ-zrbg0.js.map +0 -1
- package/dist/chunks/feedback-CuUzWKzP.js.map +0 -1
package/REVIEW.md
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
# Authenticated review surfaces
|
|
2
2
|
|
|
3
|
-
`@happyvertical/smrt-ingestion/svelte` exports `IntakeInbox`, `IntakeReview
|
|
4
|
-
`EvidenceViewer`. The browser-safe DTO entry exports their `IntakeReviewHost`
|
|
3
|
+
`@happyvertical/smrt-ingestion/svelte` exports `IntakeInbox`, `IntakeReview`,
|
|
4
|
+
`IntakeWorkspace` and `EvidenceViewer`. The browser-safe DTO entry exports their `IntakeReviewHost`
|
|
5
5
|
callback contract. Import styling/theme configuration from smrt-ui in the host.
|
|
6
6
|
Components use smrt-ui primitives and the `ingestion.*` i18n catalog.
|
|
7
|
+
Existing `IntakeInbox` hosts now keep the previous rows visible, with their
|
|
8
|
+
controls disabled, while a refresh or filter change loads; any failed read
|
|
9
|
+
(Load more included) still clears them.
|
|
7
10
|
|
|
8
11
|
The host authenticates every request, resolves tenant/actor/confidential scope
|
|
9
12
|
from its session, enforces CSRF on mutations, and constructs `IngestionService`.
|
|
@@ -103,6 +106,49 @@ Denied refresh clears prior originals, candidates, arguments and results.
|
|
|
103
106
|
Missing confidence is unavailable; page/time links exist only for observed
|
|
104
107
|
locations. Provider errors and unresolved/partial results remain review states.
|
|
105
108
|
|
|
109
|
+
## Submitter-scoped decisions
|
|
110
|
+
|
|
111
|
+
For trusted internal sources the person who submitted an item may confirm or
|
|
112
|
+
decline their own proposal without the reviewer inbox. `listOwnProposals`
|
|
113
|
+
returns only items whose server-recorded `submitter_id` equals the current
|
|
114
|
+
authenticated actor, each with its bounded saved review page reloaded under
|
|
115
|
+
current item, handler and target authority. An item the actor can no longer
|
|
116
|
+
read (the host `authorize` returns false or throws `IntakeAccessDeniedError`)
|
|
117
|
+
is skipped before projection: the page is filled from readable items only, in
|
|
118
|
+
50-row batches, and `nextCursor` names the last emitted item and is present
|
|
119
|
+
only when a further readable item was actually found. Neither the entries, the
|
|
120
|
+
page size nor the cursor reveal a hidden item. If one call has to skip 1000
|
|
121
|
+
hidden submissions it fails with `Own proposal scan limit` rather than return a
|
|
122
|
+
short page or a cursor that stops inside the hidden run; this error is the only
|
|
123
|
+
signal, and it says only that such a run exists. Any other `authorize` error
|
|
124
|
+
propagates instead of shrinking the list. An item that is still readable but
|
|
125
|
+
whose reviews are denied is returned as `{ item, reviews: null, unavailable:
|
|
126
|
+
true }`: that covers ingestion's own denials and any throw from the host
|
|
127
|
+
`assertTarget` (deleted or revoked targets), whose text is never returned.
|
|
128
|
+
Integrity, validation and database errors from ingestion itself propagate. The
|
|
129
|
+
reviewer `listReviews` surface keeps propagating host target errors unchanged.
|
|
130
|
+
`submitOwnDecision` takes the same `ReviewInput` CAS fields and request ID as
|
|
131
|
+
`submitDecision` and accepts `approve`, `reject` and `defer` only; `correct`
|
|
132
|
+
belongs to the reviewer surface. It runs under the `review_own` execution
|
|
133
|
+
operation: the host's `authorize` must return `submitter: true`, the resolved
|
|
134
|
+
policy must have `submitterReview` enabled, and the recorded submitter must be
|
|
135
|
+
the current actor and also the receipt's captured execution principal (the
|
|
136
|
+
identity the effect runs as), checked under the item lock and again at apply.
|
|
137
|
+
A receipt captured for a bot or service principal is never self-approvable. Policy enablement, handler/operation allowlists, captured
|
|
138
|
+
ceiling, confidential access, retention and the first-valid-decision lock are
|
|
139
|
+
unchanged. The decision is a human decision by that actor, recorded with
|
|
140
|
+
`mode: 'review_own'`; reviewer decisions carry `mode: 'review'`. In both modes
|
|
141
|
+
`selfDecision` is true exactly when the deciding actor is the item's
|
|
142
|
+
submitter, so a reviewer approving their own submission is never independent
|
|
143
|
+
review. A self-decision is not a reviewer judgment: it never feeds
|
|
144
|
+
automation eligibility (machine authorization remains the separate evaluated
|
|
145
|
+
`kind: 'machine'` path) and its feedback events carry the same marker. The
|
|
146
|
+
`unavailable` entry discloses nothing about the denied handler or target.
|
|
147
|
+
`applyAction` then runs once under the approver's live grants exactly as
|
|
148
|
+
for reviewer decisions. Hosts render this surface from their own authenticated
|
|
149
|
+
session; presentation state confers no authority, and a source payload cannot
|
|
150
|
+
nominate a submitter.
|
|
151
|
+
|
|
106
152
|
## Assignment and logical splits
|
|
107
153
|
|
|
108
154
|
Assignment is explicitly host-owned, durable and versioned. `assign` receives
|
|
@@ -283,6 +329,215 @@ choice and execution success. No learning, feedback storage or rule promotion is
|
|
|
283
329
|
implemented by these components; #3676 owns those services. Do not pass feedback
|
|
284
330
|
or a feedback callback to readers without the relevant host grant.
|
|
285
331
|
|
|
332
|
+
## Reviewer-chosen actions and "handled, no action"
|
|
333
|
+
|
|
334
|
+
A reviewer is not limited to model suggestions. `ItemReviewView.catalog` carries
|
|
335
|
+
the handlers the item's catalog currently offers (`listHandlers`, discovery media
|
|
336
|
+
types and live preview authority; it is presentation, never a grant). The optional
|
|
337
|
+
`startAction` callback forwards `StartActionInput` (`itemId`, `handlerId@version`,
|
|
338
|
+
explicit `args`, `requestId`, optional `reason`) to `startReviewerAction`, which
|
|
339
|
+
requires the current reviewer grant under that handler, a completed or partial
|
|
340
|
+
attempt on the current analysis revision, and an operation handler the item's
|
|
341
|
+
evidence media types offer. Plan handlers go through `previewPlan`. The server
|
|
342
|
+
creates an action keyed `reviewer:<requestId>` with origin `reviewer-selected`
|
|
343
|
+
(`createAction` refuses that key prefix and origin, so process authority cannot
|
|
344
|
+
mint reviewer provenance; re-previewing such an action with `previewProposal`
|
|
345
|
+
needs the reviewer grant and keeps its handler fixed, so only its arguments can
|
|
346
|
+
be corrected),
|
|
347
|
+
runs the handler's normal `validate`/`preview`, publishes a `waiting_review`
|
|
348
|
+
revision and requires the same explicit approval and apply as a generated
|
|
349
|
+
proposal. Replays with the same request return the same review; changed arguments
|
|
350
|
+
or reason conflict. Such actions are never automation-eligible: `authorizeAutomatic`
|
|
351
|
+
refuses them regardless of policy opt-ins. The UI pre-fills arguments only from a
|
|
352
|
+
same-handler suggestion (for example a `needs_review` one) and a single original's
|
|
353
|
+
evidence references, lists the required fields still missing, and submits exactly
|
|
354
|
+
what the reviewer typed. An item whose current attempt failed outright must be
|
|
355
|
+
reprocessed first; an interpretation that completed with `provider_error`,
|
|
356
|
+
`unknown` or no suggestions can be acted on directly. `ReviewAction.origin`
|
|
357
|
+
exposes the provenance so `ActionReview` can label human-started actions.
|
|
358
|
+
|
|
359
|
+
The optional `resolve` callback forwards `ResolveItemInput`
|
|
360
|
+
(`disposition: 'resolve_no_action'`, `requestId`, optional `reason`) to
|
|
361
|
+
`resolveItem`. It needs reviewer authority under any handler the item's captured
|
|
362
|
+
ceiling offers and refuses while an action is `authorized` with a live approval,
|
|
363
|
+
`executing`, `outcome_unknown` or `succeeded`; waiting, deferred, rejected and
|
|
364
|
+
failed actions, and authorized ones whose approval expired, may remain (neither
|
|
365
|
+
an exhausted failed action nor a lapsed approval can be rejected, so they must
|
|
366
|
+
not keep the queue open). Afterwards only reject remains: those actions can no
|
|
367
|
+
longer be approved, corrected, deferred, re-previewed, retried, applied or
|
|
368
|
+
automated. `submitDecision` refuses every decision but `reject`, no new revision
|
|
369
|
+
is published for the item, `applyAction` rechecks the disposition before any
|
|
370
|
+
effect, and `analyze`, `reviseLogicalSplit` and `createAction` refuse the item
|
|
371
|
+
under its lock before any write, so it is never reanalysed, re-split or given a
|
|
372
|
+
new action. `resolveItem` supersedes any queued or running analysis under the
|
|
373
|
+
same lock and `claimAnalysis` refuses a resolved item, so no provider is called
|
|
374
|
+
after a human closed it; the item's `processingState` keeps its last value (it
|
|
375
|
+
describes analysis, not review), and the inbox state `resolved` is authoritative.
|
|
376
|
+
`publishProposal` (process authority) refuses a resolved item and any
|
|
377
|
+
reviewer-selected action. The UI hides Approve, Defer, Apply, every edit control, suggestion
|
|
378
|
+
preview and split controls on a resolved item.
|
|
379
|
+
Resolved items hide the instruction editor and stop reinterpretation polling, even if an older processing projection is still queued. Recording an instruction cannot append a new revision after resolution.
|
|
380
|
+
|
|
381
|
+
Paged lists project the resolved state and disposition time in the same bounded, scope-filtered query, so state filters and row controls agree with a full item reload.
|
|
382
|
+
|
|
383
|
+
The append-only `intake_dispositions` record is terminal: replaying the same
|
|
384
|
+
request returns the same disposition, any other request conflicts, and
|
|
385
|
+
`startReviewerAction` refuses the item. `IntakeItemDTO.disposition` projects only
|
|
386
|
+
`{ state: 'resolved', at }` to any item reader (`listItems` reads it in the same
|
|
387
|
+
statement as the page); the reviewer and reason are returned only by
|
|
388
|
+
`getItemDisposition`, which requires reviewer authority, and the review hosts put
|
|
389
|
+
them on `ItemReviewView.disposition` (omitted when that read is denied, so the
|
|
390
|
+
view shows only "Handled, no action"). `loadReviewSummary` reports inbox state `resolved` unless an action
|
|
391
|
+
actually succeeded. Privacy deletion and retention expiry redact the reason and
|
|
392
|
+
reviewer (`intake_dispositions.data`) and keep only the kind. The disposition is
|
|
393
|
+
distinct from `reject`, which still means the proposal was wrong. Closing an item
|
|
394
|
+
records `reviewer_routing` feedback immediately; a started action records it only
|
|
395
|
+
when the reviewer approves it (see FEEDBACK.md). Capture is an optional projection
|
|
396
|
+
that never vetoes the decision or disposition.
|
|
397
|
+
|
|
398
|
+
`startReviewerAction` applies the handler's discovery media types and the live
|
|
399
|
+
reviewer grant/policy for that handler. It intentionally does not apply the
|
|
400
|
+
proposal catalog's provider-policy filter or `maxHandlers` slice: those bound what
|
|
401
|
+
a model may be offered, and a human choosing a handler sends nothing to a
|
|
402
|
+
provider. The UI picker shows the offered catalog, so a handler hidden from it is
|
|
403
|
+
only reachable by a reviewer who already holds `review` authority for it. The maintained
|
|
404
|
+
browser fixture serves both operations behind CSRF and the same session scope.
|
|
405
|
+
|
|
406
|
+
## Single-page triage workspace
|
|
407
|
+
|
|
408
|
+
`IntakeWorkspace` (#4215) composes the existing pieces so a reviewer works the
|
|
409
|
+
whole queue on one page: the paged inbox as a list pane, the review view of the
|
|
410
|
+
selected item beside it (the original above its action form), and the
|
|
411
|
+
reviewer-instruction message box of the item pane. Props: `host`, `contextKey`
|
|
412
|
+
(replace on session change, as for the other components), optional initial
|
|
413
|
+
`itemId`, `pollMs`, `rowAside` and `onselect(itemId | undefined)`, which only
|
|
414
|
+
reports the selection (to keep a URL in step) and never navigates. It adds **no
|
|
415
|
+
host callback and no server authority**: every read is the host's `list` and
|
|
416
|
+
`load`, every action still goes through the handler preview and an explicit
|
|
417
|
+
approval and apply under the approver's live grants, and the message steers the
|
|
418
|
+
model only, exactly as in `IntakeReview`.
|
|
419
|
+
|
|
420
|
+
- **Inline original.** In the item pane `EvidenceViewer` shows one part at a
|
|
421
|
+
time (`layout="pager"`), opening on the first part that renders inline (the
|
|
422
|
+
uploaded file rather than its capture record), with previous/next original.
|
|
423
|
+
A PDF with more than one *observed* page (extraction locations or proposed
|
|
424
|
+
page groups; `pages` prop) gets previous/next page, which reloads the frame
|
|
425
|
+
at the same-origin URL plus `#page=n`; only `evidenceUrl`-approved URLs ever
|
|
426
|
+
reach a frame. `IntakeReview` gets the same page navigation over its
|
|
427
|
+
unchanged stacked list of parts. The chosen part and page belong to the item
|
|
428
|
+
view (bindable `chosen`/`page`), so they survive in-place reloads, Prepare
|
|
429
|
+
review, Approve and Apply.
|
|
430
|
+
- **Keyboard.** J/K and Alt+Down/Alt+Up move to the next/previous row (loading
|
|
431
|
+
the next page at the end), ignored while focus is in a text field, select or
|
|
432
|
+
editable region; "Previous item"/"Next item" buttons carry
|
|
433
|
+
`aria-keyshortcuts`. The selected row is `aria-current`, and every selection
|
|
434
|
+
moves focus to the item pane region, named by the row's label. A navigation
|
|
435
|
+
that waited for the next page is dropped if the reviewer chose another item
|
|
436
|
+
meanwhile.
|
|
437
|
+
- **Unsent input.** The pane holds unsent input when it has instruction text,
|
|
438
|
+
an edited suggestion draft or split draft, a chosen start handler or start
|
|
439
|
+
reason, a no-action reason, or an action editor with edited arguments, a
|
|
440
|
+
reason or a feedback comment. It is read from the drafts themselves, so it
|
|
441
|
+
holds while a reload has cleared the view; while the view is denied nothing
|
|
442
|
+
is shown and nothing counts (the drafts stay in memory and return with an
|
|
443
|
+
authorized refresh, as before). A reviewer-initiated move (J/K, Alt+arrows,
|
|
444
|
+
Next/Previous item, a row's Review button) then asks inline, in a group
|
|
445
|
+
labelled by its note: "Stay on this item" takes focus and returns it to the
|
|
446
|
+
field the reviewer last used; "Discard and open the other item" proceeds.
|
|
447
|
+
When an item is closed without action (here or by another session), input a
|
|
448
|
+
closed item cannot take
|
|
449
|
+
(instruction, suggestion and split drafts, a started action, a no-action
|
|
450
|
+
reason) is discarded.
|
|
451
|
+
- **Live state.** A row's status follows the item: "Interpreting…", "Ready to
|
|
452
|
+
review", "Needs review" with missing argument names or the generation
|
|
453
|
+
outcome, "Awaiting approval", "Approved, not yet applied", "Failed" with a
|
|
454
|
+
safe category, "Handled, no action", or the listed inbox state. Rows the
|
|
455
|
+
reviewer has not opened show the listed state only; reasons come from a
|
|
456
|
+
loaded view, never from server exception text. The open item's own polling
|
|
457
|
+
(`ReviewView`, unchanged backoff and three-failure fail-closed) reports each
|
|
458
|
+
displayed view and the workspace patches that row in place
|
|
459
|
+
(`InboxView.patch`, keeping the listed label and preview, and the row's
|
|
460
|
+
assignment unless the patch carries a newer assignment version, so a view
|
|
461
|
+
read before an assignment never rolls the row or the next expected version
|
|
462
|
+
back), so the row follows each newer read of the form and the list is
|
|
463
|
+
**not** re-listed per tick. An item the
|
|
464
|
+
reviewer leaves while its stage is in flight, or before the pane showed the
|
|
465
|
+
result of its last operation (an instruction just recorded), keeps
|
|
466
|
+
following: the workspace re-reads followed items with the same `load` (first
|
|
467
|
+
review page) on the pane's backoff, at most **5** reads per tick, one after
|
|
468
|
+
another, least recently read first (so every followed row is reached), with
|
|
469
|
+
only one tick running at a time. At most **20** items are followed; beyond
|
|
470
|
+
that the longest-followed one (the earliest started, whatever the read
|
|
471
|
+
rotation) stops. An item left while its pane was mid-operation (an Apply not
|
|
472
|
+
yet answered) keeps following while a read still shows an action authorized
|
|
473
|
+
or executing: exactly **3** more reads when it never answers, the fourth
|
|
474
|
+
ending the follow. That debt belongs to the operation, so it survives
|
|
475
|
+
reopening and leaving the item again until a view shows the operation
|
|
476
|
+
answered or the reads run out; leaving mid-operation again starts a full
|
|
477
|
+
debt for the new operation. Each follow has a
|
|
478
|
+
generation: an answer from an earlier follow of the same item (one asked
|
|
479
|
+
before the reviewer reopened or left it again) is dropped, and its failure
|
|
480
|
+
does not count toward the new follow's three. An item stops being followed
|
|
481
|
+
when it settles, its view is denied, three reads in a row throw, its row
|
|
482
|
+
leaves the displayed list (a filter or a refreshed page removed it), the
|
|
483
|
+
list fails closed, or the cap drops it; a denial, the failure stop or the
|
|
484
|
+
cap drops its cached status and restores its entry exactly as its page read
|
|
485
|
+
returned it (never a patched entry). Opening an item drops its cached
|
|
486
|
+
status until the pane shows a view; a denial in the open pane (a failed
|
|
487
|
+
action, the pane's own fail-closed poll, or a first load that failed) does
|
|
488
|
+
the same as a follow denial, clears the announcement, and the item is not
|
|
489
|
+
followed when left. A settled stage that was seen running is announced in a
|
|
490
|
+
polite status region ("badger-po.pdf: Ready to review"), only while its row
|
|
491
|
+
is displayed; a refreshed page or a failed list clears it.
|
|
492
|
+
Other rows update on Refresh, a filter change or when opened; there is no
|
|
493
|
+
streaming. While a refresh or "Load more" is pending, the displayed rows stay
|
|
494
|
+
(controls disabled) and keep taking patches. Every read (a page, a pane
|
|
495
|
+
view, including an unchanged poll, and a follow read) is numbered when its
|
|
496
|
+
request starts, and each row shows the entry of its newest read, whatever
|
|
497
|
+
the source: a page read before a pane or follow read never regresses the
|
|
498
|
+
row, and a page read after them replaces what they showed. The same rule
|
|
499
|
+
governs a follow: a follow read older than the row's newest read changes
|
|
500
|
+
no status, does not stop the follow and announces nothing (a newer read
|
|
501
|
+
settles it), so the announcement never contradicts the row. Cached status reasons follow the same ordering, even when entries have identical
|
|
502
|
+
fingerprints. "Load more" continues the
|
|
503
|
+
filter the rows were listed under, not filter inputs edited since, and does
|
|
504
|
+
not repeat a row already shown. A failed "Load more" fails closed like any
|
|
505
|
+
failed read (the host cannot tell a transient failure from a revoked
|
|
506
|
+
grant): the rows clear and the list shows its error.
|
|
507
|
+
- **Approve and advance.** After an approve, apply or "Handled, no action" whose
|
|
508
|
+
reload left nothing for the reviewer (closed without action, or an action
|
|
509
|
+
succeeded and none is waiting for approval, waiting to be applied, executing,
|
|
510
|
+
unknown or failed; stale, rejected and deferred actions do not hold it), the
|
|
511
|
+
item's row is patched and leaves the list when it no longer matches the
|
|
512
|
+
listed state/assignee filter, and the next row that is not closed or
|
|
513
|
+
completed opens and takes focus, with no navigation and no re-listing. An
|
|
514
|
+
approval that leaves an apply step keeps the item open with Apply in place.
|
|
515
|
+
Unloaded saved-review pages and an active background stage also prevent
|
|
516
|
+
settlement. Advancing skips completed inbox pages until a reviewable row
|
|
517
|
+
appears; a page that makes no progress stops the walk.
|
|
518
|
+
It never advances over unsent input on the same item: the item stays open,
|
|
519
|
+
the input stays, and a note says it is finished but has unsent input; the
|
|
520
|
+
reviewer moves on explicitly, through the confirmation; input typed while
|
|
521
|
+
the next page loads counts too. When no open row is displayed, the advance
|
|
522
|
+
appends pages of finished rows until one has an open row or no page
|
|
523
|
+
remains, at most **5** appends. After each append, the advance (like
|
|
524
|
+
J/K at the end of the list) finds its place again by id: just after the
|
|
525
|
+
item it left, or, if that row has gone, at the first row that followed it
|
|
526
|
+
and is still listed, or at the first new row, so a row that left the list
|
|
527
|
+
meanwhile never shifts it onto the wrong item. Its notes (and "nothing else
|
|
528
|
+
needs review") are written into one persistent polite status region. The queue is never reported empty when
|
|
529
|
+
that is unknown: past the bound, or when an append cannot start (a refresh
|
|
530
|
+
is pending) or is superseded, the item stays open with a note to try Next
|
|
531
|
+
item again, which still works; when an append fails, the list has failed
|
|
532
|
+
closed (no rows, Next disabled), so the item stays open with a note to
|
|
533
|
+
refresh the list to continue. The open item stays listed while the
|
|
534
|
+
reviewer works on it even if its state stops matching the filter; it
|
|
535
|
+
leaves once they move on.
|
|
536
|
+
When nothing is left the pane says so. The per-row "Handled, no action"
|
|
537
|
+
control is hidden in the workspace (`resolveInRow={false}`) so closing always
|
|
538
|
+
goes through the item pane and advances.
|
|
539
|
+
|
|
540
|
+
|
|
286
541
|
## Maintained browser proof
|
|
287
542
|
|
|
288
543
|
`pnpm test:components` runs real Svelte component tests; `pnpm test:e2e` runs the
|
|
@@ -323,8 +578,13 @@ Import `createIntakeReviewHost` and `IntakeReviewHostOptions` from
|
|
|
323
578
|
options)` with a service constructed from the current authenticated session.
|
|
324
579
|
The returned `IntakeReviewHost` implements inbox state projection across saved
|
|
325
580
|
review pages, current generation loading, stable `ui:<index>` intention keys,
|
|
326
|
-
action/plan preview CAS lookup, decisions, apply, corrections, candidates, split
|
|
327
|
-
|
|
581
|
+
action/plan preview CAS lookup, decisions, apply, corrections, candidates, split,
|
|
582
|
+
feedback, the offered `catalog`, `startAction` and `resolve`. Any catalog
|
|
583
|
+
failure (not configured, bound exceeded, denied) omits `catalog`, so the picker
|
|
584
|
+
disappears while existing reviews stay reviewable; item, review and evidence
|
|
585
|
+
denials still propagate as before; the reference host (`reference/review-host.ts`)
|
|
586
|
+
degrades the same way. Repeated
|
|
587
|
+
preparation of an unchanged authorized suggestion retains
|
|
328
588
|
its current review instead of creating a new revision. Changed arguments still
|
|
329
589
|
require explicit re-preview and a fresh human decision.
|
|
330
590
|
`list({ state?, assigneeId?, cursor?, limit? })` returns one page newest receipt
|
|
@@ -518,3 +778,11 @@ choosing the same local file explicitly starts a new upload identity. Opening
|
|
|
518
778
|
then dismissing the chooser preserves the separately retained originals and
|
|
519
779
|
request identity for retry; it does not clear the shared FilePicker's native-form
|
|
520
780
|
behavior. Successful upload and host/context disposal still discard retry state.
|
|
781
|
+
|
|
782
|
+
Workspace ordering also protects an open item during an awaited automatic page walk:
|
|
783
|
+
the selected row remains until navigation succeeds, except that a failed list read
|
|
784
|
+
clears the inbox. Applying an action is disabled
|
|
785
|
+
while that action card holds unsent arguments, reason, or feedback comment. A list
|
|
786
|
+
refresh retires earlier follow snapshots; only follow reads started after that
|
|
787
|
+
page request may override its rows, so a newer refresh cannot be regressed by an
|
|
788
|
+
already settled or older in-flight follow read.
|
package/agents/execution.md
CHANGED
|
@@ -37,6 +37,34 @@ requires human review; machine authorization requires explicit evaluated opt-ins
|
|
|
37
37
|
at all three authority layers and remains separate provenance from human review.
|
|
38
38
|
The reference handlers always require review; no evaluation claim is made here.
|
|
39
39
|
|
|
40
|
+
`operation: 'review_own'` is the submitter-scoped decision gate (#4085). The
|
|
41
|
+
service accepts it only when the host's `authorize` result carries
|
|
42
|
+
`submitter: true`, the resolved policy's `submitterReview` is enabled (the
|
|
43
|
+
application layer opts in; tenant/source layers may only set it `false`), and
|
|
44
|
+
the item's server-recorded `submitter_id` (the receiving service's authenticated
|
|
45
|
+
`scope.actorId`) equals the current actor and the captured
|
|
46
|
+
`execution.principalId`; apply rechecks that a `review_own` decision's reviewer
|
|
47
|
+
is that submitter and principal. All other `review` checks apply
|
|
48
|
+
unchanged; `correct` is refused. The decision row records `mode` and
|
|
49
|
+
`selfDecision` (decider is the submitter, in either mode), and `feedbackEvent` copies them as `decisionMode`/`selfDecision`;
|
|
50
|
+
`authorizeAutomatic` never reads human decisions, so a self-decision cannot
|
|
51
|
+
become automation evidence. `listOwnProposals` reads with the ordinary `read`
|
|
52
|
+
operation per handler after an item-level `read` check. Unreadable items are
|
|
53
|
+
skipped before projection (1000 hidden rows per call, then `Own proposal scan
|
|
54
|
+
limit`) and the cursor names only emitted items with a confirmed readable
|
|
55
|
+
successor. On this surface every host `assertTarget` throw is mapped to the
|
|
56
|
+
exported `IntakeAccessDeniedError` (message `Intake unavailable`, the type of
|
|
57
|
+
every execution denial), so the item becomes an `unavailable` entry and host
|
|
58
|
+
error text is not returned; hosts need no change. Other errors propagate,
|
|
59
|
+
including a throwing grant store in `authorize`. The feedback anti-widening
|
|
60
|
+
check also refuses a re-evaluation that turns `submitterReview` on.
|
|
61
|
+
Deletion redaction blanks `submitter_id` and `owner_key` with the payloads and
|
|
62
|
+
drops `execution.principalId` unless an external action is still `executing`
|
|
63
|
+
or `outcome_unknown`; `finish` and the reconcile state update release it once
|
|
64
|
+
none is left, and later deletion sweeps do the same.
|
|
65
|
+
Hosts whose `authorize` switches exhaustively on the operation fail closed for
|
|
66
|
+
the new value until they opt in.
|
|
67
|
+
|
|
40
68
|
`mutationBoundary: 'serialized'` is an explicit host guarantee, not an inferred
|
|
41
69
|
property of a permissions array: grant revocation must serialize with the supplied
|
|
42
70
|
transaction, or the owning mutation must revalidate under its own lock. The host
|
|
@@ -47,6 +75,50 @@ row lock before public `Contents` reads. A current `PrincipalRun` intersects liv
|
|
|
47
75
|
permissions with the captured ceiling and checks the exact registered collection
|
|
48
76
|
operation at every effect. No `runOnce` atomicity is assumed.
|
|
49
77
|
|
|
78
|
+
## Reviewer-chosen actions and dispositions
|
|
79
|
+
|
|
80
|
+
`startAction(StartActionInput)` lets an authenticated reviewer begin any
|
|
81
|
+
operation handler the item's evidence media types offer, without a model
|
|
82
|
+
suggestion. It checks `review` access under that handler before any callback,
|
|
83
|
+
refuses plan handlers and resolved items, binds the current analysis revision's
|
|
84
|
+
completed/partial attempt, inserts an action keyed `reviewer:<requestId>` with
|
|
85
|
+
`data.origin = 'reviewer-selected'`, and reuses `buildBinding`/`publish`: schema
|
|
86
|
+
validation, `validate`, `preview`, exact normalized arguments, target locks,
|
|
87
|
+
evidence pins and the request ledger behave exactly as for `previewProposal`.
|
|
88
|
+
Replays re-preview against the original revision and return the same review.
|
|
89
|
+
Approval, apply, retention, redaction and reconciliation are unchanged.
|
|
90
|
+
`authorizeAutomatic` refuses `reviewer-selected` actions and resolved items;
|
|
91
|
+
human routing is never machine authority.
|
|
92
|
+
|
|
93
|
+
`resolveItem(ResolveItemInput)` appends one terminal `intake_dispositions` row
|
|
94
|
+
(`disposition = 'resolved_no_action'`, reason/reviewer in redactable `data`)
|
|
95
|
+
under the item lock. Reviewer authority is taken from the first handler the
|
|
96
|
+
captured ceiling and policy offer; no new grant exists. It refuses while any
|
|
97
|
+
action is `authorized` with an unexpired approval for its current review
|
|
98
|
+
version, `executing`, `outcome_unknown` or `succeeded`; `failed` actions and
|
|
99
|
+
lapsed approvals do not block it (neither can be rejected). Afterwards
|
|
100
|
+
`submitDecision` refuses everything but `reject`, `publish` refuses any new
|
|
101
|
+
revision (correction, re-preview, plan preview), the apply gate (`approved`)
|
|
102
|
+
refuses any execution or retry, and `IngestionService.analyze`,
|
|
103
|
+
`reviseLogicalSplit` and `createAction` refuse the item under its lock before
|
|
104
|
+
any write. `createAction` (process authority) also refuses the reserved
|
|
105
|
+
`reviewer:` key prefix and `reviewer-selected` origin, which only
|
|
106
|
+
`startReviewerAction` mints; `previewProposal` on such an action requires
|
|
107
|
+
`review` access and the current handler@version. `resolveItem` supersedes
|
|
108
|
+
queued/running analyses and completes dispatch intents under the item lock, and
|
|
109
|
+
`claimAnalysis` returns `null` for a resolved item; `publishProposal` refuses
|
|
110
|
+
a resolved item and any reviewer-selected action. `[feedbackActionTransaction]`
|
|
111
|
+
takes `targetRevisions` (default true); routing lineage re-checks pass false. The feedback context's
|
|
112
|
+
`settled` flag gates selection only; lineage re-checks ignore it (FEEDBACK.md). `intake_dispositions` is a new model: deploy
|
|
113
|
+
its manifest migration (`pnpm ... db:migrate`) before upgrading hosts;
|
|
114
|
+
`assertReady` checks it, and both privacy deletion and retention expiry
|
|
115
|
+
(`redactExecution`) redact its payload with the other item-owned tables.
|
|
116
|
+
`IntakeItemDTO.disposition` is `{ state: 'resolved', at }` (row `created_at`,
|
|
117
|
+
never redacted); `getItemDisposition` returns reviewer and reason under
|
|
118
|
+
`reviewerAccess`. `resolveItem` captures an optional `reviewer_routing` feedback projection in the
|
|
119
|
+
same transaction; a reviewer-selected action is captured when its approval is
|
|
120
|
+
recorded (FEEDBACK.md).
|
|
121
|
+
|
|
50
122
|
## Durable review and effects
|
|
51
123
|
|
|
52
124
|
Preview appends a proposal bound to exact analysis attempt/evidence, handler,
|