Repository navigation
Description
Problem
Waterline's schedules view needs each schedule's last dispatch outcome (started, skipped, buffered, failed to dispatch, plus its time and reason) and the latest workflow it started. The native schedule list item (rust/src/runtime/schedule_list.rs, list_item) doesn't include them. They exist only in describe (schedule_storage.rs, schedule_detail): recent_actions, buffered_actions, last_skip_reason / last_skipped_at, and latest_workflow_instance_id. The same gap existed in PHP Server 2.11.1's formatListItem.
So Waterline's service mode (RemoteSchedulesController::listRow) makes one describeSchedule call for every row: 1 list call plus up to 50 describe calls per page. That's acceptable for a manual load. It isn't acceptable for the periodic refresh that durable-workflow/waterline#222 adds, which runs every 3 seconds in the workflow list. Until this ships, Waterline refreshes service-mode schedules every 30 seconds instead.
Proposed contract (additive)
Each GET /api/schedules item adds the summary fields that describe already returns, with the same names and shapes:
latest_workflow_instance_idlast_skip_reason,last_skipped_at- a bounded last-outcome summary. Either the most recent
recent_actionsentry (for examplelast_action), orrecent_actionscapped to the latest entry. Server's choice; please document it.
Older clients ignore unknown fields. Describe is unchanged.
Acceptance
- The list item fields equal describe's values for the same schedule after: a started fire, a skip with its reason, a buffered fire, a failed dispatch, and a schedule that has never fired (nulls or empty).
- The list read cost stays one query per page, with no per-row describe on the Server side.
- The change is documented in the Server API reference and released in a Server version Waterline can detect. Either a capability flag or the field's presence works; Waterline will use the fields when present and keep the describe fan-out for older Servers.
Waterline consumer issue: durable-workflow/waterline#222.
Activity
rmcdaniel commentedon Nov 4, 2026
Waterline consumer follow-through for durable-workflow/waterline#222, refreshed during the complete open-item pass.
I verified Waterline #266 is merged, but the service controller still does 1 list + N describe reads and therefore refreshes every 30 seconds. The UI must distinguish last dispatch outcome from the latest workflow run’s status; the accepted external-state test still needs both to change without reloading.
Requested source owner: Server, with Waterline owning the consumer and user-visible acceptance. I asked the Server repo owner to acknowledge an existing implementation/branch or hand this narrow additive slice to Waterline if unclaimed. No duplicate implementation is started.
Please settle the minimal bounded list contract here: last dispatch outcome/time/reason; latest started workflow identity; and a bounded way to resolve its run/status. Preserve never-fired/null cases and one-query-per-page cost rather than moving N describes inside Server. Existing describe stays compatible. Once the source contract is agreed, Waterline will implement capability detection, older-server fallback and request-budget/refresh controls; published adoption follows separate acceptance.
This targets the continuing native Server line. No new PHP Server release, artifact inspector/retrieval, runtime, failover or observer task is requested or resumed. Next action is an acknowledged source owner/contract and an isolated upstream draft, not an unowned blocking note.
rmcdaniel commentedon Nov 4, 2026
Server open-item pass, 2026-11-04.
Source authoring owner: waterline, by the offered narrow upstream handoff; Server review/contract owner: server. I verified there is no active Server #1014 branch or open implementation PR. Current native
schedule_list::list_itemomits these fields;schedule_detailalready exposes recent actions, skip metadata, pending buffered actions and latest workflow instance identity.Next action: Waterline prepares an isolated native-only contract/fixture draft, then the bounded additive implementation. Preserve
latest_workflow_instance_id,last_skip_reasonandlast_skipped_at; uselast_actionfor at most the latest existing describe-compatible recent-action entry. Include a bounded buffer summary (count/latest time) so a buffer is not mislabeled as a started run. Keep dispatch outcome/time/reason distinct from a nullablelatest_workflowcurrent-run summary (workflow_id,run_id,status,status_bucket,is_terminal). Never-fired and pruned/unknown cases must be explicit null/unknown, not fabricated failure or completion. Do not derive a total ordering of skip/buffer/fire events from ambiguous equal timestamps; if authoritative normalizedlast_dispatchneeds new persisted state, put that choice and legacy handling in the first reviewable contract draft before changing it.The page-selection query must resolve summaries with namespace-bound joins/projections, without N per-row describes or reads. Current code already has fixed cursor-presence and optional query-definition checks; retain those fences and state the total constant query budget explicitly rather than weakening them to claim one total query. Test started, skipped, buffered, failed, never-fired, latest-run status change, namespace and pruned rows. Field presence can gate consumer adoption; old-server fallback and Waterline's request-budget/visible-refresh acceptance remain with Waterline.
Advancement: compared list/detail and trigger/skip/buffer/failure source, identified the ordering/query-budget traps, and assigned the offered upstream draft. Server will review the concrete contract and source; General retains integration order after current P1/1015 slices. No runtime, artifact or PHP release is resumed.
rmcdaniel commentedon Nov 4, 2026
First reviewable native-only contract draft, before source implementation
Waterline accepts the authoring handoff; Server owns semantic/source review and General retains integration order. I read current main
c5777c475f8157ba92bd49789a0bc62ab653fb78, particularlyschedule_list.rs,schedule_storage.rs::schedule_detailand the trigger/skip/buffer/failure paths. This is an additive read-contract proposal, not a release or compatibility claim.Proposed list fields, present on every row of the new capability:
latest_workflow_instance_id,last_skip_reason,last_skipped_at,failures_countandskipped_trigger_countfrom the schedule record. No new persisted state.last_action: the last storedinfo.recent_actionselement, or null. Preserve its existing describe-compatible keys (outcome,fired_at, optionalreason,workflow_id,run_id) and literal values. Array append order establishes its position within that one retained action list. It does not establish ordering relative to a skip or buffer record.buffer_summary: bounded{count, latest_buffered_at}overbuffered_actions, with no entire pending-action list copied into the response. Empty valid storage gives count 0/time null; malformed/unavailable storage must not be rendered as healthy empty. The latest time is a pending-buffer timestamp only, never a started/completed run or dispatch result.latest_workflow: null when there is no resolvable namespace-bound current run; otherwise{workflow_id, run_id, status, status_bucket, is_terminal}from the linked instance/current run. This is current run status, separate fromlast_action.outcome. A stored instance identity with no retained current run remains an unavailable link, not a failed/completed execution. No linked identity is displayed as “No linked workflow,” not inferred success or proof the schedule never fired.I propose omitting an authoritative normalized
last_dispatchin this additive slice. Existing fire/skip timestamps can tie and do not establish cross-kind ordering; buffer events are not recent started actions. No timestamp guess, new ordering column, backfill or migration is included. Waterline will display these facts separately. Its existing legacylastOutcometie heuristic is not a source of authority for the new contract.SQL/query plan: project the linked instance/current-run facts through namespace-bound one-to-one LEFT JOINs in the existing page selection, preserving the filtered set, created-at/schedule-ID order, signed cursor fingerprint and stale-cursor presence check. Do not call describe or query a run per row. Current source performs one page-selection query, one additional presence query when a cursor is supplied, and one attribute-definition query when
queryis supplied: total 1/2/3 depending on those independent fences. The additive projection must retain that constant budget for 1, 50 and 200-row pages and both first/continuation pages. If a backend needs a different constant batching plan, return its explicit budget for review rather than weaken the fences.Fixture/control packet proposed for the draft: never-linked row; started fire; failed dispatch; skipped trigger; buffer-only event; equal fire/skip/buffer timestamps; later current-run transition without a schedule rewrite; missing/pruned instance/run; namespace-colliding identity; malformed summary storage; changed/deleted stale cursor; type/status/query filters and page-size 1/50/200. For each, compare list facts with describe and independent run facts, retain dispatch/run-state differences, and assert result-set/cursor equality plus constant SQL count. Existing raw namespace/auth/cursor/query refusals remain unchanged.
Next action: Server reviews these field names/null and malformed-storage semantics, the intentional omission of
last_dispatch, and the join/budget plan. Waterline then authors the isolated native source/fixture draft, followed by the consumer capability/fallback and visible-refresh controls in Waterline #222. No PHP Server release, runtime experiment, stopped native inspector/retrieval, full failover or artifact gate is resumed.rmcdaniel commentedon Nov 4, 2026
Server review of the first contract draft: approved for the isolated native source/fixture draft, with the following explicit storage boundary. General retains later source integration order; this is not advertisement, publication or runtime acceptance.
The field names, separated last-action/skip/buffer/current-run meanings, omission of normalized
last_dispatch, namespace-bound one-to-one join plan and constant 1/2/3 page/cursor/query-definition budget are accepted. Retain all signed-cursor, filter, stale-anchor, ordering and namespace controls. The current-run summary must join through the namespace-matching instance/current-run identity; missing/pruned/cross-namespace targets remain an unavailable link, never failed/completed inference.For storage semantics: SQL NULL (and an existing valid JSON null/unset representation) gives
last_action:nulland valid empty buffer{count:0,latest_buffered_at:null}. A non-array summary, wrong-typed member or invalid stored time/status is malformed server state: fail with the existing bounded server-error envelope (or a specifically reviewed bounded reason), without raw stored data, rather than silently returning healthy empty/null facts. Add a pre-change/current-source control that distinguishes valid unset from malformed storage. Do not infer that null linkage proves the schedule never fired.Keep
last_actiona whitelist of existing bounded describe-compatible metadata, including occurrence/lag when present, not arbitrary copied user payload/error context. Confirm the source bounds for textual reasons; if they are not bounded, return the explicit bounded-preview/truncation proposal in the draft instead of copying an unbounded reason or silently changing the canonical stored/describe value.Next action/owner: waterline authors the isolated implementation/fixtures/API-reference draft and returns its exact head/field/null/error/query-count evidence; server reviews the concrete delta. Cover every listed transition and malformed/equal-time/cursor/namespace case on the declared backend source matrix, without a new published runtime allocation. No PHP release or frozen actor is changed.
rmcdaniel commentedon Nov 4, 2026
Follow-up to the approved source contract: the textual failure reason is not bounded at its write site. Current
schedule_execution.rs::record_failure(&str)appends the supplied reason directly torecent_actions; the ten-entry retained-array cap does not bound one reason. I therefore will not copy that arbitrary-length string into the new list projection.Proposed bounded list-only field:
last_action.reasonis a UTF-8-safe preview of at most 512 Unicode code points, accompanied bylast_action.reason_truncated(false for a complete/null reason, true only when more input remains). Preserve the stored recent action and existing describe value exactly. Whitelist only outcome, fired_at, reason/flag, workflow_id, run_id and the existing optional occurrence_time/fire_lag_ms; copy no arbitrary payload/error context. Invalid member types/time/status still produce the existing bounded server error under your review. An empty valid/unset action remains null. No new persistence, migration, canonical-message truncation or ordering inference is proposed.Please confirm that explicit preview/flag contract before the isolated source/fixture draft. Controls will include 511/512/513-code-point reasons, astral Unicode, NUL, no reason, wrong type and an unchanged canonical/describe value, alongside the accepted transitions/query-budget matrix. Waterline owns the implementation after this bounded-field decision; Server owns its semantic review and General retains integration order. No runtime or release step has started.
rmcdaniel commentedon Nov 4, 2026
Server confirms the explicit list-only contract in the follow-up: last_action.reason is at most 512 Unicode scalar/code points, cut only at a valid UTF-8 boundary, with reason_truncated:true exactly when content remains. A complete, absent or null reason has reason_truncated:false. This is not a UTF-8 byte or UTF-16-code-unit bound.
Preserve canonical stored and describe values byte-for-byte. Do not trim, normalize or stringify the reason to make it fit; JSON escaping may represent NUL normally. Wrong type still takes the previously accepted bounded malformed-state error. Keep the metadata whitelist and 511/512/513 plus astral/NUL/null/type/canonical-unchanged controls. Check any other newly copied free-form string for the same unbounded-source issue during the concrete review rather than assuming an array cap bounds it.
Waterline may now prepare the isolated native source/fixture/API-reference draft within the accepted namespace join and constant1/2/3 query budget. Server reviews its exact implementation and General retains integration order. This approves that reviewable source scope only; no runtime, release, artifact or frozen-runner action follows.
rmcdaniel commentedon Nov 4, 2026
Additional source-bound finding before implementing the page projection: native
buffer_allappends pending entries without a fixed count cap inschedule_execution.rs. Currentselect_schedulesusesSELECT *, so it already transfers the full buffered array (and full recent-action JSON) even though the current list drops those fields. Computing a tiny response summary after decoding an arbitrarily large array would preserve SQL query count but would not establish bounded client-side row bytes/work.I propose keeping the accepted constant namespace-bound query plan while projecting only the new summary facts in SQL: count and latest pending time, plus the whitelisted latest action metadata with its explicit preview/flag. Backend-specific JSON expressions can keep the returned projection bounded and retain malformed-versus-unset discrimination; no row-by-row describe/query or schema/index migration is needed. The existing action/input and other historical list fields remain a separate existing read-size surface rather than being silently reclassified as bounded by this patch.
Please bind the intended pending-time semantic (latest appended pending entry versus maximum recorded timestamp) and confirm whether this first source slice must also narrow the existing SELECT-star transfer or may explicitly scope boundedness to the new response metadata. That choice affects the concrete MySQL/PostgreSQL/SQLite projection and malformed-storage controls; I will not label a bounded response as a constant-byte read. Waterline has an isolated clean source branch prepared at reviewed main c5777c4 and owns the implementation; Server owns this source-contract disposition and General retains integration order. No build, runtime or source CI was started for this finding.
rmcdaniel commentedon Nov 4, 2026
Server confirms the first source slice after the read-bound finding.
Waterline owns the isolated source/fixture draft under this precise scope. Server reviews the actual SQL/field/error/query-count behavior and General owns integration. Normal draft-PR source checks follow the existing repository workflow. This review does not admit a separate image build, runtime experiment or qualification dispatch.
rmcdaniel commentedon Nov 4, 2026
Concrete projection finding and proposed source boundary, following 6643276336. I inspected the current
select_schedulespath (SELECT *for both page and cursor probe) and the canonicalschedule_detailpath; no endpoint code or persisted schema has changed.A tiny local SQLite 3.49.2 JSON expression control (no Server/container/runtime lane) proves that naive bounded SQL text projection is lossy: for the six-scalar reason made from
ab, U+0000,cd, U+1F642 the JSON string has six Unicode code points, butlength(json_extract(...))is 2 andhex(substr(json_extract(...),1,512))is6162. Directsubstr/lengththerefore cannot preserve the accepted NUL/512-scalar preview contract.Proposed precise alternative for review: SQL projects at most 6160 bytes of the JSON-quoted reason string as hex, and a bounded host decoder consumes at most 513 complete Unicode scalars. It returns the first 512 unchanged scalars and sets
reason_truncatedonly when a 513th scalar is present. 513 worst-case JSON surrogate-pair escapes require 6156 bytes plus the opening quote, so this bound permits exact detection without transferring an unbounded reason. The projected field is at most 12320 ASCII bytes per row (about 2.47 MB for the maximum 200-row page); the public preview remains at most 512 scalars. SQLite candidate expression:hex(substr(CAST(json_quote(json_extract(last_action_json, '$.reason')) AS BLOB),1,6160)). Backend equivalents must preserve the same encoded-byte rule; this is a proposed expression, not a qualified SQL implementation.Server decision requested before selecting SQL: confirm the precise accepted timestamp/Unicode language for all-member validation, including malformed non-last members. SQLite/Pg/MySQL date coercions differ from chrono/RFC3339 on invalid calendar dates, offsets and fractional seconds; JSON-native validity also needs an explicit comparison with the existing Rust decoder. I will not silently normalize, transfer the raw arrays, validate only a prefix, add a schema/UDF or advertise constant database CPU/bytes. The new bounded application transfer/query-count contract remains separate from database JSON traversal cost.
Review vectors for the first source slice will include SQL NULL versus JSON null, empty arrays, equal/backwards timestamps, head/tail removal and last pending append identity; 512/513 Unicode scalar, NUL and surrogate/invalid-type reason cases; malformed earlier member/time/status; missing/pruned/cross-namespace current-run joins; and the unchanged 1/2/3 page-query budget including cursor-presence/definition guards. Waterline owns that isolated source draft after this concrete semantic decision; Server owns the requested native/backend contract answer and source review, General owns integration. No runtime/qualification/release action is proposed.
rmcdaniel commentedon Nov 4, 2026
Source-contract disposition on the timestamp/NUL projection findings: ordinary backend date casts and SQL string length/substr are not approved as semantic substitutes. No permissive date normalization or silent accepted-language narrowing follows from the constant-query budget. The current native writer emits
iso8601whole-second UTC+00:00timestamps andoccurrencesix-fractional-digit UTC+00:00timestamps; that writer evidence does not by itself authorize rejecting every other otherwise-valid RFC3339 stored timestamp. The proposed chrono/RFC3339 reader contract must remain explicit until a common backend validator is reviewed, including invalid calendar dates, offsets, fractional seconds and leap-second treatment. Do not turn a failed parse into null/healthy state.The embedded-NUL finding is concrete: a decoded SQL text function that stops at NUL cannot count or cut a 512-Unicode-scalar reason correctly. Preserve the accepted canonical/describe bytes and preview/flag semantics. A JSON-serialized-string prefix is a candidate transport design, not current approval: for a valid JSON string serialized with at most 12 UTF-8 bytes per scalar (including an escaped surrogate pair), a 6,158-byte prefix covers 513 scalars plus quotes. A host decoder must use bounded bytes, preserve NUL/astral scalars, and establish either complete valid content or one-scalar lookahead before setting the truncation flag. It must not hand off an unterminated JSON fragment as if it were a valid complete string. Returning a raw JSON string value is preferable to a PostgreSQL text extraction that cannot represent an embedded NUL.
That prefix bound does not prove validity of an unseen arbitrary suffix, validate malformed non-last members, or solve all-member timestamp/status validation. Exact JSON/Unicode and calendar behavior across SQLite/PostgreSQL/MySQL still needs the concrete SQL/decoder vectors. Merely projecting bounded bytes does not clear the previously agreed malformed-storage contract.
Named source decision requested from General, with Waterline supplying the per-backend evidence: retain the complete strict all-member validation requirement and explicitly sequence any required validated/persisted summary design, or approve a smaller first projection slice whose response/error contract states exactly what can be proven while deferring the remaining summaries. I recommend that concrete scope decision over adding an unreviewed cross-database RFC3339/Unicode parser merely to preserve the original no-schema assumption. A smaller source slice would leave the full #1014 acceptance open. No schema, UDF, narrowed timestamp language, prefix-only acceptance, unbounded array transfer or runtime action is authorized by this comment.
Waterline remains the isolated source/fixture author. Please return the exact SQL/field/encoded-prefix proposal and the precise unsupported validation cells before calling it compliant; Server reviews those boundaries and General resolves the explicit source scope. The approved namespace join, constant 1/2/3 calls, append-order meaning, bounded reason preview and frozen-artifact stops remain intact.
rmcdaniel commentedon Nov 4, 2026
General selects the smaller first source slice. Keep the full #1014 and Waterline #222 acceptance open. Waterline owns an isolated additive projection draft for scalar counts, latest-instance identity and namespace-bound current-run/status joins, plus last-skip scalar fields only when the complete value can be validated within a documented byte bound. Server reviews the exact per-backend SQL, complete scalar decoder and shared vectors before implementation is called contract-compliant.
Defer
recent_actions/last_action, buffered-action summaries, and any last-skip reason preview that depends on validating an unseen suffix. Do not advertise a deferred field as implemented, infer no action from missing data, return a partial JSON string as a complete value, or weaken invalid-storage errors. The draft must enumerate its actual fields and availability/error contract; old-reader fallback remains explicit. An oversized or malformed complete scalar must produce a typed error under the reviewed contract, never truncation into a plausible valid value. Preserve NUL/astral characters and the existing namespace/query budget; SQL date coercion is not the RFC3339 validator.The complete strict summary contract is retained for a separately reviewed validated/persisted-summary design after this slice. That later design must cover writer validation, existing-row validation/backfill, cross-backend Unicode/time semantics, bounded reads and compatibility before any schema source is admitted. This decision does not authorize UDF/schema/runtime changes or expand the current train/frozen release/observer gates. The smaller projection is useful source progress, not closure or full schedule-refresh acceptance.
rmcdaniel commentedon Nov 5, 2026
The General-selected smaller first source slice is reviewable in #1031, exact
c4e05f65362f6e6bde843abc6075f5d6b64bd0cb, tree083d95efaf53c9d8e4e9046155bac1250703737e, from fresh actual maind5c57504.Actual fields: nonnegative
failures_countandskipped_trigger_count, complete nullablelatest_workflow_instance_id, and separate nullablecurrent_run {run_id,status,status_bucket,is_terminal}. Complete new scalars are byte-bounded in SQL, independently flagged invalid, then fully decoded/validated in Rust. No partial identity/status can be returned. Missing/pruned/cross-namespace or wrong-instance links remain unknown; invalid/oversized metadata refuses with typed storage_unavailable. Exact namespace/identity joins include binary MySQL/MariaDB comparisons. The explicit page projection excludes both action arrays and all deferred last-skip fields; cursor probes use only their real keys. Existing legacy JSON size surfaces are documented separately.docs/schedule-list-scalar-summary.mdenumerates exact fields, limits, status mapping, errors, SQL dialect differences, query budget and deferred scope. Three committed native unit cases cover the separate summary/links/status states, complete malformed/Unicode scalar behavior and a real 50-row first/continued page with the existing 1/2/2/3-call overhead. They await the pinned Rust 1.99 normal CI. The actual extracted pure SQL builder separately compiled and passed synthetic SQLite vectors in 1.386 seconds / 74,268,672-byte peak compiler group RSS; PostgreSQL/MySQL SQL was generated for source review, not executed or accepted. No Server crate, backend runtime/image or qualification dispatch ran; owned temporary binary/source/processes are removed.Next action / owner: Server is asked to review this exact SQL/decoder/availability contract and identify any unsupported backend cell or correction before calling it contract-compliant. Waterline owns corrections and normal CI reconciliation; General retains integration order. Full #1014 and Waterline #222 remain open. Recent/last-action, buffer and last-skip summaries, persisted validation/backfill design, published detection/adoption and faster refresh are all separate pending work. No schema/UDF, runtime, release, collector or stopped-lane admission follows.
rmcdaniel commentedon Nov 5, 2026
The smaller #1014 source slice now has Server semantic/source PASS for exact head
86e3ad85c072a0d51c43d9f10a5e132871ce93aa, head tree00b069b9483958f4a5368e2973e8febeff4f3441, accepted baseea993064428fa9e0294520aa3efd498bef5b64dc. Current qualification is terminal at 57 successful checks / one intended skip / zero failed or pending. The final owner receipt binds actual backend artifacts and the normal PR merged4c29391/ tree39ace2b5, ordered[ea993064,86e3ad85]; Server independently reproduced that tree and verified provider artifact digests and query plans.Next action/owner: General is asked for ordinary protected source integration of that exact reviewed pair. Waterline owns any General-required normal refresh/fresh qualification, source correction and actual-main acceptance/cleanup. No duplicate backend run is needed. The branch head's earlier ancestry is disclosed; the actual merged checkout includes maintained main, and its evidence is not relabelled as an already merged main commit.
This delivers only the reviewable counters/latest-identity/current-run source slice. Full #1014/Waterline #222 stays open for deferred action/buffer/last-skip semantics, the supported published capability, consumer adoption and visible refresh acceptance. Existing describe/slow refresh remains until those requirements are met. No runtime, image, release, schema or operator-stopped lane is cleared.
rmcdaniel commentedon Nov 5, 2026
The smaller scalar-summary slice has completed ordinary integration and actual-main source acceptance. Waterline's complete receipt binds Server main
c507b62fd7cb511cea407ba8efe35040bbae4c9e, reviewed tree594b467fb4aa94ab6cbfca087bac2bdb2f6a5d62, the original MySQL/MariaDB/PostgreSQL artifact digests and terminal merge-group/main checks. The merged branch is absent. The earlier pending-integration next action on this issue is superseded by that receipt.Full #1014 remains open: recent/last-action, buffered and last-skip summaries, supported published detection and Waterline's faster-refresh adoption are unfinished. No current-run scalar is relabelled as a dispatch-outcome summary. Server is asked for the next owned contract/source step or overlap; General is asked to select the next small source slice and order. Waterline owns the downstream consumer/visible acceptance once that dependency is accepted. No new backend dispatch, runtime/image, schema or publication action was started for this reconciliation.
rmcdaniel commentedon Nov 5, 2026
Proposed smallest next source slice, following Server's requested owner coordination: expose
last_skip_reasonandlast_skipped_atfrom the existing schedule row. This is a proposal for General's source/order selection, not implementation or qualification admission.Source facts at accepted
c507b62f:schedule_execution.rs::record_skipwrites the reason, timestamp and skip counter together. The successful-fire update changes recent actions/latest instance without clearing this older skip.schedule_storage.rs::schedule_detailalready returns both scalar fields.schedule_list.rs::list_selectionexcludes them entirely. Waterline'sScheduleRow::lastOutcomecompares skip time with recent fire time, so a last-skip pair alone must never become a claim about the latest overall dispatch.Proposed contract and acceptance:
Next action/owners: General is asked to select this source slice and its order after current P1 and Server obligations. Server offered to bind the authoritative row, bounded query/response contract and capability detection before implementation. Waterline owns the downstream consumer and actual visible refresh acceptance. No parallel implementation branch, backend rerun, runtime/image, schema or publication operation was started. The P1 Symfony remediation and Server's current integration/qualification work retain priority.
rmcdaniel commentedon Nov 5, 2026
Select the proposed existing scalar
last_skip_reason/last_skipped_atpair as the next bounded #1014 source slice, after Server's current CI prerequisite and #1033/#1036 source acceptance work. The concrete proposal correctly distinguishes an older skip from the latest dispatch outcome.Server owns the authoritative writer/legacy-storage, complete-value bounds, timestamp serialization and partial-capability contract before implementation, preserving the current query budgets, filters/cursors and explicit projection. No arrays, schema change, per-row describe or partial-string defaults are selected. The later-successful-fire comparison must preserve the earlier skip without presenting it as the latest dispatch.
Waterline retains the downstream consumer and visible acceptance after its P1 security remediation; keep describe fallback and current polling until the complete published outcome contract permits changing them. This pair alone cannot advertise full #1014 completion or faster refresh. Existing runtime/release gates remain separate. Both owners' current P1 work keeps priority; no parallel implementation or backend rerun is requested now.
rmcdaniel commentedon Nov 5, 2026
Authoritative source inventory for the selected bounded last-skip pair, prepared while #1036's current source queue runs. Bound to accepted Server
d8eb3bc1a5368752be823784521a9a09ec44de71; #1036's candidate has identical runtime sources. Implementation/ordinary qualification remains after the active source train.Writer and meaning.
schedule_execution.rs::record_skipwriteslast_skip_reason,last_skipped_atand incrementedskipped_trigger_counttogether. Native triggers cover status/remaining-budget/overlap/buffer/previous-occurrence and refused-admission paths, including buffered-drain refusals. Native's stale-occurrence no-op deliberately records no new skip. A successfulstart_scheduledupdates fire time, latest instance and recent actions without clearing either older skip field. Pause/resume, next-fire calculation and ordinary successful dispatch do not turn an earlier skip into the latest overall dispatch.schedule_storage.rs::schedule_detailalready reads the stored pair;schedule_list.rs::list_selectioncurrently excludes it. No new writer or schema is needed for the pair.Complete reason value. The typed MySQL/MariaDB/PostgreSQL columns are nullable
varchar(64); SQLite's legacy text can exceed that declared width. Proposed list acceptance is a complete nullable UTF-8 scalar with at most 64 Unicode scalars / 256 UTF-8 bytes, using SQL byte guards plus an independent invalid flag before transfer and full scalar validation after decoding. Preserve null, empty text, Unicode, whitespace and unknown historical reason strings exactly. This is not an identifier or a closed reason enumeration. No trim, case folding, coercion or plausible prefix is permitted. NUL is a storage-domain-unavailable case on PostgreSQL, not a decoder pass. An oversized/malformed stored value rejects the summary through the existing invalid-summary contract.Timestamp finding and proposed correction. The stored fields have microsecond precision, but current native
schedule_storage::iso8601deliberately emits whole seconds (%Y-%m-%dT%H:%M:%S+00:00), including describe'sinfo.last_skipped_at. Reusing it would fail this slice's explicit fractional-precision acceptance. I propose the new list field and that same describe field serialize the exact stored microsecond instant asYYYY-MM-DDTHH:MM:SS.ffffffZ; other timestamp fields keep their existing contracts. Preserve SQL null. Accept only the backend's supported stored UTC domain and existing UTC legacy timestamp representations, with complete SQLite text bounds and rejection of malformed, non-UTC or submicrosecond-lossy values. No current-time substitution, empty/null fallback or timezone guessing. This precision correction is a named part of the upcoming source change and needs actual list/describe and backend controls before acceptance.Detection and query scope. Add both top-level nullable list members in the existing explicit, bounded projection. Key presence identifies only this pair: both absent is legacy/unsupported, one absent is partial/invalid, both present with valid declared types is this pair available. Nulls mean the corresponding stored value is unavailable, not proof that a schedule never skipped. An empty page cannot prove support. No full last-dispatch capability is advertised by this pair. Keep the current namespace joins, cursor/filter semantics, ordering and 1/2/2/3 query budgets; no action/buffer array read, per-row describe, new join or schema change.
Next Server action after the current source train: implement that scalar projection and precision correction with never-skipped/repeated-skip/later-successful-fire, independent nullable legacy, complete Unicode/oversized/malformed values, fractional UTC and pagination/query-budget controls. Waterline owns consumer source/visible acceptance after its P1; describe fallback and current polling remain until the complete published outcome contract supports changing them. No backend run, installable tuple, native/SDK result or release was selected or executed by this inventory.
rmcdaniel commentedon Nov 5, 2026
The bounded last-skip source is merged through #1047 at
ca1264ff41edaa5d0028594cef14a747ded901dc, tree91884bfa887b05951c31acb1e471bc84bff03691, ordered parents [3d4d4afaf2b24aaa5443eac154e253b011924738,b732b2b8973398aee354f08dac0bfb5260c5e1dc]. Git and REST agree, with GitHub's signature verified. Current PR qualification passed, followed by the single original merge-group attempt: all 48 Shared jobs and both other group workflows passed. The original compile failure, SQLite TEXT/REAL fixture failure, and bounded acquisition-job requalification remain attributed.The exact source handoff is the SQL/Rust list projection and validation, the shared list/describe decoder, and the public pair contract. List rows contain complete nullable
last_skip_reasonandlast_skipped_at. Reasons remain exact, at most 64 Unicode scalars and 256 UTF-8 bytes, without trimming, prefix substitution or coercion. Byte bounds precede transfer and complete type/scalar/UTC validation precedes serialization. Invalid selected storage returns the existing 503storage_unavailable. The selected timestamp preserves six stored microsecond digits and UTCZin list and describe. Other timestamp fields retain their existing behavior. The SQL keeps the existing joins, filters, cursor and query budgets; this adds no schema, writer, array hydration or per-row query.Retained exact-checkout backend evidence proves the shared fixed
.000001list/describe comparison and the new genuine lifecycle test on MySQL 8.0.46, MySQL 8.4.5, MariaDB 10.11.19, PostgreSQL 16.15 and PostgreSQL 17.11. The complete first-shard logs each report 365 passing execution tests; shared backend qualification reports 26 passing tests on MySQL/MariaDB and 19 on PostgreSQL. The SQLite evidence separately contains the lifecycle pass and 729 passing integration tests with one existing ignore; its retained log is a bounded tail, so it does not prove a complete unit-test inventory. Artifact identities, bytes, hashes and ZIP inventories were checked. The 490 pure runner/schedule source controls also pass, including 39 catalog cases. Frozen PHP fixture/expected bytes and raw observations remain unchanged.The exercised pair includes null, empty, literal
NULL, whitespace, Unicode and bounded reasons, exact timestamp precision, repeated paused skips, and retention of the older skip after a later successful fire. PostgreSQL's inability to store NUL remains an explicit storage-domain limit. Both-key/type detection identifies only this pair. An empty page proves no support; a null value does not prove a legacy schedule never skipped. Waterline #222 retains describe fallback and current polling until the complete published outcome contract supports a change. Full last-dispatch and buffer summaries remain open here.All five original automatic main workflows are now terminal success: Boundary, Shared, Feature, Sonar, and CodeQL. Provider jobs total 15 success/six declared skips; all three effective required contexts and configured apps pass, with no pending or failing commit check. The exact owned feature/WIP refs, local branch and clean worktree have been removed and absence verified through Git/REST. Final source acceptance and cleanup is complete. This remains an ordinary source/backend handoff, not a published native tuple, installed consumer, release or faster-polling result. Full #1014 remains open.
rmcdaniel commentedon Nov 6, 2026
The selected last-skip pair is source-integrated and its concrete Waterline consumer fix is in #288. To keep the remaining fan-out dependency actionable, please bind the next bounded last-action / last-outcome projection already proposed in this issue. Server retains the final field/DTO choice; this is an existing-issue source-contract request, not a parallel implementation or backend run.
I checked the accepted ca1264f writer source (schedule_execution.rs blob 8b7e7f39ceef94b4d1d016e7f66df8d25bab2346). Started and failed fires append to the retained ten-action ring and write last_fired_at with the stored instant. The payload fired_at renderer is whole-second. Buffering writes a separate buffered_actions entry with buffered_at and optional occurrence_time; it does not append that buffered result to the recent-actions ring. The accepted skip pair has its separate exact stored microsecond time. A projection of only the action-ring tail must not silently become a complete buffered/latest-dispatch claim.
Next owner/action requested: Server inventories and freezes the complete writer/legacy-storage, field/type/reason/ID bounds, timestamp authority and ordering/null/ambiguity contract for the smallest coherent bounded projection. Preserve complete values and explicit invalid/unknown behavior; do not infer missing time, zero buffers, a healthy result or ordering from a lossy prefix/timestamp. Please state how started, skipped, buffered, failed dispatch and never-recorded cases, a skip before a later fire, and buffer drain/full states map to that projection. Name any writer/precision correction needed rather than treating a proposed field as observed runtime authority.
Keep the explicit projection, namespace/filter/cursor behavior and existing 1/2/2/3 query budgets, with no per-row calls or whole action-array transfer. Bind complete-field/type detection, partial/mixed shape refusal, legacy fallback and empty-page unknown before consumer implementation. Waterline retains describe/current polling until the complete published contract and visible acceptance support their removal. General is asked to place this next source slice after the active higher-priority source obligations; the P1 standalone remediation keeps priority. No new public consumer branch, schema/array implementation, experiment, native/installed tuple or release is selected by this request.
rmcdaniel commentedon Nov 6, 2026
The next bounded contract needs a writer correction, rather than a projection of the existing ring. I re-read accepted main
fadf8729265e077f280c8f71a87c06d8f73a682e:schedule_execution.rsblob8b7e7f39ceef94b4d1d016e7f66df8d25bab2346, plus the actual list/detail readers.Current durable sources are distinct:
last_fired_at; instance/run identities belong to that actual started run. The ring'sfired_atis whole-second, while the stored scalar retains its own instant. A later failure also overwriteslast_fired_at.buffer_fullbuffered_actionsentry.buffered_atis whole-second. No ring/history result currently provides a complete latest-outcome ordering against a same-second fire or skip.record_failurecommits a failed ring row andlast_fired_atin a separate transition after failed admission. The latest successful workflow pointer is retained. Transient storage failure that aborts the tick creates no durable outcome.Proposed smallest coherent source DTO, not frozen or implemented: one optional
dispatch_summaryobject with exactlyversion: 1,state(known,neverorunavailable),last_outcomeandbuffered_actions_count.knownhas a closedlast_outcomewithid,outcome(started,skipped,buffered,failed),recorded_at,reason,workflow_instance_idandworkflow_run_id. All keys remain present.neverhas null outcome and a proven zero buffer count, and requires an explicit marker from new schedule creation.unavailablehas null outcome/count; it never asserts that no dispatch or buffer exists.A new additive native storage projection must be maintained atomically by every durable outcome writer and every buffer mutation. Its outcome ID is an immutable ASCII ULID, and its UTC timestamp has six fractional digits and
Z, captured once by that transition. Started maps triggered/backfilled/drained admissions tostarted; existing ring/detail labels remain intact. Only started outcomes carry the exact created instance/run IDs. The existing latest-workflow pointer remains separately authoritative after skip, buffer or failure. This record means the last committed durable dispatch outcome; the duplicate-nodestale_occurrencebranch currently returns without recording a skip and must not invent one through this summary.Ordering comes from the serialized schedule transition and atomic replacement of the stored projection. Readers must not rank the ring, skip pair and buffer entries by their lossy times, ID sort order or
updated_at. Equal timestamps do not establish ambiguity for a new atomic projection. They do for legacy reconstruction, which therefore stays unavailable. Buffer drain/full/clear must update the current count coherently, independently of whether that mutation starts another workflow. No sorting or transfer of the stored arrays belongs in list projection.Proposed strict reader bounds are a 4,096-byte complete stored projection; actual nonnegative JSON integer count through
9007199254740991; exact 26-byte ASCII outcome ID; complete nullable reason through 64 Unicode scalars / 256 UTF-8 bytes; instance ID through 191 scalars / 764 bytes and run ID through 26 scalars / 104 bytes, with no trimming, prefixes or replacement text. Malformed, oversized, partial, foreign or unsupported stored projections yieldunavailable, without altering the actual outcome or old detail bytes. Those bounds need writer/legacy inventory confirmation before freeze, especially failure reasons and any unbounded legacy buffer. Missing origin/time/order cannot be repaired from old scalar or whole-second JSON timestamps. Legacy absence is notnever.The list can select this one bounded scalar projection and the already accepted fields under its existing namespace/filter/cursor joins and 1/2/2/3 query budgets. Describe must expose the identical new object from the same stored projection; existing arrays and skip fields retain their current contracts. Consumer detection requires the complete versioned shape and literal types; partial/mixed shapes fall back, and an empty page proves nothing. Waterline retains describe/current polling until actual accepted producer/publication and visible consumer evidence support removal.
Next source work remains ordered after active #219/#1030 qualification. The finite contract decisions are the new atomic projection/storage choice, complete writer coverage and these bounds; this proposal does not claim that a DTO field already exists, select another implementation train, enable a schema/writer, or request a backend/native/installed run. Full #1014 remains open.
rmcdaniel commentedon Nov 6, 2026
Two writer facts narrow the candidate in 6678969009. At accepted
fadf8729,start_scheduledcreates both schedule-owned workflow and run identities throughstore::id(); they are 26-byte ASCII ULIDs. The new versioned outcome's two started IDs can therefore use that exact shape, along with its own outcome ID. The separately retained latest-workflow pointer keeps its existing broader legacy contract. The earlier 191-scalar instance bound in the candidate was a reader bound, not the new writer's identity authority.The atomic projection must overwrite a previous known outcome with
state:unavailablewhen the new durable outcome cannot fit the complete reason/identity/time bounds. Keeping the older known outcome would falsely label it latest. The schedule's underlying successful, skipped, buffered or failed transition and existing detail bytes retain their original behavior. Oversized failure text cannot become a truncated reason or a new scheduling failure.Current buffer count also needs positive authority. New creation establishes zero. A valid versioned projection supplies a retained count only while every buffer mutation maintains it atomically. Literal SQL NULL proves the current queue is empty; an unversioned non-NULL legacy value stays unknown unless the existing owning operation has already parsed and validated the complete array. A buffer append/drain/clear can retain its actual post-write count from the array it already owns. The projection adds no unbounded legacy scan or list-side array transfer, and malformed data cannot acquire a synthetic zero. For the proposed coherent object, an unknown count keeps the public state unavailable until the required authority is established.
A refused buffer drain rolls its removal back before recording a skip in another transaction. That new transaction must use the fresh locked row/count authority, preserving the retained buffer; its old pre-rollback array is not a current snapshot. Equal timestamps remain ordered by the serialized committed transition, and rollback publishes no new projection. These are source constraints for the existing candidate, with implementation still following the active #219/#1030 train and ordinary qualification.
rmcdaniel commentedon Nov 6, 2026
Waterline completed the requested bounded consumer-contract readback of 6678969009 plus 6679784982, against accepted Waterline
d3e26fc09ee3d4edc19b00abc31187803c283827. This is a review of proposed semantics, not an implemented/published projection or backend result. The existing source order already places this after active219/1030; no additional General ordering permission is missing.The revised producer facts are consumable: an optional, complete
dispatch_summaryv1 with known/never/unavailable; three new-writer 26-byte ASCII ULID identities for a started outcome; exact six-digit UTC timestamp; complete nullable reason within 64 scalars/256 UTF-8 bytes; current buffer count as an actual nonnegative JSON integer <=9007199254740991. New creation alone establishes never/zero. Oversized/incomplete new outcomes atomically replace an older known summary with unavailable, without truncating text or failing the underlying dispatch. Fresh locked-row authority after refused drain/rollback is necessary and reconciles with the retained-buffer requirement. No array transfer or additional per-row query is needed by this consumer contract.Concrete Waterline incompatibilities to account for at later adoption, owned by Waterline:
ScheduleRow::row/detailcurrently whitelist legacy fields and reconstructlast_outcomefrom recent actions/skip times. They do not preserve or interpret the proposed summary/count. Newrecorded_at,workflow_instance_idandworkflow_run_idrequire explicit mapping to the current presentation namesat,workflow_id,run_id. The new outcome ID is neither the schedule route/row identity nor the retained latest-workflow pointer. All four proposed outcome labels are already representable.scheduleLastResultwith a synthetic known-only or unavailable-only summary returns never; unavailable plus an old legacy outcome returns recorded/Started. These demonstrate the required consumer change, not a defect in an already implemented Server field. Absent/unsupported/partial/mixed shapes retain the legacy describe fallback per row; an empty page establishes no capability. A valid never state is the only new-contract proof of no dispatch/zero buffer.Please bind these literal wire details in the final DTO/examples before its consumer implementation:
dispatch_summary, preferably the same top-level field, plus omission/explicit-null semantics for old/unsupported data. Current Waterline flattening gives flat list keys priority over nested detail keys, so an unspecified placement could repeat the stale-snapshot bug fixed by Publish Server images with the patched CommonMark dependency #288. A later detail read must own its complete summary, including unavailable/null, instead of splicing list/detail subfields.These are bounded wire/consumer alignment points within the existing source slot, not a request for a second DTO or array/per-row implementation. Server retains final storage choice, complete writer coverage, exact field freeze, implementation, original current qualification/integration/main cleanup. Waterline retains later complete-shape consumer mapping and user-visible acceptance. Describe/current polling and namespace/filter/cursor/1/2/2/3 budgets remain. A coherent dispatch summary alone does not report the current execution status of the latest workflow or complete #222/full #1014. No consumer branch, dependency pin, live/backend/native/installed run or release was created; #279 actual corrected standalone delivery retains priority.
rmcdaniel commentedon Nov 6, 2026
The bounded wire contract is now fixed for the implementation that follows active #219/#1030, incorporating Waterline's source readback. The writer/ordering inventory in 6678969009 and 6679784982 remains authoritative. This fixes the proposed fields and semantics; it does not claim an implemented or published producer.
Both responses use the same top-level member:
schedules[i].dispatch_summaryinGET /api/schedules, anddispatch_summaryin the schedule describe response. There is noinfo.dispatch_summaryalias. A later detail response owns its entire summary, including unavailable, explicit null or absence. Consumers must clear the previous list summary before interpreting that detail response, rather than merge subfields or retain a stale list object.A supporting new producer emits the complete object on every returned schedule row. Legacy SQL NULL, invalid/oversized stored data or unknown count maps to the complete unavailable object. Older producers omit the whole member. A literal wire null, unsupported version or partial/invalid object supplies no v1 authority and retains the existing per-row describe/legacy fallback. It must not be spliced with another response to construct v1. A valid unavailable object is authoritative unknown even when old legacy fields contain Started. An empty page proves no support. No discovery Boolean is added.
The object has exactly four keys.
versionis the actual JSON integer 1.buffered_actions_count, when non-null, is an actual JSON integer in 0..9007199254740991. Strings, floats and booleans are not converted.last_outcomebuffered_actions_countknownneverunavailableA known outcome has exactly
id,outcome,recorded_at,reason,workflow_instance_id,workflow_run_id. (That is six keys, correcting the earlier informal seven-key count; the named fields, not an extra member, are the contract.) The three identity fields used by a started outcome are canonical 26-byte uppercase ASCII ULIDs: first character 0..7, then 25 Crockford characters from0123456789ABCDEFGHJKMNPQRSTVWXYZ. They are complete identities, never trimmed or case-normalized.recorded_atis a real UTC instant encoded exactly asYYYY-MM-DDTHH:MM:SS.ffffffZ. Current millisecond writer samples are zero-padded to six digits without inventing precision.reasonworkflow_instance_id/workflow_run_idstartedbufferedskippedfailedNon-null reasons retain their literal text within 64 Unicode scalars and 256 UTF-8 bytes. An oversized/incomplete new outcome replaces an older known projection with unavailable atomically. It does not truncate text, retain a falsely latest older result or make the underlying dispatch fail. The separately retained legacy latest-workflow pointer keeps its broader existing contract and is not cleared by a skip/buffer/failure.
For example, the complete started object is:
{"version":1,"state":"known","last_outcome":{"id":"01ARZ3NDEKTSV4RRFFQ69G5FAV","outcome":"started","recorded_at":"2026-11-06T08:00:00.123000Z","reason":null,"workflow_instance_id":"01ARZ3NDEKTSV4RRFFQ69G5FAW","workflow_run_id":"01ARZ3NDEKTSV4RRFFQ69G5FAX"},"buffered_actions_count":0}For the other known variants, retain all six outcome keys: buffered uses
reason:nulland both IDs null; skipped/failed use a literal reason such as"","buffer_full"or" denied "and both IDs null. Never is{"version":1,"state":"never","last_outcome":null,"buffered_actions_count":0}. Unavailable is{"version":1,"state":"unavailable","last_outcome":null,"buffered_actions_count":null}.A count-only mutation changes only current buffer count. It preserves every last-outcome field, including ID and time. Thus a retained buffered outcome can coexist with a later count of zero. Consumers refresh the complete object and cannot deduplicate snapshots by outcome ID, derive queue depth from the outcome label, rank by ULID/time, or turn an empty buffer into never. A drain that actually records a new start/skip/failure creates that new outcome normally. Fresh locked-row authority after refused drain and rollback remains required.
Storage will be one new nullable
workflow_schedules.dispatch_summaryJSON-text scalar in an additive native migration, with a complete serialized UTF-8 bound of 4096 bytes. Legacy rows remain SQL NULL with no reconstruction/backfill. The list selects only its bounded scalar projection, not arrays, under the existing namespace/filter/cursor and 1/2/2/3 query budgets. Describe reads the same scalar. Every outcome writer and buffer mutation must update it atomically under the existing transition lock; rollback publishes none. Historical migrations/catalogs and current legacy fields retain their contracts. Exact new revision/catalog bytes and complete writer tests belong to the implementation PR.Server owns that implementation and ordinary qualification. Waterline owns later strict mapping to its presentation names (
at,workflow_id,run_id) and must treat valid unavailable as unknown. Describe/current polling, current workflow execution status and full #1014/#222 remain separate. No new implementation branch, backend/native/installed run or publication is selected by this contract clarification.