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
4 changes: 4 additions & 0 deletions .config/mise/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ run = [
"jsonschema metaschema spec/workbench/draft.5/schemas",
"jsonschema test spec/workbench/draft.5/tests -r spec/workbench/schemas -r spec/workbench/draft.5/schemas",
"jsonschema validate spec/workbench/draft.5/schemas/mck-corpus.schema.json spec/workbench/mck/draft.5/cases.json -r spec/workbench/schemas -r spec/workbench/draft.5/schemas",
"jsonschema fmt spec/workbench/jobs/draft.1/schemas --check",
"jsonschema lint spec/workbench/jobs/draft.1/schemas -r spec/workbench/jobs/draft.1/schemas -r spec/workbench/draft.5/schemas -r spec/workbench/schemas",
"jsonschema metaschema spec/workbench/jobs/draft.1/schemas",
"jsonschema test spec/workbench/jobs/draft.1/tests -r spec/workbench/jobs/draft.1/schemas -r spec/workbench/draft.5/schemas -r spec/workbench/schemas",
]

# Not part of `check`: website:build-schemas depends on website:install, whose
Expand Down
7 changes: 7 additions & 0 deletions spec/workbench/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,10 @@ terminal evidence to an admitted invocation suite and public manifest. Its
[377-case kit](mck/draft.5/README.md) preserves returned values, SDK model errors,
primary failures, cancellation and separate cleanup failures. Offline admission
does not establish actual host execution or authoritative job cancellation.

[Snapshot-bound job receipts](jobs/draft.1/README.md) define a separate exact
`morphir/workbench/jobs` `0.1.0-draft.1` profile. The portable reducer binds
authority, snapshot and call inventory; separates cancellation acknowledgement
from terminal evidence; and retains host receipts across same-session reconnect.
This contribution does not change any offline MCK draft or add live RPC methods.
Shared transition MCK qualification and authenticated host adapters remain pending.
128 changes: 128 additions & 0 deletions spec/workbench/jobs/draft.1/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Snapshot-bound Workbench job receipts

This transport-neutral profile is exactly `0.1.0-draft.1`, named
`morphir/workbench/jobs`. It is a new contract namespace, separate from value and
terminal admission drafts. Its terminal payload references the frozen
`0.1.0-draft.5` outcome envelope without changing it.

This contribution defines receipt admission and client reduction. It does not
add JSON-RPC methods, authenticate hosts, execute jobs or advertise support in
the existing connected host. Connected `protocolVersion: 1`, generated-driver
v1 and the five offline Workbench MCK drafts remain unchanged. A host adapter
must implement and qualify the profile before a client enables live controls.

## Authority and negotiation

An authenticated host offers exactly:

```json
{"profile":"morphir/workbench/jobs","contractVersion":"0.1.0-draft.1","hostId":"host","sessionId":"session","providerId":"provider","operations":["query","cancel"]}
```

`query` is required; `cancel` is optional. Operations are a unique set. Unknown
operations, members, profile names, versions and version build suffixes reject.
Only this exact prerelease is supported. No generic SemVer comparison is needed
for this fixed draft; a reader with version ranges must use a SemVer library.
An offline schema fixture is not a live capability advertisement.

The caller obtains the expected binding from authenticated submission, never
from the receipt it is about to validate. The identity contains exactly
`hostId`, `sessionId`, `providerId`, `jobId` and `snapshotId`. Each contains
1 to 128 UTF-16 code units. The offer's first three fields must match. Provider
and snapshot are immutable for the job, including while reconnecting. A new
session or provider cannot borrow the old job's authority. A snapshot handle
denotes immutable execution input; it is not a local path or a content hash
unless a separately qualified host contract says so.

Host authentication, provider trust, public-entry admission and snapshot
creation remain host responsibilities. The reducer checks binding and receipt
consistency, not cryptographic provenance. New project-scoped metadata bindings
must remain scoped when a host translates a planned engine job into this identity.

## Receipts

A receipt has exactly these six fields:

```json
{"contractVersion":"0.1.0-draft.1","identity":{"hostId":"host","sessionId":"session","providerId":"provider","jobId":"job","snapshotId":"snapshot"},"sequence":0,"state":"submitted","cancelRequested":false,"outcomes":null}
```

| State | Outcomes | Meaning |
| --- | --- | --- |
| `submitted` | `null` | Host accepted this exact job and snapshot. |
| `running` | `null` | Host reports execution in progress. |
| `terminal` | Exact draft.5 envelope | Primary outcomes and separate cleanup are final. |

Sequence numbers range from 0 through 2147483647 on every target. The initial
submission receipt is sequence 0, submitted, with `cancelRequested: false`.
Running and terminal receipts have positive sequences. Host revisions advance
for a state change or cancellation acknowledgement, not merely because a query
was issued. Polling may skip revisions; it does not require a complete event log.

The client captures a nonempty ordered inventory of at most 1,000 invocation
call IDs at submission. IDs contain 1 to 128 UTF-16 units and are unique. A
terminal must have exactly that inventory, in order. Typed values retain the
draft.5 bounds and shape; the host must also admit every output against the
submitted manifest. This receipt reducer alone cannot establish output types.
`Result.Err` remains returned data. SDK errors, primary failures, cancellation
and cleanup keep their distinct variants. Cleanup never changes primary status.

## Reduction and cancellation races

Validate the entire supplied receipt and its identity before sequence handling.
Malformed or wrongly bound stale evidence rejects. With the exact binding:

- A lower sequence is stale and leaves the cursor unchanged.
- An equal sequence and identical admitted payload is an idempotent replay.
- An equal sequence with conflicting payload rejects.
- A higher sequence can move submitted to running or directly to terminal.
Running cannot move back to submitted. Terminal is immutable.
- Cancellation acknowledgement can change false to true while submitted or
running. Once acknowledged it cannot return to false. It requires a negotiated
cancellation operation. The same state and acknowledgement cannot acquire a
new revision without a change.

`cancelRequested` means the host acknowledged a request. It does not mean any
call terminated. Sending a client cancellation request changes no authoritative
receipt. A host may finish with returned values or failures after acknowledgement.
The host may also cancel by its own policy without a client request. The primary
terminal payload decides each call's outcome.

If cancellation is unavailable, the client must not send it. If the job is
terminal, use the retained terminal rather than sending another request. If
disconnected, the client retains the last receipt and reports unavailable
transport. Disconnect and timeout are never synthesized as failed or cancelled
job receipts. A timeout can leave the host running.

## Disconnect and reconnect

Connection availability is client state, separate from submitted/running/terminal.
An offline cursor cannot adopt incoming receipts. Reconnect requires a newly
authenticated offer with the exact previous host, session, provider, draft and
operation set. It restores transport availability and permits a fresh query of
the existing identity. The ordinary stale/replay/advance rules admit that reply.

This qualifies in-memory same-session reconciliation only. It does not promise
durable job retention, recovery after host restart, transferable sessions,
exactly-once submission or an authenticated transport implementation. A changed
session requires explicit host recovery support, not a fresh label on old evidence.

## Bounds and qualification

Raw receipt framing must be capped at 1 MiB before JSON parsing. JSON parsers at
the host boundary must reject duplicate members. Closed parsed shapes then feed
the portable reducer. No Ion receipt mapping is defined in this first job draft;
existing JSON and Ion evaluation inputs and outcome codecs remain supported.

The schemas check shape, discriminants, closed members, versions and numeric
bounds. Schema string lengths count code points; the reducer enforces UTF-16
identity limits. Sequence transitions, authority equality, call inventory,
acknowledgement monotonicity and authentication are semantic requirements, not
established by schema validation.

Structural fixtures run through the existing `workbench:schema-check` task.
The MoonBit implementation has portable semantic tests on JS, native, Wasm and
Wasm GC. These are implementation tests, not a shared MCK adapter qualification
or evidence that the Rust connected host implements job execution. A future
shared MCK slice must add parent-owned transition goldens to the existing runner,
then qualify host adapters and bind query/cancel transport methods.
45 changes: 45 additions & 0 deletions spec/workbench/jobs/draft.1/schemas/identity.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://morphir.finos.org/spec/workbench/jobs/0.1.0-draft.1/identity.schema.json",
"title": "Snapshot-bound job identity",
"description": "Immutable host, session, provider, job and model snapshot binding. Identity strings also require bounded UTF-16 units in the reducer",
"examples": [
{
"hostId": "host",
"sessionId": "session",
"providerId": "provider",
"jobId": "job",
"snapshotId": "snapshot"
}
],
"type": "object",
"required": [ "hostId", "sessionId", "providerId", "jobId", "snapshotId" ],
"properties": {
"hostId": {
"type": "string",
"maxLength": 128,
"minLength": 1
},
"sessionId": {
"type": "string",
"maxLength": 128,
"minLength": 1
},
"providerId": {
"type": "string",
"maxLength": 128,
"minLength": 1
},
"jobId": {
"type": "string",
"maxLength": 128,
"minLength": 1
},
"snapshotId": {
"type": "string",
"maxLength": 128,
"minLength": 1
}
},
"additionalProperties": false
}
61 changes: 61 additions & 0 deletions spec/workbench/jobs/draft.1/schemas/offer.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://morphir.finos.org/spec/workbench/jobs/0.1.0-draft.1/offer.schema.json",
"title": "Exact Workbench jobs offer",
"description": "Exact prerelease receipt profile offered by an authenticated host. Query is required and cancellation is optional. This schema does not grant authority",
"examples": [
{
"profile": "morphir/workbench/jobs",
"contractVersion": "0.1.0-draft.1",
"hostId": "host",
"sessionId": "session",
"providerId": "provider",
"operations": [ "query", "cancel" ]
}
],
"type": "object",
"required": [
"profile",
"contractVersion",
"hostId",
"sessionId",
"providerId",
"operations"
],
"properties": {
"profile": {
"const": "morphir/workbench/jobs"
},
"contractVersion": {
"const": "0.1.0-draft.1"
},
"hostId": {
"type": "string",
"maxLength": 128,
"minLength": 1
},
"sessionId": {
"type": "string",
"maxLength": 128,
"minLength": 1
},
"providerId": {
"type": "string",
"maxLength": 128,
"minLength": 1
},
"operations": {
"type": "array",
"maxItems": 2,
"minItems": 1,
"uniqueItems": true,
"contains": {
"const": "query"
},
"items": {
"enum": [ "query", "cancel" ]
}
}
},
"additionalProperties": false
}
122 changes: 122 additions & 0 deletions spec/workbench/jobs/draft.1/schemas/receipt.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://morphir.finos.org/spec/workbench/jobs/0.1.0-draft.1/receipt.schema.json",
"title": "Authoritative job receipt",
"description": "Closed host receipt with separate cancellation acknowledgement and exact draft.5 terminal evidence. Cross-receipt semantics require the portable reducer",
"examples": [
{
"contractVersion": "0.1.0-draft.1",
"identity": {
"hostId": "host",
"sessionId": "session",
"providerId": "provider",
"jobId": "job",
"snapshotId": "snapshot"
},
"sequence": 0,
"state": "submitted",
"cancelRequested": false,
"outcomes": null
}
],
"type": "object",
"allOf": [
{
"if": {
"properties": {
"state": {
"const": "running"
}
}
},
"then": {
"properties": {
"sequence": {
"minimum": 1
}
}
}
},
{
"if": {
"properties": {
"sequence": {
"const": 0
}
}
},
"then": {
"properties": {
"state": {
"const": "submitted"
},
"cancelRequested": {
"const": false
}
}
}
}
],
"if": {
"properties": {
"state": {
"const": "terminal"
}
}
},
"then": {
"properties": {
"sequence": {
"minimum": 1
},
"outcomes": {
"$ref": "https://morphir.finos.org/spec/workbench/0.1.0-draft.5/terminal-outcomes.schema.json"
}
}
},
"else": {
"properties": {
"outcomes": {
"type": "null"
}
}
},
"required": [
"contractVersion",
"identity",
"sequence",
"state",
"cancelRequested",
"outcomes"
],
"properties": {
"contractVersion": {
"const": "0.1.0-draft.1"
},
"identity": {
"$ref": "https://morphir.finos.org/spec/workbench/jobs/0.1.0-draft.1/identity.schema.json"
},
"sequence": {
"type": "integer",
"maximum": 2147483647,
"minimum": 0
},
"state": {
"enum": [ "submitted", "running", "terminal" ]
},
"cancelRequested": {
"type": "boolean"
},
"outcomes": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "https://morphir.finos.org/spec/workbench/0.1.0-draft.5/terminal-outcomes.schema.json"
}
]
}
},
"additionalProperties": false
}
Loading
Loading