@mu-cabin/coding-cli 0.1.0
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.
- package/README.md +78 -0
- package/dist/cli.js +1181 -0
- package/generated/index.json +1 -0
- package/package.json +30 -0
- package/skills/coding/SKILL.md +188 -0
- package/skills/coding/references/admin.md +65 -0
- package/skills/coding/references/artifacts.md +138 -0
- package/skills/coding/references/cd.md +103 -0
- package/skills/coding/references/ci.md +99 -0
- package/skills/coding/references/code.md +145 -0
- package/skills/coding/references/export.md +42 -0
- package/skills/coding/references/files.md +66 -0
- package/skills/coding/references/issues.md +175 -0
- package/skills/coding/references/org.md +115 -0
- package/skills/coding/references/permissions.md +164 -0
- package/skills/coding/references/program.md +79 -0
- package/skills/coding/references/project.md +150 -0
- package/skills/coding/references/servicehook.md +112 -0
- package/skills/coding/references/test.md +143 -0
- package/skills/coding/references/wiki.md +118 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# coding-test
|
|
2
|
+
|
|
3
|
+
A curated playbook for the **测试管理 (15)** category — the 29 test-management actions.
|
|
4
|
+
|
|
5
|
+
## 1. The entities and how they connect
|
|
6
|
+
|
|
7
|
+
**`ProjectName` (the project *slug*, a string) is the universal required key on every one of the 29
|
|
8
|
+
actions.** There is **no `ProjectId`** anywhere in this category — every other identifier is numeric
|
|
9
|
+
(mostly `uint64`, a few `int64`, and some are integer *arrays* like `Cases`/`RunIds`).
|
|
10
|
+
|
|
11
|
+
| Entity (中文) | What it is | Its id param | Linked by |
|
|
12
|
+
|---|---|---|---|
|
|
13
|
+
| **测试计划 / TestRun** | a *plan/run* — a container of cases to execute | `RunId` | built from `Cases[]` (case ids) or `IncludeAll`; optionally tied to a git release (`GitReleaseId`) |
|
|
14
|
+
| **测试用例 / TestCase** | a reusable case *definition* (the "what to test") | `CaseId` | lives in a 分组 `SectionId`; linkable to a requirement `IssueId` |
|
|
15
|
+
| **测试任务 / Test** | an *instance* of a case inside a plan (the execution row) | `TestId` | has `CaseId` (source case) under a `RunId`; **this is what you record results on** |
|
|
16
|
+
| **分组 / Section** | a folder tree for cases | `SectionId` | self-nests via `ParentId`; optional `CaseDepotId` (用例库) |
|
|
17
|
+
| **测试报告 / Report** | aggregates one or more plans | `ReportId` | built from `RunIds[]` |
|
|
18
|
+
| **缺陷 / Defect** | an issue/defect | `DefectId` | attached to a `TestId` |
|
|
19
|
+
|
|
20
|
+
**Lifecycle:** define a `Case` → add it to a `Run` → it becomes a `Test` instance → record a result on
|
|
21
|
+
that `TestId` → aggregate `Runs` into a `Report`.
|
|
22
|
+
|
|
23
|
+
> ⚠️ **Every list/detail payload nests under `Data`** (e.g. `Data.Runs[]`, `Data.Case`). Nothing in
|
|
24
|
+
> this category returns a top-level array — match your `--select` accordingly.
|
|
25
|
+
|
|
26
|
+
## 2. Test plans (测试计划 / TestRun)
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# list plans (filters are mutually exclusive: pick at most one of IterationId / IterationStatus /
|
|
30
|
+
# GitReleaseState / SectionId). State: 0 未开始 · 1 进行中 · 2 已测完.
|
|
31
|
+
coding call DescribeTestRunList --param ProjectName=demo --param Keyword=regression \
|
|
32
|
+
--select 'Data.Runs[].Id,Data.Runs[].Name,Data.Runs[].State,Data.Runs[].PassedCount,Data.Runs[].FailedCount,Data.Runs[].UntestedCount' --json
|
|
33
|
+
|
|
34
|
+
coding call DescribeTestRun --param ProjectName=demo --param RunId=88 --json # detail → Data.Run
|
|
35
|
+
|
|
36
|
+
# create a plan. IncludeAll=true takes every case; otherwise Cases is an Array of case ids (required).
|
|
37
|
+
coding call CreateTestRun --data '{"ProjectName":"demo","Name":"Release 2.0 regression",
|
|
38
|
+
"IncludeAll":false,"Cases":[1001,1002,1003],"ExecuteType":1}' --json # ExecuteType 1 手动 · 2 自动化流水线
|
|
39
|
+
# → Data.Run.Id is the new RunId
|
|
40
|
+
|
|
41
|
+
coding call ModifyTestRun --data '{"ProjectName":"demo","RunId":88,"Name":"Release 2.0 (rev b)"}' --json
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 3. Test cases (测试用例) & sections (分组)
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# sections form a tree (ParentId nests them). CaseDepotId scopes to a 用例库 if you have multiple.
|
|
48
|
+
coding call DescribeTestCaseSectionList --param ProjectName=demo --select 'Data.Sections[].Id,Data.Sections[].Name,Data.Sections[].ParentId' --json
|
|
49
|
+
coding call CreateTestCaseSection --data '{"ProjectName":"demo","Name":"Login flows","ParentId":0}' --json # → Data.Section
|
|
50
|
+
|
|
51
|
+
# list / read cases (paginated list carries Data.{Page,PageSize,Total}; detail adds Attachments[])
|
|
52
|
+
coding call DescribeTestCaseList --param ProjectName=demo --param SectionId=12 --param PageSize=50 \
|
|
53
|
+
--select 'Data.Cases[].Id,Data.Cases[].Title,Data.Cases[].Priority,Data.Cases[].TemplateType' --json
|
|
54
|
+
coding call DescribeTestCase --param ProjectName=demo --param CaseId=1001 --json # → Data.Case
|
|
55
|
+
|
|
56
|
+
# create a STEPS case: CustomSteps is an Array of {Content,Expected}. For a TEXT case use Steps/Expected strings.
|
|
57
|
+
coding call CreateTestCase --data '{"ProjectName":"demo","SectionId":12,"TemplateType":"STEPS","Title":"Login with valid creds",
|
|
58
|
+
"Priority":1,"CustomSteps":[{"Content":"Open /login","Expected":"Form shown"},{"Content":"Submit valid creds","Expected":"Dashboard"}]}' --json
|
|
59
|
+
|
|
60
|
+
# cases linked to a requirement (需求): IssueId is the requirement's id
|
|
61
|
+
coding call DescribeRequirementTestCaseList --param ProjectName=demo --param IssueId=42 --select 'Data.Cases[].Id,Data.Cases[].Title' --json
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
> ⚠️ **Test-case `Priority` is the *inverse* of issue priority.** Here `0`=紧急 · `1`=高 · `2`=中 (default)
|
|
65
|
+
> · `3`=低 — opposite to the issue/`CreateIssue` scale in SKILL.md (`0`低…`3`紧急). Don't carry
|
|
66
|
+
> the issue mapping over. `TemplateType` ∈ `STEPS` (步骤用例) | `TEXT` (文本用例).
|
|
67
|
+
|
|
68
|
+
## 4. Recording results — three different keys, read carefully
|
|
69
|
+
|
|
70
|
+
The result endpoints look similar but key off **different** ids. Getting this wrong is the #1 trap.
|
|
71
|
+
Result `Status` ∈ `UNTESTED` | `PASSED` | `BLOCKED` | `RETEST` | `FAILED`.
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# (a) by Test-task instance — the normal path. TestId comes from DescribeTestList (its `Id`).
|
|
75
|
+
coding call DescribeTestList --param ProjectName=demo --param RunId=88 --select 'Data.Tests[].Id,Data.Tests[].CaseId,Data.Tests[].Title,Data.Tests[].Status' --json
|
|
76
|
+
coding call CreateTestResult --data '{"ProjectName":"demo","TestId":5001,"Status":"PASSED"}' --json
|
|
77
|
+
|
|
78
|
+
# (b) step-level result on a STEPS test task (StepIndex starts at 1; StepStatus ∈ PASSED|FAILED only)
|
|
79
|
+
coding call CreateTestStepResult --data '{"ProjectName":"demo","TestId":5001,"StepIndex":1,"StepStatus":"PASSED","Actual":"Dashboard loaded"}' --json
|
|
80
|
+
|
|
81
|
+
# (c) CreateCaseResult — ⚠️ its `CaseId` param is actually the 测试任务 ID (a TestId), NOT a test-case
|
|
82
|
+
# id, and it also needs RunId. Despite the name, it behaves like (a) plus the plan context.
|
|
83
|
+
coding call CreateCaseResult --data '{"ProjectName":"demo","RunId":88,"CaseId":5001,"Status":"FAILED"}' --json
|
|
84
|
+
|
|
85
|
+
# (d) bulk status update across a plan — here CaseIds[] ARE real test-case ids, plus RunId.
|
|
86
|
+
coding call CreateTestResults --data '{"ProjectName":"demo","RunId":88,"CaseIds":[1001,1002],"Status":"PASSED"}' --json
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
| Action | Keys off | `CaseId`/`CaseIds` means |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| `CreateTestResult` | `TestId` | — |
|
|
92
|
+
| `CreateTestStepResult` | `TestId` + `StepIndex` | — |
|
|
93
|
+
| `CreateCaseResult` | `RunId` + `CaseId` | **`CaseId` = a TestId** (测试任务 id) |
|
|
94
|
+
| `CreateTestResults` (bulk) | `RunId` + `CaseIds[]` | real test-**case** ids |
|
|
95
|
+
|
|
96
|
+
## 5. Defects & reports
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# attach an existing defect (DefectId) to a test task (TestId), then list a task's defects
|
|
100
|
+
coding call CreateTestDefect --data '{"ProjectName":"demo","TestId":5001,"DefectId":7788}' --json
|
|
101
|
+
coding call DescribeTestDefectList --param ProjectName=demo --param TestId=5001 --select 'Data.Defects[].Id,Data.Defects[].Name,Data.Defects[].StatusName,Data.Defects[].AssignedTo' --json
|
|
102
|
+
|
|
103
|
+
# reports aggregate plans: RunIds is an Array of plan ids (required). Status ∈ CREATING|AVAILABLE|UNAVAILABLE.
|
|
104
|
+
coding call DescribeReportList --param ProjectName=demo --select 'Data.Reports[].Id,Data.Reports[].Name,Data.Reports[].Status' --json
|
|
105
|
+
coding call CreateReport --data '{"ProjectName":"demo","Name":"Release 2.0 report","RunIds":[88,89]}' --json # → Data.Report
|
|
106
|
+
coding call DescribeReport --param ProjectName=demo --param ReportId=301 --json # full detail incl. Data.Report.ReportOverview stats
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## 6. Attachments (for cases & reports)
|
|
110
|
+
|
|
111
|
+
`AttachmentIds[]` on `CreateTestCase`/`ModifyTestCase`/`CreateReport` reference pre-uploaded files. The
|
|
112
|
+
upload is a two-step out-of-band flow:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
coding call CreateAttachmentPrepareSignUrl --param ProjectName=demo --param FileName=evidence.png --json
|
|
116
|
+
# → Data.AttachmentPrepare.{AttachmentId, PrepareSignUrl}. PUT the file bytes to PrepareSignUrl yourself
|
|
117
|
+
# (the CLI won't upload for you), then pass the returned AttachmentId in AttachmentIds:[…].
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## 7. Destructive actions (exit-10 gated)
|
|
121
|
+
|
|
122
|
+
Five actions need `--yes` (preview with `--dry-run`, get explicit user approval, re-run the **same argv
|
|
123
|
+
with `--yes`** — see SKILL.md's exit-10 protocol):
|
|
124
|
+
|
|
125
|
+
- **`DeleteTestRun`** (`ProjectName`,`RunId`) — delete a plan.
|
|
126
|
+
- **`ArchiveTestRun`** (`ProjectName`,`RunId`) — archive/finalize a plan; gated even though it returns the
|
|
127
|
+
Run object, because the gate keys off the `Archive` verb.
|
|
128
|
+
- **`DeleteTestCase`** (`ProjectName`,`CaseId`), **`DeleteTestCaseSection`** (`ProjectName`,`SectionId`),
|
|
129
|
+
**`DeleteReport`** (`ProjectName`,`ReportId`).
|
|
130
|
+
|
|
131
|
+
## 8. Gotchas
|
|
132
|
+
|
|
133
|
+
- **`ProjectName` (slug), never `ProjectId`** — this whole category keys off the project *name*.
|
|
134
|
+
- **The three "Test/Case/Run" ids are distinct.** `RunId` = a plan, `CaseId` = a case definition,
|
|
135
|
+
`TestId` = an execution instance inside a plan. `DescribeTestList.Data.Tests[].Id` is the `TestId`;
|
|
136
|
+
its `CaseId` field points back to the source case.
|
|
137
|
+
- **`CreateCaseResult.CaseId` is a TestId, not a case id** (§4). The bulk `CreateTestResults.CaseIds[]`
|
|
138
|
+
*are* real case ids. Don't swap them.
|
|
139
|
+
- **`Priority` is inverted vs issues** (§3): `0`紧急 … `3`低 here.
|
|
140
|
+
- **Array params**: `Cases`/`CaseIds`/`RunIds`/`AttachmentIds`/`CustomStepStatus` are all arrays even
|
|
141
|
+
when sending one element; `CustomSteps` is an array of `{Content,Expected}` objects.
|
|
142
|
+
- **Everything wraps under `Data`** — `Data.Runs[]`, `Data.Cases[]`, `Data.Sections[]`, `Data.Tests[]`,
|
|
143
|
+
`Data.Defects[]`, `Data.Reports[]`; singletons at `Data.Run`/`Data.Case`/`Data.Report`.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# coding-wiki
|
|
2
|
+
|
|
3
|
+
A curated playbook for the **Wiki (13)** category — the 12 wiki actions.
|
|
4
|
+
|
|
5
|
+
## 1. Identifiers
|
|
6
|
+
|
|
7
|
+
- **`ProjectName`** (the project *slug*, a string) is required on **every** action — there is **no
|
|
8
|
+
`ProjectId`** in this category.
|
|
9
|
+
- **`Iid`** (int64, "wiki 编号") is the **per-project page handle** — what every *page-scoped* action
|
|
10
|
+
takes (`DescribeWiki`, `ModifyWiki`, `ModifyWikiTitle`, `ModifyWikiOrder`, `DeleteWiki`). The
|
|
11
|
+
exceptions are `DescribeWikiList` (`ProjectName` only) and the job-status reads (`ProjectName` +
|
|
12
|
+
`JobId`). Responses also carry a global **`Id`**; **never feed `Id` back as `Iid`.**
|
|
13
|
+
- **`ParentIid`** (int64) places a page in the tree; **`0` = top level / root**.
|
|
14
|
+
|
|
15
|
+
> ⚠️ **Response wrapping is uneven.** `DescribeWikiList` → `Data` is an **array** (the page tree);
|
|
16
|
+
> `DescribeWiki` / job-status → `Data` is an **object**; but `CreateUploadToken` returns top-level
|
|
17
|
+
> **`Token`** and the ZIP imports return top-level **`JobId`** — neither is under `Data`. Match
|
|
18
|
+
> `--select` to the right wrapper per call.
|
|
19
|
+
|
|
20
|
+
## 2. Browse & read
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# the page tree (whole thing; no pagination). Tree linkage: ParentIid (0=root), ordering: Order (a float).
|
|
24
|
+
coding call DescribeWikiList --param ProjectName=demo \
|
|
25
|
+
--select 'Data[].Iid,Data[].Title,Data[].ParentIid,Data[].Order,Data[].Children[].Iid,Data[].Children[].Title' --json
|
|
26
|
+
|
|
27
|
+
# read one page — ⚠️ VersionId is REQUIRED with no "latest" sentinel, yet DescribeWikiList does NOT
|
|
28
|
+
# expose a version. In practice you need a known version number — from the wiki UI, or a prior
|
|
29
|
+
# DescribeWiki response's CurrentVersion/LastVersion. Content = raw source; Html = rendered.
|
|
30
|
+
coding call DescribeWiki --param ProjectName=demo --param Iid=12 --param VersionId=5 \
|
|
31
|
+
--select 'Data.Title,Data.Content,Data.CurrentVersion,Data.LastVersion' --json
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 3. Create & edit
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# create — Title, Content, AND ParentIid are all required (use ParentIid:0 for a root page). Msg = commit note.
|
|
38
|
+
coding call CreateWiki --data '{"ProjectName":"demo","Title":"Onboarding","Content":"# Onboarding\n…","ParentIid":0,"Msg":"initial"}' --json
|
|
39
|
+
# → Data.Iid / Data.Id of the new page
|
|
40
|
+
|
|
41
|
+
# full edit — ⚠️ ModifyWiki requires BOTH Title and Content (you can't update body alone). Identified by Iid.
|
|
42
|
+
coding call ModifyWiki --data '{"ProjectName":"demo","Iid":12,"Title":"Onboarding","Content":"# Onboarding (v2)\n…","Msg":"revise"}' --json
|
|
43
|
+
|
|
44
|
+
# rename only — use this instead of ModifyWiki when you don't want to resupply Content
|
|
45
|
+
coding call ModifyWikiTitle --data '{"ProjectName":"demo","Iid":12,"Title":"Onboarding Guide"}' --json
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Content is passed inline as the `Content` string (markdown-style source rendered server-side; the read
|
|
49
|
+
response gives you both `Content` and rendered `Html`). There is no markdown-vs-HTML selector param.
|
|
50
|
+
|
|
51
|
+
## 4. Move & reorder
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# "Wiki 父级修改" — reparent and/or reorder a page. Iid = the page to move; ParentIid = its new parent
|
|
55
|
+
# (0 = root). Position among the new siblings via Before/After (sibling anchors, both optional ints) —
|
|
56
|
+
# ⚠️ there is NO numeric Order input here despite the action name. Forced (bool,
|
|
57
|
+
# default false) = 是否检查权限 (whether to enforce the permission check).
|
|
58
|
+
coding call ModifyWikiOrder --data '{"ProjectName":"demo","Iid":12,"ParentIid":3,"After":7}' --json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 5. ZIP import / update (async, multi-step)
|
|
62
|
+
|
|
63
|
+
A 4-step pipeline. The values that thread through it: `CreateUploadToken` yields **`AuthToken` + `Time`**
|
|
64
|
+
(carried into the ByZip call) and COS upload credentials; the upload yields a **`Key`** (the stored
|
|
65
|
+
object name); the ByZip call yields a **`JobId`** you then poll.
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# 1. get an upload token + COS credentials for the file
|
|
69
|
+
coding call CreateUploadToken --param ProjectName=demo --param FileName=wiki-export.zip \
|
|
70
|
+
--select 'Token.UploadLink,Token.UpToken,Token.AuthToken,Token.Time,Token.SecretId,Token.SecretKey,Token.Provider' --json
|
|
71
|
+
|
|
72
|
+
# 2. upload the zip to Token.UploadLink (Tencent COS) using UpToken/SecretId/SecretKey — OUT OF BAND
|
|
73
|
+
# (the CLI does not upload bytes). The upload produces the stored object name `Key` (a uuid .zip).
|
|
74
|
+
|
|
75
|
+
# 3a. import as NEW pages under ParentIid — carries FileName, Key, AuthToken, Time from above
|
|
76
|
+
coding call CreateWikiByZip --data '{"ProjectName":"demo","FileName":"wiki-export.zip","Key":"<uuid>.zip",
|
|
77
|
+
"AuthToken":"<AuthToken>","Time":<Time>,"ParentIid":0}' --json # → top-level JobId
|
|
78
|
+
|
|
79
|
+
# 3b. OR update an EXISTING page from the zip — same body but `Iid` (the page) instead of `ParentIid`
|
|
80
|
+
coding call ModifyWikiByZip --data '{"ProjectName":"demo","FileName":"wiki-export.zip","Key":"<uuid>.zip",
|
|
81
|
+
"AuthToken":"<AuthToken>","Time":<Time>,"Iid":12}' --json # → top-level JobId
|
|
82
|
+
|
|
83
|
+
# 4. poll the matching status action (NOT interchangeable): Import↔Create, Update↔Modify.
|
|
84
|
+
coding call DescribeImportJobStatus --param ProjectName=demo --param JobId=<JobId> --select 'Data.JobId,Data.Status' --json
|
|
85
|
+
coding call DescribeUpdateJobStatus --param ProjectName=demo --param JobId=<JobId> --select 'Data.JobId,Data.Status' --json
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
- **`UpToken` vs `AuthToken` are different tokens** — `UpToken` (with `SecretId`/`SecretKey`) authorizes
|
|
89
|
+
the COS upload in step 2; only **`AuthToken` + `Time`** feed the CODING ByZip call in step 3.
|
|
90
|
+
- **Create→`ParentIid`, Modify→`Iid`** is the one param difference between the two ByZip calls, and they
|
|
91
|
+
have **separate** status pollers (`DescribeImportJobStatus` for create, `DescribeUpdateJobStatus` for
|
|
92
|
+
modify). `Status` is a string enum (e.g. `WAIT_PROCESS` while pending) — poll until it leaves the
|
|
93
|
+
pending state.
|
|
94
|
+
|
|
95
|
+
## 6. Destructive actions (exit-10 gated)
|
|
96
|
+
|
|
97
|
+
One action needs `--yes`:
|
|
98
|
+
|
|
99
|
+
- **`DeleteWiki`** (`ProjectName`, `Iid`) — **moves the page to the recycle bin (reversible)**, not a
|
|
100
|
+
hard delete. Still gated: preview with `--dry-run`, get explicit approval, re-run the **same argv with
|
|
101
|
+
`--yes`** (see SKILL.md's exit-10 protocol).
|
|
102
|
+
|
|
103
|
+
## 7. Gotchas
|
|
104
|
+
|
|
105
|
+
- **`ProjectName` (slug), never `ProjectId`** for this whole category.
|
|
106
|
+
- **`Iid` is the input handle; `Id` is a different (global) id in responses** — never round-trip `Id`
|
|
107
|
+
as `Iid`. `ParentIid:0` = root, and `ParentIid` is **required** on `CreateWiki` and `CreateWikiByZip`.
|
|
108
|
+
- **`DescribeWiki` requires `VersionId`** with no "latest" sentinel — and `DescribeWikiList` does **not**
|
|
109
|
+
return a version, so you can't bootstrap one from the tree. You need a version number from elsewhere
|
|
110
|
+
(the UI, or a prior read's `CurrentVersion`/`LastVersion`); surface this to the user if you can't
|
|
111
|
+
determine one.
|
|
112
|
+
- **`ModifyWiki` requires both `Title` and `Content`** — for a rename use `ModifyWikiTitle`.
|
|
113
|
+
- **`ModifyWikiOrder` positions via `Before`/`After` sibling anchors, not a numeric order**; `ParentIid`
|
|
114
|
+
is required; `Forced` (default false) toggles the 是否检查权限 permission check.
|
|
115
|
+
- **Response wrapping is uneven**: list → `Data[]` (array), single read / job status → `Data` (object),
|
|
116
|
+
ByZip → top-level `JobId`. Field types also drift (`Order` is a float in the list but an int in the
|
|
117
|
+
single read; timestamps are epoch-ms in the list but datetime strings in the single read) — don't
|
|
118
|
+
assume one shape across calls.
|