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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
{
"id": "recipes",
"title": "Recipes",
"body": "### Submit and wait\n\n```sh\nprose cli run quote --json\nprose cli run submit hello.prose.md --input topic=otters --preview\nprose cli run submit hello.prose.md --input topic=otters --yes --json > run.json\njq -r '.result.run.response' run.json\n```\n\n`cli run quote` is the wallet hold a run reserves, not its price; the settled\nprice is `result.run.price_cents`. A saved program runs with\n`--from SLUG` (your own) or `--from OWNER/SLUG@REV`; every program record\n(`program list|show|save|revisions --json`) carries that pinned reference as\n`ref`, and `prose cli program show SLUG@1 --json` resolves a revision number\nof your own program to it. On exit 21, rerun\n`problem.details.resumeArgv`; for a long run submit with `--detach` and follow\nwith `prose cli run watch RUN_ID --wait 10m --json`.\n\n### Publish a result\n\nA published result is a completed run of a saved revision of a public\nprogram that wrote `outputs/result.json`.\n\n```sh\nprose cli program save haiku haiku.prose.md --json\nprose cli run submit --from haiku --yes --json\nprose cli program visibility haiku public --preview\nprose cli result publish haiku --run RUN_ID --yes --json\nprose cli result show haiku --json\n```\n\nMaking a program public exposes its source; the CLI never adds `--yes` to that\nstep for you. Reading published results needs no key; a bare `SLUG` (your\nown program) is resolved with your key first, and without a publication id\n`result show` reads the newest.\n\n### Create a webhook job\n\nA job starts runs on its own. The spec is snake_case JSON; a test-mode webhook\nreceives events and never starts runs.\n\n```sh\nprintf '%s' '{\"type\":\"webhook\",\"name\":\"my-hook\",\"delivery_mode\":\"test\"}' > hook.json\nprose cli job create --spec-file hook.json --preview\nprose cli job create --spec-file hook.json --yes --json\nprose cli job deliveries JOB_ID --json\n```\n\n`result.endpoint` and `result.signing_secret` appear only in the create result\n(and in `cli job rotate-secret`); store them then. `result.endpointUrl` is the\nabsolute URL to configure in the sender; `cli job show` repeats it. `prose cli job create --help`\nlists the schedule and webhook spec keys; `prose cli job list --json` lists the\njob types. The keys each type accepts are the `spec` of `job.create` in\n`prose cli service operations --json`; a spec is checked against it before any\nrequest and every problem is reported at once in `details.violations`. A\nwebhook with no `program_ref` starts no runs, so its plan has effect `write`\nand no hold."
"body": "### Submit and wait\n\n```sh\nprose cli run quote --json\nprose cli run submit hello.prose.md --input topic=otters --preview\nprose cli run submit hello.prose.md --input topic=otters --yes --json > run.json\njq -r '.result.run.response' run.json\n```\n\n`cli run quote` is the wallet hold a run reserves, not its price; the settled\nprice is `result.run.price_cents`. A saved program runs with\n`--from SLUG` (your own) or `--from OWNER/SLUG@REV`; every program record\n(`program list|show|save|revisions --json`) carries that pinned reference as\n`ref`, and `prose cli program show SLUG@1 --json` resolves a revision number\nof your own program to it. On exit 21, rerun\n`problem.details.resumeArgv`; for a long run submit with `--detach` and follow\nwith `prose cli run watch RUN_ID --wait 10m --json`.\n\n### Publish a result\n\nA published result is a completed run of a saved revision of a public\nprogram that wrote `outputs/result.json`.\n\n```sh\nprose cli program save haiku haiku.prose.md --json\nprose cli run submit --from haiku --yes --json\nprose cli program visibility haiku public --preview\nprose cli result publish haiku --run RUN_ID --yes --json\nprose cli result show haiku --json\n```\n\nMaking a program public exposes its source; the CLI never adds `--yes` to that\nstep for you. Reading published results needs no key; a bare `SLUG` (your\nown program) is resolved with your key first, and without a publication id\n`result show` reads the newest.\n\n### Create a webhook job\n\nA job starts runs on its own. The spec is snake_case JSON; a test-mode webhook\nreceives events and never starts runs.\n\n```sh\nprintf '%s' '{\"type\":\"webhook\",\"name\":\"my-hook\",\"delivery_mode\":\"test\"}' > hook.json\nprose cli job create --spec-file hook.json --preview\nprose cli job create --spec-file hook.json --yes --json\nprose cli job deliveries JOB_ID --json\n```\n\n`result.endpoint` and `result.signing_secret` appear only in the create result\n(and in `cli job rotate-secret`); store them then. `result.endpoint_url` is the\nabsolute URL to configure in the sender; `cli job show` repeats it. `prose cli job create --help`\nlists the schedule and webhook spec keys; `prose cli job list --json` lists the\njob types. The keys each type accepts are the `spec` of `job.create` in\n`prose cli service operations --json`; a spec is checked against it before any\nrequest and every problem is reported at once in `details.violations`. A\nwebhook with no `program_ref` starts no runs, so its plan has effect `write`\nand no hold.\n\nA sender POSTs the event as JSON (`Content-Type: application/json`, at most\n256 KiB) to the endpoint URL. A webhook created without `receiver` checks two\nheaders:\n\n- `X-OpenProse-Delivery`: a new id for each event, 1 to 200 letters, digits,\n `.`, `_`, `:` or `-`, starting with a letter or digit. A retry sends the\n same id and the same body.\n- `X-OpenProse-Signature`: `sha256=` and the lowercase hex HMAC-SHA256, keyed\n with the signing secret, of the delivery id, one newline and the exact body\n bytes. Sign the bytes you send; do not reserialize the body after signing.\n\n```sh\nsig=$(printf '%s\\n%s' \"$DELIVERY_ID\" \"$BODY\" | openssl dgst -sha256 -hmac \"$SIGNING_SECRET\" | sed 's/^.* //')\ncurl -X POST \"$ENDPOINT_URL\" -H 'Content-Type: application/json' \\\n -H \"X-OpenProse-Delivery: $DELIVERY_ID\" -H \"X-OpenProse-Signature: sha256=$sig\" \\\n --data-binary \"$BODY\"\n```\n\n`202` with `\"accepted\":true` means the event was accepted, not that a run\nfinished; `\"test_only\":true` means no run was started. A missing or malformed\ndelivery id, or a body that is not JSON, is `400`; a body over 256 KiB is\n`413`; a wrong signature is `401`. `prose cli job deliveries JOB_ID --json`\nlists the events received, including those rejected for a wrong signature."
}
]
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
{
"id": "recipes",
"title": "Recipes",
"body": "### Submit and wait\n\n```sh\nprose cli run quote --json\nprose cli run submit hello.prose.md --input topic=otters --preview\nprose cli run submit hello.prose.md --input topic=otters --yes --json > run.json\njq -r '.result.run.response' run.json\n```\n\n`cli run quote` is the wallet hold a run reserves, not its price; the settled\nprice is `result.run.price_cents`. A saved program runs with\n`--from SLUG` (your own) or `--from OWNER/SLUG@REV`; every program record\n(`program list|show|save|revisions --json`) carries that pinned reference as\n`ref`, and `prose cli program show SLUG@1 --json` resolves a revision number\nof your own program to it. On exit 21, rerun\n`problem.details.resumeArgv`; for a long run submit with `--detach` and follow\nwith `prose cli run watch RUN_ID --wait 10m --json`.\n\n### Publish a result\n\nA published result is a completed run of a saved revision of a public\nprogram that wrote `outputs/result.json`.\n\n```sh\nprose cli program save haiku haiku.prose.md --json\nprose cli run submit --from haiku --yes --json\nprose cli program visibility haiku public --preview\nprose cli result publish haiku --run RUN_ID --yes --json\nprose cli result show haiku --json\n```\n\nMaking a program public exposes its source; the CLI never adds `--yes` to that\nstep for you. Reading published results needs no key; a bare `SLUG` (your\nown program) is resolved with your key first, and without a publication id\n`result show` reads the newest.\n\n### Create a webhook job\n\nA job starts runs on its own. The spec is snake_case JSON; a test-mode webhook\nreceives events and never starts runs.\n\n```sh\nprintf '%s' '{\"type\":\"webhook\",\"name\":\"my-hook\",\"delivery_mode\":\"test\"}' > hook.json\nprose cli job create --spec-file hook.json --preview\nprose cli job create --spec-file hook.json --yes --json\nprose cli job deliveries JOB_ID --json\n```\n\n`result.endpoint` and `result.signing_secret` appear only in the create result\n(and in `cli job rotate-secret`); store them then. `result.endpointUrl` is the\nabsolute URL to configure in the sender; `cli job show` repeats it. `prose cli job create --help`\nlists the schedule and webhook spec keys; `prose cli job list --json` lists the\njob types. The keys each type accepts are the `spec` of `job.create` in\n`prose cli service operations --json`; a spec is checked against it before any\nrequest and every problem is reported at once in `details.violations`. A\nwebhook with no `program_ref` starts no runs, so its plan has effect `write`\nand no hold."
"body": "### Submit and wait\n\n```sh\nprose cli run quote --json\nprose cli run submit hello.prose.md --input topic=otters --preview\nprose cli run submit hello.prose.md --input topic=otters --yes --json > run.json\njq -r '.result.run.response' run.json\n```\n\n`cli run quote` is the wallet hold a run reserves, not its price; the settled\nprice is `result.run.price_cents`. A saved program runs with\n`--from SLUG` (your own) or `--from OWNER/SLUG@REV`; every program record\n(`program list|show|save|revisions --json`) carries that pinned reference as\n`ref`, and `prose cli program show SLUG@1 --json` resolves a revision number\nof your own program to it. On exit 21, rerun\n`problem.details.resumeArgv`; for a long run submit with `--detach` and follow\nwith `prose cli run watch RUN_ID --wait 10m --json`.\n\n### Publish a result\n\nA published result is a completed run of a saved revision of a public\nprogram that wrote `outputs/result.json`.\n\n```sh\nprose cli program save haiku haiku.prose.md --json\nprose cli run submit --from haiku --yes --json\nprose cli program visibility haiku public --preview\nprose cli result publish haiku --run RUN_ID --yes --json\nprose cli result show haiku --json\n```\n\nMaking a program public exposes its source; the CLI never adds `--yes` to that\nstep for you. Reading published results needs no key; a bare `SLUG` (your\nown program) is resolved with your key first, and without a publication id\n`result show` reads the newest.\n\n### Create a webhook job\n\nA job starts runs on its own. The spec is snake_case JSON; a test-mode webhook\nreceives events and never starts runs.\n\n```sh\nprintf '%s' '{\"type\":\"webhook\",\"name\":\"my-hook\",\"delivery_mode\":\"test\"}' > hook.json\nprose cli job create --spec-file hook.json --preview\nprose cli job create --spec-file hook.json --yes --json\nprose cli job deliveries JOB_ID --json\n```\n\n`result.endpoint` and `result.signing_secret` appear only in the create result\n(and in `cli job rotate-secret`); store them then. `result.endpoint_url` is the\nabsolute URL to configure in the sender; `cli job show` repeats it. `prose cli job create --help`\nlists the schedule and webhook spec keys; `prose cli job list --json` lists the\njob types. The keys each type accepts are the `spec` of `job.create` in\n`prose cli service operations --json`; a spec is checked against it before any\nrequest and every problem is reported at once in `details.violations`. A\nwebhook with no `program_ref` starts no runs, so its plan has effect `write`\nand no hold.\n\nA sender POSTs the event as JSON (`Content-Type: application/json`, at most\n256 KiB) to the endpoint URL. A webhook created without `receiver` checks two\nheaders:\n\n- `X-OpenProse-Delivery`: a new id for each event, 1 to 200 letters, digits,\n `.`, `_`, `:` or `-`, starting with a letter or digit. A retry sends the\n same id and the same body.\n- `X-OpenProse-Signature`: `sha256=` and the lowercase hex HMAC-SHA256, keyed\n with the signing secret, of the delivery id, one newline and the exact body\n bytes. Sign the bytes you send; do not reserialize the body after signing.\n\n```sh\nsig=$(printf '%s\\n%s' \"$DELIVERY_ID\" \"$BODY\" | openssl dgst -sha256 -hmac \"$SIGNING_SECRET\" | sed 's/^.* //')\ncurl -X POST \"$ENDPOINT_URL\" -H 'Content-Type: application/json' \\\n -H \"X-OpenProse-Delivery: $DELIVERY_ID\" -H \"X-OpenProse-Signature: sha256=$sig\" \\\n --data-binary \"$BODY\"\n```\n\n`202` with `\"accepted\":true` means the event was accepted, not that a run\nfinished; `\"test_only\":true` means no run was started. A missing or malformed\ndelivery id, or a body that is not JSON, is `400`; a body over 256 KiB is\n`413`; a wrong signature is `401`. `prose cli job deliveries JOB_ID --json`\nlists the events received, including those rejected for a wrong signature."
}
]
},
Expand Down
2 changes: 1 addition & 1 deletion cli/shared/schemas/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ named by each operation's `output.schema` in
service field names verbatim (snake_case; camelCase for jobs) and drop every
field the projection does not name. Signed `file_urls`, `customer_id`, webhook
endpoints and secrets are never projected, except the secret-bearing field of
the one operation that creates it (and a webhook's `endpointUrl` in `job show`
the one operation that creates it (and a webhook's `endpoint_url` in `job show`
when its endpoint is the secret-free job-id path). No property name may match `/cost/i`; open
maps exclude such names with `propertyNames`. Run the checks with
`python3 -m unittest cli/shared/tests/test_service_contract.py`.
Expand Down
26 changes: 25 additions & 1 deletion cli/shared/service/guide.v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,11 +351,35 @@ prose cli job deliveries JOB_ID --json
```

`result.endpoint` and `result.signing_secret` appear only in the create result
(and in `cli job rotate-secret`); store them then. `result.endpointUrl` is the
(and in `cli job rotate-secret`); store them then. `result.endpoint_url` is the
absolute URL to configure in the sender; `cli job show` repeats it. `prose cli job create --help`
lists the schedule and webhook spec keys; `prose cli job list --json` lists the
job types. The keys each type accepts are the `spec` of `job.create` in
`prose cli service operations --json`; a spec is checked against it before any
request and every problem is reported at once in `details.violations`. A
webhook with no `program_ref` starts no runs, so its plan has effect `write`
and no hold.

A sender POSTs the event as JSON (`Content-Type: application/json`, at most
256 KiB) to the endpoint URL. A webhook created without `receiver` checks two
headers:

- `X-OpenProse-Delivery`: a new id for each event, 1 to 200 letters, digits,
`.`, `_`, `:` or `-`, starting with a letter or digit. A retry sends the
same id and the same body.
- `X-OpenProse-Signature`: `sha256=` and the lowercase hex HMAC-SHA256, keyed
with the signing secret, of the delivery id, one newline and the exact body
bytes. Sign the bytes you send; do not reserialize the body after signing.

```sh
sig=$(printf '%s\n%s' "$DELIVERY_ID" "$BODY" | openssl dgst -sha256 -hmac "$SIGNING_SECRET" | sed 's/^.* //')
curl -X POST "$ENDPOINT_URL" -H 'Content-Type: application/json' \
-H "X-OpenProse-Delivery: $DELIVERY_ID" -H "X-OpenProse-Signature: sha256=$sig" \
--data-binary "$BODY"
```

`202` with `"accepted":true` means the event was accepted, not that a run
finished; `"test_only":true` means no run was started. A missing or malformed
delivery id, or a body that is not JSON, is `400`; a body over 256 KiB is
`413`; a wrong signature is `401`. `prose cli job deliveries JOB_ID --json`
lists the events received, including those rejected for a wrong signature.
38 changes: 38 additions & 0 deletions docs/service/jobs.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,44 @@ printf '%s' '{"type":"webhook","delivery_mode":"test","name":"my-hook"}' \
results also carry `endpoint_url`, the absolute URL to configure in the
sender (the service origin + `endpoint`).

## Sending events to a webhook

The sender POSTs JSON (`Content-Type: application/json`, at most 256 KiB) to
`endpoint_url`. A webhook created without `receiver` checks two headers:

- `X-OpenProse-Delivery`: a new id for each event matching
`[A-Za-z0-9][A-Za-z0-9._:-]{0,199}`. A retry reuses the id and the exact body.
- `X-OpenProse-Signature`: `sha256=` followed by the lowercase hex
HMAC-SHA256 of `deliveryId + "\n" + rawBody`, keyed with the UTF-8
`signing_secret`. Sign the bytes you send; do not reserialize the body.

```sh
sig=$(printf '%s\n%s' "$DELIVERY_ID" "$BODY" | openssl dgst -sha256 -hmac "$SIGNING_SECRET" | sed 's/^.* //')
curl -X POST "$ENDPOINT_URL" -H 'Content-Type: application/json' \
-H "X-OpenProse-Delivery: $DELIVERY_ID" -H "X-OpenProse-Signature: sha256=$sig" \
--data-binary "$BODY"
```

| Response | Meaning |
| --- | --- |
| `202 {"accepted":true,"duplicate":false}` | Accepted. This acknowledges receipt, not run completion. |
| `202` with `"test_only":true` | Test mode: recorded, no run started. Test mode does not deduplicate: a repeated id is recorded again. |
| `202` with `"duplicate":true` | Live mode: a retry of a delivery already accepted. |
| `400` | Missing or malformed `X-OpenProse-Delivery`, or a body that is not valid UTF-8 JSON. |
| `401` | Wrong signature. `job deliveries` records it as `rejected` with reason `invalid_signature`. |
| `409` | Live mode: a reused delivery id with a different body, or no contract attached. Don't retry it unchanged. `"reason":"configuration_changed"` means the webhook changed during the request; retry it. |
| `413` | Body over 256 KiB. |

Retry only network failures, `429` and `5xx`, with backoff, keeping the id
and body. `job deliveries JOB_ID` shows what arrived. The job's
`last_event_at` records live deliveries only, so it stays empty in test mode.
A webhook can be live only with a contract: create it with `program_ref`, or
run `job contract attach` before `job update` sets `"delivery_mode":"live"`.
Otherwise the service answers `SERVICE_REQUEST_REJECTED` ("Runs stay
disabled. Connect a contract to this webhook first.").
`openssl -hmac` puts the secret in the process arguments; on a shared host,
sign with a library instead.

## Reading jobs

`job list` returns `{jobs, max_jobs, job_limit, types}` (each type
Expand Down
Loading