Feat/databox v2 support - #7
Conversation
Complete transition from V1 to V2 API — no V1 calls remain. - API client: V2 envelope unwrapping, added patch/put methods, x-account-id header support - Migrated all 15 existing commands to V2 paths and response shapes - Added ~65 new commands covering full V2 surface (profile, billing, users, clients, connections, integrations, metrics, activity-log, databoards, plus new data-source and dataset sub-commands) - Renamed account list → account info (V2 returns single account) - Dataset IDs now numeric (validated client-side) - primaryKeys → primaryKey, schema name → columnId - Global --account-id flag for multi-account access - 123 tests (68 new + 36 updated), all passing - Updated README with 86 commands, 11 skills - Added CHANGELOG.md with full migration guide - Version bump 0.3.1 → 1.0.0 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add CLI commands for all remaining V2 dataset endpoints: - dataset lineage — show parents/children - dataset sync-statistics — sync history statistics - dataset update-modification — update a modification (PUT) - dataset preview-modification — preview before applying - dataset modification-rules — list available rules - dataset modification-formulas — list available formulas Update skills, changelog, and README (92 commands total). All 133 tests passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
High: - Extract requireNumericId() to BaseCommand (30 dataset commands) - Extract showPagination() to output.ts (16 list commands) - Standardize query type to Record<string, string | number | undefined> - Pass accountHeaders on all API calls for uniform x-account-id support Medium: - Add typed interfaces to 6 key commands (account, billing, data-source, dataset lineage) - Standardize test import order across 62 test files - Move mockApi to beforeEach in 6 test files Net -51 lines. 133 tests passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- activity-log list: route changed to /v2/account/activity-log - New command: profile metadata-options (departments + roles) - Updated tests, README (93 commands), CHANGELOG Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- databoard list: sourceTypes → integrationKeys - client create/update: added --managed-by-id flag - New command: account metadata-options - New command: account countries - Updated tests and mocks Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Multi-agent review pipeline (4 specialists + validator) adapted for CLI-specific patterns: command structure, flag conventions, output formatting, test coverage, and security concerns. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix accountHeaders passed as query instead of headers in dataset/sync-frequencies and dataset/ingestion - Wrap all bare JSON.parse calls in try/catch with user-friendly errors - Add requireNumericId validation to 23 non-dataset commands - Add empty-body guard to dataset/update and dataset/set-metadata - Fix dataset/data --json output to use formatOutput consistently - Add file existence check in dataset/ingest before readFileSync - Normalize pagination defaults (remove from dataset/list) - Add second example to 13 commands that only had one - Fix set-permissions output consistency across domains - Add --json tests for 38 commands, 3 error path tests (182 total) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Rename --title to --name on data-source and dataset commands - Rename --key to --integration-key on data-source create - Rename --data-source-id to --source-id on metric list - Update interfaces, table columns, and examples to match Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AndreLei
left a comment
There was a problem hiding this comment.
http://markdown-server.prod/view/d7b1bc6b-2c8b-4ae9-9222-9bd8afedc9c4
We need to address the above issues and improvements
The CLI was migrated to the V2 API without a layer that checks it against a
real server, so the unit suite mocked the shapes we believed the API returned
and kept passing when that belief was wrong. Derived the full contract from
ingestion-api (88 V2 routes, 137 contract classes in
IngestionApi.Core/Contracts/{Request,Response}/V2) and reconciled every
command against it, confirming each finding against a live endpoint.
Commands that could never succeed — the request body used a field name the
API does not accept:
data-source set-sync-frequency {interval} -> {syncInterval}
dataset set-sync-frequency {interval} -> {syncInterval}
dataset set-verification {status} -> {isVerified: boolean}
metric set-verification {status} -> {isVerified: boolean}
dataset set-metadata {tags} -> no such field; --tags is now
--synonyms
metric dimension-values wrong on both sides: now sends
{metrics:[{dataSourceId,metricId,dimensions}]}
and reads {dimensionValues}
Commands that rendered blank columns — the response field does not exist:
dataset list / account datasets dataSourceId -> parentDataSourceId, no createdAt
dataset get dataSourceId -> parentDataSourceId
dataset create no createdAt (returns a DatasetListItem)
data-source datasets no createdAt
data-source get title -> name
activity-log list timestamp/userName -> createdAt/user
client list accountType -> isSelfManaged/managedBy
connection list status -> statusInfo.status
metric list type -> dimensions/supportsDrilldown/verificationInfo
dataset ingestions / ingestion timestamp -> startedAt/finishedAt
dataset sync-history completedAt -> finishedAt
billing invoices no id; adds currency/description
account usage {current} -> {count}, adds a clients bucket
Envelope handling was inconsistent in both directions: the two
sync-frequencies commands read response.items where the API returns a bare
array, and dataset column-metadata did the reverse, throwing
"data.map is not a function" in table mode.
Also brings each command up to the full contract surface, since nothing is
released and there is no compatibility to preserve: every request field and
query parameter is now reachable (~50 new flags, including the required
aggregationFunction on metric create and sharedWithClients on connection
set-permissions), and --json returns what the endpoint returned rather than a
hand-picked subset. Optional string fields are guarded with `!== undefined`
so an empty string can clear a nullable field.
test/helpers.ts now records request bodies and exposes lastBody(), because
MockRoute.body was declared but never asserted — which is why every one of the
request-body defects above shipped with a passing unit test. The stale
fixtures using title/dataSourceId and the removed --title flag are updated to
the real contracts, so the unit suite is green for the first time on this
branch: 203 passing, 0 failing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WZNCsKbnNUKY5nNrJNEgxj
Every existing test monkey-patches global.fetch, so the CLI had never been run against a live server. This adds a second layer that spawns the built binary and asserts on exit codes and stdout — the counterpart to ingestion-api's ExternalTests/v2 scripts, one level up: where those assert on HTTP responses, these assert on what a user actually sees. 16 suites, one per command group, plus cli-contract for the surface that is uniquely the CLI's: exit-code semantics (1 general, 2 validation), --json emitting only parseable JSON, table headers, the three dataset-ingest input modes, and that the API key never reaches stdout or stderr. npm run test:e2e develop6 + its key, zero setup npm run test:e2e -- --grep "^dataset " one suite npm run test:e2e:cleanup sweep after an interrupted run Design notes: - Everything goes through the CLI. No suite makes a direct HTTP call — setup, assertions and teardown all shell out, so the harness has no API client of its own to drift out of sync. - Targets are named (develop6 default, develop10, local, production). An unknown name becomes https://ingestion-api-<name>.databox.com and DATABOX_E2E_API_URL takes any URL, so ephemeral environments need no code change; resolveEnvironment() is the single seam for making that dynamic. - develop6 and local carry a default key, as ingestion-api does. Production never does, and needs DATABOX_E2E_ALLOW_PROD=1 on top of being named — these suites create and delete real resources. - The child environment is scrubbed of every DATABOX_* variable and given an empty HOME, so an exported key cannot silently redirect a run and ~/.config/databox-cli/config.json is neither read nor written. (The unit harness overwrites the developer's real config; this must not inherit that.) - Resources are named cli-e2e-* and tracked for teardown, with a sweeper for runs that die early. The prefix is distinct from the ingestion-api scripts' so the two suites never collect each other's resources. - Skips are always explained, and distinguish an unhealthy environment from a CLI defect. A confirmed defect gets fixed rather than parked as a skip: a skipped test is green and CI cannot tell it from a passing one. .e2e.ts keeps these out of npm test, whose glob is test/**/*.test.ts, so no change to .mocharc.yml is needed. Environment resolution and the production guard are pure logic, so they are covered in the fast suite (test/e2e-config.test.ts) rather than left to manual checks. Current state against develop6: 115 passing, 23 pending, 0 failing. The pending are develop6's ingestion pipeline being down plus account capability limits, each printing its reason. test/e2e/README.md records the API-side issues found along the way (unreliable dataSourceId filtering on GET /v2/datasets with totalItems ignoring the filter, stale reads after a delete, and duplicate rejecting DataboxAPI-sourced datasets). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WZNCsKbnNUKY5nNrJNEgxj
Two gaps the first full green run exposed.
Reverting what the tests change. Almost everything the suites touch is a
cli-e2e-* fixture they created, but three things cannot be: the account, the
signed-in profile, and an existing connection — there is no way to exercise
account/profile/connection update without changing something real. Those were
restored in a `finally`, which does not survive Ctrl-C, a crash, or a restore
that itself fails; an interrupted run could leave a shared environment renamed
with no record of the original value.
withRestore() now writes the undoing command to .e2e-restore.json *before* the
mutation and removes it only once the value is back. Anything left in that file
is an outstanding change, so it is replayed by the root before() hook (so
suites read real values), by the root after() hook, and by
`npm run test:e2e:cleanup` for a run that died. Entries record the environment
they were taken against and are never replayed onto a different one.
Verified by simulating a crash: mutate the account, kill without restoring,
then `npm run test:e2e:cleanup` puts the name back and clears the log.
Metric coverage. datasets-engine was redeployed and ingestion works again, and
that turned out to be why the whole metric suite was skipping: the metric
service rejects a dataset that has never received data, and the fixture was
never populated. The suite now ingests before building a metric, which unblocks
9 tests, and adds coverage for paths that had never run — dimension-values
(whose batch request shape and {dimensionValues} response were both wrong until
recently), drilldown, and creating a metric with an explicit aggregation and
dimensions.
A metric created with dimensions comes back with a compound
"<source>|<query>|attribute" id that DELETE /v2/metrics/{id} rejects; it is
removed when the fixture data source is torn down, so it is deliberately not
tracked, with the sweeper as the backstop.
Against develop6: 134 passing, 7 pending, 0 failing (was 115/23). The pending
are the account-id flag needing DATABOX_E2E_ACCOUNT_ID, dataset duplicate being
rejected for DataboxAPI sources, and transient service failures.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WZNCsKbnNUKY5nNrJNEgxj
The filter bug is fixed in ingestion-api on fix/v2-dataset-list-datasource-filter: ListDatasets now passes parentId to account-service instead of filtering an already-paged result in memory. The e2e assertion stays unfiltered until that reaches develop6, with a note to switch it back once deployed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WZNCsKbnNUKY5nNrJNEgxj
Duplicating a dataset created through the API is not supported: a pushed dataset has no connector source to copy, and the ingestion identity of a copy is undefined. ingestion-api now rejects it with an actionable message (fix/v2-duplicate-ingestion-dataset-message) rather than passing through account-service's "Data source type not found." So this stops being a skip and becomes a real assertion: duplicating a pushed dataset must fail. Both messages are accepted until the clearer one is deployed, and the run notes which it saw. `dataset duplicate --help` now says up front that it does not apply to datasets created through the API. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WZNCsKbnNUKY5nNrJNEgxj
…ntial safety
Review findings W1, W2, W3, W6, S1, S2, S8, S9, S11 plus the failing CodeQL check.
B2–B5 were already resolved by the earlier contract-sync work; each was verified
against the current code rather than assumed.
Requests were unbounded. No fetch in the CLI carried an AbortSignal, so a stalled
connection hung any of ~90 call sites forever. request() now uses
AbortSignal.timeout — 30s by default, 300s for ingest, where "slow" and "dead" are
otherwise indistinguishable — and maps TimeoutError to a message that says so.
Credential handling:
- extraHeaders was spread *after* x-api-key, so a caller-supplied header could
overwrite the API key. Spread first.
- The config file holds a plaintext key and was written at default umask. Now
0600, with the directory 0700.
- --account-id was interpolated unvalidated and surfaced garbage as "Could not
connect". Now digits-only, exit 2.
The test harness was overwriting the developer's real
~/.config/databox-cli/config.json. This had already happened: the file on this
machine contained {"apiKey":"bad-key"} from `auth login --api-key bad-key` in the
unit suite. config.ts now resolves its path per call, so the harness can redirect
HOME to a temp dir; three tests in test/helpers.test.ts pin that.
Input validation:
- dataset ingestion interpolated ingestionId raw into the path, where the route
constraint is {id:guid} — an ID containing ../ rewrote the request. Added
requireUuid to BaseCommand, applied it, encoded the segment, and replaced the
`ing-456` examples, which could never have worked.
- Permissions commands advertised `specific_users`, which the API rejects. The
real values are everyone|selectedUsers (+private for connections), now enforced
via `options`, with a local guard for selectedUsers without --access-list
(the API requires a non-empty list).
- dataset ingest now preflights shape and the API's 10k-record / 100 MB limits
before paying the upload cost, and caps the stdin read.
- metric data's --dataset-id/--data-source-id are alternatives, now `exclusive`.
- client create/update dropped --managed-by-id 0 to a truthy check.
Also: preview-modification declared --page/--page-size and sent neither (post()
gained query support); databoard list crashed on null tags/integrationKeys; empty
2xx bodies made response.json() throw; showPagination printed "Page 1 of 0" for an
empty list and divided by zero on pageSize 0.
CodeQL js/clear-text-logging (high) flagged the e2e preflight banner. apiKeySource
is a provenance label — one of 'none' | 'DATABOX_E2E_API_KEY' | 'environment
default' — and the key itself is never logged; the rule matched on the apiKey*
name. Renamed to apiKeyOrigin rather than suppressed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WZNCsKbnNUKY5nNrJNEgxj
…ames BREAKING CHANGE: `account data-sources` and `account datasets` are removed. They were v1 spellings of `data-source list` and `dataset list` against the same endpoints — two independent implementations that had already drifted apart (column order, missing --search, and each carrying its own copy of the wrong contract fields). The CLI is v2-only and unreleased, so there is nothing to preserve. Use `data-source list` / `dataset list`, with the global --account-id flag to target another account. An e2e case pins that they stay gone. Review B1: the flag renames in 2ce5213 were applied to src/ only, so every user-facing document still taught flags that no longer parse. - README: regenerated the generated block with `oclif readme` (93 commands), and fixed the two hand-written `--title` examples above it. - skills/databox-datasets and skills/databox-data-sources: 11 references to `--title` and `--key`. These ship in the npm package. - CHANGELOG claimed "`--key` flag preserved"; it is now `--integration-key`. Note the review also lists `--data-source-id` as renamed to `--source-id`. It was not: `--data-source-id` is still live on dataset create, dataset list and metric data, and only metric list uses --source-id. The README's references to it are correct and were left alone. Review W4: oclif.topics described only 5 of the 14 command groups, so most of `databox --help` listed topics with no description. All 14 are now described. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WZNCsKbnNUKY5nNrJNEgxj
Review S4, S5, S6, S7, S12. No behaviour change — the commands call shared helpers instead of each carrying a copy. The review's own conclusion about B2–B5 was that per-command guesswork is what let wrong shapes ship, so consolidating the repeated scaffolding is worth more here than tidiness. - src/lib/flags.ts: paginationFlags, sortFlags, addPagination, addSorting. 14 list commands declared and read page/pageSize themselves, with four different help strings for the same flag and no bounds. Now one definition, "Page number (0-indexed)" everywhere, with min 0 / min 1 so `--page -1` fails as a flag error rather than an API error. Fixed the one example that used `--page 1` on a 0-indexed flag. - BaseCommand.parseJsonFlag replaces 12 duplicated try/catch blocks across 9 commands. Two commands had grown their own private copy; both now inherit it. On S4 I deviated. It asks for response interfaces on the untyped apiClient calls. All 30 of those render via formatSingle, which iterates Object.entries and prints whatever arrived — so a declared interface has no runtime effect there, and inventing field names is exactly the B5 failure mode for no benefit. They are annotated <Record<string, unknown>> to state that the response is rendered verbatim and its shape is not relied upon. The 15 genuinely untyped calls that remain are deletes and purges whose response is discarded. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WZNCsKbnNUKY5nNrJNEgxj
The suite was failing roughly one run in three for reasons that were never the CLI's: develop6 intermittently returns "Authentication required" for a valid key, or an upstream 5xx, in bursts. A suite that red-flags at that rate stops being believed, which defeats the point of having it. cliWithRetry only retries a failure whose output matches a known transient pattern, and returns the last result either way — a genuine failure does not match one, and a matched one is not retried into a pass. So it is safe for assertions, not just fixtures, and its doc comment and the repo rule now say so rather than restricting it to setup. Applied where the flakiness actually bit: - every before() hook, since a transient there takes out a whole suite - the idempotent set-verification / set-timezone / set-sync-frequency / set-permissions assertions, where re-running is indistinguishable from running once Creates are deliberately left un-retried: one that succeeds server-side while reporting a 5xx would be duplicated, and the cli-e2e- sweeper is the backstop for that case. Three consecutive full runs, the last clean: 134 passing, 6 pending, 0 failing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WZNCsKbnNUKY5nNrJNEgxj
Review W7 and S13. The new-logic test gap the review identified: 0 tests asserted a malformed-JSON path against 12 parse sites, 4 asserted requireNumericId against 53 call sites, and 1 asserted an exit code of 2 at all. Three sweeps under test/validation/, as tables rather than one case per command file — each covers a single rule, and keeping the list in one place makes a gap visible: - json-flags: every JSON-valued flag rejects malformed input with exit 2 and names the flag. No API mock needed; parsing happens before any request. - resource-ids: all 53 requireNumericId call sites reject a non-numeric ID. - empty-body: all 8 guarded update commands refuse an empty PATCH with exit 1 — which is what .claude/rules/commands.md specifies for this case, not exit 2. The two ID/body sweeps end with a test that walks src/commands and asserts the table still covers every call site, so adding a command without a case fails rather than silently going untested. test/output-contract.test.ts covers S13: the pagination line, its 0-based-to-1-based conversion, the empty state, and the two degenerate cases fixed in acff7a2 (totalItems 0 printing "Page 1 of 0", pageSize 0 dividing to Infinity). Asserted once against the shared formatOutput/showPagination rather than per list command. Also folds metric create and metric data into parseJsonFlag; they kept bespoke try/catch blocks because their messages and loop differed. 295 unit tests passing, up from 204. CodeQL: the rename in acff7a2 did not clear the alert, because apiKeyOrigin still matched the apiKey* family the rule treats as key material — I renamed the suffix and left the trigger. Now keyResolvedFrom, with a comment saying why the name matters. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WZNCsKbnNUKY5nNrJNEgxj
My previous attempt at the CodeQL js/clear-text-logging alert was a bypass: renaming
apiKeySource to apiKeyOrigin only changed whether a name-matching heuristic fired. It
left the structure untouched — assigning the real key to that field would have leaked
it without tripping anything. (It also did not work: apiKeyOrigin still matched the
apiKey* family.)
The finding is fair. preflight() held the credential in scope and built log lines in
the same function, so nothing but care kept them apart. Fixed by construction:
- E2eTarget holds the printable parts of a resolved environment; E2eEnvironment adds
apiKey. targetOf() copies the printable fields out explicitly — not a spread, so the
result provably carries no credential at runtime rather than only in the type.
- describeTarget(target, extras) builds the banner and takes an E2eTarget, so the key
is not in scope and cannot be printed.
- KeySource is now a tag ('default' | 'env' | 'none') rendered by describeKeySource,
where every branch returns a literal. No value from the resolved config reaches the
log, whatever the field is called.
test/e2e-banner.test.ts is the guarantee that does not depend on a scanner agreeing:
six cases, including that a supplied key and a built-in default key never appear in the
banner, that targetOf drops the credential at runtime, and that even passing a whole
E2eEnvironment to describeTarget prints no key.
Found while doing this: tsconfig.test.json sets include: ["test/**/*"] but inherited
exclude: ["node_modules", "lib", "test"] from the base config, and exclude filters
include — so `tsc -p tsconfig.test.json --noEmit` never type-checked a single test
file. A deliberate type error passed. It now excludes only node_modules and lib, which
immediately surfaced five stale references this rename had left behind. Runtime type
errors were still caught by ts-node during `npm test`, which is why this went unnoticed.
301 unit tests passing. Also extended cliWithRetry to the metric query commands
(data, drilldown, dimension-values) — POSTs that create nothing, so a transient retry
is as safe as on a GET.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WZNCsKbnNUKY5nNrJNEgxj
|
Thanks for this — the report was genuinely useful, and the blocker category it identified One thing worth saying up front: the review was written before the contract-sync commits, so
B1 was fixed in code but not in the docs. That's now swept: README regenerated, both skill Everything else is addressed — warnings and all 14 suggestions:
Three places I deliberately did not follow the report:
S3 ( Two things the review led to that were API bugs, both now merged in On B4 — you were right that the CLI side was only half the story. CodeQL's |
The refactor onto the shared paginationFlags left four commands importing Flags without using it, which is what the code-quality checks on PR #7 flagged. preview-modification still declared its own page/page-size, so it kept the old help text and its own defaults; it now uses the shared flags like every other paginated command. Regenerating the README turned up that it had never been regenerated after the shared flags landed — twelve commands still documented "Page number" where the source says "Page number (0-indexed)". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WZNCsKbnNUKY5nNrJNEgxj
GetIngestion returned only ingestionId and status, because ingestion-api read the ingest through its V1 contract, which has no startedAt/finishedAt/duration/ user at all. Fixed upstream on fix/v2-ingestion-detail-fields; this asserts it, and asserts the detail agrees with its own list row rather than just being non-empty. Expect it red until that deploys. waitForIngestion also treated inProgress as terminal, so a test claiming to wait for a terminal state could return while the ingest was still running. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WZNCsKbnNUKY5nNrJNEgxj
Every blocker in the PR #7 report came from fields guessed from an endpoint name instead of read from the C# contract, and each one had a passing unit test — the mock encoded the same guess as the code, so test and bug agreed. That is now the first Critical pattern, along with a High entry on flag renames sweeping every surface; the README staleness fixed in this branch is exactly that. Retires three entries the contract-sync work made stale: missing requireNumericId, inconsistent pagination defaults, and inconsistent update validation. Each is now enforced by a sweep under test/validation/ whose final test walks src/commands to assert the table still covers every call site. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WZNCsKbnNUKY5nNrJNEgxj
npm run lint has never worked in this repo: eslint and the two oclif configs are installed, but there is no config file and no eslintConfig key, so the script always errored out. That is why five unused imports accumulated until the code-quality bot caught four of them on PR #7 — the fifth, in dataset/ingestions.ts, it never reported. The bulk of this diff is `eslint --fix` across 158 files. Three rules are turned off deliberately: - array-element-newline / array-bracket-newline: their autofix splits array elements onto new lines without indenting them, and no indent rule in the oclif config catches the result. A first pass with them on produced 133 lines starting at column 0. - valid-jsdoc: deprecated, and it wants @param/@returns tags on doc comments this codebase deliberately writes as prose. Two files carry local disables with reasons: ask-genie's snake_case is the agentic service's wire format, and the e2e cleanup script's exit code is its contract. There is no CI in this repo — the checks on the PR are org-level CodeQL — so lint runs from a pretest hook. Without it this rots again the way it already did. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WZNCsKbnNUKY5nNrJNEgxj
This repo had no CI at all — the checks on a PR come from org-level CodeQL, so nothing verified that the tests or the typecheck still passed. npm test now runs the linter first via the pretest hook, so one step covers both. Uses ubuntu-latest rather than the databox-arm64 self-hosted runner that ingestion-api uses: this repo is public, so GitHub-hosted runners are free here, and pointing a public repo's pull_request workflow at a self-hosted runner would let a fork's PR run untrusted code on our infrastructure. The e2e suite is deliberately excluded — it needs a live API and a key. Making this block a merge is a separate step: "Tests" has to be added as a required status check in the branch protection rules for main. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WZNCsKbnNUKY5nNrJNEgxj
Migrates the CLI to the Databox V2 API and brings every command into line with the
ingestion-apicontracts. Breaking: the CLI no longer speaks V1 at all.What's in it
V2 migration — all endpoints, the
{data, requestId, status}envelope, 0-based pagination,numeric resource IDs, and
--account-idpromoted to a global flag. Six dataset commands added forfull V2 coverage;
activity-logmoved under/v2/account/activity-log.Contract sync —
ingestion-api's C# contracts are the source of truth, and a mechanical auditagainst them fixed ~24 commands: request bodies that sent field names the API ignores
(
intervalforsyncInterval,statusforisVerified,tagsforsynonyms), responsereaders that assumed the wrong envelope, and six permanently blank table columns. Commands now
expose every field of their request contract and every query parameter their controller declares —
roughly 50 new flags — and
--jsonreturns what the endpoint returned rather than a hand-pickedsubset.
End-to-end suite (
test/e2e/,npm run test:e2e) — spawns the built binary against a realenvironment and asserts exit codes and output. Everything goes through the CLI, so the suite cannot
drift from the client it tests. This is what found the contract bugs above; none of them were
visible to the mocked unit suite, because each mock encoded the same guess as the code it tested.
Defaults to develop6, takes
DATABOX_E2E_ENV/DATABOX_E2E_API_URL/DATABOX_E2E_API_KEY, andrefuses to touch production without
DATABOX_E2E_ALLOW_PROD=1. Mutations to shared resources arerecorded in a durable undo log so an interrupted run can be unwound with
npm run test:e2e:cleanup.Review fixes — request timeouts (30s, 300s for uploads) where there were none, UUID validation
and path encoding on
dataset ingestion,--access-levelconstrained to the values the APIaccepts, pagination flags on
dataset preview-modificationactually sent, the 9 missingoclif.topicsentries, empty-2xx-body handling, and credential hygiene: the config file is written0600, the unit suite no longer writes to the developer's real~/.config/databox-cli/config.json,and a caller-supplied header can no longer overwrite
x-api-key.Shared helpers —
parseJsonFlag,requireUuid,paginationFlags/sortFlags, replacingpatterns that had been copied across 17 commands and had already drifted.
Breaking changes
account data-sourcesandaccount datasetsare removed. They were v1 spellings ofdata-source listanddataset listagainst the same endpoints; use those with--account-id.--primary-keys→--primary-key,--key→--integration-key,--tags→--synonyms).Full command-by-command mapping in the CHANGELOG migration guide.
Testing
bodies whose final test walks
src/commandsto assert the table still covers every call site.dataset ingestiondetail assertion expectsthe fields the V2 contract declares, and stays red until the
ingestion-apifix for that isdeployed (see below).
ingestion-apibugs this surfaced:dataset listapplied thedataSourceIdfilter afterpaging, and
dataset duplicatesurfaced an opaque upstream refusal — both merged (#48). Thethird,
GetIngestionreturning less than a list row, is onfix/v2-ingestion-detail-fields.