@carllee1983/dbcli 2.1.0 → 4.0.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dbcli-agent",
3
- "version": "1.51.2",
3
+ "version": "4.0.0",
4
4
  "description": "Database CLI skill and command reference for AI agents.",
5
5
  "contextFileName": "AGENTS.md"
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carllee1983/dbcli",
3
- "version": "2.1.0",
3
+ "version": "4.0.0",
4
4
  "description": "Database CLI for AI agents",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -70,6 +70,8 @@
70
70
  "release:check": "bash scripts/release-check.sh",
71
71
  "plugin:sync": "bun run scripts/sync-plugin-assets.ts --write",
72
72
  "plugin:check": "bun run scripts/sync-plugin-assets.ts",
73
+ "manifest:sync": "bun run scripts/check-plugin-manifests.ts --write",
74
+ "manifest:check": "bun run scripts/check-plugin-manifests.ts",
73
75
  "test": "bun test --timeout 30000",
74
76
  "test:unit": "bun test tests/unit tests/core",
75
77
  "test:integration": "bun test tests/integration",
@@ -82,6 +84,7 @@
82
84
  "platform:check": "bun run scripts/check-platform-parity.ts",
83
85
  "agent-core:check": "bun run scripts/check-agent-core-purity.ts",
84
86
  "core-stdout:check": "bun run scripts/check-core-no-stdout.ts",
87
+ "plan:check": "bun run scripts/check-plan-acceptance.ts",
85
88
  "typecheck": "tsc --noEmit --pretty false",
86
89
  "typecheck:tests": "tsc --noEmit --pretty false -p tsconfig.tests.json",
87
90
  "test:perf": "bun test ./tests/perf/*.bench.ts",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dbcli-agent",
3
- "version": "1.51.2",
3
+ "version": "4.0.0",
4
4
  "description": "Database CLI skill and command reference for AI agents",
5
5
  "author": {
6
6
  "name": "Carl Lee",
@@ -1573,7 +1573,7 @@ Opt-in flag trio that persists a **VerificationArtifact JSON** (schema v1) under
1573
1573
 
1574
1574
  **Planned vs Result evidence.** `dbcli skill tasks plan safe-backfill-verify --format json` returns a plan containing a `verification` block with `status: "planned"`. That block is the **planned** evidence definition — it describes which check will run. Running `assert --write-verification-artifact` on the actual data produces **result** evidence (`status: "verified"` or `status: "not_verified"`). The two records are distinct; `"planned"` does **not** indicate that verification has run or passed.
1575
1575
 
1576
- > **Casting note:** Postgres returns `count(*)` and `sum()` as bigint (a string in the result set). `value ==` uses strict equality, so `"0" == 0` is false. Cast to `::int` (`count(*)::int`) to ensure numeric comparison works correctly.
1576
+ > **Numeric note:** Postgres returns `count(*)` and `sum()` as bigint, which arrives as a string. A numeric expectation is compared numerically, so `value == 0` matches it without a cast. A quoted expectation stays a text comparison: `value == "0"` matches the text, not the number. The `::int` casts in the examples below are harmless and no longer required.
1577
1577
 
1578
1578
  ```bash
1579
1579
  dbcli assert "SELECT count(*)::int FROM orders WHERE status IS NULL" \
@@ -2637,7 +2637,8 @@ Inside the shell:
2637
2637
  > not as the answer. Tier one (the y/N on ordinary writes) is deliberately not wired here — every line
2638
2638
  > is typed by a person. Piped input (`dbcli shell < script.sql`) has nobody to answer, so a
2639
2639
  > tier-two statement is refused and the remaining lines still run. Redis, MongoDB and
2640
- > Elasticsearch shells are unaffected.
2640
+ > Elasticsearch shells do not use the write gate; their writes are bounded by the
2641
+ > connection's permission tier instead.
2641
2642
  >
2642
2643
  > dbcli **subcommands** typed in the shell (`query "..."`, `delete ...`) run as separate
2643
2644
  > processes with no stdin, so they cannot ask anything: a tier-two statement there is
@@ -2721,7 +2722,7 @@ dbcli evidence compose --claims ./claims.json --verification ver_abcd --audit 1a
2721
2722
 
2722
2723
  | Flag | Purpose | Default |
2723
2724
  |---|---|---|
2724
- | `--claims <file>` | Required JSON file with exactly `subject` and `claims`; every claim has an `id` and plain-language `text`. SQL-shaped text, credentials, error content, and blacklisted identifiers are rejected. | — |
2725
+ | `--claims <file>` | Required JSON file with exactly `subject` and `claims`; every claim has an `id` and plain-language `text`. SQL-shaped text, credentials, error content, and blacklisted identifiers are rejected. A blacklisted identifier has to appear as an identifier — a term like `id` matches `orders.id` but not the word "identifier" — and the refusal names the field, never the matched term. | — |
2725
2726
  | `--verification <selector...>` | One or more verification artifact ids, unique prefixes, filenames, or in-bounds paths. | none |
2726
2727
  | `--audit <selector...>` | One or more audit ids or unique prefixes (minimum four characters) from the active connection. | none |
2727
2728
  | `--receipt <path...>` | Explicit workspace-relative receipt path(s). Receipts contribute safe provenance only; they are not execution approval. | none |
@@ -2729,8 +2730,11 @@ dbcli evidence compose --claims ./claims.json --verification ver_abcd --audit 1a
2729
2730
  | `--format <json\|markdown>` | Compose receipt format printed to stdout. | `json` |
2730
2731
 
2731
2732
  At least one `--verification`, `--audit`, or `--receipt` reference is required. The resulting pack
2732
- contains a canonical SHA-256 integrity digest and `coverage.completeForDeclaredEvidence:
2733
- true` for its explicitly selected references.
2733
+ carries a canonical SHA-256 digest over its content, and its `id` is derived from that digest, so
2734
+ composing the same claims and references twice produces the same pack. `createdAt` records when the
2735
+ pack was written and is deliberately outside the digest — it is the one field a restamp can change
2736
+ without breaking validation. Whether a referenced source is still resolvable is reported by
2737
+ `evidence validate`, not stored in the pack.
2734
2738
 
2735
2739
  #### `evidence validate`
2736
2740
 
@@ -2748,6 +2752,22 @@ verification artifact later rotates, is cleared, disappears, or belongs to a dif
2748
2752
  active connection, output stays parseable with `integrity: "valid"` and
2749
2753
  `references: "source-expired"`, then exits `1`.
2750
2754
 
2755
+ `validate` first names the pack's artifact format, which carries its own `version`
2756
+ independent of the dbcli package version (ADR-0013). The current format is `version: 2`;
2757
+ dbcli 3.0.0 and earlier wrote `version: 1` in two incompatible layouts. The JSON report
2758
+ leads with `status` and `trust`:
2759
+
2760
+ | `status` | `trust` | Meaning | Exit |
2761
+ |---|---|---|---|
2762
+ | `current-valid` | `current-valid` | Current format, digest verified, references resolvable. | `0` |
2763
+ | `current-references-expired` | `current-valid` | Current format and digest, a referenced source is gone. | `1` |
2764
+ | `recognized-legacy` | `not-current-valid` | Written by an older dbcli; `legacyFormat` and `producedBy` name which, `integrity` reports that format's own digest, references are not evaluated. | `1` |
2765
+ | `unsupported` | `not-current-valid` | Unknown version, or version and structure disagree. | `1` |
2766
+
2767
+ A legacy pack is never silently treated as current-valid and is never migrated: a pack's
2768
+ `id` derives from its digest, so rewriting one would mint a new artifact carrying an old
2769
+ one's provenance. Compose a new pack from current evidence instead.
2770
+
2751
2771
  #### `evidence render`
2752
2772
 
2753
2773
  ```bash
@@ -3659,7 +3679,19 @@ GET /orders/_search
3659
3679
  ```
3660
3680
 
3661
3681
  - Enter a request line `<METHOD> /<path>`, then an optional multi-line JSON body; a **blank line** submits the block. Responses render as pretty-printed JSON.
3662
- - Read-focused: index-level blacklist rejects protected indices at the front end; a `_search` whose body omits `size` is auto-capped at 1000 hits.
3682
+ - **The connection's permission tier applies, as it does to `dbcli query`.** The request is classified through the same classifier the query path uses, on the path the server will actually route — the query string is discarded and percent-encoding and dot segments are resolved first, so `?filter_path=...` cannot change the verdict.
3683
+ - **Reads are an allowlist; anything else needs `admin`.** Permitted below `admin`: `_search` and `_count` on an index or unscoped, `_doc` / `_source` reads, `_mapping` / `_settings` / `_alias` reads, `_cat/*` (except `_cat/aliases` and `_cat/tasks`), `_cluster/health`, and `GET` / `HEAD` of a bare index name. Writes to one document are the usual tiers. **Everything else — including any endpoint not listed here — requires `admin`**, so `DELETE /<index>`, `DELETE /_all`, `_delete_by_query`, `_update_by_query`, `_reindex`, `_aliases`, `PUT /<index>/_mapping`, `PUT /<index>/_settings`, `_sql`, `_scripts`, `_security`, `_snapshot`, `_nodes` and `_cluster/state` are all refused below it. The list is a floor, not a claim that everything absent from it is dangerous: a missing entry costs you an unnecessary `admin` requirement rather than a bypass. See [ADR-0014](https://github.com/CarlLee1983/dbcli/blob/main/docs/adr/0014-elasticsearch-reads-are-an-allowlist.md).
3684
+ - A path is refused unless it is **byte-identical** to what the URL parser produces — the same parser the request goes through — and the refusal tells you the canonical spelling to write instead. This is what stops a path that reads one way here and routes another way at the server: `#` truncates a path in transit (`POST /_reindex#/_count` reaches Elasticsearch as `POST /_reindex`), and tab, newline and `\` are rewritten too. A document id containing a space or a non-ASCII character must be written percent-encoded; the error message gives you the exact string.
3685
+ - A quoted string request body is refused; write the body as JSON. So is a `source=` query parameter — Elasticsearch accepts it in place of a body, where none of the body checks can see it. `_source`, `_source_includes` and `_source_excludes` are unaffected.
3686
+ - Blacklisted field names are refused in the query string as well as the body, because the URI-search form names fields directly (`?q=…`, `?sort=…`, `?docvalue_fields=…`). A name matches on any dot component, so `password.keyword` — the multi-field every `text` field gets by default — and `params._source.password` inside a script are both refused.
3687
+ - **Three limits worth knowing.** A mapping-level `alias` pointing at a protected field names it under a different word, which dbcli cannot resolve. A script that builds the name from pieces defeats any text scan. And a wildcard field expression is expanded by Elasticsearch after the request leaves, so `?q=pass*:hunter*` can be used to confirm a value from hit counts — the value itself is still stripped from the response, but the match is not hidden. Blacklist column rules remain a display filter, not an access control.
3688
+ - A refusal names the tier that would work, and the request is never sent.
3689
+ - Every request is written to the audit log whether it executed or was refused, tiered by what the request would do rather than by the command that issued it.
3690
+ - Index-level blacklist rejects protected indices at the front end; a `_search` whose body omits `size` is auto-capped at 1000 hits.
3691
+
3692
+ > **Before v4.0.0 this path applied no permission check at all.** A `query-only`
3693
+ > Elasticsearch connection could delete documents, drop an index or rewrite a mapping
3694
+ > through the shell, and nothing was recorded. See the 4.0.0 entry in `CHANGELOG.md`.
3663
3695
 
3664
3696
  ### Doctor and diagnostics
3665
3697
 
@@ -1573,7 +1573,7 @@ Opt-in flag trio that persists a **VerificationArtifact JSON** (schema v1) under
1573
1573
 
1574
1574
  **Planned vs Result evidence.** `dbcli skill tasks plan safe-backfill-verify --format json` returns a plan containing a `verification` block with `status: "planned"`. That block is the **planned** evidence definition — it describes which check will run. Running `assert --write-verification-artifact` on the actual data produces **result** evidence (`status: "verified"` or `status: "not_verified"`). The two records are distinct; `"planned"` does **not** indicate that verification has run or passed.
1575
1575
 
1576
- > **Casting note:** Postgres returns `count(*)` and `sum()` as bigint (a string in the result set). `value ==` uses strict equality, so `"0" == 0` is false. Cast to `::int` (`count(*)::int`) to ensure numeric comparison works correctly.
1576
+ > **Numeric note:** Postgres returns `count(*)` and `sum()` as bigint, which arrives as a string. A numeric expectation is compared numerically, so `value == 0` matches it without a cast. A quoted expectation stays a text comparison: `value == "0"` matches the text, not the number. The `::int` casts in the examples below are harmless and no longer required.
1577
1577
 
1578
1578
  ```bash
1579
1579
  dbcli assert "SELECT count(*)::int FROM orders WHERE status IS NULL" \
@@ -2637,7 +2637,8 @@ Inside the shell:
2637
2637
  > not as the answer. Tier one (the y/N on ordinary writes) is deliberately not wired here — every line
2638
2638
  > is typed by a person. Piped input (`dbcli shell < script.sql`) has nobody to answer, so a
2639
2639
  > tier-two statement is refused and the remaining lines still run. Redis, MongoDB and
2640
- > Elasticsearch shells are unaffected.
2640
+ > Elasticsearch shells do not use the write gate; their writes are bounded by the
2641
+ > connection's permission tier instead.
2641
2642
  >
2642
2643
  > dbcli **subcommands** typed in the shell (`query "..."`, `delete ...`) run as separate
2643
2644
  > processes with no stdin, so they cannot ask anything: a tier-two statement there is
@@ -2721,7 +2722,7 @@ dbcli evidence compose --claims ./claims.json --verification ver_abcd --audit 1a
2721
2722
 
2722
2723
  | Flag | Purpose | Default |
2723
2724
  |---|---|---|
2724
- | `--claims <file>` | Required JSON file with exactly `subject` and `claims`; every claim has an `id` and plain-language `text`. SQL-shaped text, credentials, error content, and blacklisted identifiers are rejected. | — |
2725
+ | `--claims <file>` | Required JSON file with exactly `subject` and `claims`; every claim has an `id` and plain-language `text`. SQL-shaped text, credentials, error content, and blacklisted identifiers are rejected. A blacklisted identifier has to appear as an identifier — a term like `id` matches `orders.id` but not the word "identifier" — and the refusal names the field, never the matched term. | — |
2725
2726
  | `--verification <selector...>` | One or more verification artifact ids, unique prefixes, filenames, or in-bounds paths. | none |
2726
2727
  | `--audit <selector...>` | One or more audit ids or unique prefixes (minimum four characters) from the active connection. | none |
2727
2728
  | `--receipt <path...>` | Explicit workspace-relative receipt path(s). Receipts contribute safe provenance only; they are not execution approval. | none |
@@ -2729,8 +2730,11 @@ dbcli evidence compose --claims ./claims.json --verification ver_abcd --audit 1a
2729
2730
  | `--format <json\|markdown>` | Compose receipt format printed to stdout. | `json` |
2730
2731
 
2731
2732
  At least one `--verification`, `--audit`, or `--receipt` reference is required. The resulting pack
2732
- contains a canonical SHA-256 integrity digest and `coverage.completeForDeclaredEvidence:
2733
- true` for its explicitly selected references.
2733
+ carries a canonical SHA-256 digest over its content, and its `id` is derived from that digest, so
2734
+ composing the same claims and references twice produces the same pack. `createdAt` records when the
2735
+ pack was written and is deliberately outside the digest — it is the one field a restamp can change
2736
+ without breaking validation. Whether a referenced source is still resolvable is reported by
2737
+ `evidence validate`, not stored in the pack.
2734
2738
 
2735
2739
  #### `evidence validate`
2736
2740
 
@@ -2748,6 +2752,22 @@ verification artifact later rotates, is cleared, disappears, or belongs to a dif
2748
2752
  active connection, output stays parseable with `integrity: "valid"` and
2749
2753
  `references: "source-expired"`, then exits `1`.
2750
2754
 
2755
+ `validate` first names the pack's artifact format, which carries its own `version`
2756
+ independent of the dbcli package version (ADR-0013). The current format is `version: 2`;
2757
+ dbcli 3.0.0 and earlier wrote `version: 1` in two incompatible layouts. The JSON report
2758
+ leads with `status` and `trust`:
2759
+
2760
+ | `status` | `trust` | Meaning | Exit |
2761
+ |---|---|---|---|
2762
+ | `current-valid` | `current-valid` | Current format, digest verified, references resolvable. | `0` |
2763
+ | `current-references-expired` | `current-valid` | Current format and digest, a referenced source is gone. | `1` |
2764
+ | `recognized-legacy` | `not-current-valid` | Written by an older dbcli; `legacyFormat` and `producedBy` name which, `integrity` reports that format's own digest, references are not evaluated. | `1` |
2765
+ | `unsupported` | `not-current-valid` | Unknown version, or version and structure disagree. | `1` |
2766
+
2767
+ A legacy pack is never silently treated as current-valid and is never migrated: a pack's
2768
+ `id` derives from its digest, so rewriting one would mint a new artifact carrying an old
2769
+ one's provenance. Compose a new pack from current evidence instead.
2770
+
2751
2771
  #### `evidence render`
2752
2772
 
2753
2773
  ```bash
@@ -3659,7 +3679,19 @@ GET /orders/_search
3659
3679
  ```
3660
3680
 
3661
3681
  - Enter a request line `<METHOD> /<path>`, then an optional multi-line JSON body; a **blank line** submits the block. Responses render as pretty-printed JSON.
3662
- - Read-focused: index-level blacklist rejects protected indices at the front end; a `_search` whose body omits `size` is auto-capped at 1000 hits.
3682
+ - **The connection's permission tier applies, as it does to `dbcli query`.** The request is classified through the same classifier the query path uses, on the path the server will actually route — the query string is discarded and percent-encoding and dot segments are resolved first, so `?filter_path=...` cannot change the verdict.
3683
+ - **Reads are an allowlist; anything else needs `admin`.** Permitted below `admin`: `_search` and `_count` on an index or unscoped, `_doc` / `_source` reads, `_mapping` / `_settings` / `_alias` reads, `_cat/*` (except `_cat/aliases` and `_cat/tasks`), `_cluster/health`, and `GET` / `HEAD` of a bare index name. Writes to one document are the usual tiers. **Everything else — including any endpoint not listed here — requires `admin`**, so `DELETE /<index>`, `DELETE /_all`, `_delete_by_query`, `_update_by_query`, `_reindex`, `_aliases`, `PUT /<index>/_mapping`, `PUT /<index>/_settings`, `_sql`, `_scripts`, `_security`, `_snapshot`, `_nodes` and `_cluster/state` are all refused below it. The list is a floor, not a claim that everything absent from it is dangerous: a missing entry costs you an unnecessary `admin` requirement rather than a bypass. See [ADR-0014](https://github.com/CarlLee1983/dbcli/blob/main/docs/adr/0014-elasticsearch-reads-are-an-allowlist.md).
3684
+ - A path is refused unless it is **byte-identical** to what the URL parser produces — the same parser the request goes through — and the refusal tells you the canonical spelling to write instead. This is what stops a path that reads one way here and routes another way at the server: `#` truncates a path in transit (`POST /_reindex#/_count` reaches Elasticsearch as `POST /_reindex`), and tab, newline and `\` are rewritten too. A document id containing a space or a non-ASCII character must be written percent-encoded; the error message gives you the exact string.
3685
+ - A quoted string request body is refused; write the body as JSON. So is a `source=` query parameter — Elasticsearch accepts it in place of a body, where none of the body checks can see it. `_source`, `_source_includes` and `_source_excludes` are unaffected.
3686
+ - Blacklisted field names are refused in the query string as well as the body, because the URI-search form names fields directly (`?q=…`, `?sort=…`, `?docvalue_fields=…`). A name matches on any dot component, so `password.keyword` — the multi-field every `text` field gets by default — and `params._source.password` inside a script are both refused.
3687
+ - **Three limits worth knowing.** A mapping-level `alias` pointing at a protected field names it under a different word, which dbcli cannot resolve. A script that builds the name from pieces defeats any text scan. And a wildcard field expression is expanded by Elasticsearch after the request leaves, so `?q=pass*:hunter*` can be used to confirm a value from hit counts — the value itself is still stripped from the response, but the match is not hidden. Blacklist column rules remain a display filter, not an access control.
3688
+ - A refusal names the tier that would work, and the request is never sent.
3689
+ - Every request is written to the audit log whether it executed or was refused, tiered by what the request would do rather than by the command that issued it.
3690
+ - Index-level blacklist rejects protected indices at the front end; a `_search` whose body omits `size` is auto-capped at 1000 hits.
3691
+
3692
+ > **Before v4.0.0 this path applied no permission check at all.** A `query-only`
3693
+ > Elasticsearch connection could delete documents, drop an index or rewrite a mapping
3694
+ > through the shell, and nothing was recorded. See the 4.0.0 entry in `CHANGELOG.md`.
3663
3695
 
3664
3696
  ### Doctor and diagnostics
3665
3697