@carllee1983/dbcli 3.0.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
  "author": {
6
6
  "name": "Carl Lee",
@@ -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",
@@ -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
@@ -2751,6 +2752,22 @@ verification artifact later rotates, is cleared, disappears, or belongs to a dif
2751
2752
  active connection, output stays parseable with `integrity: "valid"` and
2752
2753
  `references: "source-expired"`, then exits `1`.
2753
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
+
2754
2771
  #### `evidence render`
2755
2772
 
2756
2773
  ```bash
@@ -3662,7 +3679,19 @@ GET /orders/_search
3662
3679
  ```
3663
3680
 
3664
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.
3665
- - 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`.
3666
3695
 
3667
3696
  ### Doctor and diagnostics
3668
3697
 
@@ -2,7 +2,7 @@
2
2
  "name": "dbcli-agent",
3
3
  "displayName": "dbcli Agent",
4
4
  "description": "Database CLI skill and command reference for AI agents.",
5
- "version": "1.51.2",
5
+ "version": "4.0.0",
6
6
  "author": {
7
7
  "name": "Carl Lee",
8
8
  "url": "https://github.com/CarlLee1983"
@@ -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
@@ -2751,6 +2752,22 @@ verification artifact later rotates, is cleared, disappears, or belongs to a dif
2751
2752
  active connection, output stays parseable with `integrity: "valid"` and
2752
2753
  `references: "source-expired"`, then exits `1`.
2753
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
+
2754
2771
  #### `evidence render`
2755
2772
 
2756
2773
  ```bash
@@ -3662,7 +3679,19 @@ GET /orders/_search
3662
3679
  ```
3663
3680
 
3664
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.
3665
- - 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`.
3666
3695
 
3667
3696
  ### Doctor and diagnostics
3668
3697