Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions docs/schedule-list-scalar-summary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Schedule list scalar summary draft

This is the smaller source slice selected in Server #1014 comment 6644878850.
It is not the full last-dispatch outcome contract or published capability. Waterline
#222 keeps the existing describe fallback. No faster polling is justified by this
partial projection, and describe remains unchanged.

Each list item adds:

| Field | Complete value | Unavailable or malformed storage |
| --- | --- | --- |
| `failures_count` | Nonnegative stored signed-64-bit-decodable integer | Decode/negative value fails the request |
| `skipped_trigger_count` | Same counter contract | Same failure |
| `latest_workflow_instance_id` | Stored identity or null, at most 191 Unicode scalars / 764 UTF-8 bytes | Empty/oversized/invalid UTF-8 fails; a pruned instance preserves this identity |
| `current_run` | Nullable `{run_id,status,status_bucket,is_terminal}` from that instance's current run | Missing/pruned/wrong-namespace/wrong-instance target is null, meaning unknown |

The instance and run joins require exact identities and exact schedule namespace
on both sides. MySQL/MariaDB retain a candidate equality usable by each existing index, then
apply an exact binary residual to avoid case/accent/trailing-space collation
matches. The page namespace predicate uses the same candidate/residual pattern. A current run ID and the instance's complete current-run
pointer have at most 26 Unicode scalars / 104 UTF-8 bytes. Status is a complete
value at most 32 UTF-8 bytes and must be one of the existing statuses:
`pending`, `running`, `waiting` map to running/nonterminal; `completed` maps to
completed/terminal; `failed`, `cancelled`, `terminated` map to failed/terminal.
An unknown or empty status is invalid storage, never a default healthy state.
The dispatch count is not a run status and the current run is not a last-dispatch
outcome. Missing current-run information does not imply completion or no action.

For newly projected scalars, SQL transfers the **whole** value only within its
byte bound and returns an independent invalid flag otherwise. Rust checks the
complete decoded scalar count and enum before returning a response. No prefix
can become a plausible identity or status. NUL and astral scalars remain exact in SQLite/MySQL. PostgreSQL text cannot
store NUL, which is an unsupported storage-domain cell, not a decoder pass.
Invalid UTF-8 setup limitations remain distinct from SQLite decoder refusal.
Malformed/oversized scalar metadata refuses the page with the existing typed
HTTP 503 `storage_unavailable` envelope; values are not included in diagnostics.
Backend decode failures retain the existing storage-unavailable response.

| SQL boundary | SQLite | PostgreSQL | MySQL / MariaDB |
| --- | --- | --- | --- |
| Byte size including NUL | `length(CAST(column AS BLOB))` | `OCTET_LENGTH(column)` | `OCTET_LENGTH(column)` |
| Scalar projection | `CASE WHEN bytes<=bound THEN column ELSE NULL END` | Same | Same |
| Invalid flag | Non-null and empty or over-bound, separately from SQL null | Same | Same |
| Namespace / join identity | Exact text comparison | Exact text comparison | Candidate `left=right` plus `BINARY left=BINARY right` |

The page selects explicit existing list columns and bounded scalar expressions.
It excludes `recent_actions`, `buffered_actions`, `last_skip_reason` and
`last_skipped_at` entirely. Recent/last-action, buffer and last-skip fields remain
**deferred**, including their malformed-member, Unicode and timestamp semantics.
No SQL date conversion, substring preview, JSON prefix or invented event ordering
is introduced. The existing legacy action/input/spec/note/search-attribute fields
keep their previous size surface; this does not claim a bounded whole response
or constant database bytes/CPU.

The one-to-one joins add no row-level calls. Existing request normalization,
closed grammar, bound values, filter fingerprint, signed cursor, stale-anchor
check, order, page size and plus-one lookahead stay intact. Cursor probes select
only schedule ID/created-at and retain the same filters. A plain page uses one
query; either cursor presence or registered-definition lookup adds one; both use
three. Registered attributes still have the existing native unsupported-value
behavior, outside this slice.

Source vectors cover a 50-row page and its continuation with/without a system
query (1/2/2/3 calls), malformed excluded arrays, independent counters, missing
and wrong-namespace links, all seven statuses, pruned targets, exact NUL/astral
identities and empty/scalar-count/byte-count failures. The existing MySQL/MariaDB and PostgreSQL disposable source suites invoke the
same complete decoder/join controls, including maximum legal unsigned counters
and exact HTTP 503/no-value responses on MySQL/MariaDB. The shared unsigned
decoder remains unchanged with its original 409 behavior. MySQL/MariaDB EXPLAIN
controls retain actual primary-key/access-type output for each referenced join
after seeding 256 unrelated instances/runs. Those controls require successful
normal backend qualification; source authorship alone is not plan evidence.
The existing HTTP schedule-catalog corpus retains its frozen published-PHP
fixture and expected responses. Native recordings require all four new fields
on every list row. Before comparing the older shared response, the comparator
checks counters and the latest identity against preceding backfill responses,
the unclaimed current-run state, and the retained trigger-audit identity pair.
Only those checked additions are removed from that comparison; raw observations
and every existing field remain intact. Older published PHP recordings can
retain their original list shape.
Normal source checks and Server review must qualify the exact SQL/decoder on each backend before this
can be called contract-compliant. No new runtime/image/qualification dispatch,
schema/UDF, persisted summary, release or frozen-artifact admission follows.
4 changes: 4 additions & 0 deletions docs/server-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1203,6 +1203,10 @@ heartbeat, complete, and fail responses include `run_closed_reason` and
- `POST /api/schedules/{id}/trigger` — Trigger immediately
- `POST /api/schedules/{id}/backfill` — Backfill missed runs

The native development [scalar summary draft](schedule-list-scalar-summary.md) adds
counters, latest-instance identity and a nullable current-run summary. Full last-
dispatch/buffer/skip summaries and published capability remain pending Server #1014.

Schedule listing is namespace-scoped and never returns deleted schedules. The
optional `status` (`active` or `paused`) and `workflow_type` filters are exact
matches. `query` accepts equality predicates joined by `AND` for
Expand Down
10 changes: 10 additions & 0 deletions rust/src/runtime/mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -951,6 +951,16 @@ mod tests {
}
}

#[tokio::test]
#[ignore = "requires the existing disposable mysql source suite"]
async fn qualification_schedule_list_complete_summary_and_join_plans() {
let db=Database::new().await;
let store=super::super::store::Store::new(MySqlStorage::open(db.options.clone()).await.unwrap().into_pool());
store.qualify_schedule_list_summary().await;
store.close().await;
db.remove().await;
}

#[tokio::test]
#[ignore = "requires disposable MySQL/MariaDB; run qualification_ explicitly"]
async fn qualification_schedule_creation_keeps_original_audit_and_unsigned_counters() {
Expand Down
10 changes: 10 additions & 0 deletions rust/src/runtime/postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,16 @@ mod tests {
/// 0.9.0 it did (vendor/sqlx-postgres/DURABLE-PATCH.md): the next borrower's
/// probe ran inside it, native's read snapshot `BEGIN` then failed with
/// 25001, and every later statement on that connection with 25P02.
#[tokio::test]
#[ignore = "requires the existing disposable postgres source suite"]
async fn qualification_schedule_list_complete_summary_and_join_plans() {
let db=Database::new().await;
let store=super::super::store::Store::new(PostgresStorage::open(db.options.clone()).await.unwrap().into_pool());
store.qualify_schedule_list_summary().await;
store.close().await;
db.remove().await;
}

#[tokio::test]
#[ignore = "requires a disposable PostgreSQL; run the qualification_ tests explicitly"]
async fn qualification_a_begin_cancelled_in_flight_leaves_no_transaction_behind() {
Expand Down
Loading
Loading