feat: caller-selected Backend Jobs and bounded subjektiv consolidation (T-704)

This commit is contained in:
2026-10-06 19:45:37 +09:00
parent 6f9621ca2e
commit d12cdc9106
36 changed files with 4085 additions and 291 deletions
+24 -3
View File
@@ -330,9 +330,30 @@ Exact dispatch retry reuses the deterministic attempt and Worker allocation key.
Only explicit re-evaluation of a definitively failed attempt increments the
bounded attempt number; unknown outcomes are not replayable. The runner
uses the existing embedded Runtime spawn, Worker registry, Session input,
observation, usage, and lifecycle paths with no Workdir attachment. It uses the
dedicated `builtin:backend-job` profile, whose tools cannot mutate Workspace
state, recursively create Workers or Jobs, or inherit caller Workdir authority.
observation, usage, and lifecycle paths with no Workdir attachment. Each client
explicitly selects an existing registry Profile. The runner resolves the complete
Builtin/Workspace project catalog through the normal virtual config bundle and
prompt projection path; browser launch candidates remain a narrower UI policy.
Raw Profile source and filesystem/path selectors are not accepted. A selected
recipe's instruction and features are not replaced with `backend-job` defaults.
Profiles requiring ungranted Workspace mutation, ordinary Memory, child/peer,
Flow, Workdir or MCP authority fail explicitly; no caller scope is inherited.
The Ticket checker continues to select `builtin:backend-job` itself.
Job identity/result capability comes from an immutable Host-authored create and
restore binding, not the Profile name, tag, instruction, Worker display name or
Runtime placement. Backend checks the exact live Job/attempt/input revision and
authenticated Worker before accepting results or domain operations. Profile
policy alone never confers a subject grant.
A common optional resource key serializes Jobs using their existing durable
intent/attempt records (schema 81), not a second domain workflow table. Exact
request replay is distinct from a resource collision: the latter returns the
original active immutable request with `resource_reused`, and never claims that
new input was processed. Pending/unknown Jobs and **all** unfinished attempt
cleanups retain the fence. Retry requires definitive failure, remaining attempt
budget, completed prior cleanups and no competing resource owner. Unknown intent
remains attention-required even after its Worker is removed.
Success is accepted only by the structured result boundary. Before Runtime
input can execute, the attempt is fenced by Workspace, Job ID, attempt ID, input
@@ -0,0 +1,97 @@
# Backend subjektiv consolidation Jobs (T-704)
subjektiv requests the common Backend Job runner with an explicit
`builtin:subjektiv-memory-consolidation` Profile. The runner has no purpose-based
Profile switch. Subject-body Workers and the independent Workspace Memory feature
are not Jobs and are not migrated or reset here. The shared/standalone adapters
planned in T-708/T-709 are outside this Backend change.
## Input, grants and concurrency
The existing backlog/byte thresholds and missing/stale/failed surface policy
remain the launch decision. A Job captures an immutable bounded batch (up to 100
candidate IDs) plus the Subject store revision. An empty batch is valid for a
surface-only rebuild. The Backend explicitly binds the Subject and candidate
batch grant in durable Job intent before Worker execution. No subject-body
singleton ownership or Session attribution is transferred to the Job.
Authenticated `(Workspace, Runtime, Worker)` registry binding and the exact
current dispatched attempt authorize each operation. Names, Profile tags and
model-provided `subject_id` are not authority. Candidate listing exposes only
unresolved IDs in the captured batch; reads/decisions outside it are denied.
Surface prepare requires all batch candidates to have durable dispositions.
Surface generation records preserve immutable Job/attempt provenance (subjektiv
schema migration 6); publication, failure and completion reject another attempt's
generation, including pre-Job/legacy generations.
The common resource key `subjektiv-consolidation:<subject>` prevents parallel
Jobs for a Subject. Concurrent requests return the original active intent and
report `skipped_existing_job`; they do not expand its grants. New candidates and
items beyond the bounded batch stay in staging. A subsequent consolidation
request snapshots remaining work; it is not counted as processed by the first
Job. There is no domain-specific status ledger, pool or persistent idle Worker.
Definitive failures use the common bounded retry API with the original intent;
unknown execution stays fenced for attention. A later explicitly requested Job
may snapshot remaining candidates once all previous Workers are safely cleaned.
## Memory and surface completion
`MemoryApplyCandidate` still uses the existing expected-revision checks, atomic
apply/close and immutable decision receipts. Partial application survives model
failure, timeout, lost response, retry, Worker deletion and Backend recovery.
Job replay is not a second memory apply: resolved candidates retain their
existing dispositions, and exact candidate decisions converge on their receipts.
`SubmitBackendJobResult` for a bound consolidation Job receives only the Subject
and captured candidate IDs. Its Host capability awaits the existing bounded
clean-context surface editor and publish-or-recorded-failure, then adds the actual
structured surface outcome. It does **not** schedule a Worker-owned post-Run task.
The result call serializes and caches the exact generated outcome for ambiguous
submission replay. The Backend verifies every batch disposition and the current
store revision, generation ownership and persisted ready/failed surface fields;
it enriches the durable result with actual immutable candidate dispositions.
Thus confirmed-memory consolidation and surface readiness are separate outcomes.
A recorded surface failure does not undo Memory. A stale/unfinished/fabricated
surface cannot be reported completed. Final prose or Idle is never success.
Only after durable structured result acceptance does the existing terminal Job
cleanup run. Its tracked Run teardown fence lets the result tool finish before
stop/remove. Timeout/unknown paths cancel unfinished Worker execution through
that same cleanup; later operations/results are rejected. The next request can
regenerate failed/stale surfaces without touching confirmed memory.
## Legacy cutover and recovery
The subject-specific direct spawn and Idle resubmission paths are removed.
On a consolidation request the Backend checks the **persisted singleton owner**
for the old `subjektiv-consolidation:<subject>` key. It revalidates Workspace,
key and old dedicated recipe provenance, not the display name. A running/paused
legacy foreground operation is left alone and returns `skipped_legacy_inflight`;
no Job is launched alongside it. Legacy owned operations can finish during this
bounded cutover.
After foreground Idle (or Stopped) is confirmed, Backend stop is confirmed before
canonical WorkerRemovalService removal. Any legacy surface background task is
cancelled at stop; the subsequent Job rebuilds from confirmed Memory and does not
infer successful surface completion from Idle. Busy/unobservable/mismatched
owners fail closed. Empty/ready Subjects also retire their idle legacy owner.
Removal failures remain diagnosable through existing durable removal recovery;
a subsequent request rechecks ownership instead of deleting storage directly.
Subject-body/ordinary/Workspace-Memory Workers are never selected by this key.
The Job runner uses its existing durable dispatch allocation, tracked input,
T-701 registry-bound restore/recovery, deadlines, unknown outcomes, result replay,
delivery and T-690 cleanup. Job results, candidate receipts, Memory revisions,
provenance and surfaces remain in their existing Backend stores after Worker
removal. No live dogfood Worker or environment is stopped/updated by development
validation.
## Validation boundary
Server recording-backend fixtures prove reservation/binding/auth/result/cleanup
contracts, and existing store tests prove candidate CAS/atomic receipts and
surface freshness. Worker scripted-client tests prove the clean-context editor
completion-before-result boundary and result replay. They are not real-provider
LLM or live dogfood process evidence. See the T-704 validation report for exact
commands/results and inherited intermittent failures; later passes do not erase
those failures.
+9 -5
View File
@@ -289,11 +289,15 @@ active/resolved→retracted, and resolved→active. Retraction remains terminal.
## Candidate consolidation and corrections (T-670)
The Backend starts a restricted `builtin:subjektiv-memory-consolidation` Worker
for exactly one authenticated subject. The Host binds the subject; model-visible
inputs cannot select a Workspace, subject, Runtime, Worker, or Session. Its tools
list/read pending immutable candidates, query/read confirmed revisions, and make
one candidate decision. Candidate text is evidence rather than authority.
subjektiv requests a bounded Backend-owned Job with the explicit
`builtin:subjektiv-memory-consolidation` Profile and an immutable Subject/candidate
batch grant. The common runner resolves the recipe; Profile names do not grant
permissions. The Host derives authority from the authenticated current
Job/attempt/Worker binding; model-visible inputs cannot select a Workspace,
subject, Runtime, Worker, or Session. Its tools list/read only pending candidates
in that batch, query/read confirmed revisions, and make one candidate decision.
Candidate text is evidence rather than authority. No subject-specific idle Worker
is resubmitted; see [Job execution and cutover](subjektiv-consolidation-jobs.md).
Every decision carries a stable request ID. An exact retry returns the committed
receipt, while reuse with different input is a conflict. Applying a candidate
+90
View File
@@ -0,0 +1,90 @@
# T-704 implementation and validation ledger
Base: `6f9621ca` (clean detached assigned checkout). Parent created
`work/t-704-backend-subjektiv-jobs` without replacing a branch or discarding changes.
Parent owns Backend/Profile/Subject authorization, cutover, documentation and
integration. Two direct implementation SubWorkers supplied the common durable
Job/store and Worker/Runtime contracts; parent inspected their committed sessions,
diffs and test evidence, integrated them and corrected the boundaries below.
Independent MR review is separate from implementation delegation.
## Implemented contracts
- Caller-selected existing Builtin/project registry Profiles, normal virtual
config/prompt bundle resolution and explicit execution-requirement rejection.
Chosen instructions/features/model are not replaced with backend-job defaults.
- Immutable Host-authored Job/attempt create/restore binding and result capability,
independent of Profile names/instructions/tags. No inherited Workdir, parent or
subject-body execution authority.
- Common transactional resource serialization (Workspace schema 81), immutable
Subject/candidate batch grants, exact replay vs resource collision, unknown
fences and all-old-attempt cleanup fences before retry/followup.
- subjektiv requests its Profile; immutable bounded batches, empty surface-only
Jobs, candidate authorization, existing CAS/atomic receipts and dispositions.
- Synchronous bounded clean-context surface generation before result submission.
Preparation reports an already-current snapshot to avoid needless conflicting
regeneration. Job failure acknowledgements are `failed_confirmed` only after
checking the actual failed marker/reason/revision and generation provenance.
Subject feature migration 6 adds immutable generation Job/attempt provenance,
without changing Subject/Memory/candidate content.
- Durable results contain actual candidate dispositions and a separate verified
ready/failed surface outcome. Existing terminal cleanup retains results/memory.
- Removed subject direct spawn and Idle resubmission. Persisted legacy ownership
plus actual foreground WorkerState and confirmed stop/removal fence the cutover.
Busy legacy workers are not interrupted or duplicated; subject bodies and the
independent old Workspace Memory feature are not selected for removal.
## Final validation (bounded `CARGO_BUILD_JOBS=2`)
| Command/boundary | Result |
| --- | --- |
| `cargo test -p yoi-workspace-server --lib backend_job` | 33 passed (before final surface acknowledgement addition; final whole-crate run below covers it) |
| `cargo test -p yoi-workspace-server --lib subjektiv_jobs_tests` | 5 passed, including project recipe, batch/auth/receipts, actual failed acknowledgement, current surface and legacy cutover |
| `cargo test -p worker --lib memory_surface_lifecycle` | 16 passed, including publication/failure before result, cancellation, definitive correction, ambiguous exact retry and already-current surface |
| Root `cargo check` | Passed; normal TUI/Server/standalone compile closure |
| `cargo test -p worker -p worker-runtime -p manifest -p runtime-api -p server-api -- --test-threads=4` | Passed: manifest 185; runtime-api 9; server-api 70; worker 815 lib + 110 integration; runtime 335 lib + 34 binary tests |
| `cargo test -p yoi-workspace-server -- --test-threads=4` (final) | 637 lib + 11 binary passed, no skips |
| `cargo test -p server-api --features typescript` | 77 passed, including generated/OpenAPI/TypeScript contract freshness; existing ts-rs serde-attribute warnings |
| `cargo fmt --all -- --check` / `git diff --check HEAD` | Passed |
These are recording-backend/store and scripted-client fixtures, plus real compiler
and test binaries. They are **not** real-provider LLM execution, a live Backend /
Runtime deployment, live dogfood consolidation or an environment update. No live
Worker/environment was stopped or updated.
## Failed checks and fixes (not erased by later passes)
- During integration, new request fields, resource replay metadata and surface
validation arguments initially failed compilation; all were corrected before
final validation.
- Initial Backend tests exposed the narrower browser builtin candidate allowlist;
Host launch now resolves the complete existing registry without widening browser
choices or adding a purpose-specific Profile allowlist.
- Initial surface-only test exposed rejection of empty candidate grants; empty
batches now retain the subject lock and grant no candidate access (owned store
tests 9 + contract tests 9 passed).
- Legacy fixture initially compared catalog compatibility state rather than
actual WorkerState. Cutover now requires the actual Idle snapshot, and the test
synchronizes on that authoritative observation before asserting behavior.
- First full Server run: **635 passed / 2 failed**, both stale migration-history
fixtures expecting schema 80. Their explicit retained chain/history is updated
to 81; migration filter passed 14 tests and subsequent whole-crate runs passed.
- Exploratory `cargo check -p worker-runtime --no-default-features` and
`--no-default-features --features fs-store` both **failed**, with 16 errors from
existing unconditional `workspace_request` / `reqwest` imports while HTTP is
disabled (plus unused-import warnings). The source/gates are present in the
original baseline and are recorded in the
[T-701 ledger](2026-10-06-t701-validation.md#validation-results). No isolated
baseline build was performed here. These remain failed exploratory unsupported
configurations, not feature passes or a T-704 feature-gating fix. The default
Runtime fs-store/ws-server configuration is fully tested.
- The inherited intermittent Ticket checker failure described in the
[T-702 ledger](2026-10-06-t702-stale-checker-validation.md) and T-701 report was
not observed in the final T-704 full Server runs, but is **not** claimed fixed.
Raw parent logs are under `target/t704-validation/` (ignored local artifacts),
including `server-full-initial-migration-fixtures.log`, `server-full-final.log`,
`changed-crates.log`, `root-check.log`, `worker-surface-final.log`,
`server-subjektiv-final.log`, `server-api-typescript.log`, `runtime-no-default.log`
and `runtime-fs-only.log`. The durable report records their concrete results;
local log paths are not a substitute for the published immutable MR source.