@datarails-ai/dr-cli 1.6.152 → 1.6.154

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "cliVersion": "1.6.152",
3
+ "cliVersion": "1.6.154",
4
4
  "_contract": "Resolve a command path through `aliases` (canonical prefix + alias segment -> canonical path) before any lookup. A path listed in `pendingCommands`, or whose first segment is a key of `pending`, is UNDECIDED, not unrestricted: fail closed or skip it, and never read a path's absence from `commands` as 'no requirement'. An entry carrying `denied` is UNAVAILABLE to any AGENT-SCOPED credential — one whose `scopes` claim carries at least one `resource:action` entry; a claim that is absent, empty, capability-only or wholly unparseable is UNRESTRICTED, and this mark does not apply to it — whatever else that entry says: it is not a ceiling and it is not always-available, its `denied.reason` says why and `denied.until` names the ticket that lifts it, and the absence of `requires` on it must NEVER be read as 'no requirement'. Only an entry in `commands` with an empty `requires` means always-available.",
5
5
  "commands": {
6
6
  "login": {
@@ -2207,6 +2207,24 @@
2207
2207
  "until": "DR-56164"
2208
2208
  }
2209
2209
  },
2210
+ "connectors aws set-role": {
2211
+ "denied": {
2212
+ "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2213
+ "until": "DR-56164"
2214
+ }
2215
+ },
2216
+ "connectors aws rotate-external-id": {
2217
+ "denied": {
2218
+ "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2219
+ "until": "DR-56164"
2220
+ }
2221
+ },
2222
+ "connectors aws delete": {
2223
+ "denied": {
2224
+ "reason": "The connectors control plane runs no dr-auth-sdk guard and checks no scope, so the `integrations` ceiling this command declares would be enforced by nobody. Closed to scoped credentials until that control plane adopts the SDK.",
2225
+ "until": "DR-56164"
2226
+ }
2227
+ },
2210
2228
  "filebox list": {
2211
2229
  "requires": [
2212
2230
  {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: schedule-drip-task
3
- description: Use when a customer needs files or data pushed OUT of Datarails on a schedule through DRIP (the connectors platform) - an SFTP export, "send this CSV to the customer's SFTP every hour", "push the file to their server", "schedule an export", "scheduled task", "dr connectors tasks", "sftp_export", "lib/sftp_export", or when operating an existing schedule - "is the export running", "why did the export fail", "the task is paused", "run the export now", "pause the export", "task runs", "consecutive failures", "paused (failures)", "paused (schedule)", "no active version", "newest source file is older than max_age_seconds", "the target must not be a customer org-*-sftp container", a 403 "scheduled tasks need a support user or an org admin". Not for pulling data INTO Datarails (drip-implement-connector) or for polled SFTP ingestion (dr connectors poll).
3
+ description: Use when a customer needs files or data pushed OUT of Datarails on a schedule through DRIP (the connectors platform) - an SFTP export, "send this CSV to the customer's SFTP every hour", "push the file to their server", "schedule an export", "scheduled task", "dr connectors tasks", "sftp_export", "lib/sftp_export", or when operating an existing schedule - "is the export running", "why did the export fail", "the task is paused", "run the export now", "pause the export", "task runs", "consecutive failures", "paused (failures)", "paused (schedule)", "no active version", "newest source file is older than max_age_seconds", "the target must not be a customer org-*-sftp container", a 403 "scheduled tasks need a support user or an org admin", or when writing a new task type - "write a scheduled task", "new DRIP task", "process() TaskResult", "run hook", "task state". Not for pulling data INTO Datarails (drip-implement-connector) or for polled SFTP ingestion (dr connectors poll).
4
4
  ---
5
5
 
6
6
  # Schedule a DRIP task
@@ -10,6 +10,9 @@ datasources, and keeps a run history. The control plane only schedules; the runn
10
10
  artifact's `process()`. The first task type is **SFTP Export** (`lib/sftp_export`): copy files from
11
11
  our hosted SFTP to a customer's SFTP under a name the customer wants.
12
12
 
13
+ To write a task type other than SFTP Export (the `process()` hook, `TaskResult`, state, retries),
14
+ see [reference/writing-a-task.md](reference/writing-a-task.md).
15
+
13
16
  Commands: `dr connectors tasks create | list | get | update | delete | run | pause | resume | runs`.
14
17
  Every one takes `--server`, `--account` and `--json`; a `<task>` is its id or its name.
15
18
 
@@ -16,10 +16,11 @@ datasources (the control plane refuses secret-looking keys with a 422).
16
16
  | `settle_seconds` | no | 120 | Skip files modified more recently (the producer may still be writing). 0 to 7 days |
17
17
  | `max_age_seconds` | no | none | `newest` only: fail the run if the newest file is older than this before the scheduled time, or if no file matches (producer late or stopped) |
18
18
  | `max_files_per_run` | no | 50 | `all_new`: at most this many files per run (1 to 1000); the rest go next run |
19
+ | `upload` | no | `atomic` | `atomic`: write a `.part`, then rename it, so no reader sees a half-written file. `direct`: write the final name itself, for a target folder that takes files away as they arrive (the `.part` would be gone before the rename). `newest` only |
19
20
 
20
21
  Behaviour worth knowing:
21
22
 
22
- - Upload is atomic: a random `.<name>.<hex>.part` is written, then renamed. The customer's login must
23
+ - Upload is atomic by default: a random `.<name>.<hex>.part` is written, then renamed. A run that fails with "a file vanished while publishing the upload" means the target takes files as they arrive: set `"upload": "direct"`. The customer's login must
23
24
  be allowed to create and rename files in `target_dir`. Stale parts of ours older than 2 h are swept.
24
25
  - The source is never written, moved or deleted. Cleaning it is the producer's job; with `all_new`,
25
26
  a folder that keeps every sent file eventually fails the run with "archive or delete them".
@@ -0,0 +1,92 @@
1
+ # Writing a new task type
2
+
3
+ A scheduled task is an ordinary DRIP artifact in dr-connectors (a connection plus a dataquery,
4
+ published and made active like any connector, see `drip-implement-connector`), whose dataquery
5
+ implements `process()`. The control plane only schedules it. On each run it sends the flavor's
6
+ runner a job with `hook: "run"`, and the runner calls `connect()`, then `process()`, in one job.
7
+ The full contract is in dr-connectors-runner `docs/connector-kit.md`, "Scheduled tasks: the `run`
8
+ hook". `lib/sftp_export` in dr-connectors is the reference implementation.
9
+
10
+ ## The code
11
+
12
+ ```python
13
+ # lib/<task>/dataqueries/<name>.py
14
+ from dr_integrations_sdk import DataQuery, PermanentError, SyncRequest, TaskResult, kit
15
+
16
+ OBJECT_TYPES = ("<objectType>",)
17
+
18
+
19
+ class MyTask(DataQuery):
20
+ async def process(self, session, req: SyncRequest) -> TaskResult:
21
+ config = req.config # the task's --config, validated here
22
+ state = (req.metadata or {}).get("state") or {} # what the last ok run returned
23
+ scheduled_for = req.metadata["scheduled_for"] # ISO UTC time of the slot this run is for
24
+ other = await kit.auth.get_credentials_for(req, "source") # a non-primary datasource, by role
25
+ sent = await do_the_work(session, other, config, state)
26
+ return TaskResult(state={"sent": sent}, summary={"sent": len(sent)})
27
+
28
+
29
+ def get_query_class(object_type: str):
30
+ if object_type == "<objectType>":
31
+ return MyTask
32
+ raise PermanentError(f"unknown objectType {object_type!r}")
33
+ ```
34
+
35
+ | Piece | Rule |
36
+ |---|---|
37
+ | `connect()` | Gets the **primary** datasource's credentials as for any connector. Log in here, so a wrong password or host key fails before any work |
38
+ | `req.config` | The task's `--config` JSON. Must carry `objectType` (it picks the dataquery class). Validate it before any login. Never put credentials in it: the control plane refuses secret-looking keys with a 422 |
39
+ | Other datasources | `kit.auth.get_credentials_for(req, "<role>")`, one per `--datasource <role>=<id>`. An unbound role is a `PermanentError` naming it |
40
+ | `req.metadata` | `state`, `scheduled_for`, `run_id`, `attempt` (1, or 2 on the retry), `trigger` (`schedule` or `manual`) |
41
+ | Imports | stdlib, `dr_integrations_sdk`, `lib` and the flavor's allowlist only (dr-connector-images). Set `RUNTIME_FLAVOR` in the connection |
42
+ | Logs | `kit.logging.get_logger("<task>")`. They reach Groundcover under the runner flavor's workload |
43
+
44
+ ## The result
45
+
46
+ `TaskResult(state=…, summary=…, verdict=…)`. Every field is optional.
47
+
48
+ - **`state`**: the task's own bookkeeping (ids, cursors, names already sent), never the data
49
+ processed. It must be a JSON object or `None`, at most 64 KiB. The control plane keeps it only
50
+ from an `ok` run; after a failure the next run gets the last good state.
51
+ - **`summary`**: what the run did, shown by `dr connectors tasks runs`.
52
+ - **`verdict`**: `ok` (default), `retry` (the run is sent once more after 60 s) or `block` (the run
53
+ fails).
54
+ - The whole result must be JSON (no `datetime`, `set`, `Decimal`, NaN) and at most 128 KiB.
55
+ Anything else fails the run as a connector error.
56
+
57
+ ## Errors
58
+
59
+ | Raised | Run |
60
+ |---|---|
61
+ | `PermanentError`, `AuthError` | Fails, no retry. Use for bad config, refused credentials, missing folders |
62
+ | `TransientError`, `httpx` 429/5xx, `httpx.TransportError` | Retried once after 60 s, then fails |
63
+ | Anything else | Fails as a connector error |
64
+
65
+ - After 5 failed runs in a row the task pauses itself (`paused (failures)`).
66
+ - The control plane stores only the first few hundred characters of a run's error. Keep the message
67
+ short. Log anything longer, such as a key or a listing, and read it in Groundcover.
68
+ - Never put a secret in an error or a log line.
69
+
70
+ ## Make it safe to run twice
71
+
72
+ A retry runs `process()` again, a manual run can land next to a scheduled one, and a run that dies
73
+ leaves no state. So:
74
+
75
+ - Record what was done in `state`, and skip it next time.
76
+ - Write the output so that a half-finished run leaves nothing a reader would take as done. For
77
+ example, upload to a temporary name and rename it.
78
+ - Use `scheduled_for`, not the clock, for anything named after the run's time: a retry then makes
79
+ the same name.
80
+
81
+ ## Ship it
82
+
83
+ 1. **Shared code goes in `lib/<task>/`**, with tests. A thin per-org artifact re-exports it. The
84
+ dataquery must set its own `CONNECTION` (the thin connection file's name), as in
85
+ [SKILL.md](../SKILL.md) step 3.
86
+ 2. **Merge to dr-connectors.** CI publishes the artifact to JFrog.
87
+ 3. **Register and activate it on the region:** `dr connectors publish <dataquery file>`, then
88
+ `dr connectors promote <artifact> --artifact-version v<N>`, as the customer's support user.
89
+ 4. **Schedule it:** `dr connectors tasks create` (see [SKILL.md](../SKILL.md)), then `tasks run` once
90
+ and check `tasks runs`.
91
+ 5. **Test on dev-1 first.** The heartbeat artifact in dr-connectors
92
+ (`dev-1/69_(test-connectors.com)/heartbeat/`) is a minimal task that carries a counter in `state`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datarails-ai/dr-cli",
3
- "version": "1.6.152",
3
+ "version": "1.6.154",
4
4
  "description": "Datarails CLI",
5
5
  "type": "module",
6
6
  "engines": {