Skip to content

feat(missions): add byte-bounded panel page reads - #1246

Merged
pascalandr merged 2 commits into
devfrom
feat/mission-list-pages
Nov 1, 2026
Merged

pascalandr merged 2 commits into
devfrom
feat/mission-list-pages

Conversation

@pascalandr

Copy link
Copy Markdown
Contributor

First server-contract slice of 4b, following #1241. This PR adds an opt-in byte-bounded read; the UI still uses its existing APIs. Panel-level boundedness is not complete yet.

Contract

  • panelPage RPC and POST /api/workspaces/:id/missions/panel-page.
  • Responses fit 64 KiB of serialized UTF-8 JSON, envelopes and continuations included; requests fit 16 KiB.
  • Sources: summary/cleanup hints, one compact mission/root field, or an existing task selector.
  • At most 50 exact row pieces per response. Oversized rows are sliced as exact JSON text, never silently truncated.
  • Digest-bound cursors refuse changed collections. Partial rows prove neither absence nor mutation state.
  • Directory reads return no maps; mission/task reads use the named range only. No legacy-list/snapshot fallback.
  • Existing APIs and full authoritative/internal reads are unchanged. Future Missions: keep recurring schedules running across OpenCode service restarts (OS scheduler + @opencode/client) #926 metadata (document, documentPending, requestID) survives serialization.

Measurements

  • 400 escaped/nested rows including a 5,000-actor row: recovered exactly in 95 transfers, maximum 65,536 bytes.
  • Real journal: 300 live missions, 300 pending cleanups, 300 task actors.
  • Directory: 9 summary + 6 cleanup transfers, no warm storage scans.
  • Actors: 7 transfers, maximum 44,605 bytes, one named-range observation per transfer.

Validation

Eight red checks cover byte accounting, cursor integrity, source scope, selector bounds and forbidden fan-out. Passing: typechecks; 1,498 server tests; 1,845 UI units; 409 serial browser cases; help; plugin build; isolated OpenCode 2.2.3 native gates. One existing environment-gated test skipped.

Remaining 4b work

Bounded live activity/attention for off-page actors and nonselected missions, UI adoption with per-page caches/discovery/completion seeding/cleanup retries, then removal of old list fan-out. Each transfer currently serializes/hashes its source collection afresh, with linear per-transfer CPU cost.

First server-contract slice of panel-level paging (4b), following task
pages (#1241). Task rows are bounded, but the current panel list still
grows with summaries, cleanups, actor arrays and open missions. A bounded
transport must exist before the UI can move off those existing reads.

Add the opt-in panelPage RPC and POST /missions/panel-page route:
- Every successful serialized JSON response, including escaping,
  envelopes and continuations, fits 64 KiB; requests fit 16 KiB.
- Sources are summary or cleanup index hints, one compact mission or
  its own root field, or an existing tasks selector. Directory reads
  return no maps; mission/task sources read only the named range.
- At most 50 exact row pieces fit a response. A row too large for one
  transfer is split into JSON text slices rather than truncated.
- Cursors bind the chosen collection's digest and row/character position.
  Changed collections refuse the cursor; a partial row supplies no
  absence or mutation evidence.
- The HTTP route validates the selector, response size and owned project
  and source identity. It has no snapshot or legacy-list fallback.

Existing panel, task, content and authoritative APIs stay unchanged.
The UI does not consume panelPage yet; activity projection, discovery,
completion seeding, cleanup retries and page caches follow separately.
No user-visible behavior changes in this slice.

Tests recover 400 escaped/nested rows including a 5,000-actor row over
95 bounded transfers; a real journal fixture covers 300 missions,
300 cleanups and 300 task actors. Warm directory traversal performs no
storage scans, and mission fields read only their own range. Eight
mutations fail the byte-budget, cursor, scope and selector regressions.
Server/UI typechecks, 1,498 server tests, 1,845 UI units, 409 serial
browser cases, help checks, plugin build and isolated OpenCode 2.2.3
qualification pass. Browser runners include the console-hiding fix.
…plies

Panel field and task reads discarded the range damage information they
had just observed. Consumers could mistake a damaged reconstruction for
clean absence, or treat a field page as complete when the same range
already reported unavailable controls, notifications or cleanup.

Carry the observed discarded count and all three unavailable flags in
every successful range envelope. An unreadable damaged mission now
returns damaged; a cleanly absent mission still returns mission-not-found.
This uses the existing range observation without reading project hints or
another journal.

The native output contract also required only available. Replace it with
complete, strict success and refusal alternatives, and validate the same
portable schema at the HTTP seam before publishing it. Byte limits and
source/project identity checks remain independent.

Regression tests cover whole-map, field, task and damaged/no-map reads,
incomplete/mixed successes, missing refusal reasons and excess fields.
All seven red checks fail with their protections removed. Server and UI
typechecks, 1,501 server tests, help checks, plugin build and 20 isolated
OpenCode 2.2.3 native gates pass. The native test exercises registration,
strict outputs and range damage using its private database.

Document the retained source-cost limits: full collection hashing per
transfer and directory serialization inside the append exclusion.
@pascalandr

Copy link
Copy Markdown
Contributor Author

Gatekeeper review

Round 1 (3b501ba61): request changes — P2 1, P3 1.

  • R1, P2: named-range field/task pages dropped known journal damage, and damaged reconstruction could be mistaken for clean absence.
  • R2, P3: the native output schema and HTTP seam accepted an incomplete success without a page.

Round 2 (6b4a41fe0): approve — P0 0 · P1 0 · P2 0 · P3 0 · P4 0.

  • R1 resolved: every range success carries its observation's discarded count and all unavailable flags; damaged reconstruction refuses as damaged, distinct from clean mission-not-found.
  • R2 resolved: complete strict success/refusal alternatives and HTTP validation using the same portable schema, plus separate byte and identity checks.
  • Independent probes covered 27 range selectors across control/RPC/HTTP with exactly one named-range scan each, stale directory hints, damaged and clean absence, 21 malformed responses, actual native registration and exact escaped/surrogate reconstruction.
  • Actual transfer maximum remains 65,536 serialized UTF-8 bytes; damage metadata is included in the fitted envelope.

Validation: server/UI typechecks; 1,307 server tests, one environment-gated skip; plugin build; 20 native gates on isolated OpenCode 2.2.3 plus supplemental real-transport checks. Browser/UI runtime suites were not rerun for this unchanged opt-in server surface.

Scope: server-only 4b.1. Existing UI reads are unchanged. Source hashing per transfer and directory work inside the append exclusion remain documented costs; bounded activity and UI adoption follow separately.

@pascalandr
pascalandr merged commit 2b57a67 into dev Nov 1, 2026
17 of 18 checks passed
@pascalandr
pascalandr deleted the feat/mission-list-pages branch November 1, 2026 02:39
pascalandr added a commit that referenced this pull request Nov 1, 2026
Server-only slice 4b.2 of bounded Missions reads. The byte-bounded
panelPage transport (#1246) is in place, but a future picker cannot stop
loading all open maps until it can observe their activity separately.

Extend panelPage with activity and marks sources:
- activity reads one mission range and a window of at most 160 actor
  roots; actor offsets and an order digest allow traversal.
- marks reads up to 16 indexed mission contexts, with up to 10 actors
  each, sharing the same 160-root observation budget.
- Native RPC returns only persisted context. The owning HTTP backend
  validates and reconstructs that context, then observes native sessions,
  Forms and permissions outside the journal append exclusion. It returns
  an observed phase with observation time, coverage, requests and activity.
- Observed, partial and unknown remain distinct. A failed observation
  never establishes idle or request absence, and off-page actors and
  descendants stay reachable through actor windows.
- Context carries range damage, lifecycle request identity and compact
  receipt counts, graceful Pause/Interrupt-now and owed-report evidence.
- Every reply uses the existing 64 KiB exact JSON transport; requests
  keep the 16 KiB limit. Continuations re-observe and refuse changed
  content rather than mixing observations. No observation cache or
  automatic restart is introduced.

Factor shared owed-work/task-attribution rules so compact task maps and
the observation context agree. Keep digest/evidence imports narrow so UI
shared types do not import server-only execution code.

Existing UI reads and authoritative/internal maps are unchanged. The next
slice adopts the new windows with per-page provenance, explicit retries
and last-good display; partial windows must never prove whole-mission
absence or authorize recovery.

Tests: real route/control/native pipeline with over 160 actors, off-page
Forms/permissions, failed/foreign/moved observations, partial coverage,
clock-free volatile digests, stale order, journal damage and no native IO
under the append lock. Thirteen red checks fail with their protection
removed. Warm marks over 50 missions perform no source scans. Eight
hundred requests reconstruct over 22 transfers, at most 65,536 bytes.
Server/UI typechecks, 1,507 server tests, 1,845 UI units, help checks,
plugin build and isolated OpenCode 2.2.3 qualification pass.

Native request lists remain whole per Location. Re-observation and full
source serialization can be expensive, and churning sources may refuse
continuations repeatedly; these are documented display-read limits.
pascalandr added a commit that referenced this pull request Nov 1, 2026
Server-only **4b.2** of bounded Missions reads, following #1246. No UI
conversion or user-visible behavior change yet.

## New sources on `panelPage`
```ts
{ source: "activity", missionID, actorOffset?, actorOrder?, cursor? }
{ source: "marks", missionOffset?, missionOrder?, cursor? }
```
- Activity: one named mission range, up to 160 actor roots.
- Marks: up to 16 indexed mission contexts × 10 actors, sharing 160
roots.
- RPC returns persisted `phase: "context"`; the owning HTTP backend
validates it and observes native state outside the append lock,
returning `phase: "observed"` with `observedAt`.
- Rows carry context, honest coverage, real Form/permission requests and
native activity. Actor windows make off-page requests reachable.
- Range damage, graceful Pause/Interrupt now, retry identities and
report-owed evidence are preserved.
- Existing byte limits remain: **64 KiB per response**, **16 KiB per
request**. Continuations re-observe and refuse changed content. No
retained observation cache or automatic restart.

Internal/authoritative reads, existing UI APIs and #926 metadata stay
unchanged. Shared owed-work/task-attribution rules are reused by compact
maps and projections.

## Measurements and validation
- Warm marks across 50 missions: zero source scans.
- A 31-root off-page window: 31 root GETs, 32 child lists, 32 inbox
reads; one shared read each of active/Shells/Forms/permissions.
- 800 exact requests: 22 transfers, maximum 65,536 bytes.
- Thirteen red checks cover budgets, scope, coverage, ownership, saved
Location, positive attention, uncertainty, digest, append lock,
ordering, damage and receipt evidence.
- Passing: typechecks; 1,507 server tests; 1,845 UI units; help; plugin
build; isolated OpenCode 2.2.3 qualification. One existing
environment-gated skip. No browser runs for this opt-in server-only
surface.

## Remaining work
UI adoption with per-window provenance, parked Retry and last-good
display. Partial evidence must never become whole-mission absence or
recovery authority. Native request lists still arrive whole per
Location, and re-observation/source serialization can be expensive; busy
sources may refuse continuations repeatedly.
pascalandr added a commit that referenced this pull request Nov 1, 2026
First UI adoption slice of panelPage (#1246 and #1249). Selected task
windows, attention windows and named task/report reads now use the
64 KiB panel-page transport instead of the legacy task GET route.
Picker discovery, selected maps, activity and cleanup display still use
the existing mission list; this slice does not complete list boundedness.

Add a browser-safe task-page consumer that checks actual response bytes
before parsing, validates the complete envelope and requested selector,
pins digest, total length and provenance across contiguous slices, and
verifies the final UTF-16LE collection hash before publishing a window.
Requests stay within 16 KiB. Each logical read uses the secondary-read
budget and a 30-second deadline; no automatic cursor restart or legacy
task-route fallback is introduced.

Keep last-good rows and toolbars on failures. Changed content receives a
distinct localized message; explicit Retry begins at the first slice.
The task store clones queued selectors and fences reads against owner,
project/client generation, reconnect and plugin changes. Obsolete slices
never publish, and unwanted missions do not acquire revision-driven reads.
Admission and all native mutations remain untouched.

Update help in all ten locales for the changed-page retry, and add
browser-independent contract tests to CI alongside the store/browser
regressions. Eleven red checks cover byte/digest/length/header validation,
stale reads, reconnect, parked failures, changed messaging, queued owners,
unwanted revision reads and immutable selectors.

Measured fixture: 151 tasks, four logical windows, seven HTTP transfers,
maximum 65,536 bytes, zero legacy task GETs and zero mutations. Root/UI
and server typechecks, actual UI production build, 1,865 UI/contract
tests, 1,520 server tests, 456 serial browser cases and help checks pass.
No server product code changes in this slice; existing isolated native
qualification remains unchanged.
pascalandr added a commit that referenced this pull request Nov 1, 2026
First UI adoption slice of panelPage (#1246 and #1249). Selected task
windows, attention windows and named task/report reads now use the
64 KiB panel-page transport instead of the legacy task GET route.
Picker discovery, selected maps, activity and cleanup display still use
the existing mission list; this slice does not complete list boundedness.

Add a browser-safe task-page consumer that checks actual response bytes
before parsing, validates the complete envelope and requested selector,
pins digest, total length and provenance across contiguous slices, and
verifies the final UTF-16LE collection hash before publishing a window.
Requests stay within 16 KiB. Each logical read uses the secondary-read
budget and a 30-second deadline; no automatic cursor restart or legacy
task-route fallback is introduced.

Keep last-good rows and toolbars on failures. Changed content receives a
distinct localized message; explicit Retry begins at the first slice.
The task store clones queued selectors and fences reads against owner,
project/client generation, reconnect and plugin changes. Obsolete slices
never publish, and unwanted missions do not acquire revision-driven reads.
Admission and all native mutations remain untouched.

Update help in all ten locales for the changed-page retry, and add
browser-independent contract tests to CI alongside the store/browser
regressions. Eleven red checks cover byte/digest/length/header validation,
stale reads, reconnect, parked failures, changed messaging, queued owners,
unwanted revision reads and immutable selectors.

Measured fixture: 151 tasks, four logical windows, seven HTTP transfers,
maximum 65,536 bytes, zero legacy task GETs and zero mutations. Root/UI
and server typechecks, actual UI production build, 1,865 UI/contract
tests, 1,520 server tests, 456 serial browser cases and help checks pass.
No server product code changes in this slice; existing isolated native
qualification remains unchanged.
pascalandr added a commit that referenced this pull request Nov 2, 2026
…1264)

Main **bounded-inventory UI adoption** following #1246, #1249 and #1250.
The live panel now uses `createMissionPanelInventory` →
`fetchMissionPanel`; public `fetchMissions` fan-out is removed.

## Contract and UI
- Independently verifiable 50-row directory/field windows: echoed
`{order,offset,end,total}`, window content digest, collection-order
continuation fence.
- Inventory summaries carry actor counts. A selected header excludes
whole actor/receipt/task collections; those are separately paged.
Native-scheduler `document`, cleanup `documentPending` and history
`requestID` survive.
- Initial durable `finishHighWater` is captured before discovery and
seeds once: later-discovered old completions stay seen; new completions
above the fixed baseline stay unseen, including deletion/purge counter
gaps.
- Explicit Show more/Retry for summaries, cleanup, fields, marks and
actors; loaded-only search. Selected/reader identities work outside the
first inventory page and do not wait for stalled discovery.
- Last-good display, owner/generation/page-revision fencing,
failed-middle Retry, no automatic changed-cursor restart. Separate actor
observations never become atomic whole-mission absence or recovery
authority.
- Exact cleanup retry/lost-ACK identity preserved.
Snapshot/admission/internal/mutation authority stays full. No mass
snapshot fallback.
- All ten help locales updated.

Every response stays within **64 KiB**, request within **16 KiB**;
logical reconstruction has explicit budgets/deadlines before allocation
and publication. Native per-Location lists and producer-sized
enumeration/hash costs remain disclosed.

## Measurements
- 50/120 summary rows: 11,915 bytes, one transfer, zero warm range/map
reads.
- Valid 601-actor selected header: 3,226 bytes.
- Explicit actor continuation: 160 root observations, largest measured
body 61,471 bytes.
- All 2,101 pending cleanups reachable; initial finish baseline remains
fixed during new completions.
- Notes-only revision: one wanted header, no task transfer or
expanded-task reread.

## Qualification fixes included
- Directory-only project Location omitted `workspaceID:undefined`, which
strict document CAS rejects; adapter now omits only an absent optional
field. Regression verifies directory-only and supplied-workspace
placement without weakening validation.
- Missing edit still returns mission-not-found/404.
- Native document-cleanup receipts/replay are checked exactly; safe
help-fixture link teardown and a data favicon fix remain test-only.

## Validation and recovery disclosure
Implementation recovered after another session's dependency-junction
cleanup deleted the original worktree. Tracked changes came from saved
Git state; nine new sources were reconstructed from 137 archived
successful operations and nine read checkpoints. All sources were
checkpointed, then qualified in a new independently installed worktree.
Original/evidence/recovery checkpoint remain untouched.

Passing: typechecks, real UI build, 1,322 UI tests, 1,381 server tests
plus one optional skip, 615 latest-complete serial browser cases, 31
help checks, plugin rebuild and 21 isolated OpenCode 2.2.3 gates. Twelve
negative controls caught/restored. This is not a claim that the initial
browser sweep was entirely green: one extension-fixture activation
failure passed two fresh full-file reruns unchanged. Test manifests are
explicit/nonempty; no borrowed dependencies or implicit Node discovery.

Qualification report/provenance retained by the coordinator.
Current-passage compaction (slice 5) remains the next separate work;
scheduler #1259 is already merged.
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